Skip to content

Commit 25c1847

Browse files
committed
feat: reworked swap functionality to improve reliability
1 parent 0b68096 commit 25c1847

File tree

5 files changed

+90
-79
lines changed

5 files changed

+90
-79
lines changed

packages/wallet-widget/src/components/SequenceWalletProvider/ProviderComponents/SwapProvider.tsx

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { SwapQuote } from '@0xsequence/api'
2-
import { sendTransactions } from '@0xsequence/connect'
2+
import { getNativeTokenInfoByChainId, sendTransactions } from '@0xsequence/connect'
33
import { compareAddress, useToast } from '@0xsequence/design-system'
44
import { useAPIClient, useIndexerClient } from '@0xsequence/hooks'
55
import { ReactNode, useEffect, useState } from 'react'
6-
import { Hex, zeroAddress } from 'viem'
7-
import { useAccount, useChainId, usePublicClient, useWalletClient } from 'wagmi'
6+
import { formatUnits, Hex, zeroAddress } from 'viem'
7+
import { useAccount, useChainId, useChains, usePublicClient, useWalletClient } from 'wagmi'
88

99
import { SwapContextProvider } from '../../../contexts/Swap'
1010
import { useNavigation } from '../../../hooks/useNavigation'
@@ -16,13 +16,12 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
1616
const { setNavigation } = useNavigation()
1717
const apiClient = useAPIClient()
1818
const connectedChainId = useChainId()
19-
19+
const chains = useChains()
2020
const [fromCoin, _setFromCoin] = useState<TokenBalanceWithPrice>()
21-
const [fromAmount, setFromAmount] = useState<number>(0)
2221
const [toCoin, _setToCoin] = useState<TokenBalanceWithPrice>()
23-
const [toAmount, setToAmount] = useState<number>(0)
22+
const [amount, _setAmount] = useState<number>(0)
23+
const [nonRecentAmount, setNonRecentAmount] = useState<number>(0)
2424
const [recentInput, setRecentInput] = useState<'from' | 'to'>('from')
25-
const [setNonRecentAmount, _setNonRecentAmount] = useState<() => void>(() => 0)
2625

2726
const [isSwapReady, setIsSwapReady] = useState(false)
2827
const [swapQuoteData, setSwapQuoteData] = useState<SwapQuote>()
@@ -39,10 +38,9 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
3938

4039
const resetSwapStates = () => {
4140
setFromCoin(undefined)
42-
setFromAmount(0)
4341
setToCoin(undefined)
44-
setToAmount(0)
45-
setRecentInput('from')
42+
setAmount(0, 'from')
43+
setNonRecentAmount(0)
4644
setIsSwapReady(false)
4745
setSwapQuoteData(undefined)
4846
setIsSwapQuotePending(false)
@@ -55,23 +53,15 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
5553
resetSwapStates()
5654
}, [userAddress, connectedChainId])
5755

58-
useEffect(() => {
59-
if (recentInput === 'from') {
60-
_setNonRecentAmount(() => setFromAmount)
61-
} else {
62-
_setNonRecentAmount(() => setToAmount)
63-
}
64-
}, [recentInput])
65-
6656
useEffect(() => {
6757
setIsSwapReady(false)
6858
setSwapQuoteData(undefined)
6959
setIsErrorSwapQuote(false)
70-
}, [fromCoin, toCoin, fromAmount, toAmount])
60+
}, [fromCoin, toCoin, amount])
7161

7262
useEffect(() => {
7363
const fetchSwapQuote = async () => {
74-
if (!fromCoin || !toCoin || (fromAmount === 0 && toAmount === 0)) {
64+
if (!fromCoin || !toCoin || amount === 0) {
7565
return
7666
}
7767

@@ -94,7 +84,7 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
9484
userAddress: String(userAddress),
9585
buyCurrencyAddress: toCoin.contractAddress,
9686
sellCurrencyAddress: fromCoin.contractAddress,
97-
buyAmount: String(toAmount),
87+
buyAmount: String(amount),
9888
chainId: connectedChainId,
9989
includeApprove: true
10090
})
@@ -103,11 +93,7 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
10393

10494
// TODO: change this to "amount" from return
10595

106-
if (recentInput === 'from') {
107-
setToAmount(Number(transactionValue))
108-
} else {
109-
setFromAmount(Number(transactionValue))
110-
}
96+
setNonRecentAmount(Number(transactionValue))
11197

11298
setSwapQuoteData(swapQuote?.swapQuote)
11399
setIsSwapReady(true)
@@ -120,7 +106,7 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
120106
}
121107

122108
fetchSwapQuote()
123-
}, [fromCoin, toCoin, fromAmount, toAmount])
109+
}, [fromCoin, toCoin, amount])
124110

125111
const setFromCoin = (coin: TokenBalanceWithPrice | undefined) => {
126112
if (coin?.chainId === toCoin?.chainId && coin?.contractAddress === toCoin?.contractAddress) {
@@ -138,20 +124,28 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
138124
}
139125
}
140126

127+
const setAmount = (newAmount: number, type: 'from' | 'to') => {
128+
if (type === recentInput) {
129+
_setAmount(newAmount)
130+
} else {
131+
const tempAmount = amount
132+
setRecentInput(recentInput === 'from' ? 'to' : 'from')
133+
_setAmount(newAmount)
134+
setNonRecentAmount(tempAmount)
135+
}
136+
}
137+
141138
const switchCoinOrder = () => {
142139
const tempFrom = fromCoin
143140
const tempTo = toCoin
144-
const tempFromAmount = fromAmount
145-
const tempToAmount = toAmount
146141
_setFromCoin(tempTo)
147142
_setToCoin(tempFrom)
148-
setFromAmount(tempToAmount)
149-
setToAmount(tempFromAmount)
143+
setRecentInput(recentInput === 'from' ? 'to' : 'from')
150144
}
151145

152146
const onSubmitSwap = async () => {
153-
if (isErrorSwapQuote || !userAddress || !publicClient || !walletClient || !connector) {
154-
console.error('Please ensure validation before submitting')
147+
if (isErrorSwapQuote || !userAddress || !publicClient || !walletClient || !connector || !fromCoin || !toCoin || !amount) {
148+
console.error('Please ensure validation before allowing users to submit a swap')
155149
return
156150
}
157151

@@ -197,6 +191,21 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
197191
await walletClient.switchChain({ id: connectedChainId })
198192
}
199193

194+
const isFromCoinNative = fromCoin.contractType === 'NATIVE'
195+
const isToCoinNative = toCoin.contractType === 'NATIVE'
196+
const fromCoinNativeInfo = getNativeTokenInfoByChainId(fromCoin.chainId, chains)
197+
const toCoinNativeInfo = getNativeTokenInfoByChainId(toCoin.chainId, chains)
198+
const toastFromCoinName = isFromCoinNative ? fromCoinNativeInfo.symbol : fromCoin.contractInfo?.symbol
199+
const toastToCoinName = isToCoinNative ? toCoinNativeInfo.symbol : toCoin.contractInfo?.symbol
200+
const toastFromAmount = formatUnits(
201+
BigInt(recentInput === 'from' ? amount : nonRecentAmount),
202+
(isFromCoinNative ? fromCoinNativeInfo.decimals : fromCoin.contractInfo?.decimals) || 18
203+
)
204+
const toastToAmount = formatUnits(
205+
BigInt(recentInput === 'from' ? nonRecentAmount : amount),
206+
(isToCoinNative ? toCoinNativeInfo.decimals : toCoin.contractInfo?.decimals) || 18
207+
)
208+
200209
await sendTransactions({
201210
connector,
202211
walletClient,
@@ -209,7 +218,7 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
209218

210219
toast({
211220
title: 'Transaction sent',
212-
description: `Successfully swapped ${fromAmount} ${fromCoin?.contractInfo?.name} for ${toAmount} ${toCoin?.contractInfo?.name}`,
221+
description: `Successfully swapped ${toastFromAmount} ${toastFromCoinName} for ${toastToAmount} ${toastToCoinName}`,
213222
variant: 'success'
214223
})
215224

@@ -228,9 +237,9 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
228237
<SwapContextProvider
229238
value={{
230239
fromCoin,
231-
fromAmount,
232240
toCoin,
233-
toAmount,
241+
amount,
242+
nonRecentAmount,
234243
recentInput,
235244
isSwapReady,
236245
isSwapQuotePending,
@@ -239,11 +248,8 @@ export const SwapProvider = ({ children }: { children: ReactNode }) => {
239248
isTxnPending,
240249
isErrorTxn,
241250
setFromCoin,
242-
setFromAmount,
243251
setToCoin,
244-
setToAmount,
245-
setRecentInput,
246-
setNonRecentAmount,
252+
setAmount,
247253
switchCoinOrder,
248254
onSubmitSwap,
249255
resetSwapStates

packages/wallet-widget/src/contexts/Swap.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { createGenericContext } from './genericContext'
55
export interface SwapContext {
66
fromCoin: TokenBalanceWithPrice | undefined
77
toCoin: TokenBalanceWithPrice | undefined
8-
fromAmount: number
9-
toAmount: number
8+
amount: number
9+
nonRecentAmount: number
1010
recentInput: 'from' | 'to'
1111
isSwapReady: boolean
1212
isSwapQuotePending: boolean
@@ -16,10 +16,7 @@ export interface SwapContext {
1616
isErrorTxn: boolean
1717
setFromCoin: (coin: TokenBalanceWithPrice | undefined) => void
1818
setToCoin: (coin: TokenBalanceWithPrice | undefined) => void
19-
setFromAmount: (amount: number) => void
20-
setToAmount: (amount: number) => void
21-
setRecentInput: (input: 'from' | 'to') => void
22-
setNonRecentAmount: (amount: number) => void
19+
setAmount: (amount: number, type: 'from' | 'to') => void
2320
switchCoinOrder: () => void
2421
onSubmitSwap: () => void
2522
resetSwapStates: () => void

packages/wallet-widget/src/hooks/useSwap.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ export const useSwap = () => {
44
const {
55
fromCoin,
66
toCoin,
7-
fromAmount,
8-
toAmount,
7+
amount,
8+
nonRecentAmount,
99
recentInput,
1010
isSwapReady,
1111
isSwapQuotePending,
@@ -15,10 +15,7 @@ export const useSwap = () => {
1515
isErrorTxn,
1616
setFromCoin,
1717
setToCoin,
18-
setFromAmount,
19-
setToAmount,
20-
setRecentInput,
21-
setNonRecentAmount,
18+
setAmount,
2219
switchCoinOrder,
2320
onSubmitSwap,
2421
resetSwapStates
@@ -27,8 +24,8 @@ export const useSwap = () => {
2724
return {
2825
fromCoin,
2926
toCoin,
30-
fromAmount,
31-
toAmount,
27+
amount,
28+
nonRecentAmount,
3229
recentInput,
3330
isSwapReady,
3431
isSwapQuotePending,
@@ -38,10 +35,7 @@ export const useSwap = () => {
3835
isErrorTxn,
3936
setFromCoin,
4037
setToCoin,
41-
setFromAmount,
42-
setToAmount,
43-
setRecentInput,
44-
setNonRecentAmount,
38+
setAmount,
4539
switchCoinOrder,
4640
onSubmitSwap,
4741
resetSwapStates

packages/wallet-widget/src/utils/formatBalance.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { formatDisplay } from '@0xsequence/connect'
22
import { getNativeTokenInfoByChainId } from '@0xsequence/connect'
33
import { compareAddress } from '@0xsequence/design-system'
4+
import { TokenBalance } from '@0xsequence/indexer'
45
import { Chain, formatUnits } from 'viem'
56
import { zeroAddress } from 'viem'
67

78
import { TokenBalanceWithPrice } from './tokens'
89

10+
//TODO: rename these and maybe do a refactor
11+
912
export const formatTokenInfo = (
1013
balance: TokenBalanceWithPrice | undefined,
1114
fiatSign: string,
@@ -48,6 +51,20 @@ export const formatFiatBalance = (balance: number, price: number, decimals: numb
4851
return `${fiatSign}${(price * Number(bal)).toFixed(2)}`
4952
}
5053

54+
export const formatTokenUnits = (token: TokenBalance | undefined, chains: readonly [Chain, ...Chain[]]) => {
55+
if (!token) {
56+
return ''
57+
}
58+
59+
const isNativeToken = token.contractType === 'NATIVE'
60+
const nativeTokenInfo = getNativeTokenInfoByChainId(token.chainId, chains)
61+
62+
if (isNativeToken) {
63+
return formatUnits(BigInt(Number(token.balance)), nativeTokenInfo.decimals)
64+
}
65+
return formatUnits(BigInt(Number(token.balance)), token.contractInfo?.decimals || 18)
66+
}
67+
5168
export const decimalsToWei = (balance: number, decimals: number) => {
5269
const scaledBalance = balance * Math.pow(10, decimals)
5370

packages/wallet-widget/src/views/Swap/CoinInput.tsx

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,48 +7,45 @@ import { useSwap } from '../../hooks/useSwap'
77
import { formatFiatBalance, decimalsToWei } from '../../utils/formatBalance'
88

99
export const CoinInput = ({ type, disabled }: { type: 'from' | 'to'; disabled?: boolean }) => {
10-
const { toCoin, fromCoin, toAmount, fromAmount, setToAmount, setFromAmount, setRecentInput } = useSwap()
10+
const { toCoin, fromCoin, amount, nonRecentAmount, recentInput, setAmount } = useSwap()
1111
const coin = type === 'from' ? fromCoin : toCoin
12-
const amount = type === 'from' ? fromAmount : toAmount
13-
const setAmount = type === 'from' ? setFromAmount : setToAmount
1412

1513
const { fiatCurrency } = useSettings()
1614

1715
const [inputValue, setInputValue] = useState<string>('')
1816

1917
const fiatBalance = formatFiatBalance(
20-
amount || 0,
18+
type === recentInput ? amount : nonRecentAmount,
2119
coin?.price.value || 0,
2220
coin?.contractInfo?.decimals || 18,
2321
fiatCurrency.sign
2422
)
2523

2624
useEffect(() => {
27-
const formattedAmount = formatUnits(BigInt(amount || 0), coin?.contractInfo?.decimals || 18)
28-
if (formattedAmount !== '0') {
29-
setInputValue(formattedAmount)
30-
} else if (Number(inputValue) > 0) {
31-
setInputValue('')
25+
if (type === recentInput) {
26+
if (amount > 0) {
27+
setInputValue(formatUnits(BigInt(amount), coin?.contractInfo?.decimals || 18))
28+
} else if (Number(inputValue)) {
29+
setInputValue('')
30+
}
31+
} else if (type !== recentInput) {
32+
if (nonRecentAmount > 0) {
33+
setInputValue(formatUnits(BigInt(nonRecentAmount), coin?.contractInfo?.decimals || 18))
34+
} else if (Number(inputValue)) {
35+
setInputValue('')
36+
}
3237
}
33-
}, [amount])
38+
}, [recentInput, amount, nonRecentAmount])
3439

3540
const handleChange = (ev: ChangeEvent<HTMLInputElement>) => {
3641
const { value } = ev.target
3742
const changedValue = Number(value)
38-
39-
if (type === 'from') {
40-
setFromAmount(decimalsToWei(changedValue, coin?.contractInfo?.decimals || 18))
41-
setRecentInput('from')
42-
} else {
43-
setToAmount(decimalsToWei(changedValue, coin?.contractInfo?.decimals || 18))
44-
setRecentInput('to')
45-
}
46-
4743
setInputValue(value)
44+
setAmount(decimalsToWei(changedValue, coin?.contractInfo?.decimals || 18), type)
4845
}
4946

5047
const handleMax = () => {
51-
setAmount(Number(formatUnits(BigInt(coin?.balance || 0), coin?.contractInfo?.decimals || 18)))
48+
setAmount(Number(formatUnits(BigInt(coin?.balance || 0), coin?.contractInfo?.decimals || 18)), type)
5249
}
5350

5451
return (
@@ -60,7 +57,7 @@ export const CoinInput = ({ type, disabled }: { type: 'from' | 'to'; disabled?:
6057
disabled={disabled}
6158
controls={
6259
<>
63-
{amount && Number(amount) > 0 && fiatBalance !== '' && (
60+
{fiatBalance && (
6461
<Text className="whitespace-nowrap" variant="small" color="muted">
6562
~{fiatBalance}
6663
</Text>

0 commit comments

Comments
 (0)