Skip to content

Commit

Permalink
refactor: replaced the token list source
Browse files Browse the repository at this point in the history
  • Loading branch information
stackchain committed Nov 1, 2023
1 parent 743f209 commit 573a422
Show file tree
Hide file tree
Showing 29 changed files with 610 additions and 187 deletions.
11 changes: 6 additions & 5 deletions apps/wallet-mobile/src/components/AmountItem/AmountItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down Expand Up @@ -63,13 +62,15 @@ export const AmountItem = ({isPrivacyOff, wallet, style, amount, inWallet, suppl
</Middle>

<Right>
{tokenInfo.kind !== 'nft' && (
{tokenInfo.kind !== 'nft' && variant !== 'swap' && (
<Text style={styles.quantity} testID="tokenAmountText">
{isPrivacyOff ? '**.*******' : variant === 'swap' ? `${supply}` : formattedQuantity}
{isPrivacyOff ? '**.*******' : formattedQuantity}
</Text>
)}

{isPrimary && <PairedBalance isPrivacyOff={isPrivacyOff} amount={{quantity, tokenId: tokenInfo.id}} />}
{isPrimary && variant !== 'swap' && (
<PairedBalance isPrivacyOff={isPrivacyOff} amount={{quantity, tokenId: tokenInfo.id}} />
)}
</Right>
</View>
)
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet-mobile/src/features/Swap/SwapNavigator.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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])
Expand Down
22 changes: 9 additions & 13 deletions apps/wallet-mobile/src/features/Swap/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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<Balance.Token> = React.useMemo(() => {
if (pairsByToken === undefined) return []
return pairsByToken
}, [pairsByToken])
const tokenInfos: Array<Balance.TokenInfo> = 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 (
<View style={styles.list}>
Expand All @@ -83,8 +83,6 @@ const TokenList = () => {

<View style={styles.labels}>
<Text style={styles.label}>{strings.asset}</Text>

<Text style={styles.label}>{strings.volume}</Text>
</View>

<Spacer height={16} />
Expand All @@ -95,13 +93,13 @@ const TokenList = () => {

<FlashList
data={filteredTokenList}
renderItem={({item: token}: {item: Balance.Token}) => (
renderItem={({item: tokenInfo}: {item: Balance.TokenInfo}) => (
<Boundary loading={{fallback: <AmountItemPlaceholder style={styles.item} />}}>
<SelectableToken token={token} wallet={wallet} walletTokenIds={walletTokenIds} />
<SelectableToken tokenInfo={tokenInfo} wallet={wallet} walletTokenIds={walletTokenIds} />
</Boundary>
)}
bounces={false}
keyExtractor={({info: {id, name}}) => `${name}-${id}`}
keyExtractor={({id, name}) => `${name}-${id}`}
testID="assetsList"
estimatedItemSize={72}
ListEmptyComponent={<EmptyList filteredTokensForList={filteredTokenList} />}
Expand Down Expand Up @@ -130,10 +128,11 @@ const TokenList = () => {
type SelectableTokenProps = {
wallet: YoroiWallet
walletTokenIds: Array<string>
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 {
Expand All @@ -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()
Expand All @@ -168,18 +166,17 @@ const SelectableToken = ({wallet, token, walletTokenIds}: SelectableTokenProps)
disabled={isDisabled}
>
<AmountItem
amount={{tokenId: token.info.id, quantity: balanceAvailable}}
amount={{tokenId: id, quantity: balanceAvailable}}
wallet={wallet}
status={token.status}
status="verified"
inWallet={inUserWallet}
supply={supplyFormatted}
variant="swap"
/>
</TouchableOpacity>
)
}

const EmptyList = ({filteredTokensForList}: {filteredTokensForList: Array<Balance.Token>}) => {
const EmptyList = ({filteredTokensForList}: {filteredTokensForList: Array<Balance.TokenInfo>}) => {
const {search: assetSearchTerm, visible: isSearching} = useSearch()

if (isSearching && assetSearchTerm.length > 0 && filteredTokensForList.length === 0)
Expand Down
7 changes: 6 additions & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"npmClient": "yarn",
"version": "independent"
"version": "independent",
"command": {
"run": {
"ignore": ["e2e/*"]
}
}
}
11 changes: 8 additions & 3 deletions packages/openswap/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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})
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/openswap/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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:
Expand Down
6 changes: 4 additions & 2 deletions packages/openswap/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
79 changes: 79 additions & 0 deletions packages/openswap/src/token-pairs.spec.ts
Original file line number Diff line number Diff line change
@@ -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<typeof axiosClient>
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<typeof axiosClient>

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<typeof axiosClient>
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: [],
},
},
]
25 changes: 25 additions & 0 deletions packages/openswap/src/token-pairs.ts
Original file line number Diff line number Diff line change
@@ -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<TokenPairsResponse> {
const {network, client} = deps
if (network === 'preprod') return []

const apiUrl = SWAP_API_ENDPOINTS[network].getTokenPairs
const response = await client.get<TokenPairsResponse>('', {
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
}
Loading

0 comments on commit 573a422

Please sign in to comment.