Skip to content

Commit

Permalink
fix: resolved switch swap (#11194)
Browse files Browse the repository at this point in the history
<!-- start pr-codex -->

## PR-Codex overview
This PR focuses on enhancing error handling, improving loading states,
and refining the logic for currency handling in the swap functionality.
It also updates the token sorting mechanism for better accuracy and
consistency.

### Detailed summary
- Improved error handling for `poolAddress` fetching.
- Increased retry attempts from 1 to 10 in `poolDataQueriesAtom`.
- Modified `sortsBefore` method in `AgnosticToken` for better address
comparison.
- Updated `PRESET_TOKENS` to use `TonChainId` instead of `TonNetworks`.
- Enhanced `useTradeExactIn` and `useTradeExactOut` hooks to return
loading states.
- Updated `SwapForm` to reflect loading states for trade operations.
- Refined `useAllCommonPairs` to return loading states and handle empty
data gracefully.

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your
question}`

<!-- end pr-codex -->
  • Loading branch information
chef-eric authored Feb 12, 2025
1 parent bfac63a commit 7488fe0
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 59 deletions.
6 changes: 2 additions & 4 deletions apps/ton/src/atoms/swap/swapStateAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ export const independentFieldAtom = atom(Field.INPUT)
export const typedValueAtom = atom('')

export const inputCurrencyAtom = atom((get) => {
const independentField = get(independentFieldAtom)
return get(currencyFamily(independentField))
return get(currencyFamily(Field.INPUT))
})

export const outputCurrencyAtom = atom((get) => {
const independentField = get(independentFieldAtom)
return get(currencyFamily(independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT))
return get(currencyFamily(Field.OUTPUT))
})
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useTranslation } from '@pancakeswap/localization'
import { Pair } from '@pancakeswap/sdk'
import { Currency } from '@pancakeswap/ton-v2-sdk'
import { Currency, Pair } from '@pancakeswap/ton-v2-sdk'
import {
Box,
Button,
Expand Down Expand Up @@ -239,7 +238,7 @@ const CurrencyInputPanelSimplify = memo(function CurrencyInputPanel({
const { isMobile } = useMatchBreakpoints()

const mode = id
const token = pair ? pair.liquidityToken : currency?.isToken ? currency : null
// const token = pair ? pair.liquidityToken : currency?.isToken ? currency : null

const [isInputFocus, setIsInputFocus] = useState(false)

Expand Down
10 changes: 5 additions & 5 deletions apps/ton/src/config/constants/tokens.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Token, TonChainId, TonNetworks } from '@pancakeswap/ton-v2-sdk'
import { Token, TonChainId } from '@pancakeswap/ton-v2-sdk'

// Common tokens
export const PRESET_TOKENS = {
CAKE: {
[TonNetworks.Mainnet]: new Token(
[TonChainId.Mainnet]: new Token(
TonChainId.Mainnet,
'kQA8oT-HRBY-9-yFymg17hD5FE07--Z1gYc_sZTbzqpOZr1t', // TODO: Add CAKE mainnet address
9,
'CAKE',
'Pancake Token',
'https://tokens.pancakeswap.finance/images/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82.png',
),
[TonNetworks.Testnet]: new Token(
[TonChainId.Testnet]: new Token(
TonChainId.Testnet,
'kQA8oT-HRBY-9-yFymg17hD5FE07--Z1gYc_sZTbzqpOZr1t',
9,
Expand All @@ -21,15 +21,15 @@ export const PRESET_TOKENS = {
),
},
CAKE2: {
[TonNetworks.Mainnet]: new Token(
[TonChainId.Mainnet]: new Token(
TonChainId.Mainnet,
'kQAaZeQEAnKF7BKNIzgMIFdvRfBNZnbZUICo7ZkNPaRsjMLr', // TODO: Remove CAKE2
9,
'CAKE2',
'Pancake Token 2',
'https://tokens.pancakeswap.finance/images/0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82.png',
),
[TonNetworks.Testnet]: new Token(
[TonChainId.Testnet]: new Token(
TonChainId.Testnet,
'kQAaZeQEAnKF7BKNIzgMIFdvRfBNZnbZUICo7ZkNPaRsjMLr',
9,
Expand Down
72 changes: 49 additions & 23 deletions apps/ton/src/hooks/swap/useAllCommonPairs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ADDITIONAL_BASES, BASES_TO_CHECK_TRADES_AGAINST, CUSTOM_BASES } from 'c
import { useAtomValue } from 'jotai'
import { poolDataQueriesAtom } from 'ton/atom/liquidity/poolDataQueriesAtom'

export function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): Pair[] {
export function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): { isLoading: boolean; data: Pair[] } {
const chainId = currencyA?.chainId

const [tokenA, tokenB] = chainId ? [currencyA?.wrapped, currencyB?.wrapped] : [undefined, undefined]
Expand Down Expand Up @@ -59,45 +59,71 @@ export function useAllCommonPairs(currencyA?: Currency, currencyB?: Currency): P
[tokenA, tokenB, bases, basePairs, chainId],
)

const allPairs = usePairs(allPairCombinations)
const { data: allPairs, isLoading } = usePairs(allPairCombinations)

// only pass along valid pairs, non-duplicated pairs
return useMemo(
() =>
uniqBy(
allPairs.filter((result): result is NonNullable<typeof result> => Boolean(result)),
(p) => p.poolAddress,
),
[allPairs],
isLoading
? {
isLoading,
data: [],
}
: {
isLoading,
data: allPairs
? uniqBy(
allPairs.filter((result): result is NonNullable<typeof result> => Boolean(result)),
(p) => p.poolAddress,
)
: [],
},
[allPairs, isLoading],
)
}

const usePairs = (pairs: [Token, Token][]) => {
const pairsAddress = useMemo(
() =>
pairs.map(([token0, token1]) => ({
token0Address: token0.wrapped.address,
token1Address: token1.wrapped.address,
})),
pairs.map(([token0, token1]) =>
token0.sortsBefore(token1)
? {
token0Address: token0.wrapped.address,
token1Address: token1.wrapped.address,
}
: {
token0Address: token1.wrapped.address,
token1Address: token0.wrapped.address,
},
),
[pairs],
)
const result = useAtomValue(poolDataQueriesAtom(pairsAddress))
return useMemo(() => {
if (result.isLoading) {
return []
return {
isLoading: result.isLoading,
data: result.data,
}
}
return pairs.map(([token0, token1], idx) => {
const data = pairs.map(([token0_, token1_], idx) => {
const pool = result.data?.[idx]
return pool
? {
...pool,
chainId: token0.chainId,
token0,
token1,
reserve0: CurrencyAmount.fromRawAmount(token0, pool.reserve0),
reserve1: CurrencyAmount.fromRawAmount(token1, pool.reserve1),
}
: null
if (!pool) {
return null
}
const [token0, token1] = token0_.sortsBefore(token1_) ? [token0_, token1_] : [token1_, token0_]
return {
...pool,
chainId: token0.chainId,
token0,
token1,
reserve0: CurrencyAmount.fromRawAmount(token0, pool.reserve0),
reserve1: CurrencyAmount.fromRawAmount(token1, pool.reserve1),
}
})
return {
isLoading: false,
data,
}
}, [pairs, result.data, result.isLoading])
}
27 changes: 19 additions & 8 deletions apps/ton/src/hooks/swap/useTradeExactIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@ import { useAllCommonPairs } from './useAllCommonPairs'
export function useTradeExactIn(
currencyAmountIn?: CurrencyAmount<Currency>,
currencyOut?: Currency,
): Trade<Currency, Currency, TradeType> | null {
const allowedPairs = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)
): { isLoading: boolean; data: Trade<Currency, Currency, TradeType> | null } {
const { data: allowedPairs, isLoading } = useAllCommonPairs(currencyAmountIn?.currency, currencyOut)

const [singleHopOnly] = useUserSingleHopOnly()

return useMemo(() => {
if (isLoading) {
return {
isLoading,
data: null,
}
}
if (currencyAmountIn && currencyOut && allowedPairs.length > 0) {
if (singleHopOnly) {
return (
bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 1, maxNumResults: 1 })[0] ?? null
)
return {
isLoading: false,
data:
bestTradeExactIn(allowedPairs, currencyAmountIn, currencyOut, { maxHops: 1, maxNumResults: 1 })[0] ?? null,
}
}
// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade<Currency, Currency, TradeType> | null = null
Expand All @@ -34,9 +42,12 @@ export function useTradeExactIn(
bestTradeSoFar = currentTrade
}
}
return bestTradeSoFar
return { isLoading: false, data: bestTradeSoFar }
}

return null
}, [allowedPairs, currencyAmountIn, currencyOut, singleHopOnly])
return {
isLoading: false,
data: null,
}
}, [allowedPairs, currencyAmountIn, currencyOut, singleHopOnly, isLoading])
}
30 changes: 22 additions & 8 deletions apps/ton/src/hooks/swap/useTradeExactOut.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@ import { useAllCommonPairs } from './useAllCommonPairs'
export function useTradeExactOut(
currencyAmountOut?: CurrencyAmount<Currency>,
currencyIn?: Currency,
): Trade<Currency, Currency, TradeType> | null {
const allowedPairs = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)
): { isLoading: boolean; data: Trade<Currency, Currency, TradeType> | null } {
const { data: allowedPairs, isLoading } = useAllCommonPairs(currencyIn, currencyAmountOut?.currency)

const [singleHopOnly] = useUserSingleHopOnly()

return useMemo(() => {
if (isLoading) {
return {
isLoading,
data: null,
}
}
if (currencyIn && currencyAmountOut && allowedPairs.length > 0) {
if (singleHopOnly) {
return (
bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 1, maxNumResults: 1 })[0] ?? null
)
return {
isLoading: false,
data:
bestTradeExactOut(allowedPairs, currencyIn, currencyAmountOut, { maxHops: 1, maxNumResults: 1 })[0] ?? null,
}
}
// search through trades with varying hops, find best trade out of them
let bestTradeSoFar: Trade<Currency, Currency, TradeType> | null = null
Expand All @@ -33,8 +41,14 @@ export function useTradeExactOut(
bestTradeSoFar = currentTrade
}
}
return bestTradeSoFar
return {
isLoading: false,
data: bestTradeSoFar,
}
}
return {
isLoading: false,
data: null,
}
return null
}, [currencyIn, currencyAmountOut, allowedPairs, singleHopOnly])
}, [currencyIn, currencyAmountOut, allowedPairs, singleHopOnly, isLoading])
}
6 changes: 4 additions & 2 deletions apps/ton/src/ton/atom/liquidity/poolDataQueriesAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const poolDataQueriesAtom = atomFamily((pairs: PoolDataAtomParams[]) => {
pairs.map(async ({ token0Address, token1Address }) => {
const poolAddress = await get(poolAddressAtom({ token0Address, token1Address }))

if (!poolAddress) return null
if (!poolAddress) {
throw new Error('fetch poolAddress failed')
}

const poolData = await get(poolContractAtom(poolAddress)).getGetPoolData()
return {
Expand All @@ -33,6 +35,6 @@ export const poolDataQueriesAtom = atomFamily((pairs: PoolDataAtomParams[]) => {
},
enabled: !!key.length,
refetchInterval: QUERY_DEFAULT_STALE_TIME,
retry: 1,
retry: 10,
}))
}, isEqual)
16 changes: 11 additions & 5 deletions apps/ton/src/views/TONSwap/SwapForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ export const SwapForm = () => {

const isExactIn: boolean = independentField === Field.INPUT
const parsedAmount = tryParseAmount(typedValue, (isExactIn ? inputCurrency : outputCurrency) ?? undefined)
const bestTradeExactIn = useTradeExactIn(isExactIn ? parsedAmount : undefined, outputCurrency ?? undefined)
const bestTradeExactOut = useTradeExactOut(isExactIn ? undefined : parsedAmount, inputCurrency ?? undefined)
const { isLoading: isTradeExactInLoading, data: bestTradeExactIn } = useTradeExactIn(
isExactIn ? parsedAmount : undefined,
outputCurrency ?? undefined,
)
const { isLoading: isTradeExactOutLoading, data: bestTradeExactOut } = useTradeExactOut(
isExactIn ? undefined : parsedAmount,
inputCurrency ?? undefined,
)
const trade = isExactIn ? bestTradeExactIn : bestTradeExactOut

const { onUserInput, onCurrencySelection } = useSwapActionHandlers()
Expand Down Expand Up @@ -119,7 +125,7 @@ export const SwapForm = () => {
showUSDPrice
showMaxButton
showCommonBases
inputLoading={false}
inputLoading={isTradeExactOutLoading}
currencyLoading={false}
value={formattedAmounts[Field.INPUT]}
showQuickInputButton
Expand All @@ -144,12 +150,12 @@ export const SwapForm = () => {
showUSDPrice
showMaxButton
showCommonBases
inputLoading={false}
inputLoading={isTradeExactInLoading}
currencyLoading={false}
value={formattedAmounts[Field.OUTPUT]}
showQuickInputButton
currency={outputCurrency}
onUserInput={noop}
onUserInput={(val) => onUserInput(Field.OUTPUT, val)}
onPercentInput={noop}
onMax={noop}
onCurrencySelect={(currency) => onCurrencySelection(Field.OUTPUT, currency)}
Expand Down
3 changes: 2 additions & 1 deletion packages/ton-v2-sdk/src/constants/currency/AgnosticToken.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import invariant from 'tiny-invariant'
import { Address } from '@ton/core'

import { AgnosticBaseCurrency } from './AgnosticBaseCurrency'

Expand Down Expand Up @@ -60,7 +61,7 @@ export class AgnosticToken extends AgnosticBaseCurrency {
public sortsBefore(other: AgnosticToken): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
invariant(this.address !== other.address, 'ADDRESSES')
return this.address.toLowerCase() < other.address.toLowerCase()
return other.isNative ? false : Address.parse(this.address).hash < Address.parse(other.address).hash
}

/**
Expand Down
12 changes: 12 additions & 0 deletions packages/ton-v2-sdk/src/constants/currency/Native.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import invariant from 'tiny-invariant'
import { AgnosticBaseCurrency } from './AgnosticBaseCurrency'
import { Token } from './Token'
import { NATIVE, WNATIVE } from '../nativeTokens'
Expand Down Expand Up @@ -56,4 +57,15 @@ export class Native extends AgnosticBaseCurrency {
public equals(other?: AgnosticBaseCurrency): boolean {
return !!other && other.isNative && other.chainId === this.chainId
}

/**
* Returns true if the address of this token sorts before the address of the other token
* @param other other token to compare
* @throws if the tokens have the same address
* @throws if the tokens are on different chains
*/
public sortsBefore(other: AgnosticBaseCurrency): boolean {
invariant(this.chainId === other.chainId, 'CHAIN_IDS')
return true
}
}

0 comments on commit 7488fe0

Please sign in to comment.