diff --git a/.gitignore b/.gitignore index 0b7d2b9038..07ddb82ff7 100644 --- a/.gitignore +++ b/.gitignore @@ -80,3 +80,5 @@ coverage/ # .vscode **/extensions.json + +apps/test/ \ No newline at end of file diff --git a/apps/wallet-mobile/package.json b/apps/wallet-mobile/package.json index 531456418a..684d656617 100644 --- a/apps/wallet-mobile/package.json +++ b/apps/wallet-mobile/package.json @@ -222,6 +222,7 @@ "@wdio/mocha-framework": "^7.16.6", "@wdio/selenium-standalone-service": "^7.25.1", "@wdio/spec-reporter": "^7.16.4", + "@yoroi/types": "1.0.4", "babel-eslint": "^10.1.0", "babel-jest": "^29.2.1", "babel-loader": "8.2.2", diff --git a/apps/wallet-mobile/src/Catalyst/hooks.ts b/apps/wallet-mobile/src/Catalyst/hooks.ts index 9e205f7eb1..326e3c2278 100644 --- a/apps/wallet-mobile/src/Catalyst/hooks.ts +++ b/apps/wallet-mobile/src/Catalyst/hooks.ts @@ -1,15 +1,18 @@ +import {Balance} from '@yoroi/types' import {useEffect, useState} from 'react' import {YoroiWallet} from '../yoroi-wallets/cardano/types' import {CATALYST, isHaskellShelley} from '../yoroi-wallets/cardano/utils' import {useBalances} from '../yoroi-wallets/hooks' -import {Quantity} from '../yoroi-wallets/types/yoroi' import {Amounts, Quantities} from '../yoroi-wallets/utils' export const useCanVote = (wallet: YoroiWallet) => { const balances = useBalances(wallet) const primaryAmount = Amounts.getAmount(balances, '') - const sufficientFunds = Quantities.isGreaterThan(primaryAmount.quantity, CATALYST.MIN_ADA.toString() as Quantity) + const sufficientFunds = Quantities.isGreaterThan( + primaryAmount.quantity, + CATALYST.MIN_ADA.toString() as Balance.Quantity, + ) return { canVote: !wallet.isReadOnly && isHaskellShelley(wallet.walletImplementationId), diff --git a/apps/wallet-mobile/src/Dashboard/StakePoolInfos.tsx b/apps/wallet-mobile/src/Dashboard/StakePoolInfos.tsx index 5e33d48ba8..820d8a9214 100644 --- a/apps/wallet-mobile/src/Dashboard/StakePoolInfos.tsx +++ b/apps/wallet-mobile/src/Dashboard/StakePoolInfos.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' import React from 'react' import {ActivityIndicator, StyleSheet, View} from 'react-native' @@ -5,7 +6,7 @@ import {useQuery, useQueryClient, UseQueryOptions} from 'react-query' import {useSelectedWallet} from '../SelectedWallet' import {YoroiWallet} from '../yoroi-wallets/cardano/types' -import {Quantity, StakingInfo, YoroiUnsignedTx} from '../yoroi-wallets/types' +import {StakingInfo, YoroiUnsignedTx} from '../yoroi-wallets/types' import {Quantities} from '../yoroi-wallets/utils' import {StakePoolInfo} from './StakePoolInfo' @@ -93,8 +94,8 @@ export const useStakingTx = ( const stakingUtxos = await wallet.getAllUtxosForKey() const amountToDelegate = Quantities.sum([ - ...stakingUtxos.map((utxo) => utxo.amount as Quantity), - accountState.remainingAmount as Quantity, + ...stakingUtxos.map((utxo) => utxo.amount as Balance.Quantity), + accountState.remainingAmount as Balance.Quantity, ]) return wallet.createDelegationTx(poolId, new BigNumber(amountToDelegate)) diff --git a/apps/wallet-mobile/src/NftDetails/NftDetails.tsx b/apps/wallet-mobile/src/NftDetails/NftDetails.tsx index a208f41918..805b309da2 100644 --- a/apps/wallet-mobile/src/NftDetails/NftDetails.tsx +++ b/apps/wallet-mobile/src/NftDetails/NftDetails.tsx @@ -1,4 +1,5 @@ import {RouteProp, useRoute} from '@react-navigation/native' +import {Balance} from '@yoroi/types' import React, {ReactNode, useState} from 'react' import {defineMessages, useIntl} from 'react-intl' import {Dimensions, Linking, StyleSheet, TouchableOpacity, View} from 'react-native' @@ -16,7 +17,6 @@ import {useSelectedWallet} from '../SelectedWallet' import {COLORS} from '../theme' import {getNetworkConfigById} from '../yoroi-wallets/cardano/networks' import {useNft} from '../yoroi-wallets/hooks' -import {TokenInfo} from '../yoroi-wallets/types' import {isRecord, isString} from '../yoroi-wallets/utils' export const NftDetails = () => { @@ -68,7 +68,7 @@ export const NftDetails = () => { ) } -const UnModeratedNftImage = ({nft}: {nft: TokenInfo}) => { +const UnModeratedNftImage = ({nft}: {nft: Balance.TokenInfo}) => { const navigateTo = useNavigateTo() return ( navigateTo.nftZoom(nft.id)} style={styles.imageWrapper}> @@ -77,7 +77,7 @@ const UnModeratedNftImage = ({nft}: {nft: TokenInfo}) => { ) } -const ModeratedNftImage = ({nft}: {nft: TokenInfo}) => { +const ModeratedNftImage = ({nft}: {nft: Balance.TokenInfo}) => { const wallet = useSelectedWallet() const navigateTo = useNavigateTo() const {status} = useModeratedNftImage({wallet, fingerprint: nft.fingerprint}) @@ -114,7 +114,7 @@ const MetadataRow = ({title, copyText, children}: {title: string; children: Reac ) } -const NftOverview = ({nft}: {nft: TokenInfo}) => { +const NftOverview = ({nft}: {nft: Balance.TokenInfo}) => { const strings = useStrings() const wallet = useSelectedWallet() const config = getNetworkConfigById(wallet.networkId) @@ -195,7 +195,7 @@ const HR = () => ( /> ) -const NftMetadata = ({nft}: {nft: TokenInfo}) => { +const NftMetadata = ({nft}: {nft: Balance.TokenInfo}) => { const strings = useStrings() const stringifiedMetadata = JSON.stringify(nft.metadatas.mintNft, undefined, 2) diff --git a/apps/wallet-mobile/src/Nfts/Nfts.tsx b/apps/wallet-mobile/src/Nfts/Nfts.tsx index 8e779972d2..d9e4e908b5 100644 --- a/apps/wallet-mobile/src/Nfts/Nfts.tsx +++ b/apps/wallet-mobile/src/Nfts/Nfts.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import React, {ReactNode} from 'react' import {defineMessages, useIntl} from 'react-intl' import {RefreshControl, ScrollView, StyleSheet, Text, View} from 'react-native' @@ -8,7 +9,6 @@ import {useMetrics} from '../metrics/metricsManager' import {useSearch, useSearchOnNavBar} from '../Search/SearchContext' import {useSelectedWallet} from '../SelectedWallet' import {useNfts} from '../yoroi-wallets/hooks' -import {TokenInfo} from '../yoroi-wallets/types' import {filterNfts, useTrackNftGallerySearchActivated} from './filterNfts' import {useNavigateTo} from './navigation' import {NoNftsScreen} from './NoNftsScreen' @@ -195,7 +195,7 @@ const LoadingScreen = ({nftsCount}: {nftsCount: number}) => { ) } -const byName = ({name: A}: TokenInfo, {name: B}: TokenInfo) => A.localeCompare(B) +const byName = ({name: A}: Balance.TokenInfo, {name: B}: Balance.TokenInfo) => A.localeCompare(B) const styles = StyleSheet.create({ safeAreaView: { diff --git a/apps/wallet-mobile/src/Nfts/filterNfts.test.ts b/apps/wallet-mobile/src/Nfts/filterNfts.test.ts index 81b67b80e2..9e66c1733c 100644 --- a/apps/wallet-mobile/src/Nfts/filterNfts.test.ts +++ b/apps/wallet-mobile/src/Nfts/filterNfts.test.ts @@ -1,5 +1,6 @@ +import {Balance} from '@yoroi/types' + import {nft} from '../yoroi-wallets/mocks' -import {TokenInfo} from '../yoroi-wallets/types' import {filterNfts} from './filterNfts' describe('filterNfts', () => { @@ -7,7 +8,7 @@ describe('filterNfts', () => { const boredMonkey = {...nft, id: '1', fingerprint: 'fakefingerprint2', name: 'Bored Monkey #4567'} const appleBlocks = {...nft, id: '2', fingerprint: 'fakefingerprint3', name: 'Apple Blocks #7890'} - const nfts: TokenInfo[] = [cryptoWolf, boredMonkey, appleBlocks] + const nfts: Balance.TokenInfo[] = [cryptoWolf, boredMonkey, appleBlocks] it('filters NFTs correctly with case-insensitive search term', () => { const filteredNfts = filterNfts('APple bLOcks', nfts) diff --git a/apps/wallet-mobile/src/Nfts/filterNfts.ts b/apps/wallet-mobile/src/Nfts/filterNfts.ts index fb8a0d3010..20d36f9878 100644 --- a/apps/wallet-mobile/src/Nfts/filterNfts.ts +++ b/apps/wallet-mobile/src/Nfts/filterNfts.ts @@ -1,9 +1,9 @@ +import {Balance} from '@yoroi/types' import React from 'react' import {useMetrics} from '../metrics/metricsManager' -import {TokenInfo} from '../yoroi-wallets/types' -export const filterNfts = (searchTerm: string, nfts: TokenInfo[]): TokenInfo[] => { +export const filterNfts = (searchTerm: string, nfts: Balance.TokenInfo[]): Balance.TokenInfo[] => { const searchTermLowerCase = searchTerm.toLowerCase() const filteredNfts = searchTermLowerCase.length > 0 ? nfts.filter((nft) => nft.name?.toLowerCase().includes(searchTermLowerCase)) : nfts diff --git a/apps/wallet-mobile/src/Staking/DelegationConfirmation/DelegationConfirmation.tsx b/apps/wallet-mobile/src/Staking/DelegationConfirmation/DelegationConfirmation.tsx index 3e9bcf64a9..e01e60c25c 100644 --- a/apps/wallet-mobile/src/Staking/DelegationConfirmation/DelegationConfirmation.tsx +++ b/apps/wallet-mobile/src/Staking/DelegationConfirmation/DelegationConfirmation.tsx @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import {Balance} from '@yoroi/types' import React, {useEffect, useState} from 'react' import {defineMessages, useIntl} from 'react-intl' import {ScrollView, StyleSheet, View, ViewProps} from 'react-native' @@ -15,7 +16,6 @@ import {useSelectedWallet} from '../../SelectedWallet' import {COLORS} from '../../theme' import {NETWORKS} from '../../yoroi-wallets/cardano/networks' import {NUMBERS} from '../../yoroi-wallets/cardano/numbers' -import {Quantity} from '../../yoroi-wallets/types' import {Amounts, Entries, Quantities} from '../../yoroi-wallets/utils' type Params = StakingCenterRoutes['delegation-confirmation'] @@ -167,10 +167,10 @@ const messages = defineMessages({ * TODO: based on https://staking.cardano.org/en/calculator/ * needs to be update per-network */ -const approximateReward = (stakedQuantity: Quantity): Quantity => { +const approximateReward = (stakedQuantity: Balance.Quantity): Balance.Quantity => { return Quantities.quotient( Quantities.product([stakedQuantity, `${NETWORKS.HASKELL_SHELLEY.PER_EPOCH_PERCENTAGE_REWARD}`]), - NUMBERS.EPOCH_REWARD_DENOMINATOR.toString() as Quantity, + NUMBERS.EPOCH_REWARD_DENOMINATOR.toString() as Balance.Quantity, ) } diff --git a/apps/wallet-mobile/src/TxHistory/AssetList/AssetList.tsx b/apps/wallet-mobile/src/TxHistory/AssetList/AssetList.tsx index aa365c4cbe..9691043551 100644 --- a/apps/wallet-mobile/src/TxHistory/AssetList/AssetList.tsx +++ b/apps/wallet-mobile/src/TxHistory/AssetList/AssetList.tsx @@ -1,4 +1,5 @@ import {FlashList, FlashListProps} from '@shopify/flash-list' +import {Balance} from '@yoroi/types' import React from 'react' import {defineMessages, useIntl} from 'react-intl' import {Alert, Linking, StyleSheet, TouchableOpacity, View} from 'react-native' @@ -11,11 +12,10 @@ import {usePrivacyMode} from '../../Settings/PrivacyMode/PrivacyMode' import {sortTokenInfos} from '../../utils' import {getNetworkConfigById} from '../../yoroi-wallets/cardano/networks' import {useBalances, useTokenInfos} from '../../yoroi-wallets/hooks' -import {TokenInfo} from '../../yoroi-wallets/types' import {Amounts} from '../../yoroi-wallets/utils' import {ActionsBanner} from './ActionsBanner' -type ListProps = FlashListProps +type ListProps = FlashListProps type Props = Partial & { onScroll: ListProps['onScroll'] refreshing: boolean diff --git a/apps/wallet-mobile/src/TxHistory/PairedBalance.tsx b/apps/wallet-mobile/src/TxHistory/PairedBalance.tsx index da59ea54b4..88d64e085b 100644 --- a/apps/wallet-mobile/src/TxHistory/PairedBalance.tsx +++ b/apps/wallet-mobile/src/TxHistory/PairedBalance.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import * as React from 'react' import {defineMessages, useIntl} from 'react-intl' import {StyleSheet, Text} from 'react-native' @@ -8,12 +9,12 @@ import {useSelectedWallet} from '../SelectedWallet' import {useCurrencyContext} from '../Settings/Currency' import {COLORS} from '../theme' import {useExchangeRate} from '../yoroi-wallets/hooks' -import {CurrencySymbol, YoroiAmount} from '../yoroi-wallets/types' +import {CurrencySymbol} from '../yoroi-wallets/types' import {Quantities} from '../yoroi-wallets/utils' type Props = { privacyMode?: PrivacyMode - amount: YoroiAmount + amount: Balance.Amount } export const PairedBalance = React.forwardRef(({privacyMode, amount}, ref) => { const {currency} = useCurrencyContext() @@ -30,13 +31,13 @@ export const PairedBalance = React.forwardRef(({privacyMod ), }} > - + ) }) const hiddenPairedTotal = '*.**' -const Balance = ({privacyMode, amount}: Props) => { +const PairedAmount = ({privacyMode, amount}: Props) => { const wallet = useSelectedWallet() const {currency, config} = useCurrencyContext() const rate = useExchangeRate({wallet, to: currency}) diff --git a/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx b/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx index e03ba165e6..91da218ea8 100644 --- a/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx +++ b/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import * as React from 'react' import {StyleSheet, View, ViewProps} from 'react-native' @@ -7,13 +8,12 @@ import {PairedBalance} from '../../TxHistory/PairedBalance' import {isEmptyString} from '../../utils' import {YoroiWallet} from '../../yoroi-wallets/cardano/types' import {useTokenInfo} from '../../yoroi-wallets/hooks' -import {YoroiAmount} from '../../yoroi-wallets/types' import {Quantities} from '../../yoroi-wallets/utils' import {Boundary, Placeholder, Text, TokenIcon} from '..' export type AmountItemProps = { wallet: YoroiWallet - amount: YoroiAmount + amount: Balance.Amount style?: ViewProps['style'] privacyMode?: PrivacyMode } diff --git a/apps/wallet-mobile/src/components/NftImageGallery/NftImageGallery.tsx b/apps/wallet-mobile/src/components/NftImageGallery/NftImageGallery.tsx index c6b8de743e..5dd9691c6d 100644 --- a/apps/wallet-mobile/src/components/NftImageGallery/NftImageGallery.tsx +++ b/apps/wallet-mobile/src/components/NftImageGallery/NftImageGallery.tsx @@ -1,4 +1,5 @@ import {FlashList, FlashListProps} from '@shopify/flash-list' +import {Balance} from '@yoroi/types' import React from 'react' import {Dimensions, StyleSheet, TouchableOpacity, TouchableOpacityProps, View} from 'react-native' import SkeletonPlaceholder from 'react-native-skeleton-placeholder' @@ -6,7 +7,6 @@ import SkeletonPlaceholder from 'react-native-skeleton-placeholder' import {features} from '../../features' import {useModeratedNftImage} from '../../Nfts/hooks' import {useSelectedWallet} from '../../SelectedWallet/Context' -import {TokenInfo} from '../../yoroi-wallets/types' import {Icon} from '../Icon' import {NftPreview} from '../NftPreview' import {Spacer} from '../Spacer' @@ -21,12 +21,12 @@ export const SkeletonGallery = ({amount}: {amount: number}) => { } type Props = { - nfts: TokenInfo[] + nfts: Balance.TokenInfo[] onSelect: (id: string) => void onRefresh: () => void isRefreshing: boolean - bounces?: FlashListProps['bounces'] - ListEmptyComponent?: FlashListProps['ListEmptyComponent'] + bounces?: FlashListProps['bounces'] + ListEmptyComponent?: FlashListProps['ListEmptyComponent'] withVerticalPadding?: boolean readOnly?: boolean } @@ -60,7 +60,7 @@ export const NftImageGallery = ({ } type ModeratedImageProps = TouchableOpacityProps & { - nft: TokenInfo + nft: Balance.TokenInfo } const UnModeratedImage = ({nft, ...props}: ModeratedImageProps) => { return ( @@ -112,11 +112,11 @@ const ModeratedImage = ({nft, ...props}: ModeratedImageProps) => { ) } -function BlockedNft({nft}: {nft: TokenInfo}) { +function BlockedNft({nft}: {nft: Balance.TokenInfo}) { return } -function PlaceholderNft({nft}: {nft: TokenInfo}) { +function PlaceholderNft({nft}: {nft: Balance.TokenInfo}) { return ( @@ -137,11 +137,11 @@ function PlaceholderNft({nft}: {nft: TokenInfo}) { ) } -function ManualReviewNft({nft}: {nft: TokenInfo}) { +function ManualReviewNft({nft}: {nft: Balance.TokenInfo}) { return } -function RequiresConsentNft({nft}: {nft: TokenInfo}) { +function RequiresConsentNft({nft}: {nft: Balance.TokenInfo}) { return ( @@ -166,7 +166,7 @@ function RequiresConsentNft({nft}: {nft: TokenInfo}) { ) } -function ApprovedNft({nft}: {nft: TokenInfo}) { +function ApprovedNft({nft}: {nft: Balance.TokenInfo}) { return ( diff --git a/apps/wallet-mobile/src/components/NftPreview/NftPreview.tsx b/apps/wallet-mobile/src/components/NftPreview/NftPreview.tsx index 0d890eee32..b5c46e7bf3 100644 --- a/apps/wallet-mobile/src/components/NftPreview/NftPreview.tsx +++ b/apps/wallet-mobile/src/components/NftPreview/NftPreview.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import React, {useEffect, useState} from 'react' import {ErrorBoundary} from 'react-error-boundary' import {Image, ImageResizeMode, ImageStyle, StyleProp, View} from 'react-native' @@ -6,7 +7,6 @@ import {SvgUri} from 'react-native-svg' import placeholder from '../../assets/img/nft-placeholder.png' import {getNftFilenameMediaType, getNftMainImageMediaType, isSvgMediaType} from '../../yoroi-wallets/cardano/nfts' -import {TokenInfo} from '../../yoroi-wallets/types' import {isString} from '../../yoroi-wallets/utils' export const NftPreview = ({ @@ -19,7 +19,7 @@ export const NftPreview = ({ resizeMode, blurRadius, }: { - nft: TokenInfo + nft: Balance.TokenInfo showPlaceholder?: boolean style?: StyleProp showThumbnail?: boolean diff --git a/apps/wallet-mobile/src/components/TokenIcon/ModeratedNftIcon.tsx b/apps/wallet-mobile/src/components/TokenIcon/ModeratedNftIcon.tsx index d559cfd136..18d4704be0 100644 --- a/apps/wallet-mobile/src/components/TokenIcon/ModeratedNftIcon.tsx +++ b/apps/wallet-mobile/src/components/TokenIcon/ModeratedNftIcon.tsx @@ -1,12 +1,13 @@ +import {Balance} from '@yoroi/types' import React from 'react' import {StyleSheet, View} from 'react-native' -import {TokenInfo, YoroiNftModerationStatus} from '../../yoroi-wallets/types' +import {YoroiNftModerationStatus} from '../../yoroi-wallets/types' import {NftPreview} from '../NftPreview' const ICON_SIZE = 32 -export const ModeratedNftIcon = ({nft, status}: {nft: TokenInfo; status: YoroiNftModerationStatus}) => { +export const ModeratedNftIcon = ({nft, status}: {nft: Balance.TokenInfo; status: YoroiNftModerationStatus}) => { if (status === 'pending') { return } @@ -30,7 +31,7 @@ export const ModeratedNftIcon = ({nft, status}: {nft: TokenInfo; status: YoroiNf return null } -function PlaceholderNftIcon({nft}: {nft: TokenInfo}) { +function PlaceholderNftIcon({nft}: {nft: Balance.TokenInfo}) { return ( } -function ApprovedNftIcon({nft}: {nft: TokenInfo}) { +function ApprovedNftIcon({nft}: {nft: Balance.TokenInfo}) { return ( @@ -57,7 +58,7 @@ function ApprovedNftIcon({nft}: {nft: TokenInfo}) { ) } -function ConsentNftIcon({nft}: {nft: TokenInfo}) { +function ConsentNftIcon({nft}: {nft: Balance.TokenInfo}) { return ( void + amountChanged: (quantity: Balance.Quantity) => void amountRemoved: (tokenId: string) => void receiverChanged: (receiver: string) => void addressChanged: (address: Address) => void @@ -59,7 +60,7 @@ export const SendProvider = ({children, ...props}: {initialState?: Partial dispatch({type: 'yoroiUnsignedTxChanged', yoroiUnsignedTx}), tokenSelectedChanged: (tokenId: string) => dispatch({type: 'tokenSelectedChanged', tokenId}), - amountChanged: (quantity: Quantity) => dispatch({type: 'amountChanged', quantity}), + amountChanged: (quantity: Balance.Quantity) => dispatch({type: 'amountChanged', quantity}), amountRemoved: (tokenId: string) => dispatch({type: 'amountRemoved', tokenId}), }).current @@ -128,7 +129,7 @@ export type TargetAction = } | { type: 'amountChanged' - quantity: Quantity + quantity: Balance.Quantity } | { type: 'amountRemoved' @@ -286,5 +287,5 @@ export const useSelectedSecondaryAmountsCounter = (wallet: YoroiWallet) => { const isSecondaryAmountFilter = (wallet: YoroiWallet) => - ({tokenId}: YoroiAmount) => + ({tokenId}: Balance.Amount) => tokenId !== wallet.primaryTokenInfo.id diff --git a/apps/wallet-mobile/src/features/Send/common/filterByFungibility.test.ts b/apps/wallet-mobile/src/features/Send/common/filterByFungibility.test.ts index d208b28220..c45c57fd2c 100644 --- a/apps/wallet-mobile/src/features/Send/common/filterByFungibility.test.ts +++ b/apps/wallet-mobile/src/features/Send/common/filterByFungibility.test.ts @@ -1,9 +1,10 @@ -import {TokenInfo} from '../../../yoroi-wallets/types' +import {Balance} from '@yoroi/types' + import {FungibilityFilter} from '../useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen' import {filterByFungibility} from './filterByFungibility' describe('filterByFungibility', () => { - const fakeToken1: TokenInfo = { + const fakeToken1: Balance.TokenInfo = { kind: 'ft', id: 'fake-token-1', fingerprint: 'fake-fingerprint-1', @@ -18,7 +19,7 @@ describe('filterByFungibility', () => { symbol: undefined, } as const - const fakeToken2: TokenInfo = { + const fakeToken2: Balance.TokenInfo = { kind: 'ft', id: 'fake-token-2', fingerprint: 'fake-fingerprint-2', @@ -33,7 +34,7 @@ describe('filterByFungibility', () => { symbol: undefined, } as const - const nft1: TokenInfo = { + const nft1: Balance.TokenInfo = { kind: 'nft', id: 'fake-token-3', fingerprint: 'fake-fingerprint-3', @@ -48,7 +49,7 @@ describe('filterByFungibility', () => { symbol: undefined, } as const - const nft2: TokenInfo = { + const nft2: Balance.TokenInfo = { kind: 'nft', id: 'fake-token-4', fingerprint: 'fake-fingerprint-4', @@ -63,11 +64,11 @@ describe('filterByFungibility', () => { symbol: undefined, } as const - const allTokenInfos: TokenInfo[] = [fakeToken1, fakeToken2, nft1, nft2] - const nftTokenInfos: TokenInfo[] = [nft1, nft2] - const ftTokenInfos: TokenInfo[] = [fakeToken1, fakeToken2] + const allTokenInfos: Balance.TokenInfo[] = [fakeToken1, fakeToken2, nft1, nft2] + const nftTokenInfos: Balance.TokenInfo[] = [nft1, nft2] + const ftTokenInfos: Balance.TokenInfo[] = [fakeToken1, fakeToken2] - it.each<{fungibilityFilter: FungibilityFilter; result: TokenInfo[]}>([ + it.each<{fungibilityFilter: FungibilityFilter; result: Balance.TokenInfo[]}>([ { fungibilityFilter: 'all', result: allTokenInfos, diff --git a/apps/wallet-mobile/src/features/Send/common/filterByFungibility.ts b/apps/wallet-mobile/src/features/Send/common/filterByFungibility.ts index 8bc34fdb31..8a923097ac 100644 --- a/apps/wallet-mobile/src/features/Send/common/filterByFungibility.ts +++ b/apps/wallet-mobile/src/features/Send/common/filterByFungibility.ts @@ -1,9 +1,10 @@ -import {TokenInfo} from '../../../yoroi-wallets/types' +import {Balance} from '@yoroi/types' + import {FungibilityFilter} from '../useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen' export const filterByFungibility = ({fungibilityFilter}: {fungibilityFilter: FungibilityFilter}) => { if (fungibilityFilter === 'all') return () => true - if (fungibilityFilter === 'nft') return (tokenInfo: TokenInfo) => tokenInfo.kind === 'nft' - if (fungibilityFilter === 'ft') return (tokenInfo: TokenInfo) => tokenInfo.kind === 'ft' + if (fungibilityFilter === 'nft') return (tokenInfo: Balance.TokenInfo) => tokenInfo.kind === 'nft' + if (fungibilityFilter === 'ft') return (tokenInfo: Balance.TokenInfo) => tokenInfo.kind === 'ft' return () => true } diff --git a/apps/wallet-mobile/src/features/Send/common/filterBySearch.test.ts b/apps/wallet-mobile/src/features/Send/common/filterBySearch.test.ts index 0178a7b66b..66acbf11cc 100644 --- a/apps/wallet-mobile/src/features/Send/common/filterBySearch.test.ts +++ b/apps/wallet-mobile/src/features/Send/common/filterBySearch.test.ts @@ -1,8 +1,9 @@ -import {TokenInfo} from '../../../yoroi-wallets/types' +import {Balance} from '@yoroi/types' + import {filterBySearch} from './filterBySearch' describe('filterBySearch', () => { - const fakeToken1: TokenInfo = { + const fakeToken1: Balance.TokenInfo = { id: '', kind: 'ft', name: 'TADANAME', @@ -17,7 +18,7 @@ describe('filterBySearch', () => { metadatas: {}, } as const - const fakeToken2: TokenInfo = { + const fakeToken2: Balance.TokenInfo = { kind: 'ft', id: '698a6ea0ca99f315034072af31eaac6ec11fe8558d3f48e9775aab9d.7444524950', fingerprint: 'asset1nvcwnq60jnm27efjm87xnhqt6alsv024tdyxjm', @@ -32,7 +33,7 @@ describe('filterBySearch', () => { metadatas: {}, } - const tokenInfos: TokenInfo[] = [fakeToken1, fakeToken2] + const tokenInfos: Balance.TokenInfo[] = [fakeToken1, fakeToken2] it('should return all tokenInfos if searchTerm is empty', () => { const searchTerm = '' diff --git a/apps/wallet-mobile/src/features/Send/common/filterBySearch.ts b/apps/wallet-mobile/src/features/Send/common/filterBySearch.ts index 574652c433..f0db9ef219 100644 --- a/apps/wallet-mobile/src/features/Send/common/filterBySearch.ts +++ b/apps/wallet-mobile/src/features/Send/common/filterBySearch.ts @@ -1,10 +1,10 @@ -import {TokenInfo} from '../../../yoroi-wallets/types' +import {Balance} from '@yoroi/types' export const filterBySearch = (searchTerm: string) => { const searchTermLowerCase = searchTerm.toLocaleLowerCase() if (searchTermLowerCase.length === 0) return () => true - return (tokenInfo: TokenInfo) => { + return (tokenInfo: Balance.TokenInfo) => { if (tokenInfo.kind === 'ft') { return ( (tokenInfo.ticker?.toLocaleLowerCase()?.includes(searchTermLowerCase) || diff --git a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/Summary/SecondaryTotals.tsx b/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/Summary/SecondaryTotals.tsx index 5d841861c7..0449594de9 100644 --- a/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/Summary/SecondaryTotals.tsx +++ b/apps/wallet-mobile/src/features/Send/useCases/ConfirmTx/Summary/SecondaryTotals.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import * as React from 'react' import {StyleSheet, View} from 'react-native' @@ -8,7 +9,7 @@ import {useSelectedWallet} from '../../../../../SelectedWallet/Context/SelectedW import {COLORS} from '../../../../../theme/config' import {YoroiWallet} from '../../../../../yoroi-wallets/cardano/types' import {useToken} from '../../../../../yoroi-wallets/hooks' -import {YoroiAmount, YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' +import {YoroiUnsignedTx} from '../../../../../yoroi-wallets/types/yoroi' import {Amounts, Quantities} from '../../../../../yoroi-wallets/utils/utils' export const SecondaryTotals = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsignedTx}) => { @@ -29,7 +30,7 @@ export const SecondaryTotals = ({yoroiUnsignedTx}: {yoroiUnsignedTx: YoroiUnsign ) } -const Amount = ({amount, wallet}: {amount: YoroiAmount; wallet: YoroiWallet}) => { +const Amount = ({amount, wallet}: {amount: Balance.Amount; wallet: YoroiWallet}) => { const token = useToken({wallet, tokenId: amount.tokenId}) return {formatTokenWithText(amount.quantity, token)} diff --git a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen.tsx index b92704ae14..50fdc6257b 100644 --- a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen.tsx +++ b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/AddToken/SelectTokenFromListScreen.tsx @@ -1,5 +1,6 @@ import {useNavigation} from '@react-navigation/native' import {FlashList} from '@shopify/flash-list' +import {Balance} from '@yoroi/types' import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' @@ -15,7 +16,6 @@ import {sortTokenInfos} from '../../../../../utils' import {YoroiWallet} from '../../../../../yoroi-wallets/cardano/types' import {limitOfSecondaryAmountsPerTx} from '../../../../../yoroi-wallets/contants' import {useAllTokenInfos, useBalances, useIsWalletEmpty, useNfts} from '../../../../../yoroi-wallets/hooks' -import {TokenInfo} from '../../../../../yoroi-wallets/types' import {Amounts, Quantities} from '../../../../../yoroi-wallets/utils' import {filterByFungibility} from '../../../common/filterByFungibility' import {filterBySearch} from '../../../common/filterBySearch' @@ -147,7 +147,7 @@ const AssetList = ({canAddAmount, fungibilityFilter}: AssetListProps) => { ( + renderItem={({item: tokenInfo}: {item: Balance.TokenInfo}) => ( ( ) -type SelectableAssetItemProps = {disabled?: boolean; tokenInfo: TokenInfo; wallet: YoroiWallet} +type SelectableAssetItemProps = {disabled?: boolean; tokenInfo: Balance.TokenInfo; wallet: YoroiWallet} const SelectableAssetItem = ({tokenInfo, disabled, wallet}: SelectableAssetItemProps) => { const {closeSearch} = useSearch() const {tokenSelectedChanged, amountChanged} = useSend() @@ -240,8 +240,8 @@ const ListEmptyComponent = ({ filteredTokenInfos, allTokenInfos, }: { - filteredTokenInfos: Array - allTokenInfos: Array + filteredTokenInfos: Array + allTokenInfos: Array }) => { const {search: assetSearchTerm, visible: isSearching} = useSearch() const wallet = useSelectedWallet() @@ -342,7 +342,7 @@ const useFilteredTokenInfos = ({ tokenInfos, }: { fungibilityFilter: FungibilityFilter - tokenInfos: Array + tokenInfos: Array }) => { const wallet = useSelectedWallet() const {search: assetSearchTerm, visible: isSearching} = useSearch() @@ -376,7 +376,7 @@ const useFilteredTokenInfos = ({ const areAllTokensSelected = (selectedTokenIds: Array, tokenInfos): boolean => tokenInfos.every((tokenInfo) => selectedTokenIds.includes(tokenInfo.id)) -const filterOutSelected = (selectedTokenIds: Array) => (token: TokenInfo) => +const filterOutSelected = (selectedTokenIds: Array) => (token: Balance.TokenInfo) => !selectedTokenIds.includes(token.id) const sortNfts = (nftNameA: string, nftNameB: string): number => nftNameA.localeCompare(nftNameB) diff --git a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/EditAmount/EditAmountScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/EditAmount/EditAmountScreen.tsx index b652b02fb4..2d4a6b9849 100644 --- a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/EditAmount/EditAmountScreen.tsx +++ b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/EditAmount/EditAmountScreen.tsx @@ -1,3 +1,4 @@ +import {Balance} from '@yoroi/types' import * as React from 'react' import { KeyboardAvoidingView, @@ -18,7 +19,6 @@ import {PairedBalance} from '../../../../../TxHistory/PairedBalance' import {selectFtOrThrow} from '../../../../../yoroi-wallets/cardano/utils' import {useTokenInfo} from '../../../../../yoroi-wallets/hooks' import {Logger} from '../../../../../yoroi-wallets/logging' -import {Quantity} from '../../../../../yoroi-wallets/types' import {asQuantity, editedFormatter, pastedFormatter, Quantities} from '../../../../../yoroi-wallets/utils' import {useNavigateTo, useOverridePreviousSendTxRoute} from '../../../common/navigation' import {useSend, useTokenQuantities} from '../../../common/SendContext' @@ -36,7 +36,7 @@ export const EditAmountScreen = () => { const tokenInfo = useTokenInfo({wallet, tokenId: selectedTokenId}, {select: selectFtOrThrow}) const isPrimary = tokenInfo.id === wallet.primaryTokenInfo.id - const [quantity, setQuantity] = React.useState(initialQuantity) + const [quantity, setQuantity] = React.useState(initialQuantity) const [inputValue, setInputValue] = React.useState( Quantities.denominated(initialQuantity, tokenInfo.decimals ?? 0), ) diff --git a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx index 8424f2a76e..4cdc46142d 100644 --- a/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx +++ b/apps/wallet-mobile/src/features/Send/useCases/ListAmountsToSend/ListAmountsToSendScreen.tsx @@ -1,4 +1,5 @@ import {useNavigation} from '@react-navigation/native' +import type {Balance} from '@yoroi/types' import * as React from 'react' import {useLayoutEffect} from 'react' import {defineMessages, useIntl} from 'react-intl' @@ -17,7 +18,7 @@ import {COLORS} from '../../../../theme' import {sortTokenInfos} from '../../../../utils' import {YoroiWallet} from '../../../../yoroi-wallets/cardano/types' import {useTokenInfo, useTokenInfos} from '../../../../yoroi-wallets/hooks' -import {TokenInfo, YoroiAmount, YoroiEntry, YoroiUnsignedTx} from '../../../../yoroi-wallets/types' +import {YoroiEntry, YoroiUnsignedTx} from '../../../../yoroi-wallets/types' import {Amounts} from '../../../../yoroi-wallets/utils' import {useNavigateTo, useOverridePreviousSendTxRoute} from '../../common/navigation' import {useSend} from '../../common/SendContext' @@ -65,7 +66,7 @@ export const ListAmountsToSendScreen = () => { const onEdit = (tokenId: string) => { const tokenInfo = tokenInfos.find((tokenInfo) => tokenInfo.id === tokenId) - if (!tokenInfo || tokenInfo.kind === 'nft') return + if (tokenInfo?.kind === 'nft') return tokenSelectedChanged(tokenId) navigateTo.editAmount() @@ -91,7 +92,7 @@ export const ListAmountsToSendScreen = () => { ( + renderItem={({item: {id}}: {item: Balance.TokenInfo}) => ( @@ -117,7 +118,7 @@ export const ListAmountsToSendScreen = () => { } type ActionableAmountProps = { - amount: YoroiAmount + amount: Balance.Amount onEdit(tokenId: string): void onRemove(tokenId: string): void } diff --git a/apps/wallet-mobile/src/legacy/format.ts b/apps/wallet-mobile/src/legacy/format.ts index 5a9b1ad641..6c70dd1965 100644 --- a/apps/wallet-mobile/src/legacy/format.ts +++ b/apps/wallet-mobile/src/legacy/format.ts @@ -1,11 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import AssetFingerprint from '@emurgo/cip14-js' +import {Balance} from '@yoroi/types' import {BigNumber} from 'bignumber.js' import type {IntlShape} from 'react-intl' import {defineMessages} from 'react-intl' import {isTokenInfo} from '../yoroi-wallets/cardano/utils' -import {DefaultAsset, Quantity, Token, TokenInfo} from '../yoroi-wallets/types' +import {DefaultAsset, Token} from '../yoroi-wallets/types' import utfSymbols from './utfSymbols' export const getTokenFingerprint = ({policyId, assetNameHex}) => { @@ -23,18 +24,18 @@ export const decodeHexAscii = (text: string) => { return isAscii ? String.fromCharCode(...bytes) : undefined } -const getTicker = (token: TokenInfo | DefaultAsset) => { +const getTicker = (token: Balance.TokenInfo | DefaultAsset) => { if (isTokenInfo(token)) { return token.kind === 'ft' ? token.ticker : undefined } return token.metadata.ticker } -const getSymbol = (token: TokenInfo | DefaultAsset) => { +const getSymbol = (token: Balance.TokenInfo | DefaultAsset) => { const ticker = getTicker(token) return (ticker && utfSymbols.CURRENCIES[ticker]) ?? ticker } -const getName = (token: TokenInfo | DefaultAsset) => { +const getName = (token: Balance.TokenInfo | DefaultAsset) => { if (isTokenInfo(token)) { return token.name || token.ticker || token.fingerprint || '' } @@ -49,26 +50,26 @@ const getName = (token: TokenInfo | DefaultAsset) => { ) } -export const getDecimals = (token: TokenInfo | DefaultAsset) => { +export const getDecimals = (token: Balance.TokenInfo | DefaultAsset) => { if (isTokenInfo(token)) { return token.kind === 'nft' ? 0 : token.decimals } return token.metadata.numberOfDecimals } -export const normalizeTokenAmount = (amount: Quantity, token: TokenInfo | DefaultAsset): BigNumber => { +export const normalizeTokenAmount = (amount: Balance.Quantity, token: Balance.TokenInfo | DefaultAsset): BigNumber => { const decimals = getDecimals(token) ?? 0 const normalizationFactor = Math.pow(10, decimals) return new BigNumber(amount).dividedBy(normalizationFactor).decimalPlaces(decimals) } -export const formatTokenAmount = (amount: Quantity, token: TokenInfo | DefaultAsset): string => { +export const formatTokenAmount = (amount: Balance.Quantity, token: Balance.TokenInfo | DefaultAsset): string => { const decimals = getDecimals(token) const normalized = normalizeTokenAmount(amount, token) return normalized.toFormat(decimals) } -const getTokenV2Fingerprint = (token: TokenInfo | DefaultAsset): string => { +const getTokenV2Fingerprint = (token: Balance.TokenInfo | DefaultAsset): string => { if (isTokenInfo(token)) { return token.fingerprint } @@ -78,14 +79,14 @@ const getTokenV2Fingerprint = (token: TokenInfo | DefaultAsset): string => { }) } -export const formatTokenWithSymbol = (amount: Quantity, token: TokenInfo | DefaultAsset): string => { +export const formatTokenWithSymbol = (amount: Balance.Quantity, token: Balance.TokenInfo | DefaultAsset): string => { const denomination = getSymbol(token) ?? getTokenV2Fingerprint(token) return `${formatTokenAmount(amount, token)}${utfSymbols.NBSP}${denomination}` } // We assume that tickers are non-localized. If ticker doesn't exist, default // to identifier -export const formatTokenWithText = (amount: Quantity, token: TokenInfo | DefaultAsset) => { +export const formatTokenWithText = (amount: Balance.Quantity, token: Balance.TokenInfo | DefaultAsset) => { if (isTokenInfo(token)) { switch (token.kind) { case 'nft': @@ -99,12 +100,12 @@ export const formatTokenWithText = (amount: Quantity, token: TokenInfo | Default return `${formatTokenAmount(amount, token)}${utfSymbols.NBSP}${tickerOrId}` } -export const formatTokenWithTextWhenHidden = (text: string, token: TokenInfo | DefaultAsset) => { +export const formatTokenWithTextWhenHidden = (text: string, token: Balance.TokenInfo | DefaultAsset) => { const tickerOrId = getTicker(token) || getName(token) || getTokenV2Fingerprint(token) return `${text}${utfSymbols.NBSP}${tickerOrId}` } -export const formatTokenInteger = (amount: Quantity, token: Token | DefaultAsset) => { +export const formatTokenInteger = (amount: Balance.Quantity, token: Token | DefaultAsset) => { const normalizationFactor = Math.pow(10, token.metadata.numberOfDecimals) const bigNumber = new BigNumber(amount) const num = bigNumber.dividedToIntegerBy(normalizationFactor) @@ -117,7 +118,7 @@ export const formatTokenInteger = (amount: Quantity, token: Token | DefaultAsset } } -export const formatTokenFractional = (amount: Quantity, token: Token | DefaultAsset) => { +export const formatTokenFractional = (amount: Balance.Quantity, token: Token | DefaultAsset) => { const normalizationFactor = Math.pow(10, token.metadata.numberOfDecimals) const fractional = new BigNumber(amount).abs().modulo(normalizationFactor).dividedBy(normalizationFactor) // remove leading '0' @@ -134,14 +135,14 @@ export const truncateWithEllipsis = (s: string, n: number) => { // TODO(multi-asset): consider removing these -const formatAda = (amount: Quantity, defaultAsset: DefaultAsset) => { +const formatAda = (amount: Balance.Quantity, defaultAsset: DefaultAsset) => { const defaultAssetMeta = defaultAsset.metadata const normalizationFactor = Math.pow(10, defaultAssetMeta.numberOfDecimals) const num = new BigNumber(amount).dividedBy(normalizationFactor) return num.toFormat(6) } -export const formatAdaWithText = (amount: Quantity, defaultAsset: DefaultAsset) => { +export const formatAdaWithText = (amount: Balance.Quantity, defaultAsset: DefaultAsset) => { const defaultAssetMeta = defaultAsset.metadata return `${formatAda(amount, defaultAsset)}${utfSymbols.NBSP}${defaultAssetMeta.ticker}` } diff --git a/apps/wallet-mobile/src/metrics/helpers.ts b/apps/wallet-mobile/src/metrics/helpers.ts index 57a3e02e3f..2b6306483a 100644 --- a/apps/wallet-mobile/src/metrics/helpers.ts +++ b/apps/wallet-mobile/src/metrics/helpers.ts @@ -1,8 +1,8 @@ -import {TokenInfo, YoroiAmounts} from '../yoroi-wallets/types' +import {Balance} from '@yoroi/types' type AssetList = { - tokens: TokenInfo[] - amounts: YoroiAmounts + tokens: Balance.TokenInfo[] + amounts: Balance.Amounts } export const assetsToSendProperties = ({tokens, amounts}: AssetList) => { diff --git a/apps/wallet-mobile/src/utils/sorting.ts b/apps/wallet-mobile/src/utils/sorting.ts index f1c25a4af3..96814bb664 100644 --- a/apps/wallet-mobile/src/utils/sorting.ts +++ b/apps/wallet-mobile/src/utils/sorting.ts @@ -1,7 +1,14 @@ +import {Balance} from '@yoroi/types' + import {YoroiWallet} from '../yoroi-wallets/cardano/types' -import {TokenInfo} from '../yoroi-wallets/types' -export const sortTokenInfos = ({wallet, tokenInfos}: {wallet: YoroiWallet; tokenInfos: TokenInfo[]}): TokenInfo[] => +export const sortTokenInfos = ({ + wallet, + tokenInfos, +}: { + wallet: YoroiWallet + tokenInfos: Balance.TokenInfo[] +}): Balance.TokenInfo[] => tokenInfos .sort( alpha((tokenInfo) => { diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.test.ts index 5f76ed17bb..506d4a9399 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.test.ts @@ -1,6 +1,6 @@ -import {TokenInfo} from '../../types' -import {parseNFTs} from './metadata' +import {Balance} from '@yoroi/types' +import {parseNFTs} from './metadata' const storageUrl = 'https://example.com' describe('parseNFTs', () => { it('throws when given a value that is not an object', () => { @@ -30,7 +30,7 @@ describe('parseNFTs', () => { it('resolves with placeholder data if key 721 is present and metadata is not', () => { const result = parseNFTs({'8e2c7604711faef7c84c91b286c7327d17df825b7f0c88ec0332c0b4.0': [{key: '721'}]}, storageUrl) - const expectedValue: Partial = { + const expectedValue: Partial = { id: '8e2c7604711faef7c84c91b286c7327d17df825b7f0c88ec0332c0b4.30', name: '0', } @@ -46,7 +46,7 @@ describe('parseNFTs', () => { }, storageUrl, ) - const expectedValue: Partial = { + const expectedValue: Partial = { id: '8e2c7604711faef7c84c91b286c7327d17df825b7f0c88ec0332c0b4.30', name: 'Name', } diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.ts index 71a52661c5..7a012fb102 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/metadata.ts @@ -1,13 +1,14 @@ +import {Balance} from '@yoroi/types' import {z} from 'zod' -import {BackendConfig, NFTAsset, TokenInfo} from '../../types' +import {BackendConfig, NFTAsset} from '../../types' import {createTypeGuardFromSchema, isArray, isNonNullable, isRecord} from '../../utils' import {convertNft} from '../nfts' import {fetchTokensSupplies} from './assetSuply' import fetchDefault from './fetch' import {toAssetNameHex, toPolicyId} from './utils' -export const getNFTs = async (ids: string[], config: BackendConfig): Promise => { +export const getNFTs = async (ids: string[], config: BackendConfig): Promise => { if (ids.length === 0) { return [] } @@ -28,19 +29,19 @@ export const getNFTs = async (ids: string[], config: BackendConfig): Promise assetSupplies[nft.id] === 1) } -export const getNFT = async (id: string, config: BackendConfig): Promise => { +export const getNFT = async (id: string, config: BackendConfig): Promise => { const [nft] = await getNFTs([id], config) return nft || null } -export const parseNFTs = (value: unknown, storageUrl: string): TokenInfo[] => { +export const parseNFTs = (value: unknown, storageUrl: string): Balance.TokenInfo[] => { if (!isRecord(value)) { throw new Error('Invalid response. Expected to receive object when parsing NFTs') } const identifiers = Object.keys(value) - const tokens: Array = identifiers.map((id) => { + const tokens: Array = identifiers.map((id) => { const assets = value[id] if (!isArray(assets)) { return null diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/tokenRegistry.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/tokenRegistry.ts index 3e19652baf..c7e3d4a4fb 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/tokenRegistry.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/tokenRegistry.ts @@ -1,13 +1,18 @@ +import {Balance} from '@yoroi/types' import {z} from 'zod' import {promiseAny} from '../../../utils' -import {BackendConfig, TokenInfo} from '../../types' +import {BackendConfig} from '../../types' import {createTypeGuardFromSchema} from '../../utils' import {checkedFetch} from './fetch' import {getNFT} from './metadata' import {fallbackTokenInfo, tokenInfo, toTokenSubject} from './utils' -export const getTokenInfo = async (tokenId: string, apiUrl: string, config: BackendConfig): Promise => { +export const getTokenInfo = async ( + tokenId: string, + apiUrl: string, + config: BackendConfig, +): Promise => { const nftPromise = getNFT(tokenId, config).then((nft) => { if (!nft) throw new Error('NFT not found') return nft @@ -26,7 +31,7 @@ export const getTokenInfo = async (tokenId: string, apiUrl: string, config: Back }) try { - const result = await promiseAny([nftPromise, tokenPromise]) + const result = await promiseAny([nftPromise, tokenPromise]) return result ?? fallbackTokenInfo(tokenId) } catch (e) { return fallbackTokenInfo(tokenId) diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.test.ts index 3179e4d086..14f81d82be 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.test.ts @@ -1,4 +1,5 @@ -import {TokenInfo} from '../../types' +import {Balance} from '@yoroi/types' + import {TokenRegistryEntry} from './tokenRegistry' import { fallbackTokenInfo, @@ -148,7 +149,7 @@ describe('api utils', () => { }, } - expect(tokenInfo(entry)).toEqual({ + expect(tokenInfo(entry)).toEqual({ kind: 'ft', id: '11111111111111111111111111111111111111111111111111111111.61737365744e616d65', name: 'assetName', @@ -188,7 +189,7 @@ describe('api utils', () => { }, } - expect(tokenInfo(entry)).toEqual({ + expect(tokenInfo(entry)).toEqual({ kind: 'ft', id: '11111111111111111111111111111111111111111111111111111111.61737365744e616d65', name: 'assetName', @@ -214,7 +215,7 @@ describe('api utils', () => { }) it('fallback', () => { - expect(fallbackTokenInfo('11111111111111111111111111111111111111111111111111111111')).toEqual({ + expect(fallbackTokenInfo('11111111111111111111111111111111111111111111111111111111')).toEqual({ kind: 'ft', id: '11111111111111111111111111111111111111111111111111111111.', fingerprint: 'asset17jfppv3h7hnsjfqq5lyp52dyhwstfv9e4uauga', @@ -231,7 +232,7 @@ describe('api utils', () => { expect( fallbackTokenInfo('1111111111111111111111111111111111111111111111111111111161737365744e616d65'), - ).toEqual({ + ).toEqual({ kind: 'ft', id: '11111111111111111111111111111111111111111111111111111111.61737365744e616d65', fingerprint: 'asset1rafllrcpcurgdkesxy9vsvh40cgz2vrndle80x', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.ts index 116a053f31..edf71912ae 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/api/utils.ts @@ -1,10 +1,11 @@ import AssetFingerprint from '@emurgo/cip14-js' +import {Balance} from '@yoroi/types' import {Buffer} from 'memfs/lib/internal/buffer' -import {LegacyToken, TokenInfo} from '../../types' +import {LegacyToken} from '../../types' import {TokenRegistryEntry} from './tokenRegistry' -export const tokenInfo = (entry: TokenRegistryEntry): TokenInfo => { +export const tokenInfo = (entry: TokenRegistryEntry): Balance.TokenInfo => { const policyId = toPolicyId(entry.subject) const assetName = toAssetName(entry.subject) @@ -36,7 +37,7 @@ export const tokenInfo = (entry: TokenRegistryEntry): TokenInfo => { } } -export const fallbackTokenInfo = (tokenId: string): TokenInfo => { +export const fallbackTokenInfo = (tokenId: string): Balance.TokenInfo => { const policyId = toPolicyId(tokenId) const assetName = toAssetName(tokenId) @@ -79,7 +80,7 @@ export const toTokenId = (tokenIdentifier: string) => { export const hexToUtf8 = (hex: string) => Buffer.from(hex, 'hex').toString('utf-8') export const utf8ToHex = (text: string) => Buffer.from(text, 'utf-8').toString('hex') -export const toTokenInfo = (token: LegacyToken): TokenInfo => { +export const toTokenInfo = (token: LegacyToken): Balance.TokenInfo => { const policyId = toPolicyId(token.identifier) const assetName = toAssetName(token.identifier) diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/byron/ByronWallet.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/byron/ByronWallet.ts index 7a2abc9b1e..4f2e319ef6 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/byron/ByronWallet.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/byron/ByronWallet.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import * as yoroiLib from '@emurgo/yoroi-lib' +import {Balance} from '@yoroi/types' import assert from 'assert' import {BigNumber} from 'bignumber.js' import ExtendableError from 'es6-error' @@ -22,11 +23,9 @@ import { NETWORK_REGISTRY, NetworkId, PoolInfoRequest, - Quantity, RawUtxo, StakingInfo, TipStatusResponse, - TokenInfo, Transaction, TxStatusRequest, TxStatusResponse, @@ -124,7 +123,7 @@ const implementationId = WALLET_IMPLEMENTATION_REGISTRY.HASKELL_BYRON export class ByronWallet implements YoroiWallet { readonly primaryToken: DefaultAsset - readonly primaryTokenInfo: TokenInfo + readonly primaryTokenInfo: Balance.TokenInfo readonly id: string readonly networkId: NetworkId readonly walletImplementationId: WalletImplementationId @@ -648,15 +647,15 @@ export class ByronWallet implements YoroiWallet { const stakingUtxos = await this.getAllUtxosForKey() const amount = Quantities.sum([ - ...stakingUtxos.map((utxo) => utxo.amount as Quantity), - accountState.remainingAmount as Quantity, + ...stakingUtxos.map((utxo) => utxo.amount as Balance.Quantity), + accountState.remainingAmount as Balance.Quantity, ]) return { status: 'staked', poolId: stakingStatus.poolKeyHash, amount, - rewards: accountState.remainingAmount as Quantity, + rewards: accountState.remainingAmount as Balance.Quantity, } } @@ -1342,7 +1341,7 @@ const keys: Array = [ 'lastGeneratedAddressIndex', ] -export const primaryTokenInfo: Record<'mainnet' | 'testnet', TokenInfo> = { +export const primaryTokenInfo: Record<'mainnet' | 'testnet', Balance.TokenInfo> = { mainnet: { id: '', name: 'ADA', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/mainnet/constants.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/mainnet/constants.ts index 4c086bcf39..06df309b7d 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/mainnet/constants.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/mainnet/constants.ts @@ -1,4 +1,6 @@ -import {DefaultAsset, TokenInfo} from '../../../types' +import {Balance} from '@yoroi/types' + +import {DefaultAsset} from '../../../types' import {COIN_TYPE, COINS_PER_UTXO_WORD, KEY_DEPOSIT, LINEAR_FEE, MINIMUM_UTXO_VAL, POOL_DEPOSIT} from '../common' export * from '../common' @@ -74,7 +76,7 @@ export const NETWORK_CONFIG = { COINS_PER_UTXO_WORD, } as const -export const PRIMARY_TOKEN_INFO: TokenInfo = { +export const PRIMARY_TOKEN_INFO: Balance.TokenInfo = { kind: 'ft', id: '', name: 'ADA', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/testnet/constants.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/testnet/constants.ts index f5f238914a..98b03733d6 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/testnet/constants.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/constants/testnet/constants.ts @@ -1,4 +1,6 @@ -import {DefaultAsset, TokenInfo} from '../../../types' +import {Balance} from '@yoroi/types' + +import {DefaultAsset} from '../../../types' import {COIN_TYPE, COINS_PER_UTXO_WORD, KEY_DEPOSIT, LINEAR_FEE, MINIMUM_UTXO_VAL, POOL_DEPOSIT} from '../common' export * from '../common' @@ -72,7 +74,7 @@ export const NETWORK_CONFIG = { COINS_PER_UTXO_WORD, } as const -export const PRIMARY_TOKEN_INFO: TokenInfo = { +export const PRIMARY_TOKEN_INFO: Balance.TokenInfo = { id: '', name: 'TADA', description: 'Cardano', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/getMinAmounts.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/getMinAmounts.ts index b77b7819b9..0c85119c7a 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/getMinAmounts.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/getMinAmounts.ts @@ -1,14 +1,15 @@ import {BigNum} from '@emurgo/cross-csl-core' +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' -import {Token, YoroiAmounts} from '../types' +import {Token} from '../types' import {Amounts, asQuantity, Quantities} from '../utils' import {CardanoMobile} from '../wallets' import {COINS_PER_UTXO_WORD} from './constants/common' import {MultiToken} from './MultiToken' import {cardanoValueFromMultiToken} from './utils' -export const withMinAmounts = async (amounts: YoroiAmounts, primaryToken: Token): Promise => { +export const withMinAmounts = async (amounts: Balance.Amounts, primaryToken: Token): Promise => { const amountsWithPrimaryToken = withPrimaryToken(amounts, primaryToken) const minAmounts = await getMinAmounts(amountsWithPrimaryToken, primaryToken) @@ -18,7 +19,7 @@ export const withMinAmounts = async (amounts: YoroiAmounts, primaryToken: Token) })) } -export const getMinAmounts = async (amounts: YoroiAmounts, primaryToken: Token) => { +export const getMinAmounts = async (amounts: Balance.Amounts, primaryToken: Token) => { const multiToken = new MultiToken( [ {identifier: primaryToken.identifier, networkId: primaryToken.networkId, amount: new BigNumber('0')}, @@ -42,10 +43,10 @@ export const getMinAmounts = async (amounts: YoroiAmounts, primaryToken: Token) return { [primaryToken.identifier]: minAda, - } as YoroiAmounts + } as Balance.Amounts } -const withPrimaryToken = (amounts: YoroiAmounts, primaryToken: Token): YoroiAmounts => { +const withPrimaryToken = (amounts: Balance.Amounts, primaryToken: Token): Balance.Amounts => { if (Amounts.includes(amounts, primaryToken.identifier)) return amounts return { diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.test.ts index 00474e32eb..21d5076aa7 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.test.ts @@ -1,5 +1,6 @@ +import {Balance} from '@yoroi/types' + import {nft} from '../mocks' -import {TokenInfo} from '../types' import {convertNft, getNftFilenameMediaType} from './nfts' describe('convertNft', () => { @@ -265,7 +266,7 @@ describe('getNftFilenameMediaType', () => { }) }) -const getNftWithCustomOriginalMetadata = (metadata: unknown): TokenInfo => ({ +const getNftWithCustomOriginalMetadata = (metadata: unknown): Balance.TokenInfo => ({ ...nft, metadatas: {mintNft: metadata}, }) diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.ts index 0a296469c5..c8aba27a48 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/nfts.ts @@ -1,8 +1,8 @@ +import {Balance} from '@yoroi/types' import {z} from 'zod' import {features} from '../../features' import {getAssetFingerprint} from '../../legacy/format' -import {TokenInfo} from '../types' import {createTypeGuardFromSchema, isArrayOfType, isString} from '../utils' import {utf8ToHex} from './api/utils' @@ -11,7 +11,7 @@ export const convertNft = (options: { storageUrl: string policyId: string shortName: string -}): TokenInfo => { +}): Balance.TokenInfo => { const {metadata, storageUrl, policyId, shortName} = options const assetNameHex = utf8ToHex(shortName) const fingerprint = getAssetFingerprint(policyId, assetNameHex) @@ -50,12 +50,12 @@ export const isSvgMediaType = (mediaType: unknown): boolean => { return mediaType === 'image/svg+xml' } -export const getNftMainImageMediaType = (nft: TokenInfo): string | undefined => { +export const getNftMainImageMediaType = (nft: Balance.TokenInfo): string | undefined => { const originalMetadata = nft.metadatas.mintNft return hasMediaTypeProperty(originalMetadata) ? normalizeProperty(originalMetadata.mediaType) : undefined } -export const getNftFilenameMediaType = (nft: TokenInfo, filename: string): string | undefined => { +export const getNftFilenameMediaType = (nft: Balance.TokenInfo, filename: string): string | undefined => { const originalMetadata = nft.metadatas.mintNft if (!hasFilesProperty(originalMetadata)) { diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/ShelleyWallet.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/ShelleyWallet.ts index 0e50ecf7ab..8d0b7e7b51 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/ShelleyWallet.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/ShelleyWallet.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import {Balance} from '@yoroi/types' import assert from 'assert' import {BigNumber} from 'bignumber.js' import ExtendableError from 'es6-error' @@ -20,14 +21,13 @@ import type { PoolInfoRequest, RawUtxo, TipStatusResponse, - TokenInfo, Transaction, TxStatusRequest, TxStatusResponse, YoroiEntry, YoroiNftModerationStatus, } from '../../types' -import {Quantity, StakingInfo, YoroiSignedTx, YoroiUnsignedTx} from '../../types' +import {StakingInfo, YoroiSignedTx, YoroiUnsignedTx} from '../../types' import {Quantities} from '../../utils' import {parseSafe} from '../../utils/parsing' import {validatePassword} from '../../utils/validators' @@ -151,7 +151,7 @@ export const makeShelleyWallet = (constants: typeof MAINNET | typeof TESTNET) => return class ShelleyWallet implements YoroiWallet { readonly primaryToken: DefaultAsset = PRIMARY_TOKEN - readonly primaryTokenInfo: TokenInfo = PRIMARY_TOKEN_INFO + readonly primaryTokenInfo: Balance.TokenInfo = PRIMARY_TOKEN_INFO readonly walletImplementationId = WALLET_IMPLEMENTATION_ID readonly networkId = NETWORK_ID readonly id: string @@ -566,15 +566,15 @@ export const makeShelleyWallet = (constants: typeof MAINNET | typeof TESTNET) => const stakingUtxos = await this.getAllUtxosForKey() const amount = Quantities.sum([ - ...stakingUtxos.map((utxo) => utxo.amount as Quantity), - accountState.remainingAmount as Quantity, + ...stakingUtxos.map((utxo) => utxo.amount as Balance.Quantity), + accountState.remainingAmount as Balance.Quantity, ]) return { status: 'staked', poolId: stakingStatus.poolKeyHash, amount, - rewards: accountState.remainingAmount as Quantity, + rewards: accountState.remainingAmount as Balance.Quantity, } } diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/mainnet/ShelleyWallet.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/mainnet/ShelleyWallet.test.ts index 458b2a8cec..14b8a9f1b4 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/mainnet/ShelleyWallet.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/mainnet/ShelleyWallet.test.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import AsyncStorage from '@react-native-async-storage/async-storage' +import {Balance} from '@yoroi/types' import {HWDeviceInfo} from '../../../../hw' import {EncryptedStorage, EncryptedStorageKeys, storage} from '../../../../storage' -import {DefaultAsset, TokenInfo} from '../../../../types' +import {DefaultAsset} from '../../../../types' import {WalletMeta} from '../../../../walletManager' import {ShelleyAddressGeneratorJSON} from '../../../chain' import {YoroiWallet} from '../../../types' @@ -105,7 +106,7 @@ describe('ShelleyWallet', () => { }, networkId: 1, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', description: 'Cardano', id: '', @@ -218,7 +219,7 @@ describe('ShelleyWallet', () => { }, networkId: 1, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', description: 'Cardano', id: '', @@ -321,7 +322,7 @@ describe('ShelleyWallet', () => { }, networkId: 1, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', description: 'Cardano', id: '', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/testnet/ShelleyWalletTestnet.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/testnet/ShelleyWalletTestnet.test.ts index 0de322550f..51557acf27 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/testnet/ShelleyWalletTestnet.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/shelley/tests/testnet/ShelleyWalletTestnet.test.ts @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import AsyncStorage from '@react-native-async-storage/async-storage' +import {Balance} from '@yoroi/types' import {HWDeviceInfo} from '../../../../hw' import {EncryptedStorage, EncryptedStorageKeys, storage} from '../../../../storage' -import {DefaultAsset, TokenInfo} from '../../../../types' +import {DefaultAsset} from '../../../../types' import {WalletMeta} from '../../../../walletManager' import {ShelleyAddressGeneratorJSON} from '../../../chain' import {WalletJSON} from '../../../shelley/ShelleyWallet' @@ -102,7 +103,7 @@ describe('ShelleyWalletTestnet', () => { }, networkId: 300, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', id: '', name: 'TADA', @@ -215,7 +216,7 @@ describe('ShelleyWalletTestnet', () => { }, networkId: 300, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', id: '', name: 'TADA', @@ -317,7 +318,7 @@ describe('ShelleyWalletTestnet', () => { }, networkId: 300, }) - expect(wallet.primaryTokenInfo).toEqual({ + expect(wallet.primaryTokenInfo).toEqual({ kind: 'ft', id: '', name: 'TADA', diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts index 91221639c2..3dae9245a0 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/types.ts @@ -11,6 +11,7 @@ import { TxMetadata as TxMetadataType, UnsignedTx as UnsignedTxType, } from '@emurgo/yoroi-lib' +import {Balance} from '@yoroi/types' import {BigNumber} from 'bignumber.js' import {HWDeviceInfo} from '../hw' @@ -38,7 +39,7 @@ import type { TxStatusResponse, WalletState, } from '../types/other' -import {DefaultAsset, TokenInfo} from '../types/tokens' +import {DefaultAsset} from '../types/tokens' import type {Addresses} from './chain' export type WalletEvent = @@ -92,7 +93,7 @@ export type YoroiWallet = { hwDeviceInfo: null | HWDeviceInfo isReadOnly: boolean primaryToken: Readonly - primaryTokenInfo: Readonly + primaryTokenInfo: Readonly // Sending createUnsignedTx(entry: YoroiEntry, metadata?: Array): Promise @@ -157,7 +158,7 @@ export type YoroiWallet = { get confirmationCounts(): Record fetchTipStatus(): Promise fetchTxStatus(request: TxStatusRequest): Promise - fetchTokenInfo(tokenId: string): Promise + fetchTokenInfo(tokenId: string): Promise // Fiat fetchCurrentPrice(symbol: CurrencySymbol): Promise diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.test.ts index 21bc24a8b6..e4264d380d 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.test.ts @@ -1,11 +1,12 @@ +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' -import {YoroiAmounts, YoroiEntries, YoroiMetadata} from '../../types' +import {YoroiEntries, YoroiMetadata} from '../../types' import {CardanoTypes} from '../types' import {toAmounts, toDisplayAddress, toEntries, toMetadata} from './unsignedTx' describe('YoroiUnsignedTx', () => { - it('toAmounts converts TokenEntry[] to YoroiAmounts', () => { + it('toAmounts converts TokenEntry[] to Balance.Amounts', () => { const tokenEntries: Array = [ {identifier: '', networkId: 1, amount: new BigNumber('1')}, {identifier: '', networkId: 1, amount: new BigNumber('3')}, @@ -18,7 +19,7 @@ describe('YoroiUnsignedTx', () => { '': '4', token123: '2', token456: '2', - } as YoroiAmounts) + } as Balance.Amounts) }) it('toMetadata converts UnsignedTx to YoroiMetadata', () => { diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.ts index 47a2f52663..070e2ada82 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/unsignedTx/unsignedTx.ts @@ -1,4 +1,6 @@ -import {Quantity, YoroiAmounts, YoroiEntries, YoroiMetadata, YoroiUnsignedTx, YoroiVoting} from '../../types' +import {Balance} from '@yoroi/types' + +import {YoroiEntries, YoroiMetadata, YoroiUnsignedTx, YoroiVoting} from '../../types' import {Amounts, Entries, Quantities} from '../../utils' import {Cardano, CardanoMobile} from '../../wallets' import {CardanoHaskellShelleyNetwork} from '../networks' @@ -85,10 +87,10 @@ export const toAmounts = (values: Array) => ...result, [current.identifier]: Quantities.sum([ Amounts.getAmount(result, current.identifier).quantity || '0', - current.amount.toString() as Quantity, + current.amount.toString() as Balance.Quantity, ]), }), - {} as YoroiAmounts, + {} as Balance.Amounts, ) export const toMetadata = (metadata: ReadonlyArray) => @@ -119,7 +121,7 @@ const Staking = { for (let i = 0; i < length; i++) { const rewardAddress = await rewardAddresses.get(i) - const amount = (await withdrawals.get(rewardAddress).then((x) => x.toStr())) as Quantity + const amount = (await withdrawals.get(rewardAddress).then((x) => x.toStr())) as Balance.Quantity const address = await rewardAddress .toAddress() .then((address) => address.toBytes()) @@ -153,7 +155,7 @@ const Staking = { return { ...(await result), - [address]: {'': KEY_DEPOSIT as Quantity}, + [address]: {'': KEY_DEPOSIT as Balance.Quantity}, } }, Promise.resolve({} as YoroiEntries)), @@ -179,7 +181,7 @@ const Staking = { return { ...(await result), - [address]: {'': KEY_DEPOSIT as Quantity}, + [address]: {'': KEY_DEPOSIT as Balance.Quantity}, } }, Promise.resolve({} as YoroiEntries)), @@ -189,11 +191,11 @@ const Staking = { }: { balances: CardanoTypes.StakingKeyBalances fee: YoroiUnsignedTx['fee'] - }): {[poolId: string]: YoroiAmounts} => + }): {[poolId: string]: Balance.Amounts} => Object.entries(balances).reduce( (result, [poolId, quantity]) => ({ ...result, - [poolId]: Amounts.diff({'': quantity} as YoroiAmounts, fee), + [poolId]: Amounts.diff({'': quantity} as Balance.Amounts, fee), }), {}, ), diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.test.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.test.ts index 028fa5a61a..704641434e 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.test.ts @@ -1,6 +1,7 @@ import {SendToken} from '@emurgo/yoroi-lib' +import {Balance} from '@yoroi/types' -import {Token, YoroiAmounts} from '../types' +import {Token} from '../types' import {PRIMARY_TOKEN} from './constants/mainnet/constants' import {toSendToken, toSendTokenList} from './utils' @@ -8,7 +9,7 @@ describe('toSendTokenList', () => { const asSendToken = toSendToken(PRIMARY_TOKEN) it('converts amounts to send token list for tx (lib)', async () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { [PRIMARY_TOKEN.identifier]: '123', [secondaryToken.identifier]: '456', } diff --git a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts index fbef2f2520..538bba13be 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/cardano/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable no-empty */ - import {SendToken} from '@emurgo/yoroi-lib' +import {Balance} from '@yoroi/types' import {BigNumber} from 'bignumber.js' import { @@ -12,8 +12,7 @@ import { WALLET_IMPLEMENTATION_REGISTRY, WalletImplementationId, } from '../types/other' -import {DefaultAsset, Token, TokenInfo} from '../types/tokens' -import {YoroiAmount, YoroiAmounts} from '../types/yoroi' +import {DefaultAsset, Token} from '../types/tokens' import {Amounts} from '../utils' import {CardanoMobile} from '../wallets' import {toAssetNameHex, toPolicyId} from './api/utils' @@ -275,13 +274,13 @@ export const toCardanoNetworkId = (networkId: number) => { throw new Error('invalid network id') } -export const toSendTokenList = (amounts: YoroiAmounts, primaryToken: Token): Array => { +export const toSendTokenList = (amounts: Balance.Amounts, primaryToken: Token): Array => { return Amounts.toArray(amounts).map(toSendToken(primaryToken)) } export const toSendToken = (primaryToken: Token) => - (amount: YoroiAmount): SendToken => { + (amount: Balance.Amount): SendToken => { const {tokenId, quantity} = amount const isPrimary = tokenId === primaryToken.identifier @@ -296,11 +295,11 @@ export const toSendToken = } } -export const isTokenInfo = (token: TokenInfo | DefaultAsset): token is TokenInfo => { - return !!(token as TokenInfo).kind +export const isTokenInfo = (token: Balance.TokenInfo | DefaultAsset): token is Balance.TokenInfo => { + return !!(token as Balance.TokenInfo).kind } -export const selectFtOrThrow = (token: TokenInfo): TokenInfo => { +export const selectFtOrThrow = (token: Balance.TokenInfo): Balance.TokenInfo => { if (token.kind === 'ft') { return token } diff --git a/apps/wallet-mobile/src/yoroi-wallets/hooks/index.ts b/apps/wallet-mobile/src/yoroi-wallets/hooks/index.ts index 5f5b03e7c4..c5f82e5f69 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/hooks/index.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/hooks/index.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import AsyncStorage, {AsyncStorageStatic} from '@react-native-async-storage/async-storage' +import {Balance} from '@yoroi/types' import * as React from 'react' import {useCallback, useMemo} from 'react' import { @@ -22,11 +23,8 @@ import {HWDeviceInfo} from '../hw' import {parseWalletMeta} from '../migrations/walletMeta' import {useStorage} from '../storage' import { - Quantity, - TokenInfo, TRANSACTION_DIRECTION, TRANSACTION_STATUS, - YoroiAmounts, YoroiNftModerationStatus, YoroiSignedTx, YoroiUnsignedTx, @@ -130,13 +128,14 @@ export const useAssetIds = (wallet: YoroiWallet): string[] => { */ export const useLockedAmount = ( {wallet}: {wallet: YoroiWallet}, - options?: UseQueryOptions, + options?: UseQueryOptions, ) => { const query = useQuery({ ...options, suspense: true, queryKey: [wallet.id, 'lockedAmount'], - queryFn: () => calcLockedDeposit(wallet.utxos, wallet.networkId).then((amount) => amount.toString() as Quantity), + queryFn: () => + calcLockedDeposit(wallet.utxos, wallet.networkId).then((amount) => amount.toString() as Balance.Quantity), }) React.useEffect(() => { @@ -198,9 +197,9 @@ export const useChangeWalletName = (wallet: YoroiWallet, options: UseMutationOpt } } -export const useTokenInfo = ( +export const useTokenInfo = ( {wallet, tokenId}: {wallet: YoroiWallet; tokenId: string}, - options?: UseQueryOptions, + options?: UseQueryOptions, ) => { const query = useQuery({ ...options, @@ -245,7 +244,7 @@ export const useNftImageModerated = ({ export const useToken = ( {wallet, tokenId}: {wallet: YoroiWallet; tokenId: string}, - options?: UseQueryOptions, + options?: UseQueryOptions, ) => { const query = useQuery({ ...options, @@ -261,7 +260,7 @@ export const useToken = ( export const useTokenInfosDetailed = ( {wallet, tokenIds}: {wallet: YoroiWallet; tokenIds: Array}, - options?: UseQueryOptions, + options?: UseQueryOptions, ) => { const queries = tokenIds.map((tokenId) => ({ ...options, @@ -274,10 +273,10 @@ export const useTokenInfosDetailed = ( export const useTokenInfos = ( {wallet, tokenIds}: {wallet: YoroiWallet; tokenIds: Array}, - options?: UseQueryOptions, + options?: UseQueryOptions, ) => { const results = useTokenInfosDetailed({wallet, tokenIds}, options) - return results.reduce((result, {data}) => (data ? [...result, data] : result), [] as Array) + return results.reduce((result, {data}) => (data ? [...result, data] : result), [] as Array) } export const useAllTokenInfos = ({wallet}: {wallet: YoroiWallet}) => { @@ -858,7 +857,7 @@ export const useExchangeRate = ({ return query.data } -export const useBalances = (wallet: YoroiWallet): YoroiAmounts => { +export const useBalances = (wallet: YoroiWallet): Balance.Amounts => { const utxos = useUtxos(wallet) return Utxos.toAmounts(utxos, wallet.primaryTokenInfo.id) @@ -897,10 +896,10 @@ export const useSaveMemo = ( } } -export const useNfts = (wallet: YoroiWallet, options: UseQueryOptions = {}) => { +export const useNfts = (wallet: YoroiWallet, options: UseQueryOptions = {}) => { const assetIds = useAssetIds(wallet) const results = useTokenInfosDetailed({wallet, tokenIds: assetIds}, options) - const nfts = results.map((r) => r.data).filter((t): t is TokenInfo => t?.kind === 'nft') + const nfts = results.map((r) => r.data).filter((t): t is Balance.TokenInfo => t?.kind === 'nft') const isLoading = results.some((r) => r.isLoading) const isError = results.some((r) => r.isError) const error = results.find((r) => r.isError)?.error @@ -908,7 +907,7 @@ export const useNfts = (wallet: YoroiWallet, options: UseQueryOptions { +export const useNft = (wallet: YoroiWallet, {id}: {id: string}): Balance.TokenInfo => { const tokenInfo = useTokenInfo({wallet, tokenId: id}, {suspense: true}) if (tokenInfo.kind !== 'nft') { diff --git a/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts b/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts index 4b49a28f47..84ee12874d 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/mocks/wallet.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-explicit-any */ import {action} from '@storybook/addon-actions' +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' import {getTokenFingerprint} from '../../legacy/format' @@ -14,9 +15,7 @@ import { StakePoolInfosAndHistories, StakingInfo, StakingStatus, - TokenInfo, TransactionInfo, - YoroiAmounts, YoroiNftModerationStatus, YoroiSignedTx, YoroiUnsignedTx, @@ -295,7 +294,7 @@ const fetchPoolInfo = { }, } -export const generateManyNfts = (): TokenInfo[] => { +export const generateManyNfts = (): Balance.TokenInfo[] => { return Array(30) .fill(undefined) .map((_, index) => ({ @@ -515,16 +514,16 @@ const fetchCurrentPrice = { const fetchTokenInfo = { success: { - nft: async (...args): Promise => { + nft: async (...args): Promise => { action('fetchTokenInfo')(...args) return nft }, - randomNft: async (...args): Promise => { + randomNft: async (...args): Promise => { action('fetchTokenInfo')(...args) const allNfts = generateManyNfts() return allNfts[Math.floor(Math.random() * allNfts.length)] }, - ft: async (...args): Promise => { + ft: async (...args): Promise => { action('fetchTokenInfo')(...args) return { kind: 'ft', @@ -550,7 +549,7 @@ const fetchTokenInfo = { }, } }, - ftNoImage: async (...args): Promise => { + ftNoImage: async (...args): Promise => { action('fetchTokenInfo')(...args) return { kind: 'ft', @@ -579,7 +578,7 @@ const fetchTokenInfo = { }, loading: async (...args) => { action('fetchTokenInfo')(...args) - return new Promise(() => null) as unknown as TokenInfo + return new Promise(() => null) as unknown as Balance.TokenInfo }, error: async (...args) => { action('fetchTokenInfo')(...args) @@ -620,7 +619,7 @@ const tokenEntries: Array = [ }, ] -const balances: YoroiAmounts = { +const balances: Balance.Amounts = { [PRIMARY_ASSET_CONSTANTS.CARDANO]: '2727363743849', '698a6ea0ca99f315034072af31eaac6ec11fe8558d3f48e9775aab9d.7444524950': '12344', '29d222ce763455e3d7a09a665ce554f00ac89d2e99a1a83d267170c6.4d494e': '215410', @@ -632,7 +631,7 @@ const balances: YoroiAmounts = { '1d129dc9c03f95a863489883914f05a52e13135994a32f0cbeacc65e.74484f444c55': '100000000000000000020', } -const tokenInfos: Record = { +const tokenInfos: Record = { '': HASKELL_SHELLEY_TESTNET.PRIMARY_TOKEN_INFO, '698a6ea0ca99f315034072af31eaac6ec11fe8558d3f48e9775aab9d.7444524950': toTokenInfo({ networkId: 300, @@ -841,7 +840,7 @@ const yoroiSignedTx: YoroiSignedTx & {mock: true} = { mock: true, } -export const nft: TokenInfo = { +export const nft: Balance.TokenInfo = { kind: 'nft', id: `8e2c7604711faef7c84c91b286c7327d17df825b7f0c88ec0332c0b4.${utf8ToHex('NFT 0')}`, group: '8e2c7604711faef7c84c91b286c7327d17df825b7f0c88ec0332c0b4', diff --git a/apps/wallet-mobile/src/yoroi-wallets/types/staking.ts b/apps/wallet-mobile/src/yoroi-wallets/types/staking.ts index 3d927ae2ca..2c0fe54f66 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/types/staking.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/types/staking.ts @@ -1,4 +1,4 @@ -import {Quantity} from './yoroi' +import {Balance} from '@yoroi/types' export type StakingInfo = | {status: 'not-registered'} @@ -6,8 +6,8 @@ export type StakingInfo = | { status: 'staked' poolId: string - amount: Quantity - rewards: Quantity + amount: Balance.Quantity + rewards: Balance.Quantity } export type StakingStatus = diff --git a/apps/wallet-mobile/src/yoroi-wallets/types/tokens.ts b/apps/wallet-mobile/src/yoroi-wallets/types/tokens.ts index f78e6c8a0d..76e7cdbf28 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/types/tokens.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/types/tokens.ts @@ -52,29 +52,3 @@ export type NFTAsset = { key: '721' metadata?: unknown } - -export type TokenInfo = { - kind: 'ft' | 'nft' - id: string - group: string - fingerprint: string - image: string | undefined - icon: string | undefined - decimals: number | undefined - symbol: string | undefined - name: string - description: string | undefined - ticker: string | undefined - metadatas: {mintNft?: NftMetadata; mintFt?: FtMetadata; tokenRegistry?: FtMetadata} -} - -type FtMetadata = { - description: string | Array | undefined - icon: string | Array | undefined - decimals: number | undefined - ticker: string | undefined - url: string | undefined - version: string | undefined -} - -export type NftMetadata = unknown diff --git a/apps/wallet-mobile/src/yoroi-wallets/types/yoroi.ts b/apps/wallet-mobile/src/yoroi-wallets/types/yoroi.ts index 27a1bf8255..26439c563f 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/types/yoroi.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/types/yoroi.ts @@ -1,3 +1,5 @@ +import {Balance} from '@yoroi/types' + import {CardanoTypes, YoroiWallet} from '../cardano/types' import {HWDeviceInfo} from '../hw' import {YoroiStorage} from '../storage' @@ -13,8 +15,8 @@ export type YoroiSignedTx = YoroiTxInfo & { export type YoroiTxInfo = { entries: YoroiEntries - amounts: YoroiAmounts - fee: YoroiAmounts + amounts: Balance.Amounts + fee: Balance.Amounts change: YoroiEntries metadata: YoroiMetadata staking: YoroiStaking @@ -38,23 +40,13 @@ export type YoroiVoting = { } export type Address = string -export type Quantity = `${number}` export type TokenId = string -export type YoroiEntries = Record +export type YoroiEntries = Record export type YoroiEntry = { address: Address - amounts: YoroiAmounts -} - -export type YoroiAmounts = { - [tokenId: TokenId]: Quantity -} - -export type YoroiAmount = { - tokenId: TokenId - quantity: Quantity + amounts: Balance.Amounts } export type YoroiMetadata = { diff --git a/apps/wallet-mobile/src/yoroi-wallets/utils/utils.test.ts b/apps/wallet-mobile/src/yoroi-wallets/utils/utils.test.ts index 73b6cd398d..03682ea5fe 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/utils/utils.test.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/utils/utils.test.ts @@ -1,31 +1,32 @@ +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' -import {Quantity, YoroiAmount, YoroiAmounts, YoroiEntries, YoroiEntry} from '../types' +import {YoroiEntries, YoroiEntry} from '../types' import {RawUtxo} from '../types/other' import {Amounts, asQuantity, Entries, Quantities, Utxos} from './utils' describe('Quantities', () => { it('sum', () => { - expect(Quantities.sum(['1', '2'])).toEqual('3' as Quantity) - expect(Quantities.sum(['1', '2', '3'])).toEqual('6' as Quantity) + expect(Quantities.sum(['1', '2'])).toEqual('3' as Balance.Quantity) + expect(Quantities.sum(['1', '2', '3'])).toEqual('6' as Balance.Quantity) }) it('diff', () => { - expect(Quantities.diff('1', '2')).toEqual('-1' as Quantity) - expect(Quantities.diff('3', '2')).toEqual('1' as Quantity) + expect(Quantities.diff('1', '2')).toEqual('-1' as Balance.Quantity) + expect(Quantities.diff('3', '2')).toEqual('1' as Balance.Quantity) }) it('negated', () => { - expect(Quantities.negated('1')).toEqual('-1' as Quantity) - expect(Quantities.negated('-1')).toEqual('1' as Quantity) + expect(Quantities.negated('1')).toEqual('-1' as Balance.Quantity) + expect(Quantities.negated('-1')).toEqual('1' as Balance.Quantity) }) it('product', () => { - expect(Quantities.product(['1', '2'])).toEqual('2' as Quantity) - expect(Quantities.product(['2', '3'])).toEqual('6' as Quantity) + expect(Quantities.product(['1', '2'])).toEqual('2' as Balance.Quantity) + expect(Quantities.product(['2', '3'])).toEqual('6' as Balance.Quantity) }) it('quotient', () => { - expect(Quantities.quotient('1', '2')).toEqual('0.5' as Quantity) - expect(Quantities.quotient('2', '1')).toEqual('2' as Quantity) + expect(Quantities.quotient('1', '2')).toEqual('0.5' as Balance.Quantity) + expect(Quantities.quotient('2', '1')).toEqual('2' as Balance.Quantity) }) it('isGreaterThan', () => { expect(Quantities.isGreaterThan('1', '2')).toBe(false) @@ -86,13 +87,13 @@ describe('Quantities', () => { }) describe('Amounts', () => { - it('sums multiple YoroiAmounts into a single YoroiAmounts', () => { - const amounts1: YoroiAmounts = { + it('sums multiple Balance.Amounts into a single Balance.Amounts', () => { + const amounts1: Balance.Amounts = { '': '1', token123: '2', token567: '-2', } - const amounts2: YoroiAmounts = { + const amounts2: Balance.Amounts = { '': '3', token456: '4', } @@ -102,16 +103,16 @@ describe('Amounts', () => { token123: '2', token456: '4', token567: '-2', - } as YoroiAmounts) + } as Balance.Amounts) }) - it('diffs 2 YoroiAmounts into a single YoroiAmounts', () => { - const amounts1: YoroiAmounts = { + it('diffs 2 Balance.Amounts into a single Balance.Amounts', () => { + const amounts1: Balance.Amounts = { '': '1', token123: '2', token567: '-2', } - const amounts2: YoroiAmounts = { + const amounts2: Balance.Amounts = { '': '3', token456: '4', } @@ -121,11 +122,11 @@ describe('Amounts', () => { token123: '2', token456: '-4', token567: '-2', - } as YoroiAmounts) + } as Balance.Amounts) }) - it('negate YoroiAmounts', () => { - const amounts1: YoroiAmounts = { + it('negate Balance.Amounts', () => { + const amounts1: Balance.Amounts = { '': '1', token123: '2', token567: '-2', @@ -135,11 +136,11 @@ describe('Amounts', () => { '': '-1', token123: '-2', token567: '2', - } as YoroiAmounts) + } as Balance.Amounts) }) it('getAmount', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { '': '1', token123: '2', token567: '-2', @@ -149,12 +150,12 @@ describe('Amounts', () => { expect(Amounts.getAmount(amounts, tokenId)).toEqual({ tokenId, quantity, - } as YoroiAmount), + } as Balance.Amount), ) }) it('includes', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { '': '1', token123: '2', token567: '-2', @@ -166,7 +167,7 @@ describe('Amounts', () => { }) it('remove', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { '': '123', token123: '456', token567: '-789', @@ -175,11 +176,11 @@ describe('Amounts', () => { expect(Amounts.remove(amounts, ['token123'])).toEqual({ '': '123', token567: '-789', - } as YoroiAmounts) + } as Balance.Amounts) }) it('toArray', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { '': '123', token123: '456', token567: '-789', @@ -189,11 +190,11 @@ describe('Amounts', () => { {tokenId: '', quantity: '123'}, {tokenId: 'token123', quantity: '456'}, {tokenId: 'token567', quantity: '-789'}, - ] as Array) + ] as Array) }) it('from Array', () => { - const amounts: Array = [ + const amounts: Array = [ {tokenId: '', quantity: '123'}, {tokenId: 'SUN', quantity: '456'}, {tokenId: 'QWE', quantity: '789'}, @@ -203,11 +204,11 @@ describe('Amounts', () => { '': '123', SUN: '456', QWE: '789', - } as YoroiAmounts) + } as Balance.Amounts) }) it('map', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { '': '1', SUN: '4', QWE: '7', @@ -222,15 +223,15 @@ describe('Amounts', () => { '': '2', SUN: '5', QWE: '8', - } as YoroiAmounts) + } as Balance.Amounts) }) describe('save', () => { it('updating when already exists', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { updateToken: '456', } - const updateAmount: YoroiAmount = { + const updateAmount: Balance.Amount = { tokenId: 'updateToken', quantity: '321', } @@ -241,10 +242,10 @@ describe('Amounts', () => { }) it('adding when it doesnt exist', () => { - const amounts: YoroiAmounts = { + const amounts: Balance.Amounts = { updateToken: '456', } - const addAmount: YoroiAmount = { + const addAmount: Balance.Amount = { tokenId: 'addToken', quantity: '789', } @@ -360,7 +361,7 @@ describe('Entries', () => { '': '3', token123: '6', token567: '-6', - } as YoroiAmounts) + } as Balance.Amounts) }) }) @@ -372,7 +373,7 @@ describe('Utxos', () => { expect(Utxos.toAmounts(utxos, primaryTokenId)).toEqual({ primaryTokenId: '0', - } as YoroiAmounts) + } as Balance.Amounts) }) it('Utxos without tokens', () => { @@ -415,7 +416,7 @@ describe('Utxos', () => { expect(Utxos.toAmounts(utxos, primaryTokenId)).toEqual({ primaryTokenId: '627690', - } as YoroiAmounts) + } as Balance.Amounts) }) it('Utxos with tokens', () => { @@ -463,7 +464,7 @@ describe('Utxos', () => { primaryTokenId: '67905', token123: '15', token567: '8', - } as YoroiAmounts) + } as Balance.Amounts) }) }) }) diff --git a/apps/wallet-mobile/src/yoroi-wallets/utils/utils.ts b/apps/wallet-mobile/src/yoroi-wallets/utils/utils.ts index 32e7485fcf..c3b2f705f7 100644 --- a/apps/wallet-mobile/src/yoroi-wallets/utils/utils.ts +++ b/apps/wallet-mobile/src/yoroi-wallets/utils/utils.ts @@ -1,6 +1,7 @@ +import {Balance} from '@yoroi/types' import BigNumber from 'bignumber.js' -import {Quantity, RawUtxo, TokenId, YoroiAmount, YoroiAmounts, YoroiEntries, YoroiEntry} from '../types' +import {RawUtxo, TokenId, YoroiEntries, YoroiEntry} from '../types' export const Entries = { first: (entries: YoroiEntries): YoroiEntry => { @@ -23,7 +24,7 @@ export const Entries = { toAddresses: (entries: YoroiEntries): Array => { return Object.keys(entries) }, - toAmounts: (entries: YoroiEntries): YoroiAmounts => { + toAmounts: (entries: YoroiEntries): Balance.Amounts => { const amounts = Object.values(entries) return Amounts.sum(amounts) @@ -31,7 +32,7 @@ export const Entries = { } export const Amounts = { - sum: (amounts: Array): YoroiAmounts => { + sum: (amounts: Array): Balance.Amounts => { const entries = amounts.map((amounts) => Object.entries(amounts)).flat() return entries.reduce( @@ -39,33 +40,33 @@ export const Amounts = { ...result, [tokenId]: result[tokenId] ? Quantities.sum([result[tokenId], quantity]) : quantity, }), - {} as YoroiAmounts, + {} as Balance.Amounts, ) }, - diff: (amounts1: YoroiAmounts, amounts2: YoroiAmounts): YoroiAmounts => { + diff: (amounts1: Balance.Amounts, amounts2: Balance.Amounts): Balance.Amounts => { return Amounts.sum([amounts1, Amounts.negated(amounts2)]) }, - includes: (amounts: YoroiAmounts, tokenId: string): boolean => { + includes: (amounts: Balance.Amounts, tokenId: string): boolean => { return Object.keys(amounts).includes(tokenId) }, - negated: (amounts: YoroiAmounts): YoroiAmounts => { + negated: (amounts: Balance.Amounts): Balance.Amounts => { const entries = Object.entries(amounts) const negatedEntries = entries.map(([tokenId, amount]) => [tokenId, Quantities.negated(amount)]) return Object.fromEntries(negatedEntries) }, - remove: (amounts: YoroiAmounts, removeTokenIds: Array): YoroiAmounts => { + remove: (amounts: Balance.Amounts, removeTokenIds: Array): Balance.Amounts => { const filteredEntries = Object.entries(amounts).filter(([tokenId]) => !removeTokenIds.includes(tokenId)) return Object.fromEntries(filteredEntries) }, - getAmount: (amounts: YoroiAmounts, tokenId: string): YoroiAmount => { + getAmount: (amounts: Balance.Amounts, tokenId: string): Balance.Amount => { return { tokenId, quantity: amounts[tokenId] || Quantities.zero, } }, - save: (amounts: YoroiAmounts, amount: YoroiAmount): YoroiAmounts => { + save: (amounts: Balance.Amounts, amount: Balance.Amount): Balance.Amounts => { const {tokenId, quantity} = amount return { @@ -73,55 +74,57 @@ export const Amounts = { [tokenId]: quantity, } }, - map: (amounts: YoroiAmounts, fn: (amount: YoroiAmount) => YoroiAmount): YoroiAmounts => + map: (amounts: Balance.Amounts, fn: (amount: Balance.Amount) => Balance.Amount): Balance.Amounts => Amounts.fromArray(Amounts.toArray(amounts).map(fn)), - toArray: (amounts: YoroiAmounts) => + toArray: (amounts: Balance.Amounts) => Object.keys(amounts).reduce( (result, current) => [...result, Amounts.getAmount(amounts, current)], - [] as Array, + [] as Array, ), - fromArray: (amounts: Array) => + fromArray: (amounts: Array) => Object.fromEntries(amounts.map((amount) => [amount.tokenId, amount.quantity])), } export const Quantities = { - sum: (quantities: Array) => { - return quantities.reduce((result, current) => result.plus(current), new BigNumber(0)).toString(10) as Quantity + sum: (quantities: Array) => { + return quantities + .reduce((result, current) => result.plus(current), new BigNumber(0)) + .toString(10) as Balance.Quantity }, - max: (...quantities: Array) => { - return BigNumber.max(...quantities).toString(10) as Quantity + max: (...quantities: Array) => { + return BigNumber.max(...quantities).toString(10) as Balance.Quantity }, - diff: (quantity1: Quantity, quantity2: Quantity) => { - return new BigNumber(quantity1).minus(new BigNumber(quantity2)).toString(10) as Quantity + diff: (quantity1: Balance.Quantity, quantity2: Balance.Quantity) => { + return new BigNumber(quantity1).minus(new BigNumber(quantity2)).toString(10) as Balance.Quantity }, - negated: (quantity: Quantity) => { - return new BigNumber(quantity).negated().toString(10) as Quantity + negated: (quantity: Balance.Quantity) => { + return new BigNumber(quantity).negated().toString(10) as Balance.Quantity }, - product: (quantities: Array) => { + product: (quantities: Array) => { return quantities.reduce((result, quantity) => { const x = new BigNumber(result).times(new BigNumber(quantity)) - return x.toString(10) as Quantity - }, '1' as Quantity) + return x.toString(10) as Balance.Quantity + }, '1' as Balance.Quantity) }, - quotient: (quantity1: Quantity, quantity2: Quantity) => { - return new BigNumber(quantity1).dividedBy(new BigNumber(quantity2)).toString(10) as Quantity + quotient: (quantity1: Balance.Quantity, quantity2: Balance.Quantity) => { + return new BigNumber(quantity1).dividedBy(new BigNumber(quantity2)).toString(10) as Balance.Quantity }, - isGreaterThan: (quantity1: Quantity, quantity2: Quantity) => { + isGreaterThan: (quantity1: Balance.Quantity, quantity2: Balance.Quantity) => { return new BigNumber(quantity1).isGreaterThan(new BigNumber(quantity2)) }, - decimalPlaces: (quantity: Quantity, precision: number) => { - return new BigNumber(quantity).decimalPlaces(precision).toString(10) as Quantity + decimalPlaces: (quantity: Balance.Quantity, precision: number) => { + return new BigNumber(quantity).decimalPlaces(precision).toString(10) as Balance.Quantity }, - denominated: (quantity: Quantity, denomination: number) => { - return Quantities.quotient(quantity, new BigNumber(10).pow(denomination).toString(10) as Quantity) + denominated: (quantity: Balance.Quantity, denomination: number) => { + return Quantities.quotient(quantity, new BigNumber(10).pow(denomination).toString(10) as Balance.Quantity) }, - integer: (quantity: Quantity, denomination: number) => { - return new BigNumber(quantity).decimalPlaces(denomination).shiftedBy(denomination).toString(10) as Quantity + integer: (quantity: Balance.Quantity, denomination: number) => { + return new BigNumber(quantity).decimalPlaces(denomination).shiftedBy(denomination).toString(10) as Balance.Quantity }, - zero: '0' as Quantity, - isZero: (quantity: Quantity) => new BigNumber(quantity).isZero(), - isAtomic: (quantity: Quantity, denomination: number) => { + zero: '0' as Balance.Quantity, + isZero: (quantity: Balance.Quantity) => new BigNumber(quantity).isZero(), + isAtomic: (quantity: Balance.Quantity, denomination: number) => { const absoluteQuantity = new BigNumber(quantity).decimalPlaces(denomination).abs() const minimalFractionalPart = new BigNumber(10).pow(new BigNumber(denomination).negated()) @@ -134,7 +137,7 @@ export const asQuantity = (value: BigNumber | number | string) => { if (bn.isNaN() || !bn.isFinite()) { throw new Error('Invalid quantity') } - return bn.toString(10) as Quantity + return bn.toString(10) as Balance.Quantity } export const Utxos = { @@ -143,7 +146,7 @@ export const Utxos = { (previousAmounts, currentUtxo) => { const amounts = { ...previousAmounts, - [primaryTokenId]: Quantities.sum([previousAmounts[primaryTokenId], currentUtxo.amount as Quantity]), + [primaryTokenId]: Quantities.sum([previousAmounts[primaryTokenId], currentUtxo.amount as Balance.Quantity]), } if (currentUtxo.assets) { @@ -152,7 +155,7 @@ export const Utxos = { ...previousAmountsWithAssets, [currentAsset.assetId]: Quantities.sum([ Amounts.getAmount(previousAmountsWithAssets, currentAsset.assetId).quantity, - currentAsset.amount as Quantity, + currentAsset.amount as Balance.Quantity, ]), } }, amounts) @@ -160,7 +163,7 @@ export const Utxos = { return amounts }, - {[primaryTokenId]: Quantities.zero} as YoroiAmounts, + {[primaryTokenId]: Quantities.zero} as Balance.Amounts, ) }, } diff --git a/apps/wallet-mobile/translations/messages/src/NftDetails/NftDetails.json b/apps/wallet-mobile/translations/messages/src/NftDetails/NftDetails.json index 429b2938ec..a041075fc9 100644 --- a/apps/wallet-mobile/translations/messages/src/NftDetails/NftDetails.json +++ b/apps/wallet-mobile/translations/messages/src/NftDetails/NftDetails.json @@ -6,12 +6,12 @@ "start": { "line": 280, "column": 9, - "index": 7824 + "index": 7572 }, "end": { "line": 283, "column": 3, - "index": 7895 + "index": 7643 } }, { @@ -21,12 +21,12 @@ "start": { "line": 284, "column": 12, - "index": 7909 + "index": 7657 }, "end": { "line": 287, "column": 3, - "index": 7980 + "index": 7728 } }, { @@ -36,12 +36,12 @@ "start": { "line": 288, "column": 12, - "index": 7994 + "index": 7742 }, "end": { "line": 291, "column": 3, - "index": 8065 + "index": 7813 } }, { @@ -51,12 +51,12 @@ "start": { "line": 292, "column": 11, - "index": 8078 + "index": 7826 }, "end": { "line": 295, "column": 3, - "index": 8148 + "index": 7896 } }, { @@ -66,12 +66,12 @@ "start": { "line": 296, "column": 13, - "index": 8163 + "index": 7911 }, "end": { "line": 299, "column": 3, - "index": 8234 + "index": 7982 } }, { @@ -81,12 +81,12 @@ "start": { "line": 300, "column": 15, - "index": 8251 + "index": 7999 }, "end": { "line": 303, "column": 3, - "index": 8328 + "index": 8076 } }, { @@ -96,12 +96,12 @@ "start": { "line": 304, "column": 10, - "index": 8340 + "index": 8088 }, "end": { "line": 307, "column": 3, - "index": 8407 + "index": 8155 } }, { @@ -111,12 +111,12 @@ "start": { "line": 308, "column": 15, - "index": 8424 + "index": 8172 }, "end": { "line": 311, "column": 3, - "index": 8501 + "index": 8249 } }, { @@ -126,12 +126,12 @@ "start": { "line": 312, "column": 12, - "index": 8515 + "index": 8263 }, "end": { "line": 315, "column": 3, - "index": 8587 + "index": 8335 } }, { @@ -141,12 +141,12 @@ "start": { "line": 316, "column": 16, - "index": 8605 + "index": 8353 }, "end": { "line": 319, "column": 3, - "index": 8682 + "index": 8430 } }, { @@ -156,12 +156,12 @@ "start": { "line": 320, "column": 16, - "index": 8700 + "index": 8448 }, "end": { "line": 323, "column": 3, - "index": 8780 + "index": 8528 } } -] \ No newline at end of file +] diff --git a/apps/wallet-mobile/translations/messages/src/Nfts/Nfts.json b/apps/wallet-mobile/translations/messages/src/Nfts/Nfts.json index 11eed4d4dd..94db214a27 100644 --- a/apps/wallet-mobile/translations/messages/src/Nfts/Nfts.json +++ b/apps/wallet-mobile/translations/messages/src/Nfts/Nfts.json @@ -6,12 +6,12 @@ "start": { "line": 246, "column": 12, - "index": 6029 + "index": 6033 }, "end": { "line": 249, "column": 3, - "index": 6102 + "index": 6106 } }, { @@ -21,12 +21,12 @@ "start": { "line": 250, "column": 14, - "index": 6118 + "index": 6122 }, "end": { "line": 253, "column": 3, - "index": 6189 + "index": 6193 } }, { @@ -36,12 +36,12 @@ "start": { "line": 254, "column": 20, - "index": 6211 + "index": 6215 }, "end": { "line": 257, "column": 3, - "index": 6304 + "index": 6308 } }, { @@ -51,12 +51,12 @@ "start": { "line": 258, "column": 13, - "index": 6319 + "index": 6323 }, "end": { "line": 261, "column": 3, - "index": 6407 + "index": 6411 } }, { @@ -66,12 +66,12 @@ "start": { "line": 262, "column": 15, - "index": 6424 + "index": 6428 }, "end": { "line": 265, "column": 3, - "index": 6504 + "index": 6508 } }, { @@ -81,12 +81,12 @@ "start": { "line": 266, "column": 18, - "index": 6524 + "index": 6528 }, "end": { "line": 269, "column": 3, - "index": 6626 + "index": 6630 } }, { @@ -96,12 +96,12 @@ "start": { "line": 270, "column": 9, - "index": 6637 + "index": 6641 }, "end": { "line": 273, "column": 3, - "index": 6712 + "index": 6716 } }, { @@ -111,12 +111,12 @@ "start": { "line": 274, "column": 10, - "index": 6724 + "index": 6728 }, "end": { "line": 277, "column": 3, - "index": 6799 + "index": 6803 } } ] \ No newline at end of file diff --git a/apps/wallet-mobile/translations/messages/src/Staking/DelegationConfirmation/DelegationConfirmation.json b/apps/wallet-mobile/translations/messages/src/Staking/DelegationConfirmation/DelegationConfirmation.json index d65a3e5781..8b192d2d07 100644 --- a/apps/wallet-mobile/translations/messages/src/Staking/DelegationConfirmation/DelegationConfirmation.json +++ b/apps/wallet-mobile/translations/messages/src/Staking/DelegationConfirmation/DelegationConfirmation.json @@ -6,12 +6,12 @@ "start": { "line": 147, "column": 23, - "index": 5310 + "index": 5296 }, "end": { "line": 150, "column": 3, - "index": 5424 + "index": 5410 } }, { @@ -21,12 +21,12 @@ "start": { "line": 151, "column": 10, - "index": 5436 + "index": 5422 }, "end": { "line": 154, "column": 3, - "index": 5536 + "index": 5522 } }, { @@ -36,12 +36,12 @@ "start": { "line": 155, "column": 22, - "index": 5560 + "index": 5546 }, "end": { "line": 158, "column": 3, - "index": 5730 + "index": 5716 } }, { @@ -51,12 +51,12 @@ "start": { "line": 159, "column": 15, - "index": 5747 + "index": 5733 }, "end": { "line": 162, "column": 3, - "index": 5866 + "index": 5852 } } -] \ No newline at end of file +] diff --git a/apps/wallet-mobile/translations/messages/src/TxHistory/AssetList/AssetList.json b/apps/wallet-mobile/translations/messages/src/TxHistory/AssetList/AssetList.json index 02f53e561a..2fdccb0e36 100644 --- a/apps/wallet-mobile/translations/messages/src/TxHistory/AssetList/AssetList.json +++ b/apps/wallet-mobile/translations/messages/src/TxHistory/AssetList/AssetList.json @@ -6,12 +6,12 @@ "start": { "line": 97, "column": 16, - "index": 3313 + "index": 3171 }, "end": { "line": 100, "column": 3, - "index": 3418 + "index": 3276 } } ] \ No newline at end of file diff --git a/apps/wallet-mobile/translations/messages/src/TxHistory/PairedBalance.json b/apps/wallet-mobile/translations/messages/src/TxHistory/PairedBalance.json index 9d1c86b4e0..06e03c6bcd 100644 --- a/apps/wallet-mobile/translations/messages/src/TxHistory/PairedBalance.json +++ b/apps/wallet-mobile/translations/messages/src/TxHistory/PairedBalance.json @@ -6,12 +6,12 @@ "start": { "line": 85, "column": 22, - "index": 2563 + "index": 2498 }, "end": { "line": 88, "column": 3, - "index": 2695 + "index": 2630 } } ] \ No newline at end of file diff --git a/apps/wallet-mobile/translations/messages/src/legacy/format.json b/apps/wallet-mobile/translations/messages/src/legacy/format.json index c6643c9c76..64572d6a45 100644 --- a/apps/wallet-mobile/translations/messages/src/legacy/format.json +++ b/apps/wallet-mobile/translations/messages/src/legacy/format.json @@ -4,14 +4,14 @@ "defaultMessage": "!!!Today", "file": "src/legacy/format.ts", "start": { - "line": 206, + "line": 207, "column": 9, - "index": 6715 + "index": 6875 }, "end": { - "line": 209, + "line": 210, "column": 3, - "index": 6782 + "index": 6942 } }, { @@ -19,14 +19,14 @@ "defaultMessage": "!!!Yesterday", "file": "src/legacy/format.ts", "start": { - "line": 210, + "line": 211, "column": 13, - "index": 6797 + "index": 6957 }, "end": { - "line": 213, + "line": 214, "column": 3, - "index": 6872 + "index": 7032 } }, { @@ -34,14 +34,14 @@ "defaultMessage": "!!![Unknown asset name]", "file": "src/legacy/format.ts", "start": { - "line": 214, + "line": 215, "column": 20, - "index": 6894 + "index": 7054 }, "end": { - "line": 217, + "line": 218, "column": 3, - "index": 6987 + "index": 7147 } } -] \ No newline at end of file +] diff --git a/packages/swap-react/.editorconfig b/packages/swap-react/.editorconfig new file mode 100644 index 0000000000..65365be68e --- /dev/null +++ b/packages/swap-react/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +indent_style = space +indent_size = 2 + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/packages/swap-react/.gitignore b/packages/swap-react/.gitignore new file mode 100644 index 0000000000..75356714f9 --- /dev/null +++ b/packages/swap-react/.gitignore @@ -0,0 +1,70 @@ +# OSX +# +.DS_Store + +# XDE +.expo/ + +# VSCode +.vscode/ +jsconfig.json + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# Android/IJ +# +.classpath +.cxx +.gradle +.idea +.project +.settings +local.properties +android.iml + +# Cocoapods +# +example/ios/Pods + +# Ruby +example/vendor/ + +# node.js +# +node_modules/ +npm-debug.log +yarn-debug.log +yarn-error.log + +# BUCK +buck-out/ +\.buckd/ +android/app/libs +android/keystores/debug.keystore + +# Expo +.expo/ + +# Turborepo +.turbo/ + +# generated by bob +lib/ diff --git a/packages/swap-react/.watchmanconfig b/packages/swap-react/.watchmanconfig new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/packages/swap-react/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/packages/swap-react/babel.config.js b/packages/swap-react/babel.config.js new file mode 100644 index 0000000000..f842b77fcf --- /dev/null +++ b/packages/swap-react/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:metro-react-native-babel-preset'], +}; diff --git a/packages/swap-react/jest.setup.js b/packages/swap-react/jest.setup.js new file mode 100644 index 0000000000..eb2d41652a --- /dev/null +++ b/packages/swap-react/jest.setup.js @@ -0,0 +1,3 @@ +jest.mock('@react-native-async-storage/async-storage', () => + require('@react-native-async-storage/async-storage/jest/async-storage-mock'), +) diff --git a/packages/swap-react/package.json b/packages/swap-react/package.json new file mode 100644 index 0000000000..8996ca35f6 --- /dev/null +++ b/packages/swap-react/package.json @@ -0,0 +1,184 @@ +{ + "name": "@yoroi/swap-react", + "version": "1.0.0", + "description": "The Swap package of Yoroi SDK for react and browser", + "main": "lib/commonjs/index", + "browser": "lib/module/index", + "module": "lib/module/index", + "types": "lib/typescript/index.d.ts", + "source": "src/index", + "files": [ + "src", + "lib", + "!ios/build", + "!android/build", + "!android/gradle", + "!android/gradlew", + "!android/gradlew.bat", + "!android/local.properties", + "!**/__tests__", + "!**/__fixtures__", + "!**/__mocks__", + "!**/.*" + ], + "scripts": { + "test": "jest", + "tsc": "tsc --noEmit", + "flow": ". ./scripts/flowgen.sh", + "clean": "del-cli lib", + "lint": "eslint \"**/*.{js,ts,tsx}\"", + "build": "yarn tsc && yarn lint && yarn test --ci --silent && yarn clean && bob build && yarn flow", + "prepack": "yarn build", + "release": "release-it" + }, + "keywords": [ + "yoroi", + "cardano", + "swap", + "browser", + "react" + ], + "repository": { + "type": "github", + "url": "https://github.com/Emurgo/yoroi.git", + "directory": "packages/swap-react" + }, + "author": "EMURGO Fintech (https://github.com/Emurgo/yoroi)", + "contributors": [ + { + "name": "Juliano Lazzarotto", + "email": "juliano@stackchain.dev" + } + ], + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/Emurgo/yoroi/issues" + }, + "homepage": "https://github.com/Emurgo/yoroi/packages/metrics-react#readme", + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "dependencies": { + "@yoroi/types": "1.0.4", + "immer": "^10.0.2" + }, + "peerDependencies": { + "react": ">= 16.8.0 <= 19.0.0", + "react-query": "^3.39.3" + }, + "optionalDependencies": { + "@react-native-async-storage/async-storage": "^1.18.1" + }, + "devDependencies": { + "@commitlint/config-conventional": "^17.0.2", + "@react-native-async-storage/async-storage": "^1.18.1", + "@react-native-community/eslint-config": "^3.0.2", + "@release-it/conventional-changelog": "^5.0.0", + "@testing-library/react-hooks": "^8.0.1", + "@types/jest": "^28.1.2", + "@types/react": "18.2.0", + "@types/react-native": "0.71.6", + "commitlint": "^17.0.2", + "del-cli": "^5.0.0", + "eslint": "^8.4.1", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-prettier": "^4.0.0", + "flowgen": "^1.21.0", + "jest": "^28.1.1", + "pod-install": "^0.1.0", + "prettier": "^2.0.5", + "react": "18.2.0", + "react-native": "0.71.6", + "react-native-builder-bob": "^0.20.4", + "react-query": "^3.39.3", + "react-test-renderer": "^18.2.0", + "release-it": "^15.0.0", + "typescript": "^4.5.2" + }, + "engines": { + "node": ">= 16.19.0" + }, + "packageManager": "^yarn@1.22.15", + "jest": { + "preset": "react-native", + "setupFiles": [ + "/jest.setup.js" + ], + "modulePathIgnorePatterns": [ + "/example/node_modules", + "/lib/" + ] + }, + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "release-it": { + "git": { + "commitMessage": "chore: release ${version}", + "tagName": "v${version}" + }, + "npm": { + "publish": true + }, + "github": { + "release": false + }, + "plugins": { + "@release-it/conventional-changelog": { + "preset": "angular" + } + } + }, + "eslintConfig": { + "root": true, + "extends": [ + "@react-native-community", + "prettier" + ], + "rules": { + "prettier/prettier": [ + "error", + { + "quoteProps": "consistent", + "bracketSpacing": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false, + "semi": false + } + ] + } + }, + "eslintIgnore": [ + "node_modules/", + "lib/", + "babel.config.js", + "jest.setup.js" + ], + "prettier": { + "quoteProps": "consistent", + "bracketSpacing": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "all", + "useTabs": false, + "semi": false + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "project": "tsconfig.build.json" + } + ] + ] + } +} diff --git a/packages/swap-react/scripts/flowgen.sh b/packages/swap-react/scripts/flowgen.sh new file mode 100644 index 0000000000..2638ef8d27 --- /dev/null +++ b/packages/swap-react/scripts/flowgen.sh @@ -0,0 +1,3 @@ +for i in $(find lib -type f -name "*.d.ts"); + do sh -c "npx flowgen $i -o ${i%.*.*}.js.flow"; +done; diff --git a/packages/swap-react/src/adapters/mocks.ts b/packages/swap-react/src/adapters/mocks.ts new file mode 100644 index 0000000000..4065da6325 --- /dev/null +++ b/packages/swap-react/src/adapters/mocks.ts @@ -0,0 +1,81 @@ +import {Swap} from '@yoroi/types' + +export function makeMockSwapStorage(): Readonly { + const slippage: Swap.Storage['slippage'] = { + read: () => { + console.debug('[swap-react] makeMockSwapStorage slippage read') + return Promise.resolve(0.1) + }, + remove: () => { + console.debug('[swap-react] makeMockSwapStorage slippage remove') + return Promise.resolve() + }, + save: (newSlippage) => { + console.debug( + '[swap-react] makeMockSwapStorage slippage save', + newSlippage, + ) + return Promise.resolve() + }, + } + + const reset: Swap.Storage['reset'] = () => { + console.debug('[swap-react] makeMockSwapStorage reset') + return Promise.all([slippage.remove]) + } + + return { + slippage, + reset, + } as const +} + +export function makeMockSwapApiClient(options: Swap.FactoryOptions) { + console.debug('[swap-react] makeMockSwapApiClient', options) + return { + getOpenOrders: (stakeKeyHash: string) => { + console.debug( + '[swap-react] makeMockSwapApiClient getOpenOrders', + stakeKeyHash, + ) + return Promise.resolve([]) + }, + getCancelOrderTx: ( + orderUTxO: string, + collateralUTxOs: string, + walletAddress: string, + ) => { + console.debug( + '[swap-react] makeMockSwapApiClient getCancelOrderTx', + orderUTxO, + collateralUTxOs, + walletAddress, + ) + return Promise.resolve({}) + }, + getOrderDatum: (order: any) => { + console.debug('[swap-react] makeMockSwapApiClient getOrderDatum', order) + return Promise.resolve({}) + }, + getSupportedTokens: (baseToken: string) => { + console.debug( + '[swap-react] makeMockSwapApiClient getSupportedTokens', + baseToken, + ) + return Promise.resolve([]) + }, + getTokenPairPools: (sendToken: string, receiveToken: string) => { + console.debug( + '[swap-react] makeMockSwapApiClient getTokenPairPools', + sendToken, + receiveToken, + ) + return Promise.resolve([]) + }, + } as const +} + +export const swapApiBaseUrls = { + mainnet: 'https://onchain2.muesliswap.com/', + testnet: 'https://onchain2.muesliswap.com/', +} as const diff --git a/packages/swap-react/src/adapters/openswap.ts b/packages/swap-react/src/adapters/openswap.ts new file mode 100644 index 0000000000..8b4d330935 --- /dev/null +++ b/packages/swap-react/src/adapters/openswap.ts @@ -0,0 +1,78 @@ +import {Swap} from '@yoroi/types' +import {makeMockSwapApiClient, makeMockSwapStorage} from './mocks' + +const initialDeps = { + apiClient: makeMockSwapApiClient({ + apiUrl: 'https://127.0.0.1:8080/api', + stakingKey: 'test', + }), + storage: makeMockSwapStorage(), +} as const + +export function makeSwapModule( + {apiUrl, stakingKey}: Readonly, + {apiClient, storage} = initialDeps, +) { + console.log(apiUrl, stakingKey, apiClient, storage) + + const dexModule = () => {} // init something + + const getOpenOrders = () => { + dexModule() + return [ + { + id: '1', + type: 'limit', + }, + ] + } + + const createOrder = ( + orderType: Swap.OrderType, + order: Swap.CreateOrderData, + ) => { + dexModule() + return { + orderType, + order, + } + } + + const cancelOrder = (orderType: Swap.OrderType, order: any) => { + dexModule() + return { + orderType, + order, + } + } + + const getSupportedTokens = () => { + dexModule() + return {} + } + + const getTokenPairPools = () => { + dexModule() + return {} + } + + return { + orders: { + create: createOrder, + cancel: cancelOrder, + list: { + open: getOpenOrders, + }, + }, + pairs: { + list: { + supportedByToken: getSupportedTokens, + }, + }, + pools: { + list: { + byPair: getTokenPairPools, + }, + }, + } as const +} diff --git a/packages/swap-react/src/adapters/storage.test.ts b/packages/swap-react/src/adapters/storage.test.ts new file mode 100644 index 0000000000..94f478990a --- /dev/null +++ b/packages/swap-react/src/adapters/storage.test.ts @@ -0,0 +1,54 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' +import {Swap} from '@yoroi/types' + +import {makeSwapStorage, swapStorageSlippageKey} from './storage' + +jest.mock('@react-native-async-storage/async-storage') + +const mockedAsyncStorage = AsyncStorage as jest.Mocked + +describe('makeSwapStorage', () => { + let swapStorage: Swap.Storage + + beforeEach(() => { + swapStorage = makeSwapStorage() + mockedAsyncStorage.setItem.mockClear() + mockedAsyncStorage.getItem.mockClear() + mockedAsyncStorage.removeItem.mockClear() + }) + + it('should save slippage', async () => { + const slippage = 0.1 + await swapStorage.slippage.save(slippage) + expect(mockedAsyncStorage.setItem).toHaveBeenCalledWith( + swapStorageSlippageKey, + JSON.stringify(slippage), + ) + }) + + it('should read slippage', async () => { + const slippage = 0.1 + mockedAsyncStorage.getItem.mockResolvedValue(JSON.stringify(slippage)) + const result = await swapStorage.slippage.read() + expect(result).toEqual(slippage) + expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith( + swapStorageSlippageKey, + ) + }) + + it('should handle non-numeric values when reading slippage', async () => { + mockedAsyncStorage.getItem.mockResolvedValue(JSON.stringify('not a number')) + const result = await swapStorage.slippage.read() + expect(result).toEqual(0) + expect(mockedAsyncStorage.getItem).toHaveBeenCalledWith( + swapStorageSlippageKey, + ) + }) + + it('should remove slippage', async () => { + await swapStorage.slippage.remove() + expect(mockedAsyncStorage.removeItem).toHaveBeenCalledWith( + swapStorageSlippageKey, + ) + }) +}) diff --git a/packages/swap-react/src/adapters/storage.ts b/packages/swap-react/src/adapters/storage.ts new file mode 100644 index 0000000000..e6d8904ff3 --- /dev/null +++ b/packages/swap-react/src/adapters/storage.ts @@ -0,0 +1,38 @@ +import AsyncStorage from '@react-native-async-storage/async-storage' +import type {Swap} from '@yoroi/types' + +const initialDeps = {storage: AsyncStorage} as const + +export function makeSwapStorage(deps = initialDeps): Readonly { + return { + slippage: { + save: (slippage) => + deps.storage.setItem(swapStorageSlippageKey, JSON.stringify(slippage)), + read: () => + deps.storage + .getItem(swapStorageSlippageKey) + .then((value) => parseNumber(value) ?? 0), + remove: () => deps.storage.removeItem(swapStorageSlippageKey), + }, + } as const as Swap.Storage +} + +export const swapStorageSlippageKey = 'swap-slippage' + +// * === UTILS === +// * NOTE copied from utils it should be imported from utils package later +const parseNumber = (data: unknown) => { + const parsed = parseSafe(data) + return isNumber(parsed) ? parsed : undefined +} + +const parseSafe = (text: any) => { + try { + return JSON.parse(text) as unknown + } catch (_) { + return undefined + } +} + +const isNumber = (data: unknown): data is number => + typeof data === 'number' && !Number.isNaN(data) && Number.isFinite(data) diff --git a/packages/swap-react/src/index.ts b/packages/swap-react/src/index.ts new file mode 100644 index 0000000000..03912a7fd3 --- /dev/null +++ b/packages/swap-react/src/index.ts @@ -0,0 +1,3 @@ +export {makeSwapStorage} from './adapters/storage' +export {makeMockSwapStorage} from './adapters/mocks' +export {SwapProvider, useSwap} from './translators/reactjs' diff --git a/packages/swap-react/src/translators/reactjs.test.tsx b/packages/swap-react/src/translators/reactjs.test.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/swap-react/src/translators/reactjs.tsx b/packages/swap-react/src/translators/reactjs.tsx new file mode 100644 index 0000000000..e19ea93166 --- /dev/null +++ b/packages/swap-react/src/translators/reactjs.tsx @@ -0,0 +1,323 @@ +import * as React from 'react' +import { + QueryKey, + UseMutationOptions, + useQuery, + useQueryClient, + useMutation, +} from 'react-query' +import {Balance, Swap} from '@yoroi/types' +import {makeMockSwapStorage} from '../adapters/mocks' +import {swapStorageSlippageKey} from '../adapters/storage' +import {produce} from 'immer' + +type SwapState = Readonly<{ + createOrder: { + type: Swap.OrderType + } & Omit + + yoroiUnsignedTx: any | undefined +}> + +type SwapCreateOrderActions = { + updateOrderType: (orderType: Swap.OrderType) => void + updateFromAmount: (fromAmount: Balance.Amount) => void + updateToAmount: (toAmount: Balance.Amount) => void + updateProtocol: (protocol: Swap.Protocol) => void + updatePoolId: (poolId: string) => void + updateSlippage: (slippage: number) => void +} + +export enum SwapOrderActionType { + ChangeOrderType = 'changeOrderType', + ChangeAmountFrom = 'changeAmountFrom', + ChangeAmountTo = 'changeAmountTo', + ChangeProtocol = 'changeProtocol', + ChangePoolId = 'changePoolId', + ChangeSlippage = 'changeSlippage', +} + +type SwapOrderAction = + | {type: SwapOrderActionType.ChangeOrderType; orderType: Swap.OrderType} + | {type: SwapOrderActionType.ChangeAmountFrom; fromAmount: Balance.Amount} + | {type: SwapOrderActionType.ChangeAmountTo; toAmount: Balance.Amount} + | {type: SwapOrderActionType.ChangeProtocol; protocol: Swap.Protocol} + | {type: SwapOrderActionType.ChangePoolId; poolId: string} + | {type: SwapOrderActionType.ChangeSlippage; slippage: number} + +type SwapActions = { + updateSwapUnsignedTx: (swapUnsignedTx: any | undefined) => void + reset: () => void +} + +enum SwapActionType { + UpdateSwapUnsignedTx = 'updateSwapUnsignedTx', + Reset = 'reset', +} + +type SwapAction = + | { + type: SwapActionType.UpdateSwapUnsignedTx + unsignedTx: any | undefined + } + | {type: SwapActionType.Reset} + +const combinedReducers = ( + state: SwapState, + action: SwapOrderAction | SwapAction, +) => { + return { + ...swapReducer( + { + ...state, + ...createOrderReducer(state, action as SwapOrderAction), + }, + action as SwapAction, + ), + } as const +} + +const defaultState: SwapState = { + createOrder: { + type: 'limit', + amounts: { + sell: { + quantity: '0', + tokenId: '', + }, + buy: { + quantity: '0', + tokenId: '', + }, + }, + slippage: 0.1, + protocol: 'muesliswap', + poolId: '', + }, + yoroiUnsignedTx: undefined, +} as const + +const defaultSwapOrderActions: SwapCreateOrderActions = { + updateOrderType: (_orderType: Swap.OrderType) => + console.error('[swap-react] missing initialization'), + updateFromAmount: (_fromAmount: Balance.Amount) => + console.error('[swap-react] missing initialization'), + updateToAmount: (_toAmount: Balance.Amount) => + console.error('[swap-react] missing initialization'), + updateProtocol: (_protocol: Swap.Protocol) => + console.error('[swap-react] missing initialization'), + updateSlippage: (_slippage: number) => + console.error('[swap-react] missing initialization'), + updatePoolId: (_poolId: string) => + console.error('[swap-react] missing initialization'), +} as const + +const defaultSwapActions: SwapActions = { + updateSwapUnsignedTx: (_swapUnsignedTx: any | undefined) => + console.error('[swap-react] missing initialization'), + reset: () => console.error('[swap-react] missing initialization'), +} as const + +const defaultActions = { + ...defaultSwapOrderActions, + ...defaultSwapActions, +} as const + +const defaultStorage: Swap.Storage = makeMockSwapStorage() + +const initialSwapProvider: SwapProvider = { + ...defaultState, + ...defaultActions, + ...defaultStorage, +} + +type SwapProvider = React.PropsWithChildren< + SwapState & SwapCreateOrderActions & SwapActions & Swap.Storage +> + +const SwapContext = React.createContext(initialSwapProvider) + +export const SwapProvider = ({ + children, + storage, + initialState, +}: { + children: React.ReactNode + storage: Readonly + initialState?: Readonly> +}) => { + const [state, dispatch] = React.useReducer(combinedReducers, { + ...defaultState, + ...initialState, + }) + const actions = React.useRef({ + updateOrderType: (orderType: Swap.OrderType) => { + dispatch({type: SwapOrderActionType.ChangeOrderType, orderType}) + }, + updateFromAmount: (fromAmount: Balance.Amount) => { + dispatch({type: SwapOrderActionType.ChangeAmountFrom, fromAmount}) + }, + updateToAmount: (toAmount: Balance.Amount) => { + dispatch({type: SwapOrderActionType.ChangeAmountTo, toAmount}) + }, + updateProtocol: (protocol: Swap.Protocol) => { + dispatch({type: SwapOrderActionType.ChangeProtocol, protocol}) + }, + updatePoolId: (poolId: string) => { + dispatch({type: SwapOrderActionType.ChangePoolId, poolId}) + }, + updateSlippage: (slippage: number) => { + dispatch({type: SwapOrderActionType.ChangeSlippage, slippage}) + }, + updateSwapUnsignedTx: (unsignedTx: any | undefined) => { + dispatch({ + type: SwapActionType.UpdateSwapUnsignedTx, + unsignedTx: unsignedTx, + }) + }, + reset: () => { + dispatch({type: SwapActionType.Reset}) + }, + }).current + + const context = React.useMemo( + () => ({...state, ...actions, ...storage}), + [state, actions, storage], + ) + + return {children} +} + +function createOrderReducer( + state: SwapState, + action: SwapOrderAction, +): SwapState['createOrder'] { + switch (action.type) { + case SwapOrderActionType.ChangeOrderType: + return produce(state.createOrder, (draft) => { + draft.type = action.orderType + }) + case SwapOrderActionType.ChangeAmountFrom: + return produce(state.createOrder, (draft) => { + draft.amounts.sell = action.fromAmount + }) + case SwapOrderActionType.ChangeAmountTo: + return produce(state.createOrder, (draft) => { + draft.amounts.buy = action.toAmount + }) + case SwapOrderActionType.ChangeProtocol: + return produce(state.createOrder, (draft) => { + draft.protocol = action.protocol + }) + case SwapOrderActionType.ChangePoolId: + return produce(state.createOrder, (draft) => { + draft.poolId = action.poolId + }) + case SwapOrderActionType.ChangeSlippage: + return produce(state.createOrder, (draft) => { + draft.slippage = action.slippage + }) + default: + return produce(state.createOrder, () => {}) + } +} +const swapReducer = (state: SwapState, action: SwapAction) => { + switch (action.type) { + case SwapActionType.UpdateSwapUnsignedTx: + return produce( + state, + (draft) => (draft.yoroiUnsignedTx = action.unsignedTx), + ) + case SwapActionType.Reset: + return produce(defaultState, () => {}) + default: + return produce(state, () => {}) + } +} + +export const useSwap = () => { + const value = React.useContext(SwapContext) + if (!value) { + throw new Error('[swap-react] useSwap must be used within a SwapProvider') + } + return value +} + +// * === SETTINGS === +// * NOTE maybe it should be moved as part of wallet settings package +export const useSwapSlippage = () => { + const {slippage} = useSwap() + const query = useQuery({ + suspense: true, + queryKey: [swapStorageSlippageKey], + queryFn: slippage.read, + }) + + if (query.data == null) + throw new Error('[swap-react] useSwapSlippage invalid state') + + return query.data +} + +export const useSwapSetSlippage = ( + options?: UseMutationOptions, +) => { + const {slippage} = useSwap() + const mutation = useMutationWithInvalidations({ + ...options, + useErrorBoundary: true, + mutationFn: slippage.save, + invalidateQueries: [[swapStorageSlippageKey]], + }) + + return mutation.mutate +} + +export const useSwapSettings = () => { + const setSlippage = useSwapSetSlippage() + const slippage = useSwapSlippage() + + const memoizedSetSlippage = React.useCallback( + (newSlippage: number) => + setSlippage(newSlippage, { + // onSuccess: metrics.enable, + }), + [setSlippage], + ) + + return React.useMemo( + () => ({ + slippage, + setSlippage: memoizedSetSlippage, + }), + [slippage, memoizedSetSlippage], + ) +} + +// * === HOOKS === +// * NOTE copied from wallet-mobile it should be imported from hooks package later +const useMutationWithInvalidations = < + TData = unknown, + TError = unknown, + TVariables = void, + TContext = unknown, +>({ + invalidateQueries, + ...options +}: UseMutationOptions & { + invalidateQueries?: Array +} = {}) => { + const queryClient = useQueryClient() + + return useMutation({ + ...options, + onMutate: (variables) => { + invalidateQueries?.forEach((key) => queryClient.cancelQueries(key)) + return options?.onMutate?.(variables) + }, + onSuccess: (data, variables, context) => { + invalidateQueries?.forEach((key) => queryClient.invalidateQueries(key)) + return options?.onSuccess?.(data, variables, context) + }, + }) +} diff --git a/packages/swap-react/tsconfig.build.json b/packages/swap-react/tsconfig.build.json new file mode 100644 index 0000000000..999d3f3c8d --- /dev/null +++ b/packages/swap-react/tsconfig.build.json @@ -0,0 +1,5 @@ + +{ + "extends": "./tsconfig", + "exclude": ["example"] +} diff --git a/packages/swap-react/tsconfig.json b/packages/swap-react/tsconfig.json new file mode 100644 index 0000000000..1cf23a1a02 --- /dev/null +++ b/packages/swap-react/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "baseUrl": "./src", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext" + } +} diff --git a/packages/swap/.editorconfig b/packages/swap/.editorconfig new file mode 100644 index 0000000000..22534a358c --- /dev/null +++ b/packages/swap/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +indent_style = space +indent_size = 2 +quote_type = single + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/packages/swap/README.md b/packages/swap/README.md index deec96e6e9..57102956ce 100644 --- a/packages/swap/README.md +++ b/packages/swap/README.md @@ -1,2 +1,3 @@ # yoroi-dex + MuesliSwap DEX Aggregator Integration diff --git a/packages/swap/api/config.ts b/packages/swap/api/config.ts new file mode 100644 index 0000000000..b5dfb6e350 --- /dev/null +++ b/packages/swap/api/config.ts @@ -0,0 +1,28 @@ +import axios from 'axios'; + +export const SWAP_API_ENDPOINTS = { + mainnet: { + getPools: 'https://onchain2.muesliswap.com/pools/pair', + getOrders: 'https://onchain2.muesliswap.com/orders/all', + getTokens: 'https://api.muesliswap.com/list', + constructSwapDatum: 'https://aggregator.muesliswap.com/constructSwapDatum', + cancelSwapTransaction: + 'https://aggregator.muesliswap.com/cancelSwapTransaction', + }, + preprod: { + getPools: 'https://preprod.pools.muesliswap.com/pools/pair', + getOrders: 'https://preprod.pools.muesliswap.com/orders/all', + getTokens: 'https://preprod.api.muesliswap.com/list', + constructSwapDatum: + 'https://aggregator.muesliswap.com/constructTestnetSwapDatum', + cancelSwapTransaction: + 'https://aggregator.muesliswap.com/cancelTestnetSwapTransaction', + }, +} as const; + +export const axiosClient = axios.create({ + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, +}); diff --git a/packages/swap/api/index.ts b/packages/swap/api/index.ts new file mode 100644 index 0000000000..8c18283fa0 --- /dev/null +++ b/packages/swap/api/index.ts @@ -0,0 +1,40 @@ +import { Swap } from '@yoroi/types'; +import { cancelOrder, createOrder, getOrders } from './orders'; +import { getPools } from './pools'; +import { getTokens } from './tokens'; + +export class SwapApi implements Swap.Api { + constructor(public readonly network: Swap.Network) {} + + public async createOrder( + order: Swap.CreateOrderData + ) { + return createOrder(this.network, order); + } + + public async cancelOrder( + orderUTxO: string, + collateralUTxO: string, + walletAddress: string + ) { + return cancelOrder(this.network, orderUTxO, collateralUTxO, walletAddress); + } + + public async getOrders(stakeKeyHash: string) { + return getOrders(this.network, stakeKeyHash); + } + + public async getPools( + tokenA: Swap.BaseTokenInfo, + tokenB: Swap.BaseTokenInfo + ) { + return getPools(this.network, tokenA, tokenB); + } + + public getTokens( + policyId = '', + assetName = '' + ) { + return getTokens(this.network, policyId, assetName); + } +} diff --git a/packages/swap/api/orders.spec.ts b/packages/swap/api/orders.spec.ts new file mode 100644 index 0000000000..f5e33e7a60 --- /dev/null +++ b/packages/swap/api/orders.spec.ts @@ -0,0 +1,175 @@ +import { describe, expect, it, vi, Mocked } from 'vitest'; +import { createOrder, cancelOrder, getOrders } from './orders'; +import axios from 'axios'; +import { axiosClient } from './config'; + +vi.mock('./config'); + +const ADA_TOKEN = { + policyId: '', + assetName: '', +}; + +const GENS_TOKEN = { + policyId: 'dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb', + assetName: '0014df1047454e53', +}; + +const mockAxios = axiosClient as Mocked; + +describe('SwapOrdersApi', () => { + describe('getOrders', () => { + it('Should return orders list using staking key hash', async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ + data: mockedOrders, + status: 200, + }) + ); + const result = await getOrders( + 'preprod', + '24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7' + ); + expect(result).to.have.lengthOf(1); + }); + }); + + describe('createOrder', () => { + it('should create order and return datum, datumHash, and contract address', async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ + status: 200, + data: mockedCreateOrderResult, + }) + ); + + const order = await createOrder('mainnet', createOrderParams); + + expect(order.contractAddress).to.eq(mockedCreateOrderResult.address); + expect(order.datum).to.eq(mockedCreateOrderResult.datum); + expect(order.datumHash).to.eq(mockedCreateOrderResult.hash); + }); + + it('should throw error for invalid order', async () => { + await expect(async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ + status: 200, + data: { status: 'failed', reason: 'error_message' }, + }) + ); + await createOrder('preprod', createOrderParams); + }).rejects.toThrowError(/^error_message$/); + }); + + it('should throw generic error for invalid response', async () => { + await expect(async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ status: 400 }) + ); + await createOrder('mainnet', createOrderParams); + }).rejects.toThrow('Failed to construct swap datum'); + }); + }); + + describe('cancelOrder', () => { + it('should cancel pending orders', async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ + status: 200, + data: { cbor: 'tx_cbor' }, + }) + ); + + const txCbor = await cancelOrder( + 'mainnet', + 'orderUtxo', + 'collateralUtxo', + 'addr1' + ); + + expect(txCbor).to.eq('tx_cbor'); + }); + + it('should throw generic error for invalid response', async () => { + await expect(async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ status: 400 }) + ); + await cancelOrder( + 'mainnet', + cancelOrderParams.utxo, + cancelOrderParams.collateralUTxOs, + cancelOrderParams.address + ); + }).rejects.toThrow('Failed to cancel swap transaction'); + }); + }); +}); + +const mockedOrders = [ + { + from: { + amount: '1000000', + token: '.', + }, + to: { + amount: '41372', + token: + '2adf188218a66847024664f4f63939577627a56c090f679fe366c5ee.535441424c45', + }, + sender: + 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', + owner: + 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', + ownerPubKeyHash: '1f4a69a22ca1018e7763d11474a7e604d6d754c716594a14ed6d4012', + ownerStakeKeyHash: + '24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7', + batcherFee: { + amount: '950000', + token: '.', + }, + deposit: '1700000', + valueAttached: [ + { + amount: '3650000', + token: '.', + }, + ], + utxo: '1e977694e2413bd0e6105303bb44da60530cafe49b864dde8f8902b021ed86ba#0', + provider: 'muesliswap_v4', + feeField: '2650000', + allowPartial: true, + }, +]; + +const mockedCreateOrderResult = { + status: 'success', + datum: + 'd8799fd8799fd8799fd8799f581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a2ffd8799fd8799fd8799f581cda22c532206a75a628778eebaf63826f9d93fbe9b4ac69a7f8e4cd78ffffffff581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a21b00000188f2408726d8799fd8799f4040ffd8799f581cdda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb480014df1047454e53ffffffd8799fd879801a0006517affff', + hash: '4ae3fc5498e9d0f04daaf2ee739e41dc3f6f4119391e7274f0b3fa15aa2163ff', + address: 'addr1wxr2a8htmzuhj39y2gq7ftkpxv98y2g67tg8zezthgq4jkg0a4ul4', +}; + +const createOrderParams = { + address: + 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', + protocol: 'sundaeswap', + poolId: '14', + sell: { + ...ADA_TOKEN, + amount: '25000000', + }, + buy: { + ...GENS_TOKEN, + amount: '50000000', + }, +} as const; + +const cancelOrderParams = { + utxo: '6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86', + collateralUTxOs: + '6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86', + address: + 'addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz', +} as const; diff --git a/packages/swap/api/orders.ts b/packages/swap/api/orders.ts new file mode 100644 index 0000000000..4c977a833b --- /dev/null +++ b/packages/swap/api/orders.ts @@ -0,0 +1,94 @@ +import { Swap } from '@yoroi/types'; +import { SWAP_API_ENDPOINTS, axiosClient } from './config'; + +export async function createOrder( + network: Swap.Network, + order: Swap.CreateOrderData +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].constructSwapDatum; + const response = await axiosClient.get< + | { status: 'failed'; reason?: string } + | { status: 'success'; hash: string; datum: string; address: string } + >('/', { + baseURL: apiUrl, + params: { + walletAddr: order.address, + protocol: order.protocol, + poolId: order.poolId, + sellTokenPolicyID: order.sell.policyId, + sellTokenNameHex: order.sell.assetName, + sellAmount: order.sell.amount, + buyTokenPolicyID: order.buy.policyId, + buyTokenNameHex: order.buy.assetName, + buyAmount: order.buy.amount, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to construct swap datum', { + cause: response.data, + }); + } + + if (response.data.status === 'failed') { + throw new Error(response.data.reason || 'Unexpected error occurred'); + } + + return { + datumHash: response.data.hash, + datum: response.data.datum, + contractAddress: response.data.address, + }; +} + +/** + * @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0" + * @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format. + * @param walletAddress address of the wallet that owns the order in cbor format. + * @returns an unsigned transaction to cancel the order. + */ +export async function cancelOrder( + network: Swap.Network, + orderUTxO: string, + collateralUTxO: string, + walletAddress: string +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].cancelSwapTransaction; + const response = await axiosClient.get('/', { + baseURL: apiUrl, + params: { + wallet: walletAddress, + utxo: orderUTxO, + collateralUTxO, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to cancel swap transaction', { + cause: response.data, + }); + } + + return response.data.cbor; +} + +export async function getOrders( + network: Swap.Network, + stakeKeyHash: string +): Promise { + const apiUrl = SWAP_API_ENDPOINTS[network].getPools; + const response = await axiosClient.get('/', { + baseURL: apiUrl, + params: { + 'stake-key-hash': stakeKeyHash, + }, + }); + + if (response.status !== 200) { + throw new Error(`Failed to get orders for ${stakeKeyHash}`, { + cause: response.data, + }); + } + + return response.data; +} diff --git a/packages/swap/api/pools.spec.ts b/packages/swap/api/pools.spec.ts new file mode 100644 index 0000000000..33b31897b4 --- /dev/null +++ b/packages/swap/api/pools.spec.ts @@ -0,0 +1,79 @@ +import { describe, expect, it, vi, Mocked } from 'vitest'; +import { getPools } from './pools'; +import { axiosClient } from './config'; + +vi.mock('./config.ts'); +const mockAxios = axiosClient as Mocked; + +describe('SwapPoolsApi', () => { + it('should get pools list for a given token pair', async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ + status: 200, + data: mockedPoolRes, + }) + ); + + const result = await getPools( + 'mainnet', + getPoolsParams.sell, + getPoolsParams.buy + ); + expect(result).to.be.of.lengthOf(1); + }); + + it('should throw error for invalid response', async () => { + await expect(async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ status: 500 }) + ); + await getPools('preprod', getPoolsParams.sell, getPoolsParams.buy); + }).rejects.toThrow('Failed to fetch pools for token pair'); + }); +}); + +const mockedPoolRes = [ + { + provider: 'minswap', + fee: '0.3', + tokenA: { + amount: '1233807687', + token: '.', + }, + tokenB: { + amount: '780', + token: + 'e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72.43414b45', + }, + price: 1581804.726923077, + batcherFee: { + amount: '2000000', + token: '.', + }, + depositFee: { + amount: '2000000', + token: '.', + }, + deposit: 2000000, + utxo: '0596860b5970ef989c56f7ae38b3c0f74bb4979ac15ee994c30760f7f4d908ce#0', + poolId: + '0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1.7339a8bcda85e2c997d9f16beddbeb3ad755f5202f5cfd9cb08db346a1292c01', + timestamp: '2023-05-31 07:03:41', + lpToken: { + amount: '981004', + token: + 'e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86.7339a8bcda85e2c997d9f16beddbeb3ad755f5202f5cfd9cb08db346a1292c01', + }, + }, +]; + +const getPoolsParams = { + sell: { + policyId: '', + assetNameHex: '', + }, + buy: { + policyId: 'e16c2dc8ae937e8d3790c7fd7168d7b994621ba14ca11415f39fed72', + assetNameHex: '43414b45', + }, +} as const; diff --git a/packages/swap/api/pools.ts b/packages/swap/api/pools.ts new file mode 100644 index 0000000000..12235d11db --- /dev/null +++ b/packages/swap/api/pools.ts @@ -0,0 +1,34 @@ +import { Swap } from '@yoroi/types'; +import { SWAP_API_ENDPOINTS, axiosClient } from './config'; + +export async function getPools( + network: Swap.Network, + tokenA: Swap.BaseTokenInfo, + tokenB: Swap.BaseTokenInfo +): Promise { + const params: { [key: string]: string } = { + 'policy-id1': tokenA.policyId, + 'policy-id2': tokenB.policyId, + }; + + if ('assetName' in tokenA) params['tokenname1'] = tokenA.assetName; + if ('assetName' in tokenB) params['tokenname2'] = tokenB.assetName; + + // note: {tokenname-hex} will overwrites {tokenname} + if ('assetNameHex' in tokenA) params['tokenname-hex1'] = tokenA.assetNameHex; + if ('assetNameHex' in tokenB) params['tokenname-hex2'] = tokenB.assetNameHex; + + const apiUrl = SWAP_API_ENDPOINTS[network].getPools; + const response = await axiosClient.get('', { + baseURL: apiUrl, + params, + }); + + if (response.status !== 200) { + throw new Error('Failed to fetch pools for token pair', { + cause: response.data, + }); + } + + return response.data; +} diff --git a/packages/swap/api/tokens.spec.ts b/packages/swap/api/tokens.spec.ts new file mode 100644 index 0000000000..353e889114 --- /dev/null +++ b/packages/swap/api/tokens.spec.ts @@ -0,0 +1,74 @@ +import { expect, describe, it, vi, Mocked } from 'vitest'; +import { getTokens } from './tokens'; +import { axiosClient } from './config'; + +vi.mock('./config.ts'); +const mockAxios = axiosClient as Mocked; + +describe('SwapTokensApi', () => { + it('should get all supported tokens list', async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ status: 200, data: mockedGetTokensRes }) + ); + const result = await getTokens('mainnet'); + expect(result).to.be.lengthOf(1); + }); + + it('should return empty list on preprod network', async () => { + expect(await getTokens('preprod')).to.be.empty; + }); + + it('should throw error for invalid response', async () => { + await expect(async () => { + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({ status: 500 }) + ); + await getTokens('mainnet'); + }).rejects.toThrow('Failed to fetch tokens'); + }); +}); + +const mockedGetTokensRes = [ + { + info: { + supply: { total: '1000000000000', circulating: null }, + status: 'unverified', + image: 'ipfs://QmPzaykTy4yfutCtwv7nRUmgbQbA7euiThyy2i9fiFuDHX', + imageIpfsHash: 'QmPzaykTy4yfutCtwv7nRUmgbQbA7euiThyy2i9fiFuDHX', + symbol: 'ARGENT', + minting: { + type: 'time-lock-policy', + blockchain: 'cardano', + mintedBeforeSlotNumber: 91850718, + }, + mediatype: 'image/png', + tokentype: 'token', + description: 'ARGENT Token', + totalsupply: 1000000000000, + address: { + policyId: 'c04f4200502a998e9eebafac0291a1f38008de3fe146d136946d8f4b', + name: '415247454e54', + }, + decimalPlaces: 0, + categories: [], + }, + price: { + volume: { base: 0, quote: 0 }, + volumeChange: { base: 0, quote: 0 }, + volumeTotal: { base: 0, quote: 0 }, + volumeAggregator: {}, + price: 0, + askPrice: 0, + bidPrice: 0, + priceChange: { '24h': 0, '7d': 0 }, + quoteDecimalPlaces: 0, + baseDecimalPlaces: 6, + quoteAddress: { + policyId: 'c04f4200502a998e9eebafac0291a1f38008de3fe146d136946d8f4b', + name: '415247454e54', + }, + baseAddress: { policyId: '', name: '' }, + price10d: [], + }, + }, +]; diff --git a/packages/swap/api/tokens.ts b/packages/swap/api/tokens.ts new file mode 100644 index 0000000000..3df38337c3 --- /dev/null +++ b/packages/swap/api/tokens.ts @@ -0,0 +1,25 @@ +import { Swap } from '@yoroi/types'; +import { SWAP_API_ENDPOINTS, axiosClient } from './config'; + +export async function getTokens( + network: Swap.Network, + policyId = '', + assetName = '' +): Promise { + if (network === 'preprod') return []; + + const apiUrl = SWAP_API_ENDPOINTS[network].getTokens; + const response = await axiosClient.get('', { + baseURL: apiUrl, + params: { + 'base-policy-id': policyId, + 'base-tokenname': assetName, + }, + }); + + if (response.status !== 200) { + throw new Error('Failed to fetch tokens', { cause: response.data }); + } + + return response.data; +} diff --git a/packages/swap/cardano/create.ts b/packages/swap/cardano/create.ts deleted file mode 100644 index 0f0e74e031..0000000000 --- a/packages/swap/cardano/create.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { - Address, TransactionBody, TransactionBuilder, TransactionOutput, -} from "@emurgo/cross-csl-core"; -import { buildValue } from "./utils"; - -export async function buildCreateOrderTx(txBuilder: TransactionBuilder, order: any): Promise { - const address = await Address.fromBech32(order.contractAddress); - const value = await buildValue(order.depositAmount); - const txOutput = await TransactionOutput.new(address, value); - - await txOutput.setDataHash(order.datumHash); - await txBuilder.addOutput(txOutput); - - return txBuilder.build(); -}; diff --git a/packages/swap/cardano/index.ts b/packages/swap/cardano/index.ts deleted file mode 100644 index 1e03cceb83..0000000000 --- a/packages/swap/cardano/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './create'; diff --git a/packages/swap/cardano/utils.ts b/packages/swap/cardano/utils.ts deleted file mode 100644 index cb6544cb9b..0000000000 --- a/packages/swap/cardano/utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AssetName, Assets, BigNum, MultiAsset, ScriptHash, Value } from "@emurgo/cross-csl-core"; -import { TokenAmount } from "../dex"; - -export const toScripthash = (hex: string) => { - return ScriptHash.fromBytes(toUint8Array(hex)); -}; - -export const fromScripthash = async (instance: ScriptHash) => { - return fromUint8Array(await instance.toBytes()); -}; - -export const toUint8Array = (hex: string) => { - if (hex.length % 2 !== 0) throw new Error("Invalid hex string"); - - const bytes = new Uint8Array(hex.length / 2); - - for (let i = 0; i < hex.length; i += 2) { - let value = parseInt(hex.substring(i, i + 2), 16); - if (isNaN(value)) throw new Error("Invalid hex string"); - bytes[i / 2] = value; - } - - return bytes; -}; - -export const fromUint8Array = (bytes: Uint8Array) => { - const hex = new Array(bytes.length); - - for (let i = 0; i < bytes.length; i++) { - hex.push(bytes[i].toString(16).padStart(2, '0')); - } - - return hex.join(''); -}; - -export const buildValue = async (token: TokenAmount) => { - if (token.address.policyId === '' && token.address.assetName === '') { - return await Value.new(await BigNum.fromStr(token.amount)); - } - - const multiasset = await MultiAsset.new(); - const assets = await Assets.new(); - await assets.insert( - await AssetName.new(toUint8Array(token.address.assetName)), - await BigNum.fromStr(token.amount), - ); - const value = await Value.new(await BigNum.fromStr('0')); - await multiasset.insert(await toScripthash(token.address.policyId), assets); - await value.setMultiasset(multiasset); - return value; -}; diff --git a/packages/swap/dex.spec.ts b/packages/swap/dex.spec.ts deleted file mode 100644 index 7f65a0ec93..0000000000 --- a/packages/swap/dex.spec.ts +++ /dev/null @@ -1,606 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { - calculateAmountsGivenInput, - calculateAmountsGivenOutput, - constructLimitOrder, - constructMarketOrder, - getOpenOrders, - getOrderDatum, - getSupportedTokens, - getTokenPairPools, -} from "./dex"; - -describe("Yoroi DEX aggregator", () => { - it("should be able to get the tokens supported by the DEX aggregator", async () => { - const tokens = await getSupportedTokens(); - - expect(tokens).containSubset([mockTokens[0]]); - }); - - it("should be able to get the pools for the selected token pair", async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - - expect(pools.length).toBeGreaterThan(0); - expect(pools).containSubset([SUNDAE_POOL]); - }); - - it("should be able to calculate the amounts given input for a trade", () => { - const tokenPair = calculateAmountsGivenInput(SUNDAE_POOL, { - address: ADA_TOKEN, - amount: "20000000", - }); - - expect(Number(tokenPair.receive.amount)).approximately(21_000_000, 500_000); - }); - - it("should be able to calculate the amounts given output for a trade", () => { - const tokenPair = calculateAmountsGivenOutput(SUNDAE_POOL, { - address: GENS_TOKEN, - amount: "21000000", - }); - - expect(Number(tokenPair.send.amount)).approximately(20_000_000, 500_000); - }); - - it("should get the same result for a trade regardless of the direction", () => { - const tokenPairInput = calculateAmountsGivenInput(SUNDAE_POOL, { - address: ADA_TOKEN, - amount: "20000000", - }); - - const tokenPairOutput = calculateAmountsGivenOutput(SUNDAE_POOL, { - address: GENS_TOKEN, - amount: "21366237", - }); - - expect(Number(tokenPairInput.send.amount)).approximately( - Number(tokenPairOutput.send.amount), - 10 - ); - expect(Number(tokenPairInput.receive.amount)).approximately( - Number(tokenPairOutput.receive.amount), - 10 - ); - }); - - it("should be able to construct a LIMIT order for a trade given a wallet address and slippage tolerance", () => { - const slippage = 15; // 5% - const address = "some wallet address here"; - - const order = constructLimitOrder( - { - address: ADA_TOKEN, - amount: "25000000", - }, - { - address: GENS_TOKEN, - amount: "50000000", - }, - SUNDAE_POOL, - slippage, - address - ); - - expect(order?.protocol).toEqual("sundaeswap"); - expect(order?.walletAddress).toEqual(address); - expect(order?.send.amount).toEqual("25000000"); - expect(Number(order?.receive.amount)).approximately( - Number("42500000"), - 100_000 - ); - }); - - it("should be able to construct a MARKET order for a trade given a wallet address and slippage tolerance", async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - const address = "some wallet address here"; - const slippage = 5; // 5% - - const order = constructMarketOrder( - { - address: ADA_TOKEN, - amount: "25000000", - }, - GENS_TOKEN, - pools, - slippage, - address - ); - - expect(order?.protocol).toEqual("wingriders"); - expect(order?.walletAddress).toEqual(address); - expect(order?.send.amount).toEqual("25000000"); - expect(Number(order?.receive.amount)).approximately( - Number("26700000"), - 100_000 - ); - }); - - it("should be able to get open orders for a wallet stake key hash", async () => { - const orders = await getOpenOrders( - "24fd15671a17a39268b7a31e2a6703f5893f254d4568411322baeeb7" - ); - console.log(JSON.stringify(orders, null, 2)); - - expect(orders.length).toBeGreaterThan(0); - expect(orders).containSubset([ - { - protocol: "sundaeswap", - depposit: "2000000", - utxo: "6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86#0", - send: { - address: { - policyId: "", - assetName: "", - }, - amount: "1000000", - }, - receive: { - address: { - policyId: - "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa", - assetName: "4d494c4b", - }, - amount: "20", - }, - }, - ]); - }); - - it("should be able to get the datum for a constructed order", async () => { - const pools = await getTokenPairPools(ADA_TOKEN, GENS_TOKEN); - const address = "some wallet address here"; - const slippage = 5; // 5% - - const order = constructMarketOrder( - { - address: ADA_TOKEN, - amount: "25000000", - }, - GENS_TOKEN, - pools, - slippage, - address - ); - - const orderDatum = await getOrderDatum(order!); - - expect(orderDatum.contractAddress).toEqual(mockOrder.address); - expect(orderDatum.datumHash).toEqual(mockOrder.hash); - expect(orderDatum.datum).toEqual(mockOrder.datum); - }); -}); - -vi.mock("./openswap", async () => { - const actual = await vi.importActual("./openswap"); - - return { - ...(actual as {}), - createOrder: async () => mockOrder, - getOrders: async () => [mockOpenOrder], - getPools: async () => mockPools, - getTokens: async () => mockTokens, - }; -}); - -const mockOpenOrder = { - from: { - amount: "1000000", - token: ".", - }, - to: { - amount: "20", - token: "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b", - }, - sender: - "addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz", - owner: - "addr1qy0556dz9jssrrnhv0g3ga98uczdd465cut9jjs5a4k5qy3yl52kwxsh5wfx3darrc4xwql43ylj2n29dpq3xg46a6mska8vfz", - ownerPubKeyHash: "1f4a69a22ca1018e7763d11474a7e604d6d754c716594a14ed6d4012", - batcherFee: { - amount: "2500000", - token: ".", - }, - deposit: "2000000", - valueAttached: [ - { - amount: "5500000", - token: ".", - }, - ], - utxo: "6c4b4e55301d79128071f05a018cf05b7de86bc3f92d05b6668423e220152a86#0", - provider: "sundaeswap", - batcherAddress: "addr1wxaptpmxcxawvr3pzlhgnpmzz3ql43n2tc8mn3av5kx0yzs09tqh8", - poolId: "14", - swapDirection: 0, -}; - -const mockOrder = { - status: "success", - datum: - "d8799fd8799fd8799fd8799f581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a2ffd8799fd8799fd8799f581cda22c532206a75a628778eebaf63826f9d93fbe9b4ac69a7f8e4cd78ffffffff581c353b8bc29a15603f0b73eac44653d1bd944d92e0e0dcd5eb185164a21b00000188f2408726d8799fd8799f4040ffd8799f581cdda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb480014df1047454e53ffffffd8799fd879801a0006517affff", - hash: "4ae3fc5498e9d0f04daaf2ee739e41dc3f6f4119391e7274f0b3fa15aa2163ff", - address: "addr1wxr2a8htmzuhj39y2gq7ftkpxv98y2g67tg8zezthgq4jkg0a4ul4", -}; - -const mockPools = [ - { - provider: "muesliswap_v2", - fee: "0.3", - tokenA: { amount: "2778918813", token: "." }, - tokenB: { - amount: "3046518484", - token: - "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", - }, - price: 0.9121621377301974, - batcherFee: { amount: "950000", token: "." }, - depositFee: { amount: "2000000", token: "." }, - deposit: 2000000, - utxo: "b9d6cef3002de24896e5949619bf5e76ddcbd44147d1a127ee04e2b7486747df#7", - poolId: - "909133088303c49f3a30f1cc8ed553a73857a29779f6c6561cd8093f.34e551a0dabee7dfddcb5dcea93d22040a8b9e36348057da2987a6f1bc731935", - timestamp: "2023-05-26 15:12:11", - lpToken: { - amount: "2864077440", - token: - "af3d70acf4bd5b3abb319a7d75c89fb3e56eafcdd46b2e9b57a2557f.34e551a0dabee7dfddcb5dcea93d22040a8b9e36348057da2987a6f1bc731935", - }, - }, - { - provider: "minswap", - fee: "0.3", - tokenA: { amount: "159431695049", token: "." }, - tokenB: { - amount: "179285403975", - token: - "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", - }, - price: 0.8892619896220417, - batcherFee: { amount: "2000000", token: "." }, - depositFee: { amount: "2000000", token: "." }, - deposit: 2000000, - utxo: "a9c6386fcca2cc9ad86e86940566b3f1b22ac3e47f59fa040d42c62811184e82#3", - poolId: - "0be55d262b29f564998ff81efe21bdc0022621c12f15af08d0f2ddb1.4705d99a4cf6bce9181e60fdbbf961edf6acad7141ed69186c8a8883600e59c5", - timestamp: "2023-05-26 15:12:11", - lpToken: { - amount: "169067370752", - token: - "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86.4705d99a4cf6bce9181e60fdbbf961edf6acad7141ed69186c8a8883600e59c5", - }, - }, - { - provider: "sundaeswap", - fee: "0.05", - tokenA: { amount: "1762028491", token: "." }, - tokenB: { - amount: "1904703890", - token: - "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", - }, - price: 0.925093134030403, - batcherFee: { amount: "2500000", token: "." }, - depositFee: { amount: "2000000", token: "." }, - deposit: 2000000, - utxo: "b28ccb32adf80618a4e78d97012754f8c402b29907f1d2e6ed5df973003844f4#0", - poolId: "0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.7020e403", - timestamp: "2023-05-26 15:12:11", - lpToken: { - amount: "1813689472", - token: - "0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.6c7020e403", - }, - }, - { - provider: "wingriders", - fee: "0.35", - tokenA: { amount: "25476044027", token: "." }, - tokenB: { - amount: "28844597339", - token: - "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb.0014df1047454e53", - }, - price: 0.8832171837099813, - batcherFee: { amount: "2000000", token: "." }, - depositFee: { amount: "2000000", token: "." }, - deposit: 2000000, - utxo: "2120d4a9ece0add45461d5d3262f3708bd6cbdd8a473c7ceb32eb5e83f4c633e#0", - poolId: - "026a18d04a0c642759bb3d83b12e3344894e5c1c7b2aeb1a2113a570.84eaa43491009c3a9f4e257370352e84ea7246ee8a27d9d3d9e458141d9bcf97", - timestamp: "2023-05-26 15:10:51", - lpToken: null, - }, -]; - -const mockTokens = [ - { - info: { - supply: { total: "10000", circulating: null }, - status: "verified", - address: { - policyId: "53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6", - name: "f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715", - }, - symbol: "OBOND [VICIS]", - website: "https://app.optim.finance/dashboard", - description: - "A lender bond issued by Optim Finance corresponding to 100 ADA_TOKEN lent. Lending APY 5.33%. Max Duration 72 Epochs. Interest Buffer 6 epochs. Verify on token homepage to assess more details.", - decimalPlaces: 0, - categories: ["2"], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 1000.0, - askPrice: 0, - bidPrice: 1000.0, - priceChange: { "24h": 0, "7d": 0 }, - fromToken: ".", - toToken: - "53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6.f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715", - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "53fb41609e208f1cd3cae467c0b9abfc69f1a552bf9a90d51665a4d6", - name: "f9c09e1913e29072938cad1fda7b70939090c0be1f7fa48150a2e63a2b9ad715", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, - { - info: { - supply: { total: "100000000000", circulating: null }, - status: "unverified", - website: "https://cardanofight.club", - symbol: "CFC", - decimalPlaces: 0, - image: - "https://tokens.muesliswap.com/static/img/tokens/71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3.434643.png", - description: - "The official token for Cardano Fight Club. The ultimate utility token for all CFC Products and Development.", - address: { - policyId: "71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3", - name: "434643", - }, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 14.5211676983, - askPrice: 0, - bidPrice: 1.112, - priceChange: { "24h": 0, "7d": 0 }, - fromToken: ".", - toToken: - "71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3.434643", - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "71ccb467ef856b242753ca53ade36cd1f8d9abb33fdfa7d1ff89cda3", - name: "434643", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, - { - info: { - supply: { total: "10000000", circulating: null }, - status: "unverified", - website: "https://www.cardanofly.io/", - symbol: "cdfly", - decimalPlaces: 0, - image: "ipfs://QmQaKcTjrBSVbmK5xZgaWjEa5yKzUSLRAbVyhyzbceXSMH", - description: "", - address: { - policyId: "5d5aadeebb07a5e48827ef6efe577c7b4db4a69b2db1b29279e0b514", - name: "6364666c79", - }, - imageIpfsHash: "QmQaKcTjrBSVbmK5xZgaWjEa5yKzUSLRAbVyhyzbceXSMH", - minting: { - type: "time-lock-policy", - blockchain: "cardano", - mintedBeforeSlotNumber: 74916255, - }, - mediatype: "image/png", - tokentype: "token", - totalsupply: 10000000, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { "24h": 0, "7d": 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "5d5aadeebb07a5e48827ef6efe577c7b4db4a69b2db1b29279e0b514", - name: "6364666c79", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, - { - info: { - supply: { total: "100000000000000", circulating: null }, - status: "unverified", - website: "https://linktr.ee/nutriemp.CRYPTO", - symbol: "BUDZ", - decimalPlaces: 2, - image: "ipfs://QmSESYYcMk9i3EDbQSWBmkfEWK4X7TokohJyvQThxpANgq", - description: "NUTRIEMP.CRYPTO - GROWERS UNITE", - address: { - policyId: "d2cb1f7a8ae3bb94117e30d241566c2dd5adbd0708c40a8a5ac9ae60", - name: "4255445a", - }, - imageIpfsHash: "QmSESYYcMk9i3EDbQSWBmkfEWK4X7TokohJyvQThxpANgq", - minting: { - type: "time-lock-policy", - blockchain: "cardano", - mintedBeforeSlotNumber: 78641478, - }, - mediatype: "image/png", - tokentype: "token", - totalsupply: 100000000000000, - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { "24h": 0, "7d": 0 }, - quoteDecimalPlaces: 2, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "d2cb1f7a8ae3bb94117e30d241566c2dd5adbd0708c40a8a5ac9ae60", - name: "4255445a", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, - { - info: { - supply: { total: "10000000", circulating: null }, - status: "unverified", - website: "https://ratsonchain.com", - description: "The official token of Rats On Chain", - image: "ipfs://QmeJPgcLWDDTBKxqTzWdDj1ruE5huBf3cbJNPVetnqW7DH", - symbol: "ROC", - decimalPlaces: 0, - address: { - policyId: "91ec8c9ae38d203c0fa1e0a31274f05bee7be4eb0133ed0beb837297", - name: "524f43", - }, - imageIpfsHash: "QmeJPgcLWDDTBKxqTzWdDj1ruE5huBf3cbJNPVetnqW7DH", - ticker: "ROC", - project: "Rats On Chain", - mediatype: "image/png", - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { "24h": 0, "7d": 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "91ec8c9ae38d203c0fa1e0a31274f05bee7be4eb0133ed0beb837297", - name: "524f43", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, - { - info: { - supply: { total: "39997832", circulating: null }, - status: "unverified", - symbol: "BURPZ", - decimalPlaces: 0, - image: "ipfs://QmZcnb7DSFXdd9crvTMyTXnbQvUU5srktxK8aZxUNFvZDJ", - description: "Official Fluffy Utility Token", - address: { - policyId: "ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb", - name: "425552505a", - }, - imageIpfsHash: "QmZcnb7DSFXdd9crvTMyTXnbQvUU5srktxK8aZxUNFvZDJ", - ticker: "BURPZ", - twitter: "@FluffysNFT", - mediatype: "image/png", - categories: [], - }, - price: { - volume: { base: "0", quote: "0" }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { "24h": "0", "7d": "0" }, - fromToken: "lovelace", - toToken: - "ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb.425552505a", - price10d: [], - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "ef71f8d84c69bb45f60e00b1f595545238b339f00b137dd5643794bb", - name: "425552505a", - }, - baseAddress: { policyId: "", name: "" }, - }, - }, - { - info: { - supply: { total: "76222812878", circulating: null }, - status: "unverified", - address: { - policyId: "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86", - name: "f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b", - }, - decimalPlaces: 0, - symbol: - "f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b", - categories: [], - }, - price: { - volume: { base: 0, quote: 0 }, - volumeChange: { base: 0, quote: 0 }, - price: 0, - askPrice: 0, - bidPrice: 0, - priceChange: { "24h": 0, "7d": 0 }, - quoteDecimalPlaces: 0, - baseDecimalPlaces: 6, - quoteAddress: { - policyId: "e4214b7cce62ac6fbba385d164df48e157eae5863521b4b67ca71d86", - name: "f86a805f257a14b127b9b54444e556ab1a066f690501d4474bbad4454324845b", - }, - baseAddress: { policyId: "", name: "" }, - price10d: [], - }, - }, -]; - -const ADA_TOKEN = { - policyId: "", - assetName: "", -}; - -const GENS_TOKEN = { - policyId: "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb", - assetName: "0014df1047454e53", -}; - -const SUNDAE_POOL = { - protocol: "sundaeswap" as const, - batcherFee: "2500000", - lvlDeposit: "2000000", - tokenA: { - address: { - policyId: "", - assetName: "", - }, - amount: "1762028491", - }, - tokenB: { - address: { - policyId: "dda5fdb1002f7389b33e036b6afee82a8189becb6cba852e8b79b4fb", - assetName: "0014df1047454e53", - }, - amount: "1904703890", - }, - poolFee: "0.05", - poolId: "0029cb7c88c7567b63d1a512c0ed626aa169688ec980730c0473b913.7020e403", -}; diff --git a/packages/swap/dex.ts b/packages/swap/dex.ts deleted file mode 100644 index 6b0a80f35d..0000000000 --- a/packages/swap/dex.ts +++ /dev/null @@ -1,292 +0,0 @@ -import { - cancelOrder, - createOrder, - getOrders, - getPools, - getTokens, -} from "./openswap"; -import type { Protocol, Token } from "./openswap"; - -export type OrderDetails = { - protocol: Protocol; - poolId?: string; // only required for SundaeSwap trades. - send: TokenAmount; - receive: TokenAmount; - walletAddress: string; -}; - -export type OrderDatum = { - contractAddress: string; - datumHash: string; - datum: string; -}; - -export type TokenAddress = { - policyId: string; - assetName: string; -}; - -export type TokenAmount = { - address: TokenAddress; - amount: string; -}; - -export type TokenPair = { - send: TokenAmount; // token to swap from aka selling - receive: TokenAmount; // token to swap to aka buying -}; - -export type TokenPairPool = { - protocol: Protocol; - lpToken?: TokenAmount; - tokenA: TokenAmount; - tokenB: TokenAmount; - batcherFee: string; - lvlDeposit: string; - poolFee: string; - poolId: string; -}; - -export const getOpenOrders = async (stakeKeyHash: string) => - (await getOrders(stakeKeyHash)).map((order) => ({ - protocol: order.provider, - depposit: order.deposit, - utxo: order.utxo, - send: { - address: { - policyId: order.from.token.split(".")[0], - assetName: order.from.token.split(".")[1], - }, - amount: order.from.amount, - }, - receive: { - address: { - policyId: order.to.token.split(".")[0], - assetName: order.to.token.split(".")[1], - }, - amount: order.to.amount, - }, - })); - -export const getCancelOrderTx = async ( - orderUTxO: string, - collateralUTxOs: string, - walletAddress: string -) => await cancelOrder(orderUTxO, collateralUTxOs, walletAddress); - -export const getOrderDatum = async (order: OrderDetails): Promise => { - const { address, hash, datum } = await createOrder({ - address: order.walletAddress, - protocol: order.protocol, - poolId: order.poolId, - sell: { - policyId: order.send.address.policyId, - assetName: order.send.address.assetName, - amount: order.send.amount, - }, - buy: { - policyId: order.receive.address.policyId, - assetName: order.receive.address.assetName, - amount: order.receive.amount, - }, - }); - - return { - contractAddress: address, - datumHash: hash, - datum, - }; -}; - -export const getSupportedTokens = async ( - baseToken?: TokenAddress -): Promise => getTokens(baseToken?.policyId, baseToken?.assetName); - -export const getTokenPairPools = async ( - sendToken: TokenAddress, - receiveToken: TokenAddress -): Promise => - (await getPools(sendToken, receiveToken)).map((pool) => ({ - protocol: pool.provider as Protocol, - lpToken: pool.lpToken && { - address: { - policyId: pool.lpToken.token.split(".")[0], - assetName: pool.lpToken.token.split(".")[1], - }, - amount: pool.lpToken.amount, - }, - batcherFee: pool.batcherFee.amount, - lvlDeposit: pool.deposit.toString(), - tokenA: { - address: { - policyId: pool.tokenA.token.split(".")[0], - assetName: pool.tokenA.token.split(".")[1], - }, - amount: pool.tokenA.amount, - }, - tokenB: { - address: { - policyId: pool.tokenB.token.split(".")[0], - assetName: pool.tokenB.token.split(".")[1], - }, - amount: pool.tokenB.amount, - }, - poolFee: pool.fee, - poolId: pool.poolId, - })); - -export const calculateAmountsGivenInput = ( - pool: TokenPairPool, - from: TokenAmount -): TokenPair => { - const poolA = BigInt(pool.tokenA.amount); - const poolB = BigInt(pool.tokenB.amount); - const poolsProduct = poolA * poolB; // fee is part of tokens sent -> this means the constant product increases after the swap! - - const fromAmount = BigInt(from.amount); - - const poolFee = ceilDivision( - BigInt(Number(pool.poolFee) * 1000) * fromAmount, - BigInt(100 * 1000) - ); - - const getReceiveAmount = (poolA_: bigint, poolB_: bigint) => { - const newPoolA = poolA_ + fromAmount - poolFee; - const newPoolB = ceilDivision(poolsProduct, newPoolA); - - return (poolB_ - newPoolB).toString(); - }; - - const receive = sameToken(from.address, pool.tokenA.address) - ? { amount: getReceiveAmount(poolA, poolB), address: pool.tokenB.address } - : { amount: getReceiveAmount(poolB, poolA), address: pool.tokenA.address }; - - return { send: from, receive }; -}; - -export const calculateAmountsGivenOutput = ( - pool: TokenPairPool, - to: TokenAmount -): TokenPair => { - const poolA = BigInt(pool.tokenA.amount); - const poolB = BigInt(pool.tokenB.amount); - const poolsProduct = poolA * poolB; // fee is part of tokens sent -> this means the constant product increases after the swap! - - const toAmount = BigInt(to.amount); - - const poolFee = BigInt(100 * 1000) - BigInt(Number(pool.poolFee) * 1000); - - const getSendAmount = (poolA_: bigint, poolB_: bigint) => { - const newPoolA = - poolA_ - (poolA_ > toAmount ? toAmount : poolA_ - BigInt(1)); - const newPoolB = ceilDivision(poolsProduct + newPoolA, newPoolA); - return ceilDivision( - (newPoolB - poolB_) * BigInt(100 * 1000), - poolFee - ).toString(); - }; - - const send = sameToken(to.address, pool.tokenA.address) - ? { amount: getSendAmount(poolA, poolB), address: pool.tokenB.address } - : { amount: getSendAmount(poolB, poolA), address: pool.tokenA.address }; - - return { send, receive: to }; -}; - -export const constructLimitOrder = ( - send: TokenAmount, - receive: TokenAmount, - pool: TokenPairPool, - slippage: number, - address: string -): OrderDetails => { - const receiveAmountWithSlippage = calculateAmountWithSlippage( - BigInt(receive.amount), - BigInt(slippage) - ); - - return { - protocol: pool.protocol, - poolId: pool.poolId, - send: send, - receive: { - address: receive.address, - amount: receiveAmountWithSlippage.toString(), - }, - walletAddress: address, - }; -}; - -export const constructMarketOrder = ( - send: TokenAmount, - receive: TokenAddress, - pools: TokenPairPool[], - slippage: number, - address: string -): OrderDetails | undefined => { - if (pools?.length === 0) { - return undefined; - } - - const findBestOrder = ( - order: OrderDetails | undefined, - pool: TokenPairPool - ): OrderDetails => { - const tokenPair = calculateAmountsGivenInput(pool, send); - - const receiveAmount = BigInt(tokenPair.receive.amount); - const receiveAmountWithSlippage = calculateAmountWithSlippage( - receiveAmount, - BigInt(slippage) - ); - - const newOrder: OrderDetails = { - protocol: pool.protocol, - poolId: pool.poolId, - send: tokenPair.send, - receive: { - address: receive, - amount: receiveAmountWithSlippage.toString(), - }, - walletAddress: address, - }; - - if ( - order === undefined || - BigInt(order.receive.amount) < BigInt(newOrder.receive.amount) - ) { - return newOrder; - } - - return order; - }; - - return pools.reduce(findBestOrder, undefined); -}; - -const calculateAmountWithSlippage = ( - amount: bigint, - slippage: bigint -): bigint => { - const slippageAmount = ceilDivision( - BigInt(1000) * slippage * amount, - BigInt(100 * 1000) - ); - - return amount - slippageAmount; -}; - -function ceilDivision(a: bigint, b: bigint): bigint { - return (a + b - BigInt(1)) / b; -} - -function sameToken(a: TokenAddress, b: TokenAddress): boolean { - return ( - a != null && - b != null && - a != undefined && - b != undefined && - a.policyId === b.policyId && - a.assetName === b.assetName - ); -} diff --git a/packages/swap/index.ts b/packages/swap/index.ts index ef81038d16..1c7b7a0582 100644 --- a/packages/swap/index.ts +++ b/packages/swap/index.ts @@ -1 +1 @@ -export * from './dex'; +export * from './api/index'; diff --git a/packages/swap/openswap/index.ts b/packages/swap/openswap/index.ts deleted file mode 100644 index 02d200648a..0000000000 --- a/packages/swap/openswap/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './orders'; -export * from './pools'; -export * from './tokens'; diff --git a/packages/swap/openswap/orders.ts b/packages/swap/openswap/orders.ts deleted file mode 100644 index eb2b2c321b..0000000000 --- a/packages/swap/openswap/orders.ts +++ /dev/null @@ -1,105 +0,0 @@ -import axios from "axios"; - -export type Protocol = "minswap" | "sundaeswap" | "wingriders" | "muesliswap"; - -export type Order = { - address: string; - protocol: Protocol; - poolId?: string; // only required for SundaeSwap trades. - sell: { - policyId: string; - assetName: string; // hexadecimal representation of token, i.e. "" for lovelace, "4d494c4b" for MILK. - amount: string; - }; - buy: { - policyId: string; - assetName: string; // hexadecimal representation of token, i.e. "" for lovelace, "4d494c4b" for MILK. - amount: string; - }; -}; - -export type OpenOrder = { - provider: Protocol; - from: { - amount: string; - token: string; - }; - to: { - amount: string; - token: string; - }; - deposit: string; - utxo: string; -}; - -const client = axios.create({ - baseURL: "https://aggregator.muesliswap.com", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, -}); - -/** - * @param orderUTxO order UTxO from the smart contract to cancel. e.g. "txhash#0" - * @param collateralUTxOs collateral UTxOs to use for canceling the order in cbor format. - * @param walletAddress address of the wallet that owns the order in cbor format. - * @returns an unsigned transaction to cancel the order. - */ -export const cancelOrder = async ( - orderUTxO: string, - collateralUTxOs: string, - walletAddress: string -) => { - const response = await client.get( - `/cancelSwapTransaction?wallet=${walletAddress}&utxo=${orderUTxO}&collateralUtxo=${collateralUTxOs}` - ); - - if (response.status !== 200) { - throw new Error("Failed to construct cancel swap transaction", { - cause: response.data, - }); - } - - return { - tx: response.data.cbor, - }; -}; - -/** - * @param order the order to construct the datum for and the address to send the order to. - * @returns the order datum, order datum hash, and address to send the order to. - */ -export const createOrder = async (order: Order) => { - const response = await client.get( - `/constructSwapDatum?walletAddr=${order.address}&protocol=${order.protocol}&poolId=${order.poolId}&sellTokenPolicyID=${order.sell.policyId}&sellTokenNameHex=${order.sell.assetName}&sellAmount=${order.sell.amount}&buyTokenPolicyID=${order.buy.policyId}&buyTokenNameHex=${order.buy.assetName}&buyAmount=${order.buy.amount}` - ); - - if (response.status !== 200) { - throw new Error("Failed to construct swap datum", { cause: response.data }); - } - - return { - hash: response.data.hash, - datum: response.data.datum, - address: response.data.address, - }; -}; - -/** - * @param stakeKeyHash the stake key hash of the wallet to get orders for. - * @returns all unfufilled orders for the given stake key hash. - */ -export const getOrders = async (stakeKeyHash: string): Promise => { - const response = await client.get(`/all?stake-key-hash=${stakeKeyHash}`, { - baseURL: "https://onchain2.muesliswap.com/orders", - }); - - if (response.status !== 200) { - throw new Error(`Failed to get orders for ${stakeKeyHash}`, { - cause: response.data, - }); - } - - return response.data; -}; diff --git a/packages/swap/openswap/pools.ts b/packages/swap/openswap/pools.ts deleted file mode 100644 index c245908fb3..0000000000 --- a/packages/swap/openswap/pools.ts +++ /dev/null @@ -1,54 +0,0 @@ -import axios from 'axios'; - -export type Pool = { - provider: 'minswap' | 'sundaeswap' | 'wingriders' | 'muesliswap_v1' | 'muesliswap_v2' | 'muesliswap_v3', - fee: string, // % pool liquidity provider fee, usually 0.3. - tokenA: { - amount: string, // amount of tokenA in the pool, without decimals. - token: string, // hexadecimal representation of tokenA, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. - }, - tokenB: { - amount: string, // amount of tokenB in the pool, without decimals. - token: string, // hexadecimal representation of tokenB, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. - }, - price: number, // float, current price in tokenA / tokenB according to the pool, NOT SUITABLE for price calculations, just for display purposes, i.e. 0.9097362621640215. - batcherFee: { - amount: string, // amount of fee taken by protocol batchers, in lovelace. - token: '.', - }, - deposit: number, // amount of deposit / minUTxO required by protocol, returned to user, in lovelace. - utxo: string, // txhash#txindex of latest transaction involving this pool. - poolId: string, // identifier of the pool across platforms. - timestamp: string, // latest update of this pool in UTC, i.e. 2023-05-23 06:13:26. - lpToken: { - amount: string, // amount of lpToken minted by the pool, without decimals. - token: string, // hexadecimal representation of lpToken, - }, -}; - -const client = axios.create({ - baseURL: 'https://onchain2.muesliswap.com/pools', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } -}); - -/** - * @param tokenA the token to swap from. - * @param tokenB the token to swap to. - * @returns a list of pools for the given token pair. - */ -export const getPools = async ( - tokenA: { policyId: string, assetName: string }, - tokenB: { policyId: string, assetName: string }, -): Promise => { - const response = await client - .get(`/pair?policy-id1=${tokenA.policyId}&tokenname1=${tokenA.assetName}&policy-id2=${tokenB.policyId}&tokenname-hex2=${tokenB.assetName}`); - - if (response.status !== 200) { - throw new Error('Failed to fetch pools for token pair', { cause: response.data }); - } - - return response.data; -} diff --git a/packages/swap/openswap/tokens.ts b/packages/swap/openswap/tokens.ts deleted file mode 100644 index 606af368cf..0000000000 --- a/packages/swap/openswap/tokens.ts +++ /dev/null @@ -1,65 +0,0 @@ -import axios from 'axios'; - -export type Token = { - info: { - supply: { - total: string, // total circulating supply of the token, without decimals. - circulating: string | null, // if set the circulating supply of the token, if null the amount in circulation is unknown. - }, - status: 'verified' | 'unverified' | 'scam' | 'outdated', - address: { - policyId: string, // policy id of the token. - name: string, // hexadecimal representation of token name. - }, - symbol: string, // shorthand token symbol. - image?: string, // http link to the token image. - website: string, - description: string, - decimalPlaces: number, // number of decimal places of the token, i.e. 6 for ADA and 0 for MILK. - categories: string[], // encoding categories as ids. - }, - price: { - volume: { - base: number, // float, trading volume 24h in base currency (e.g. ADA). - quote: number, // float, trading volume 24h in quote currency. - }, - volumeChange: { - base: number, // float, percent change of trading volume in comparison to previous 24h. - quote: number, // float, percent change of trading volume in comparison to previous 24h. - }, - price: number, // live trading price in base currency (e.g. ADA). - askPrice: number, // lowest ask price in base currency (e.g. ADA). - bidPrice: number, // highest bid price in base currency (e.g. ADA). - priceChange: { - '24h': number, // float, price change last 24 hours. - '7d': number,// float, price change last 7 days. - }, - quoteDecimalPlaces: number, // decimal places of quote token. - baseDecimalPlaces: number, // decimal places of base token. - price10d: number[], //float, prices of this tokens averaged for the last 10 days, in chronological order i.e.oldest first. - } -}; - -const client = axios.create({ - baseURL: 'https://api.muesliswap.com', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json' - } -}); - -/** - * @param policyId the policy id of the base token to calculate the price for in hexadecimal representation. - * @param assetName the asset name of the base token to calculate the price for in hexadecimal representation. - * @returns a list of tokens supported by MuesliSwap. - */ -export const getTokens = async (policyId = '', assetName = ''): Promise => { - const response = await client - .get(`/list?base-policy-id=${policyId}&base-tokenname=${assetName}`); - - if (response.status !== 200) { - throw new Error('Failed to fetch tokens', { cause: response.data }); - } - - return response.data; -} diff --git a/packages/swap/package.json b/packages/swap/package.json index c1c2dedb3f..17b09a7ff6 100644 --- a/packages/swap/package.json +++ b/packages/swap/package.json @@ -1,18 +1,18 @@ { "name": "@yoroi/swap", "version": "0.0.1", - "main": "dist/index.js", - "source": "index", "repository": { "type": "github", "url": "https://github.com/Emurgo/yoroi.git", "directory": "packages/swap" }, - "author": "EMURGO", "license": "Apache-2.0", + "author": "EMURGO", + "main": "dist/index.js", + "source": "index", "scripts": { - "clean": "rm -rf dist", "build": "tsc", + "clean": "rm -rf dist", "test": "vitest" }, "dependencies": { diff --git a/packages/swap/tsconfig.json b/packages/swap/tsconfig.json index 3e19a099b4..abb0ecde2a 100644 --- a/packages/swap/tsconfig.json +++ b/packages/swap/tsconfig.json @@ -39,7 +39,7 @@ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ + "resolveJsonModule": true /* Enable importing .json files. */, // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ diff --git a/packages/types/src/balance/token.ts b/packages/types/src/balance/token.ts new file mode 100644 index 0000000000..cc7b7be2a2 --- /dev/null +++ b/packages/types/src/balance/token.ts @@ -0,0 +1,73 @@ +type BalanceTokenInfo = { + kind: 'ft' | 'nft' + + id: string + fingerprint: string // fingerprint is temporary since we can't use it as id for now + group: string // for cardano policy id + name: string // for cardano asset name + + description: string | undefined + image: string | undefined // link to image + icon: string | undefined // base64 encoded + + decimals: number | undefined + symbol: string | undefined // shorthand as monetary i.e Ω + ticker: string | undefined // shorthand as token e.g. ADA + + // metatada should be used only for NFT gallery + metadatas: Meta +} + +export type BalanceCardanoMetadatas = { + mintNft?: NftMetadata + mintFt?: FtMetadata + tokenRegistry?: FtMetadata +} + +type FtMetadata = { + description: string | Array | undefined + icon: string | Array | undefined + decimals: number | undefined + ticker: string | undefined + url: string | undefined + version: string | undefined +} + +type NftMetadata = unknown + +type BalanceTokenPrice = { + volume: { + base: number // float, trading volume 24h in base currency (e.g. ADA). + quote: number // float, trading volume 24h in quote currency. + } + volumeChange: { + base: number // float, percent change of trading volume in comparison to previous 24h. + quote: number // float, percent change of trading volume in comparison to previous 24h. + } + price: number // live trading price in base currency (e.g. ADA). + askPrice: number // lowest ask price in base currency (e.g. ADA). + bidPrice: number // highest bid price in base currency (e.g. ADA). + priceChange: { + '24h': number // float, price change last 24 hours. + '7d': number // float, price change last 7 days. + } + quoteDecimalPlaces: number // decimal places of quote token. + baseDecimalPlaces: number // decimal places of base token. + price10d: number[] //float, prices of this tokens averaged for the last 10 days, in chronological order i.e.oldest first. +} + +export type BalanceToken = { + info: BalanceTokenInfo + price: BalanceTokenPrice +} + +export type BalanceQuantity = `${number}` + +export type BalanceAmounts = { + [tokenId: string]: BalanceQuantity +} + +export type BalanceAmount = { + tokenId: string + quantity: BalanceQuantity +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 63848ac1d9..d585b3c21a 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,6 +1,27 @@ +import { + BalanceAmount, + BalanceAmounts, + BalanceCardanoMetadatas, + BalanceQuantity, + BalanceToken, +} from './balance/token' import {MetricsModule, MetricsFactoryOptions} from './metrics/module' import {MetricsStorage} from './metrics/storage' import {MetricsTrack} from './metrics/track' +import { + SwapFactoryOptions, + SwapOrderType, + SwapProtocol, + SwapOpenOrder, + SwapNetwork, + SwapPool, + SwapTokenInfo, + SwapBaseTokenInfo, + SwapCreateOrderResponse, + SwapCreateOrderData, + SwapApi, +} from './swap/module' +import {SwapStorage} from './swap/storage' export namespace Metrics { export type Module = MetricsModule @@ -10,3 +31,32 @@ export namespace Metrics { export type Storage = MetricsStorage } + +export namespace Swap { + export type FactoryOptions = SwapFactoryOptions + + export type CreateOrderData = SwapCreateOrderData + export type CreateOrderResponse = SwapCreateOrderResponse + export type OrderType = SwapOrderType + export type Protocol = SwapProtocol + export type Network = SwapNetwork + export type OpenOrder = SwapOpenOrder + export type Pool = SwapPool + export type TokenInfo = SwapTokenInfo + export type BaseTokenInfo = SwapBaseTokenInfo + export interface Api extends SwapApi {} + + export type Storage = SwapStorage +} + +export namespace Balance { + export type Token = BalanceToken + export type TokenInfo = BalanceToken['info'] + export type TokenPrice = BalanceToken['price'] + + export type CardanoMetadatas = BalanceCardanoMetadatas + + export type Quantity = BalanceQuantity + export type Amount = BalanceAmount + export type Amounts = BalanceAmounts +} diff --git a/packages/types/src/metrics/swap.ts b/packages/types/src/metrics/swap.ts new file mode 100644 index 0000000000..24f79a8ad3 --- /dev/null +++ b/packages/types/src/metrics/swap.ts @@ -0,0 +1,86 @@ +import {SwapOrderType} from 'src/swap/module' +import {MetricsTrackProperties} from './properties' + +export type MetricsSwapEvent = + | 'swap_asset_from_changed' + | 'swap_asset_to_changed' + | 'swap_cancelation_settled' + | 'swap_cancelation_submitted' + | 'swap_initiated' + | 'swap_order_requested' + | 'swap_order_selected' + | 'swap_order_settled' + | 'swap_order_submitted' + | 'swap_pool_changed' + | 'swap_slippage_changed' + +type MetricsSwapAssetFromChanged = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_asset_from_changed' +> + +type MetricsSwapAssetToChanged = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_asset_to_changed' +> + +type MetricsSwapCancelationSettled = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_cancelation_settled' +> + +type MetricsSwapCancelationSubmitted = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_cancelation_submitted' +> + +type MetricsSwapInitiated = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_initiated', + { + orderType: SwapOrderType + } +> + +type MetricsSwapOrderRequested = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_order_requested' +> + +type MetricsSwapOrderSelected = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_order_selected' +> + +type MetricsSwapOrderSettled = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_order_settled' +> + +type MetricsSwapOrderSubmitted = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_order_submitted' +> + +type MetricsSwapPoolChanged = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_pool_changed' +> + +type MetricsSwapSlippageChanged = MetricsTrackProperties< + MetricsSwapEvent, + 'swap_slippage_changed' +> + +export type MetricsSwapTrack = + | MetricsSwapAssetFromChanged + | MetricsSwapAssetToChanged + | MetricsSwapCancelationSettled + | MetricsSwapCancelationSubmitted + | MetricsSwapInitiated + | MetricsSwapOrderRequested + | MetricsSwapOrderSelected + | MetricsSwapOrderSettled + | MetricsSwapOrderSubmitted + | MetricsSwapPoolChanged + | MetricsSwapSlippageChanged diff --git a/packages/types/src/swap/module.ts b/packages/types/src/swap/module.ts new file mode 100644 index 0000000000..8fb3de4a9d --- /dev/null +++ b/packages/types/src/swap/module.ts @@ -0,0 +1,205 @@ +import {BalanceAmount, BalanceToken} from '../balance/token' + +export type SwapOrderType = 'market' | 'limit' + +export type SwapProtocol = + | 'minswap' + | 'sundaeswap' + | 'wingriders' + | 'muesliswap' + +export type SwapCreateOrderData = { + address: string + protocol: SwapProtocol + poolId?: string // Only required for SundaeSwap trades. + sell: { + policyId: string + assetName: string // Hexadecimal representation of token, i.e. "" for lovelace, "4d494c4b" for MILK. + amount: string + } + buy: { + policyId: string + assetName: string + amount: string + } +} + +export type SwapCreateOrderResponse = Record< + 'datumHash' | 'datum' | 'contractAddress', + string +> + +// todo: add full type def. +export type SwapOpenOrder = { + provider: SwapProtocol + from: { + amount: string + token: string + } + to: { + amount: string + token: string + } + deposit: string + utxo: string +} + +export type SwapNetwork = 'mainnet' | 'preprod' + +export type SwapFactoryOptions = { + network: SwapNetwork + stakingKey: string +} + +export type SwapOrderCreateData = { + amounts: { + sell: BalanceAmount + buy: BalanceAmount + } + address: string + slippage: number +} & ( + | { + protocol: Omit + poolId: string | undefined // only required for SundaeSwap trades. + } + | { + protocol: 'sundaeswap' + poolId: string + } +) + +export type SwapOrderCancelData = { + utxos: { + order: string + collaterals: Array + } + address: string +} + +export type SwapOrderDatum = { + contractAddress: string + datumHash: string + datum: string +} + +export type SwapPool = { + provider: + | 'minswap' + | 'sundaeswap' + | 'wingriders' + | 'muesliswap_v1' + | 'muesliswap_v2' + | 'muesliswap_v3' + fee: string // % pool liquidity provider fee, usually 0.3. + tokenA: { + amount: string // amount of tokenA in the pool, without decimals. + token: string // hexadecimal representation of tokenA, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. + } + tokenB: { + amount: string // amount of tokenB in the pool, without decimals. + token: string // hexadecimal representation of tokenB, i.e. "." for lovelace, "8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa.4d494c4b" for MILK. + } + price: number // float, current price in tokenA / tokenB according to the pool, NOT SUITABLE for price calculations, just for display purposes, i.e. 0.9097362621640215. + batcherFee: { + amount: string // amount of fee taken by protocol batchers, in lovelace. + token: '.' + } + deposit: number // amount of deposit / minUTxO required by protocol, returned to user, in lovelace. + utxo: string // txhash#txindex of latest transaction involving this pool. + poolId: string // identifier of the pool across platforms. + timestamp: string // latest update of this pool in UTC, i.e. 2023-05-23 06:13:26. + lpToken: { + amount: string // amount of lpToken minted by the pool, without decimals. + token: string // hexadecimal representation of lpToken, + } +} + +export type SwapTokenInfo = { + info: { + supply: { + total: string // total circulating supply of the token, without decimals. + circulating: string | null // if set the circulating supply of the token, if null the amount in circulation is unknown. + } + status: 'verified' | 'unverified' | 'scam' | 'outdated' + address: { + policyId: string // policy id of the token. + name: string // hexadecimal representation of token name. + } + symbol: string // shorthand token symbol. + image?: string // http link to the token image. + website: string + description: string + decimalPlaces: number // number of decimal places of the token, i.e. 6 for ADA and 0 for MILK. + categories: string[] // encoding categories as ids. + } + price: { + volume: { + base: number // float, trading volume 24h in base currency (e.g. ADA). + quote: number // float, trading volume 24h in quote currency. + } + volumeChange: { + base: number // float, percent change of trading volume in comparison to previous 24h. + quote: number // float, percent change of trading volume in comparison to previous 24h. + } + price: number // live trading price in base currency (e.g. ADA). + askPrice: number // lowest ask price in base currency (e.g. ADA). + bidPrice: number // highest bid price in base currency (e.g. ADA). + priceChange: { + '24h': number // float, price change last 24 hours. + '7d': number // float, price change last 7 days. + } + quoteDecimalPlaces: number // decimal places of quote token. + baseDecimalPlaces: number // decimal places of base token. + price10d: number[] //float, prices of this tokens averaged for the last 10 days, in chronological order i.e.oldest first. + } +} + +// todo: choose better name +export type SwapBaseTokenInfo = + | {policyId: string; assetName: string} + | {policyId: string; assetNameHex: string} + +export interface SwapApi { + createOrder(order: SwapCreateOrderData): Promise + cancelOrder( + orderUTxO: string, + collateralUTxO: string, + walletAddress: string, + ): Promise + getOrders(stakeKeyHash: string): Promise + getPools( + tokenA: SwapBaseTokenInfo, + tokenB: SwapBaseTokenInfo, + ): Promise + getTokens(policyId?: string, assetName?: string): Promise +} + +export type SwapModule = { + orders: { + prepare: (order: SwapOrderCreateData) => Promise + create: (order: SwapOrderCreateData) => Promise + cancel: (order: SwapOrderCancelData) => Promise + list: { + byStatusOpen: () => Promise> + } + } + pairs: { + list: { + byToken: ( + baseTokenId: BalanceToken['info']['id'], + ) => Promise> + } + } + pools: { + list: { + byPair: ({ + from, + to, + }: { + from: BalanceToken['info']['id'] + to: BalanceToken['info']['id'] + }) => Promise> + } + } +} diff --git a/packages/types/src/swap/storage.ts b/packages/types/src/swap/storage.ts new file mode 100644 index 0000000000..7f8007e0fc --- /dev/null +++ b/packages/types/src/swap/storage.ts @@ -0,0 +1,9 @@ +export type SwapStorage = { + slippage: { + read(): Promise + remove(): Promise + save(slippage: number): Promise + } + + reset(): Promise<[() => Promise]> +} diff --git a/yarn.lock b/yarn.lock index 8cdccc693c..664a173840 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2020,11 +2020,31 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/eslintrc@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d" + integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + "@eslint/js@8.42.0": version "8.42.0" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.42.0.tgz#484a1d638de2911e6f5a30c12f49c7e4a3270fb6" integrity sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw== +"@eslint/js@8.44.0": + version "8.44.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af" + integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw== + "@ethersproject/abi@^5.0.1": version "5.7.0" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.7.0.tgz#b3f3e045bbbeed1af3947335c247ad625a44e449" @@ -4628,6 +4648,72 @@ regenerator-runtime "^0.13.7" resolve-from "^5.0.0" +"@swc/core-darwin-arm64@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.69.tgz#e22032471244ec80c22bee8efc2100e456bb9488" + integrity sha512-IjZTf12zIPWkV3D7toaLDoJPSkLhQ4fDH8G6/yCJUI27cBFOI3L8LXqptYmISoN5yYdrcnNpdqdapD09JPuNJg== + +"@swc/core-darwin-x64@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.69.tgz#4c2034ba409b9e061b9e8ad56a762b8bb7815f18" + integrity sha512-/wBO0Rn5oS5dJI/L9kJRkPAdksVwl5H9nleW/NM3A40N98VV8T7h/i1nO051mxIjq0R6qXVGOWFbBoLrPYucJg== + +"@swc/core-linux-arm-gnueabihf@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.69.tgz#3d89053b6de2fd8553ce5c5808251246f2870e1e" + integrity sha512-NShCjMv6Xn8ckMKBRqmprXvUF14+jXY0TcNKXwjYErzoIUFOnG72M36HxT4QEeAtKZ4Eg4CZFE4zlJ27fDp1gg== + +"@swc/core-linux-arm64-gnu@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.69.tgz#df12d3befc5e86555ee561202dc9c279d8865bf3" + integrity sha512-VRPOJj4idopSHIj1bOVXX0SgaB18R8yZNunb7eXS5ZcjVxAcdvqyIz3RdQX1zaJFCGzcdPLzBRP32DZWWGE8Ng== + +"@swc/core-linux-arm64-musl@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.69.tgz#8372222a3298fdd0bd18da564a3009614a6c0920" + integrity sha512-QxeSiZqo5x1X8vq8oUWLibq+IZJcxl9vy0sLUmzdjF2b/Z+qxKP3gutxnb2tzJaHqPVBbEZaILERIGy1qWdumQ== + +"@swc/core-linux-x64-gnu@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.69.tgz#6879057d28f261b051fac52daca6c256f3a7fb7d" + integrity sha512-b+DUlVxYox3BwD3PyTwhLvqtu6TYZtW+S6O0FnttH11o4skHN0XyJ/cUZSI0X2biSmfDsizRDUt1PWPFM+F7SA== + +"@swc/core-linux-x64-musl@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.69.tgz#bf4f9a74156524211472bb713d34f0bb7265669f" + integrity sha512-QXjsI+f8n9XPZHUvmGgkABpzN4M9kdSbhqBOZmv3o0AsDGNCA4uVowQqgZoPFAqlJTpwHeDmrv5sQ13HN+LOGw== + +"@swc/core-win32-arm64-msvc@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.69.tgz#8242e4a406d11f0e078da39cf9962695e6b73d2d" + integrity sha512-wn7A8Ws1fyviuCUB2Vg6IotiZeuqiO1Mz3d+YDae2EYyNpj1kNHvjBip8GHkfGzZG+jVrvG6NHsDo0KO/pGb8A== + +"@swc/core-win32-ia32-msvc@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.69.tgz#68dbd41e200e5db71aa6644d793acff3607862f0" + integrity sha512-LsFBXtXqxEcVaaOGEZ9X3qdMzobVoJqKv8DnksuDsWcBk+9WCeTz2u/iB+7yZ2HGuPXkCqTRqhFo6FX9aC00kQ== + +"@swc/core-win32-x64-msvc@1.3.69": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.69.tgz#304e1050d59bae21215a15839b05668d48a92837" + integrity sha512-ieBscU0gUgKjaseFI07tAaGqHvKyweNknPeSYEZOasVZUczhD6fK2GRnVREhv2RB2qdKC/VGFBsgRDMgzq1VLw== + +"@swc/core@^1.3.61": + version "1.3.69" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.69.tgz#b4a41e84de11832c233fbe714c6e412d8404bab0" + integrity sha512-Khc/DE9D5+2tYTHgAIp5DZARbs8kldWg3b0Jp6l8FQLjelcLFmlQWSwKhVZrgv4oIbgZydIp8jInsvTalMHqnQ== + optionalDependencies: + "@swc/core-darwin-arm64" "1.3.69" + "@swc/core-darwin-x64" "1.3.69" + "@swc/core-linux-arm-gnueabihf" "1.3.69" + "@swc/core-linux-arm64-gnu" "1.3.69" + "@swc/core-linux-arm64-musl" "1.3.69" + "@swc/core-linux-x64-gnu" "1.3.69" + "@swc/core-linux-x64-musl" "1.3.69" + "@swc/core-win32-arm64-msvc" "1.3.69" + "@swc/core-win32-ia32-msvc" "1.3.69" + "@swc/core-win32-x64-msvc" "1.3.69" + "@szmarczak/http-timer@^4.0.5": version "4.0.6" resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" @@ -5025,6 +5111,13 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/react-dom@^18.2.6": + version "18.2.7" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== + dependencies: + "@types/react" "*" + "@types/react-native-background-timer@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/react-native-background-timer/-/react-native-background-timer-2.0.0.tgz#c44c57f8fbca9d9d5521fdd72a8f55232b79381e" @@ -5062,6 +5155,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^18.2.14": + version "18.2.15" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066" + integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/recursive-readdir@^2.2.0": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/recursive-readdir/-/recursive-readdir-2.2.1.tgz#330f5ec0b73e8aeaf267a6e056884e393f3543a3" @@ -5233,6 +5335,16 @@ "@typescript-eslint/typescript-estree" "5.59.9" debug "^4.3.4" +"@typescript-eslint/parser@^5.61.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.59.9": version "5.59.9" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.9.tgz#eadce1f2733389cdb58c49770192c0f95470d2f4" @@ -5241,6 +5353,14 @@ "@typescript-eslint/types" "5.59.9" "@typescript-eslint/visitor-keys" "5.59.9" +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/type-utils@5.59.9": version "5.59.9" resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.9.tgz#53bfaae2e901e6ac637ab0536d1754dfef4dafc2" @@ -5251,6 +5371,16 @@ debug "^4.3.4" tsutils "^3.21.0" +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + "@typescript-eslint/types@5.59.0": version "5.59.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.0.tgz#3fcdac7dbf923ec5251545acdd9f1d42d7c4fe32" @@ -5261,6 +5391,11 @@ resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.9.tgz#3b4e7ae63718ce1b966e0ae620adc4099a6dcc52" integrity sha512-uW8H5NRgTVneSVTfiCVffBb8AbwWSKg7qcA4Ot3JI3MPCJGsB4Db4BhvAODIIYE5mNj7Q+VJkK7JxmRhk2Lyjw== +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + "@typescript-eslint/typescript-estree@5.59.0": version "5.59.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.0.tgz#8869156ee1dcfc5a95be3ed0e2809969ea28e965" @@ -5301,6 +5436,20 @@ eslint-scope "^5.1.1" semver "^7.3.7" +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + "@typescript-eslint/visitor-keys@5.59.0": version "5.59.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.0.tgz#a59913f2bf0baeb61b5cfcb6135d3926c3854365" @@ -5317,6 +5466,14 @@ "@typescript-eslint/types" "5.59.9" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + "@unstoppabledomains/resolution@6.0.3": version "6.0.3" resolved "https://registry.yarnpkg.com/@unstoppabledomains/resolution/-/resolution-6.0.3.tgz#a70888840c86a5918cb3134e7ac745e054c92aa5" @@ -5354,6 +5511,13 @@ "@urql/core" ">=2.3.1" wonka "^4.0.14" +"@vitejs/plugin-react-swc@^3.3.2": + version "3.3.2" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.3.2.tgz#34a82c1728066f48a86dfecb2f15df60f89207fb" + integrity sha512-VJFWY5sfoZerQRvJrh518h3AcQt6f/yTuWn4/TRB+dqmYU0NX1qz7qM5Wfd+gOQqUzQW4gxKqKN3KpE/P3+zrA== + dependencies: + "@swc/core" "^1.3.61" + "@vitest/expect@0.31.1": version "0.31.1" resolved "https://registry.yarnpkg.com/@vitest/expect/-/expect-0.31.1.tgz#db8cb5a14a91167b948f377b9d29442229c73747" @@ -10007,6 +10171,49 @@ eslint@^8.19.0, eslint@^8.4.1: strip-json-comments "^3.1.0" text-table "^0.2.0" +eslint@^8.44.0: + version "8.45.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78" + integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.4.0" + "@eslint/eslintrc" "^2.1.0" + "@eslint/js" "8.44.0" + "@humanwhocodes/config-array" "^0.11.10" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.6.0" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + esm@^3.2.25: version "3.2.25" resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" @@ -10021,6 +10228,15 @@ espree@^9.5.2: acorn-jsx "^5.3.2" eslint-visitor-keys "^3.4.1" +espree@^9.6.0: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" @@ -11838,6 +12054,11 @@ image-size@^0.6.0: resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.6.3.tgz#e7e5c65bb534bd7cdcedd6cb5166272a85f75fb2" integrity sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA== +immer@^10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.0.2.tgz#11636c5b77acf529e059582d76faf338beb56141" + integrity sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA== + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -15809,6 +16030,18 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + options-defaults@^2.0.39: version "2.0.40" resolved "https://registry.yarnpkg.com/options-defaults/-/options-defaults-2.0.40.tgz#18a1521519e68e561e2267a3dcb409f2fba81510" @@ -17024,6 +17257,14 @@ react-dom@^16.13.1: prop-types "^15.6.2" scheduler "^0.19.1" +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + react-error-boundary@^3.1.0, react-error-boundary@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" @@ -17465,7 +17706,7 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react@18.2.0: +react@18.2.0, react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== @@ -20086,6 +20327,11 @@ typescript@^4.5.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.3.tgz#8d84219244a6b40b6fb2b33cc1c062f715b9e826" integrity sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw== +typescript@^5.0.2: + version "5.1.6" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.1.6.tgz#02f8ac202b6dad2c0dd5e0913745b47a37998274" + integrity sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA== + typescript@~4.4.4: version "4.4.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" @@ -20505,6 +20751,17 @@ vite-node@0.31.1: optionalDependencies: fsevents "~2.3.2" +vite@^4.4.0: + version "4.4.4" + resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.4.tgz#b76e6049c0e080cb54e735ad2d18287753752118" + integrity sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg== + dependencies: + esbuild "^0.18.10" + postcss "^8.4.25" + rollup "^3.25.2" + optionalDependencies: + fsevents "~2.3.2" + vitest@0.31.1: version "0.31.1" resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.31.1.tgz#e3d1b68a44e76e24f142c1156fe9772ef603e52c"