diff --git a/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx b/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx index 2f5258c686..60fb652b9d 100644 --- a/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx +++ b/apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx @@ -17,11 +17,10 @@ export type AmountItemProps = { isPrivacyOff?: boolean status?: string inWallet?: boolean - supply?: string variant?: 'swap' } -export const AmountItem = ({isPrivacyOff, wallet, style, amount, inWallet, supply, variant}: AmountItemProps) => { +export const AmountItem = ({isPrivacyOff, wallet, style, amount, inWallet, variant}: AmountItemProps) => { const {quantity, tokenId} = amount const tokenInfo = useTokenInfo({wallet, tokenId}) @@ -63,13 +62,15 @@ export const AmountItem = ({isPrivacyOff, wallet, style, amount, inWallet, suppl - {tokenInfo.kind !== 'nft' && ( + {tokenInfo.kind !== 'nft' && variant !== 'swap' && ( - {isPrivacyOff ? '**.*******' : variant === 'swap' ? `${supply}` : formattedQuantity} + {isPrivacyOff ? '**.*******' : formattedQuantity} )} - {isPrimary && } + {isPrimary && variant !== 'swap' && ( + + )} ) diff --git a/apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx b/apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx index 3cbb0ffcbe..012b6c58af 100644 --- a/apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx +++ b/apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx @@ -1,5 +1,5 @@ import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs' -import {useSwap, useSwapTokensByPairToken} from '@yoroi/swap' +import {useSwap, useSwapTokensOnlyVerified} from '@yoroi/swap' import React from 'react' import {StyleSheet} from 'react-native' import {SafeAreaView} from 'react-native-safe-area-context' @@ -57,7 +57,7 @@ export const SwapTabNavigator = () => { }, [aggregatorTokenId, lpTokenHeld, lpTokenHeldChanged]) // pre load swap tokens - const {refetch} = useSwapTokensByPairToken('', {suspense: false, enabled: false}) + const {refetch} = useSwapTokensOnlyVerified({suspense: false, enabled: false}) React.useEffect(() => { refetch() }, [refetch]) diff --git a/apps/wallet-mobile/src/features/Swap/common/helpers.ts b/apps/wallet-mobile/src/features/Swap/common/helpers.ts index a10285e05f..5f25c3fca1 100644 --- a/apps/wallet-mobile/src/features/Swap/common/helpers.ts +++ b/apps/wallet-mobile/src/features/Swap/common/helpers.ts @@ -73,26 +73,22 @@ function containsOnlyValidChars(str?: string): boolean { return typeof str === 'string' && validCharsRegex.test(str) } -export const sortTokensByName = (a: Balance.Token, b: Balance.Token, wallet: YoroiWallet) => { - const isValidNameA = containsOnlyValidChars(a.info.name) - const isValidNameB = containsOnlyValidChars(b.info.name) - const isValidTickerA = containsOnlyValidChars(a.info.ticker) - const isValidTickerB = containsOnlyValidChars(b.info.ticker) +export const sortTokensByName = (a: Balance.TokenInfo, b: Balance.TokenInfo, wallet: YoroiWallet) => { + const isValidNameA = containsOnlyValidChars(a.name) + const isValidNameB = containsOnlyValidChars(b.name) + const isValidTickerA = containsOnlyValidChars(a.ticker) + const isValidTickerB = containsOnlyValidChars(b.ticker) const nameA = - a.info.ticker?.toLocaleLowerCase() && isValidTickerA - ? a.info.ticker?.toLocaleLowerCase() - : a.info.name.toLocaleLowerCase() + a.ticker?.toLocaleLowerCase() && isValidTickerA ? a.ticker?.toLocaleLowerCase() : a.name.toLocaleLowerCase() const nameB = - b.info.ticker?.toLocaleLowerCase() && isValidTickerB - ? b.info.ticker?.toLocaleLowerCase() - : b.info.name.toLocaleLowerCase() + b.ticker?.toLocaleLowerCase() && isValidTickerB ? b.ticker?.toLocaleLowerCase() : b.name.toLocaleLowerCase() - const isBPrimary = b.info.ticker === wallet.primaryTokenInfo.ticker + const isBPrimary = b.ticker === wallet.primaryTokenInfo.ticker if (isBPrimary) return 1 - const isAPrimary = a.info.ticker === wallet.primaryTokenInfo.ticker + const isAPrimary = a.ticker === wallet.primaryTokenInfo.ticker if (isAPrimary) return -1 if (!isValidNameA && isValidNameB) { diff --git a/apps/wallet-mobile/src/features/Swap/useCases/StartSwapScreen/CreateOrder/EditBuyAmount/SelectBuyTokenFromListScreen/SelectBuyTokenFromListScreen.tsx b/apps/wallet-mobile/src/features/Swap/useCases/StartSwapScreen/CreateOrder/EditBuyAmount/SelectBuyTokenFromListScreen/SelectBuyTokenFromListScreen.tsx index 24b5cb1e75..75e3544c3b 100644 --- a/apps/wallet-mobile/src/features/Swap/useCases/StartSwapScreen/CreateOrder/EditBuyAmount/SelectBuyTokenFromListScreen/SelectBuyTokenFromListScreen.tsx +++ b/apps/wallet-mobile/src/features/Swap/useCases/StartSwapScreen/CreateOrder/EditBuyAmount/SelectBuyTokenFromListScreen/SelectBuyTokenFromListScreen.tsx @@ -1,5 +1,5 @@ import {FlashList} from '@shopify/flash-list' -import {useSwap, useSwapTokensByPairToken} from '@yoroi/swap' +import {useSwap, useSwapTokensOnlyVerified} from '@yoroi/swap' import {Balance} from '@yoroi/types' import React from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' @@ -13,7 +13,7 @@ import {useSelectedWallet} from '../../../../../../../SelectedWallet' import {COLORS} from '../../../../../../../theme' import {YoroiWallet} from '../../../../../../../yoroi-wallets/cardano/types' import {useBalance, useBalances} from '../../../../../../../yoroi-wallets/hooks' -import {Amounts, asQuantity, Quantities} from '../../../../../../../yoroi-wallets/utils' +import {Amounts} from '../../../../../../../yoroi-wallets/utils' import {NoAssetFoundImage} from '../../../../../../Send/common/NoAssetFoundImage' import {Counter} from '../../../../../common/Counter/Counter' import {filterBySearch} from '../../../../../common/filterBySearch' @@ -60,20 +60,20 @@ export const SelectBuyTokenFromListScreen = () => { const TokenList = () => { const strings = useStrings() const wallet = useSelectedWallet() - const {pairsByToken} = useSwapTokensByPairToken('') + const {onlyVerifiedTokens} = useSwapTokensOnlyVerified() const {search: assetSearchTerm} = useSearch() const balances = useBalances(wallet) const walletTokenIds = Amounts.toArray(balances).map(({tokenId}) => tokenId) - const tokens: Array = React.useMemo(() => { - if (pairsByToken === undefined) return [] - return pairsByToken - }, [pairsByToken]) + const tokenInfos: Array = React.useMemo(() => { + if (onlyVerifiedTokens === undefined) return [] + return onlyVerifiedTokens + }, [onlyVerifiedTokens]) const filteredTokenList = React.useMemo(() => { const filter = filterBySearch(assetSearchTerm) - return tokens.filter((token) => filter(token.info)).sort((a, b) => sortTokensByName(a, b, wallet)) - }, [tokens, assetSearchTerm, wallet]) + return tokenInfos.filter((tokenInfo) => filter(tokenInfo)).sort((a, b) => sortTokensByName(a, b, wallet)) + }, [tokenInfos, assetSearchTerm, wallet]) return ( @@ -83,8 +83,6 @@ const TokenList = () => { {strings.asset} - - {strings.volume} @@ -95,13 +93,13 @@ const TokenList = () => { ( + renderItem={({item: tokenInfo}: {item: Balance.TokenInfo}) => ( }}> - + )} bounces={false} - keyExtractor={({info: {id, name}}) => `${name}-${id}`} + keyExtractor={({id, name}) => `${name}-${id}`} testID="assetsList" estimatedItemSize={72} ListEmptyComponent={} @@ -130,10 +128,11 @@ const TokenList = () => { type SelectableTokenProps = { wallet: YoroiWallet walletTokenIds: Array - token: Balance.Token + tokenInfo: Balance.TokenInfo } -const SelectableToken = ({wallet, token, walletTokenIds}: SelectableTokenProps) => { - const balanceAvailable = useBalance({wallet, tokenId: token.info.id}) +const SelectableToken = ({wallet, tokenInfo, walletTokenIds}: SelectableTokenProps) => { + const {id, name, ticker, group, decimals} = tokenInfo + const balanceAvailable = useBalance({wallet, tokenId: id}) const {closeSearch} = useSearch() const {buyTokenInfoChanged, orderData} = useSwap() const { @@ -143,17 +142,16 @@ const SelectableToken = ({wallet, token, walletTokenIds}: SelectableTokenProps) const navigateTo = useNavigateTo() const {track} = useMetrics() - const isDisabled = token.info.id === orderData.amounts.sell.tokenId && isSellTouched - const inUserWallet = walletTokenIds.includes(token.info.id) - const supplyFormatted = Quantities.format(asQuantity(token.supply?.total), token.info.decimals ?? 0) + const isDisabled = id === orderData.amounts.sell.tokenId && isSellTouched + const inUserWallet = walletTokenIds.includes(tokenInfo.id) const handleOnTokenSelection = () => { track.swapAssetToChanged({ - to_asset: [{asset_name: token.info.name, asset_ticker: token.info.ticker, policy_id: token.info.group}], + to_asset: [{asset_name: name, asset_ticker: ticker, policy_id: group}], }) buyTokenInfoChanged({ - decimals: token.info.decimals ?? 0, - id: token.info.id, + decimals: decimals ?? 0, + id: id, }) buyTouched() navigateTo.startSwap() @@ -168,18 +166,17 @@ const SelectableToken = ({wallet, token, walletTokenIds}: SelectableTokenProps) disabled={isDisabled} > ) } -const EmptyList = ({filteredTokensForList}: {filteredTokensForList: Array}) => { +const EmptyList = ({filteredTokensForList}: {filteredTokensForList: Array}) => { const {search: assetSearchTerm, visible: isSearching} = useSearch() if (isSearching && assetSearchTerm.length > 0 && filteredTokensForList.length === 0) diff --git a/lerna.json b/lerna.json index 140d2c6043..695b1e6b91 100644 --- a/lerna.json +++ b/lerna.json @@ -1,4 +1,9 @@ { "npmClient": "yarn", - "version": "independent" + "version": "independent", + "command": { + "run": { + "ignore": ["e2e/*"] + } + } } diff --git a/packages/openswap/src/api.ts b/packages/openswap/src/api.ts index ed83aedd86..763e73a9bf 100644 --- a/packages/openswap/src/api.ts +++ b/packages/openswap/src/api.ts @@ -5,6 +5,7 @@ import { getCompletedOrders, getOrders, // returns all orders for a given stake key hash. } from './orders' +import {getTokenPairs} from './token-pairs' import {getTokens} from './tokens' import { CancelOrderRequest, @@ -95,13 +96,17 @@ export class OpenSwapApi { ) } - public async getTokens({policyId = '', assetName = ''} = {}) { - const tokens = await getTokens( + public async getTokenPairs({policyId = '', assetName = ''} = {}) { + const tokenPairs = await getTokenPairs( {network: this.network, client: this.client}, {policyId, assetName}, ) - return tokens + return tokenPairs + } + + public async getTokens() { + return getTokens({network: this.network, client: this.client}) } } diff --git a/packages/openswap/src/config.ts b/packages/openswap/src/config.ts index 1ad154a826..aabb9a6bc1 100644 --- a/packages/openswap/src/config.ts +++ b/packages/openswap/src/config.ts @@ -7,7 +7,8 @@ export const SWAP_API_ENDPOINTS = { getLiquidityPools: 'https://api.muesliswap.com/liquidity/pools', getOrders: 'https://onchain2.muesliswap.com/orders/all/', getCompletedOrders: 'https://api.muesliswap.com/orders/v2', - getTokens: 'https://api.muesliswap.com/list', + getTokenPairs: 'https://api.muesliswap.com/list', + getTokens: 'https://api.muesliswap.com/token-list', constructSwapDatum: 'https://aggregator.muesliswap.com/constructSwapDatum', cancelSwapTransaction: 'https://aggregator.muesliswap.com/cancelSwapTransaction', @@ -18,7 +19,8 @@ export const SWAP_API_ENDPOINTS = { getLiquidityPools: 'https://preprod.api.muesliswap.com/liquidity/pools', getOrders: 'https://preprod.pools.muesliswap.com/orders/all/', getCompletedOrders: 'https://api.muesliswap.com/orders/v2', - getTokens: 'https://preprod.api.muesliswap.com/list', + getTokenPairs: 'https://preprod.api.muesliswap.com/list', + getTokens: 'https://preprod.api.muesliswap.com/token-list', constructSwapDatum: 'https://aggregator.muesliswap.com/constructTestnetSwapDatum', cancelSwapTransaction: diff --git a/packages/openswap/src/index.ts b/packages/openswap/src/index.ts index 1cef45a87f..1663abeab8 100644 --- a/packages/openswap/src/index.ts +++ b/packages/openswap/src/index.ts @@ -21,8 +21,10 @@ export namespace OpenSwap { export type LiquidityPoolResponse = Types.LiquidityPoolResponse // Tokens - export type Token = Types.Token - export type TokenResponse = Types.TokenResponse + export type TokenPair = Types.TokenPair + export type TokenInfo = Types.TokenInfo + export type TokenPairsResponse = Types.TokenPairsResponse + export type ListTokensResponse = Types.ListTokensResponse export type TokenAddress = Types.TokenAddress export type PriceAddress = Types.PriceAddress diff --git a/packages/openswap/src/token-pairs.spec.ts b/packages/openswap/src/token-pairs.spec.ts new file mode 100644 index 0000000000..9ce4b20a60 --- /dev/null +++ b/packages/openswap/src/token-pairs.spec.ts @@ -0,0 +1,79 @@ +import {expect, describe, it, vi, Mocked} from 'vitest' +import {getTokenPairs} from './token-pairs' +import {axiosClient} from './config' + +vi.mock('./config.ts') + +describe('SwapTokenPairsApi', () => { + it('should get all tokens based pairs', async () => { + const mockAxios = axiosClient as Mocked + mockAxios.get.mockImplementationOnce(() => + Promise.resolve({status: 200, data: mockedGetTokenPairsResponse}), + ) + + const result = await getTokenPairs({network: 'mainnet', client: mockAxios}) + + expect(result).to.be.lengthOf(1) + }) + + it('should return empty list on preprod network', async () => { + const mockAxios = axiosClient as Mocked + + const result = await getTokenPairs({network: 'preprod', client: mockAxios}) + + expect(result).to.be.empty + }) + + it('should throw error for invalid response', async () => { + const mockAxios = axiosClient as Mocked + mockAxios.get.mockImplementationOnce(() => Promise.resolve({status: 500})) + expect(() => + getTokenPairs({network: 'mainnet', client: mockAxios}), + ).rejects.toThrow('Failed to fetch token pairs') + }) +}) + +const mockedGetTokenPairsResponse = [ + { + 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/openswap/src/token-pairs.ts b/packages/openswap/src/token-pairs.ts new file mode 100644 index 0000000000..d6aaf46d2e --- /dev/null +++ b/packages/openswap/src/token-pairs.ts @@ -0,0 +1,25 @@ +import {SWAP_API_ENDPOINTS} from './config' +import type {ApiDeps, TokenPairsResponse} from './types' + +export async function getTokenPairs( + deps: ApiDeps, + {policyId = '', assetName = ''} = {}, +): Promise { + const {network, client} = deps + if (network === 'preprod') return [] + + const apiUrl = SWAP_API_ENDPOINTS[network].getTokenPairs + const response = await client.get('', { + baseURL: apiUrl, + params: { + 'base-policy-id': policyId, + 'base-tokenname': assetName, + }, + }) + + if (response.status !== 200) { + throw new Error('Failed to fetch token pairs', {cause: response.data}) + } + + return response.data +} diff --git a/packages/openswap/src/tokens.spec.ts b/packages/openswap/src/tokens.spec.ts index 8d0c91fe4c..638aebb6ce 100644 --- a/packages/openswap/src/tokens.spec.ts +++ b/packages/openswap/src/tokens.spec.ts @@ -8,7 +8,7 @@ describe('SwapTokensApi', () => { it('should get all supported tokens list', async () => { const mockAxios = axiosClient as Mocked mockAxios.get.mockImplementationOnce(() => - Promise.resolve({status: 200, data: mockedGetTokensRes}), + Promise.resolve({status: 200, data: mockedGetTokensResponse}), ) const result = await getTokens({network: 'mainnet', client: mockAxios}) @@ -16,14 +16,6 @@ describe('SwapTokensApi', () => { expect(result).to.be.lengthOf(1) }) - it('should return empty list on preprod network', async () => { - const mockAxios = axiosClient as Mocked - - const result = await getTokens({network: 'preprod', client: mockAxios}) - - expect(result).to.be.empty - }) - it('should throw error for invalid response', async () => { const mockAxios = axiosClient as Mocked mockAxios.get.mockImplementationOnce(() => Promise.resolve({status: 500})) @@ -33,47 +25,22 @@ describe('SwapTokensApi', () => { }) }) -const mockedGetTokensRes = [ +const mockedGetTokensResponse = [ { - 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: [], + supply: { + total: '10000000', + circulating: '6272565', }, - 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: [], + status: 'verified', + website: 'https://ada.muesliswap.com/', + symbol: 'MILK', + decimalPlaces: 0, + image: 'https://static.muesliswap.com/images/tokens/MILK.png', + description: 'MILK is the utility token powering the MuesliSwap ecosystem.', + address: { + policyId: '8a1cfae21368b8bebbbed9800fec304e95cce39a2a57dc35e2e3ebaa', + name: '4d494c4b', }, + categories: ['1', '2'], }, ] diff --git a/packages/openswap/src/tokens.ts b/packages/openswap/src/tokens.ts index 750783007d..e1fc16909c 100644 --- a/packages/openswap/src/tokens.ts +++ b/packages/openswap/src/tokens.ts @@ -1,20 +1,13 @@ import {SWAP_API_ENDPOINTS} from './config' -import type {ApiDeps, TokenResponse} from './types' +import type {ApiDeps, ListTokensResponse} from './types' -export async function getTokens( - deps: ApiDeps, - {policyId = '', assetName = ''} = {}, -): Promise { +export async function getTokens(deps: ApiDeps): Promise { const {network, client} = deps if (network === 'preprod') return [] const apiUrl = SWAP_API_ENDPOINTS[network].getTokens - const response = await client.get('', { + const response = await client.get('', { baseURL: apiUrl, - params: { - 'base-policy-id': policyId, - 'base-tokenname': assetName, - }, }) if (response.status !== 200) { diff --git a/packages/openswap/src/types.ts b/packages/openswap/src/types.ts index dc5e7b7fc8..39c32e6edb 100644 --- a/packages/openswap/src/types.ts +++ b/packages/openswap/src/types.ts @@ -119,7 +119,7 @@ export type PoolPair = { } export type PoolPairResponse = PoolPair[] -export type Token = { +export type TokenPair = { info: { supply: { total: string // total circulating supply of the token, without decimals. @@ -159,7 +159,10 @@ export type Token = { price10d: number[] //float, prices of this tokens averaged for the last 10 days, in chronological order i.e.oldest first. } } -export type TokenResponse = Token[] +export type TokenPairsResponse = TokenPair[] + +export type TokenInfo = Omit +export type ListTokensResponse = TokenInfo[] export type TokenAddress = | { diff --git a/packages/swap/src/adapters/openswap-api/api.mocks.ts b/packages/swap/src/adapters/openswap-api/api.mocks.ts index 29b8166095..f501c1eb98 100644 --- a/packages/swap/src/adapters/openswap-api/api.mocks.ts +++ b/packages/swap/src/adapters/openswap-api/api.mocks.ts @@ -125,7 +125,7 @@ const getPools: Swap.Pool[] = [ }, ] -const getTokens: Balance.Token[] = [ +const getTokenPairs: Balance.Token[] = [ { info: { id: '1c1e38cfcc815d2015dbda6bee668b2e707ee3f9d038d96668fcf63c.4567677363617065436c75624561737465725a656e6e79', @@ -263,10 +263,75 @@ const getTokens: Balance.Token[] = [ }, ] +const getTokens: Balance.TokenInfo[] = [ + { + id: '1c1e38cfcc815d2015dbda6bee668b2e707ee3f9d038d96668fcf63c.4567677363617065436c75624561737465725a656e6e79', + group: '1c1e38cfcc815d2015dbda6bee668b2e707ee3f9d038d96668fcf63c', + fingerprint: 'asset126v2sm79r8uxvk4ju64mr6srxrvm2x75fpg6w3', + name: 'EggscapeClubEasterZenny', + decimals: 0, + description: 'Eggscape Club Utility Token', + image: 'ipfs://QmNYibJoiTWRiMmWn4yXwvoakEPgq9WmaukmRXHF1VGbAU', + kind: 'ft', + symbol: undefined, + icon: undefined, + ticker: 'EZY', + + metadatas: {}, + }, + { + id: 'cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a.43415354', + group: 'cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a', + fingerprint: 'asset1yv4fx867hueqt98aqvjw5ncjymz8k3ah8zawcg', + name: 'CAST', + decimals: 0, + description: 'Utility Token for Carda Station Metaverse', + image: + 'https://tokens.muesliswap.com/static/img/tokens/cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a.43415354.png', + kind: 'ft', + symbol: undefined, + icon: undefined, + ticker: 'CAST', + metadatas: {}, + }, + { + id: 'cd5b9dd91319edbb19477ad00cbef673a221e70a17ef043951fc6786.52656465656d61626c65', + group: 'cd5b9dd91319edbb19477ad00cbef673a221e70a17ef043951fc6786', + fingerprint: 'asset18qw75gcdldlu7q5xh8fjsemgvwffzkg8hatq3s', + name: 'Redeemable', + decimals: 4, + description: + 'The fiat-backed stablecoin issued by Shareslake. Powering the fully stable branch of Cardano.', + image: + 'https://tokens.muesliswap.com/static/img/tokens/cd5b9dd91319edbb19477ad00cbef673a221e70a17ef043951fc6786.52656465656d61626c65.png', + kind: 'ft', + symbol: undefined, + icon: undefined, + ticker: 'RUSD', + metadatas: {}, + }, + { + id: '2d420236ffaada336c21e3f4520b799f6e246d8618f2fc89a4907da6.4564756c6164646572546f6b656e', + group: '2d420236ffaada336c21e3f4520b799f6e246d8618f2fc89a4907da6', + fingerprint: 'asset1ny2ehvl20cp5y7mmn5qq332sgdncdmsgrcqlwh', + name: 'EduladderToken', + decimals: 6, + description: 'Proof Of Contribution.', + image: + 'https://tokens.muesliswap.com/static/img/tokens/2d420236ffaada336c21e3f4520b799f6e246d8618f2fc89a4907da6.4564756c6164646572546f6b656e.png', + kind: 'ft', + symbol: undefined, + icon: undefined, + ticker: 'ELADR', + metadatas: {}, + }, +] + export const apiMocks = { getOpenOrders, getCompletedOrders, createOrderData, getPools, + getTokenPairs, getTokens, } diff --git a/packages/swap/src/adapters/openswap-api/api.test.ts b/packages/swap/src/adapters/openswap-api/api.test.ts index e009a5c066..ede5c3d460 100644 --- a/packages/swap/src/adapters/openswap-api/api.test.ts +++ b/packages/swap/src/adapters/openswap-api/api.test.ts @@ -20,6 +20,7 @@ describe('swapApiMaker', () => { createOrder: jest.fn(), getOrders: jest.fn(), getTokens: jest.fn(), + getTokenPairs: jest.fn(), getCompletedOrders: jest.fn(), getLiquidityPools: jest.fn(), getPoolsPair: jest.fn(), @@ -217,6 +218,53 @@ describe('swapApiMaker', () => { }) }) + describe('getTokenPairs', () => { + it('mainnet', async () => { + mockOpenSwapApi.getTokenPairs = jest + .fn() + .mockResolvedValue(openswapMocks.getTokenPairs) + const api = swapApiMaker( + { + isMainnet: true, + stakingKey, + primaryTokenId, + supportedProviders, + }, + { + openswap: mockOpenSwapApi, + }, + ) + + const result = await api.getTokenPairs('') + + expect(mockOpenSwapApi.getTokenPairs).toHaveBeenCalledTimes(1) + expect(result).toEqual>(apiMocks.getTokenPairs) + }) + + it('preprod (mocked)', async () => { + mockOpenSwapApi.getTokenPairs = jest + .fn() + .mockResolvedValue(openswapMocks.getTokenPairs) + + const api = swapApiMaker( + { + isMainnet: false, + stakingKey, + primaryTokenId, + supportedProviders, + }, + { + openswap: mockOpenSwapApi, + }, + ) + + const result = await api.getTokenPairs('') + + expect(result).toBeDefined() + expect(mockOpenSwapApi.getTokenPairs).not.toHaveBeenCalled() + }) + }) + describe('getTokens', () => { it('mainnet', async () => { mockOpenSwapApi.getTokens = jest @@ -234,13 +282,13 @@ describe('swapApiMaker', () => { }, ) - const result = await api.getTokens('') + const result = await api.getTokens() expect(mockOpenSwapApi.getTokens).toHaveBeenCalledTimes(1) - expect(result).toEqual>(apiMocks.getTokens) + expect(result).toEqual>(apiMocks.getTokens) }) - it('preprod (mocked)', async () => { + it('preprod', async () => { mockOpenSwapApi.getTokens = jest .fn() .mockResolvedValue(openswapMocks.getTokens) @@ -257,10 +305,10 @@ describe('swapApiMaker', () => { }, ) - const result = await api.getTokens('') + const result = await api.getTokens() expect(result).toBeDefined() - expect(mockOpenSwapApi.getTokens).not.toHaveBeenCalled() + expect(mockOpenSwapApi.getTokenPairs).not.toHaveBeenCalled() }) }) diff --git a/packages/swap/src/adapters/openswap-api/api.ts b/packages/swap/src/adapters/openswap-api/api.ts index 76e6f5b32b..8f7920dda4 100644 --- a/packages/swap/src/adapters/openswap-api/api.ts +++ b/packages/swap/src/adapters/openswap-api/api.ts @@ -69,13 +69,17 @@ export const swapApiMaker = ( }) .then((response) => response) - const getTokens: Swap.Api['getTokens'] = async (token) => + const getTokenPairs: Swap.Api['getTokenPairs'] = async (token) => !isMainnet - ? apiMocks.getTokens // preprod doesn't return any tokens + ? apiMocks.getTokenPairs // preprod doesn't return any tokens : api - .getTokens(transformers.asOpenswapTokenId(token)) + .getTokenPairs(transformers.asOpenswapTokenId(token)) .then(transformers.asYoroiBalanceTokens) + const getTokens: Swap.Api['getTokens'] = async () => { + return api.getTokens().then(transformers.asYoroiBalanceTokenInfos) + } + const getPools: Swap.Api['getPools'] = async ({ tokenA, tokenB, @@ -114,6 +118,7 @@ export const swapApiMaker = ( cancelOrder, createOrder, getTokens, + getTokenPairs, getPools, getCompletedOrders, stakingKey, diff --git a/packages/swap/src/adapters/openswap-api/openswap.mocks.ts b/packages/swap/src/adapters/openswap-api/openswap.mocks.ts index b5582b56d3..b623aacfa4 100644 --- a/packages/swap/src/adapters/openswap-api/openswap.mocks.ts +++ b/packages/swap/src/adapters/openswap-api/openswap.mocks.ts @@ -1,6 +1,81 @@ import {OpenSwap} from '@yoroi/openswap' -const getTokens: OpenSwap.Token[] = [ +const getTokens: OpenSwap.ListTokensResponse = [ + { + supply: { + total: '10000000', + circulating: '300', + }, + status: 'verified', + website: 'https://eggscape.io/', + image: 'ipfs://QmNYibJoiTWRiMmWn4yXwvoakEPgq9WmaukmRXHF1VGbAU', + description: 'Eggscape Club Utility Token', + address: { + policyId: '1c1e38cfcc815d2015dbda6bee668b2e707ee3f9d038d96668fcf63c', + name: '4567677363617065436c75624561737465725a656e6e79', + }, + symbol: 'EZY', + decimalPlaces: 0, + categories: [], + }, + { + supply: { + total: '1500000000', + circulating: null, + }, + status: 'verified', + symbol: 'CAST', + decimalPlaces: 0, + image: + 'https://tokens.muesliswap.com/static/img/tokens/cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a.43415354.png', + description: 'Utility Token for Carda Station Metaverse', + address: { + policyId: 'cdaaee586376139ee8c3cc4061623968810d177ca5c300afb890b48a', + name: '43415354', + }, + website: 'https://cardastation.com', + categories: [], + }, + { + supply: { + total: '387017195', + circulating: null, + }, + status: 'verified', + website: 'https://www.shareslake.com', + description: + 'The fiat-backed stablecoin issued by Shareslake. Powering the fully stable branch of Cardano.', + image: + 'https://tokens.muesliswap.com/static/img/tokens/cd5b9dd91319edbb19477ad00cbef673a221e70a17ef043951fc6786.52656465656d61626c65.png', + symbol: 'RUSD', + decimalPlaces: 4, + address: { + policyId: 'cd5b9dd91319edbb19477ad00cbef673a221e70a17ef043951fc6786', + name: '52656465656d61626c65', + }, + categories: [], + }, + { + supply: { + total: '45000000003000000', + circulating: null, + }, + status: 'verified', + website: 'https://eduladder.com', + symbol: 'ELADR', + decimalPlaces: 6, + image: + 'https://tokens.muesliswap.com/static/img/tokens/2d420236ffaada336c21e3f4520b799f6e246d8618f2fc89a4907da6.4564756c6164646572546f6b656e.png', + description: 'Proof Of Contribution.', + address: { + policyId: '2d420236ffaada336c21e3f4520b799f6e246d8618f2fc89a4907da6', + name: '4564756c6164646572546f6b656e', + }, + categories: [], + }, +] + +const getTokenPairs: OpenSwap.TokenPairsResponse = [ { info: { supply: { @@ -573,6 +648,7 @@ const getPrice: OpenSwap.PriceResponse = { } export const openswapMocks = { + getTokenPairs, getTokens, getPrice, getCompletedOrders, diff --git a/packages/swap/src/helpers/transformers.test.ts b/packages/swap/src/helpers/transformers.test.ts index c383c2ac58..85acb1b423 100644 --- a/packages/swap/src/helpers/transformers.test.ts +++ b/packages/swap/src/helpers/transformers.test.ts @@ -241,16 +241,46 @@ describe('asYoroiPool', () => { describe('asYoroiBalanceToken', () => { it('success', () => { - const result = transformers.asYoroiBalanceToken(openswapMocks.getTokens[0]!) + const result = transformers.asYoroiBalanceToken( + openswapMocks.getTokenPairs[0]!, + ) - expect(result).toEqual(apiMocks.getTokens[0]!) + expect(result).toEqual(apiMocks.getTokenPairs[0]!) }) }) describe('asYoroiBalanceTokens', () => { it('success', () => { - const result = transformers.asYoroiBalanceTokens(openswapMocks.getTokens) + const result = transformers.asYoroiBalanceTokens( + openswapMocks.getTokenPairs, + ) + + expect(result).toEqual>(apiMocks.getTokenPairs) + }) +}) + +describe('asYoroiBalanceTokenInfo', () => { + it('success', () => { + const result = transformers.asYoroiBalanceTokenInfo( + openswapMocks.getTokens[0]!, + ) + + expect(result).toEqual(apiMocks.getTokens[0]!) + }) +}) + +describe('asYoroiBalanceTokenInfos', () => { + it('success', () => { + const result = transformers.asYoroiBalanceTokenInfos( + openswapMocks.getTokens, + ) + + expect(result).toEqual>(apiMocks.getTokens) + }) + + it('success (empty)', () => { + const result = transformers.asYoroiBalanceTokenInfos([]) - expect(result).toEqual>(apiMocks.getTokens) + expect(result).toEqual>([]) }) }) diff --git a/packages/swap/src/helpers/transformers.ts b/packages/swap/src/helpers/transformers.ts index 9626bd3224..a13071fe41 100644 --- a/packages/swap/src/helpers/transformers.ts +++ b/packages/swap/src/helpers/transformers.ts @@ -93,27 +93,11 @@ export const transformersMaker = ( } const asYoroiBalanceToken = ( - openswapToken: OpenSwap.Token, + openswapTokenPair: OpenSwap.TokenPair, ): Balance.Token => { - const {info, price} = openswapToken + const {info, price} = openswapTokenPair const balanceToken: Balance.Token = { - info: { - id: asYoroiTokenId(info.address), - group: info.address.policyId, - fingerprint: asTokenFingerprint({ - policyId: info.address.policyId, - assetNameHex: info.address.name, - }), - name: asUtf8(info.address.name), - decimals: info.decimalPlaces, - description: info.description, - image: info.image, - kind: 'ft', - symbol: info?.sign, - icon: undefined, - ticker: info.symbol, - metadatas: {}, - }, + info: asYoroiBalanceTokenInfo(info), price: { ...price, }, @@ -125,6 +109,39 @@ export const transformersMaker = ( return balanceToken } + const asYoroiBalanceTokenInfo = ( + openswapToken: OpenSwap.TokenPair['info'], + ): Balance.TokenInfo => { + const tokenInfo: Balance.TokenInfo = { + id: asYoroiTokenId(openswapToken.address), + group: openswapToken.address.policyId, + fingerprint: asTokenFingerprint({ + policyId: openswapToken.address.policyId, + assetNameHex: openswapToken.address.name, + }), + name: asUtf8(openswapToken.address.name), + decimals: openswapToken.decimalPlaces, + description: openswapToken.description, + image: openswapToken.image, + kind: 'ft', + icon: undefined, + ticker: openswapToken.symbol, + symbol: openswapToken.sign, + metadatas: {}, + } + return tokenInfo + } + + const asYoroiBalanceTokenInfos = ( + openswapTokens: OpenSwap.ListTokensResponse, + ): Balance.TokenInfo[] => { + if (openswapTokens.length === 0) return [] + // filters should go into manager, but since we strip out the status is here for now + return openswapTokens + .filter((token) => token.status === 'verified') + .map(asYoroiBalanceTokenInfo) + } + const asYoroiPool = ( openswapLiquidityPool: OpenSwap.LiquidityPool, ): Swap.Pool | null => { @@ -206,22 +223,27 @@ export const transformersMaker = ( } const asYoroiBalanceTokens = ( - openswapTokens: OpenSwap.Token[], - ): Balance.Token[] => openswapTokens.map(asYoroiBalanceToken) + openswapTokenPairs: OpenSwap.TokenPair[], + ): Balance.Token[] => openswapTokenPairs.map(asYoroiBalanceToken) return { asOpenswapTokenId, asOpenswapPriceTokenAddress, asOpenswapAmount, - asYoroiTokenId, - asYoroiAmount, - asYoroiBalanceToken, - asYoroiBalanceTokens, asYoroiCompletedOrder, asYoroiOpenOrder, + asYoroiPool, asYoroiPools, + + asYoroiTokenId, + asYoroiBalanceToken, + asYoroiBalanceTokens, + asYoroiBalanceTokenInfo, + asYoroiBalanceTokenInfos, + + asYoroiAmount, } } diff --git a/packages/swap/src/index.ts b/packages/swap/src/index.ts index 1df3791fbb..a267f2ba7f 100644 --- a/packages/swap/src/index.ts +++ b/packages/swap/src/index.ts @@ -39,7 +39,8 @@ export {useSwapOrdersByStatusOpen} from './translators/reactjs/hooks/useSwapOrde export {useSwapPoolsByPair} from './translators/reactjs/hooks/useSwapPoolsByPair' export {useSwapSetSlippage} from './translators/reactjs/hooks/useSwapSetSlippage' export {useSwapSlippage} from './translators/reactjs/hooks/useSwapSlippage' -export {useSwapTokensByPairToken} from './translators/reactjs/hooks/useSwapTokensByPairToken' +export {useSwapTokensByPair} from './translators/reactjs/hooks/useSwapTokensByPair' +export {useSwapTokensOnlyVerified} from './translators/reactjs/hooks/useSwapTokensOnlyVerified' export {useSwap} from './translators/reactjs/hooks/useSwap' export {supportedProviders, milkTokenId} from './translators/constants' diff --git a/packages/swap/src/manager.mocks.ts b/packages/swap/src/manager.mocks.ts index b265e3720e..cbc9aaa243 100644 --- a/packages/swap/src/manager.mocks.ts +++ b/packages/swap/src/manager.mocks.ts @@ -26,7 +26,8 @@ const listOrdersByStatusOpenResponse: Swap.OpenOrderResponse = const listOrdersByStatusCompletedResponse: Swap.CompletedOrderResponse = apiMocks.getCompletedOrders const listPoolsByPairResponse: Swap.PoolResponse = apiMocks.getPools -const listPairsByTokenResponse: Balance.Token[] = apiMocks.getTokens +const listTokensByPairResponse: Balance.Token[] = apiMocks.getTokenPairs +const listOnlyVerifiedTokensResponse: Balance.TokenInfo[] = apiMocks.getTokens // API FUNCTIONS const createOrder = { @@ -92,10 +93,21 @@ const getPrice = { }, } +const getTokenPairs = { + success: () => Promise.resolve(listTokensByPairResponse), + delayed: (timeout?: number) => + delayedResponse({data: listTokensByPairResponse, timeout}), + empty: () => Promise.resolve([]), + loading, + error: { + unknown: unknownError, + }, +} + const getTokens = { - success: () => Promise.resolve(listPairsByTokenResponse), + success: () => Promise.resolve(listOnlyVerifiedTokensResponse), delayed: (timeout?: number) => - delayedResponse({data: listPairsByTokenResponse, timeout}), + delayedResponse({data: listOnlyVerifiedTokensResponse, timeout}), empty: () => Promise.resolve([]), loading, error: { @@ -144,7 +156,8 @@ export const swapManagerMocks = { listOrdersByStatusOpenResponse, listOrdersByStatusCompletedResponse, listPoolsByPairResponse, - listPairsByTokenResponse, + listTokensByPairResponse, + listOnlyVerifiedTokensResponse, slippageResponse, @@ -154,6 +167,7 @@ export const swapManagerMocks = { getOpenOrders, getCompletedOrders, getPools, + getTokenPairs, getTokens, slippage, @@ -174,9 +188,10 @@ export const mockSwapManager: Swap.Manager = { byPair: getPools.success, }, }, - pairs: { + tokens: { list: { - byToken: getTokens.success, + byPair: getTokenPairs.success, + onlyVerified: getTokens.success, }, }, price: { @@ -209,9 +224,10 @@ export const mockSwapManagerDefault: Swap.Manager = { price: { byPair: getPrice.error.unknown, }, - pairs: { + tokens: { list: { - byToken: getTokens.error.unknown, + byPair: getTokenPairs.error.unknown, + onlyVerified: getTokens.error.unknown, }, }, slippage: slippage.error.unknown, diff --git a/packages/swap/src/manager.test.ts b/packages/swap/src/manager.test.ts index 91b369e59e..794e75b590 100644 --- a/packages/swap/src/manager.test.ts +++ b/packages/swap/src/manager.test.ts @@ -22,6 +22,7 @@ describe('swapManagerMaker', () => { getPrice: jest.fn(), getPools: jest.fn(), getTokens: jest.fn(), + getTokenPairs: jest.fn(), getCompletedOrders: jest.fn(), primaryTokenId: '', stakingKey: 'someStakingKey', diff --git a/packages/swap/src/manager.ts b/packages/swap/src/manager.ts index a98fbdf0b6..cf5c92b830 100644 --- a/packages/swap/src/manager.ts +++ b/packages/swap/src/manager.ts @@ -19,6 +19,7 @@ export const swapManagerMaker = ({ getPools, getOpenOrders, getCompletedOrders, + getTokenPairs, getTokens, cancelOrder, createOrder, @@ -36,12 +37,6 @@ export const swapManagerMaker = ({ } as const, } - const pairs = { - list: { - byToken: getTokens, - } as const, - } - const price = { byPair: getPrice, } as const @@ -52,12 +47,19 @@ export const swapManagerMaker = ({ } as const, } + const tokens = { + list: { + byPair: getTokenPairs, + onlyVerified: getTokens, + } as const, + } + return { price, clearStorage, slippage, order, - pairs, + tokens, pools, primaryTokenId, stakingKey, diff --git a/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.test.tsx b/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.test.tsx similarity index 70% rename from packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.test.tsx rename to packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.test.tsx index 92d3569c29..4e9f376ab5 100644 --- a/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.test.tsx +++ b/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.test.tsx @@ -6,9 +6,9 @@ import {render, waitFor} from '@testing-library/react-native' import {queryClientFixture} from '../../../fixtures/query-client' import {mockSwapManager, swapManagerMocks} from '../../../manager.mocks' import {wrapperManagerFixture} from '../../../fixtures/manager-wrapper' -import {useSwapTokensByPairToken} from './useSwapTokensByPairToken' +import {useSwapTokensByPair} from './useSwapTokensByPair' -describe('useSwapTokensByPairToken', () => { +describe('useSwapTokensByPair', () => { let queryClient: QueryClient beforeEach(() => { @@ -22,7 +22,7 @@ describe('useSwapTokensByPairToken', () => { it('success', async () => { const TestPairListToken = () => { - const tokens = useSwapTokensByPairToken('tokenIdBase') + const tokens = useSwapTokensByPair('tokenIdBase') return ( {JSON.stringify(tokens.data)} @@ -30,9 +30,9 @@ describe('useSwapTokensByPairToken', () => { ) } - mockSwapManager.pairs.list.byToken = jest + mockSwapManager.tokens.list.byPair = jest .fn() - .mockResolvedValue(swapManagerMocks.listPairsByTokenResponse) + .mockResolvedValue(swapManagerMocks.listTokensByPairResponse) const wrapper = wrapperManagerFixture({ queryClient, swapManager: mockSwapManager, @@ -44,10 +44,10 @@ describe('useSwapTokensByPairToken', () => { }) expect(getByTestId('tokens').props.children).toEqual( - JSON.stringify(swapManagerMocks.listPairsByTokenResponse), + JSON.stringify(swapManagerMocks.listTokensByPairResponse), ) - expect(mockSwapManager.pairs.list.byToken).toHaveBeenCalled() - expect(mockSwapManager.pairs.list.byToken).toHaveBeenCalledWith( + expect(mockSwapManager.tokens.list.byPair).toHaveBeenCalled() + expect(mockSwapManager.tokens.list.byPair).toHaveBeenCalledWith( 'tokenIdBase', ) }) diff --git a/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.tsx b/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.tsx similarity index 59% rename from packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.tsx rename to packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.tsx index a55754961e..40b473bc99 100644 --- a/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPairToken.tsx +++ b/packages/swap/src/translators/reactjs/hooks/useSwapTokensByPair.tsx @@ -3,26 +3,26 @@ import {UseQueryOptions, useQuery} from 'react-query' import {useSwap} from './useSwap' -export const useSwapTokensByPairToken = ( +export const useSwapTokensByPair = ( tokenIdBase: Balance.Token['info']['id'], options?: UseQueryOptions< Balance.Token[], Error, Balance.Token[], - ['useSwapTokensByPairToken', string] + ['useSwapTokensByPair', string] >, ) => { - const {pairs} = useSwap() + const {tokens} = useSwap() const query = useQuery({ suspense: true, - queryKey: ['useSwapTokensByPairToken', tokenIdBase], + queryKey: ['useSwapTokensByPair', tokenIdBase], ...options, - queryFn: () => pairs.list.byToken(tokenIdBase), + queryFn: () => tokens.list.byPair(tokenIdBase), }) return { ...query, - pairsByToken: query.data, + tokensByPair: query.data, } } diff --git a/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.test.tsx b/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.test.tsx new file mode 100644 index 0000000000..f359bbd7eb --- /dev/null +++ b/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.test.tsx @@ -0,0 +1,51 @@ +import * as React from 'react' +import {QueryClient} from 'react-query' +import {Text, View} from 'react-native' +import {render, waitFor} from '@testing-library/react-native' + +import {queryClientFixture} from '../../../fixtures/query-client' +import {mockSwapManager, swapManagerMocks} from '../../../manager.mocks' +import {wrapperManagerFixture} from '../../../fixtures/manager-wrapper' +import {useSwapTokensOnlyVerified} from './useSwapTokensOnlyVerified' + +describe('useSwapTokensOnlyVerified', () => { + let queryClient: QueryClient + + beforeEach(() => { + jest.clearAllMocks() + queryClient = queryClientFixture() + }) + + afterEach(() => { + queryClient.clear() + }) + + it('success', async () => { + const TestListToken = () => { + const tokens = useSwapTokensOnlyVerified() + return ( + + {JSON.stringify(tokens.data)} + + ) + } + + mockSwapManager.tokens.list.onlyVerified = jest + .fn() + .mockResolvedValue(swapManagerMocks.listOnlyVerifiedTokensResponse) + const wrapper = wrapperManagerFixture({ + queryClient, + swapManager: mockSwapManager, + }) + const {getByTestId} = render(, {wrapper}) + + await waitFor(() => { + expect(getByTestId('tokens')).toBeDefined() + }) + + expect(getByTestId('tokens').props.children).toEqual( + JSON.stringify(swapManagerMocks.listOnlyVerifiedTokensResponse), + ) + expect(mockSwapManager.tokens.list.onlyVerified).toHaveBeenCalled() + }) +}) diff --git a/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.tsx b/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.tsx new file mode 100644 index 0000000000..3deced4d91 --- /dev/null +++ b/packages/swap/src/translators/reactjs/hooks/useSwapTokensOnlyVerified.tsx @@ -0,0 +1,27 @@ +import {Balance} from '@yoroi/types' +import {UseQueryOptions, useQuery} from 'react-query' + +import {useSwap} from './useSwap' + +export const useSwapTokensOnlyVerified = ( + options?: UseQueryOptions< + Balance.TokenInfo[], + Error, + Balance.TokenInfo[], + ['useSwapTokensOnlyVerified'] + >, +) => { + const {tokens} = useSwap() + + const query = useQuery({ + suspense: true, + queryKey: ['useSwapTokensOnlyVerified'], + ...options, + queryFn: () => tokens.list.onlyVerified(), + }) + + return { + ...query, + onlyVerifiedTokens: query.data, + } +} diff --git a/packages/types/src/swap/api.ts b/packages/types/src/swap/api.ts index 6c60cb71e2..1534c0a2c9 100644 --- a/packages/types/src/swap/api.ts +++ b/packages/types/src/swap/api.ts @@ -18,7 +18,10 @@ export interface SwapApi { tokenB: BalanceToken['info']['id'] providers?: ReadonlyArray }): Promise - getTokens(tokenIdBase: BalanceToken['info']['id']): Promise + getTokenPairs( + tokenIdBase: BalanceToken['info']['id'], + ): Promise + getTokens(): Promise getPrice(args: { baseToken: BalanceToken['info']['id'] quoteToken: BalanceToken['info']['id'] diff --git a/packages/types/src/swap/manager.ts b/packages/types/src/swap/manager.ts index e47e6f53e7..2078f00c0c 100644 --- a/packages/types/src/swap/manager.ts +++ b/packages/types/src/swap/manager.ts @@ -16,9 +16,10 @@ export type SwapManager = Readonly<{ byStatusCompleted: SwapApi['getCompletedOrders'] } } - pairs: { + tokens: { list: { - byToken: SwapApi['getTokens'] + onlyVerified: SwapApi['getTokens'] + byPair: SwapApi['getTokenPairs'] } } price: {