|
1 | 1 | import { Panel } from "@namada/components"; |
2 | 2 | import { AccountType } from "@namada/types"; |
3 | | -import { MaspSyncCover } from "App/Common/MaspSyncCover"; |
4 | | -import { params } from "App/routes"; |
5 | | -import { TransferModule } from "App/Transfer/TransferModule"; |
| 3 | +import { SwapModule } from "App/Transfer/SwapModule"; |
6 | 4 | import { allDefaultAccountsAtom } from "atoms/accounts"; |
| 5 | +import { namadaShieldedAssetsAtom } from "atoms/balance"; |
7 | 6 | import { |
8 | | - lastCompletedShieldedSyncAtom, |
9 | | - namadaShieldedAssetsAtom, |
10 | | -} from "atoms/balance"; |
11 | | -import { chainParametersAtom } from "atoms/chain"; |
12 | | -import { namadaChainRegistryAtom } from "atoms/integrations"; |
13 | | -import { ledgerStatusDataAtom } from "atoms/ledger"; |
14 | | -import { rpcUrlAtom } from "atoms/settings"; |
| 7 | + getChainRegistryByChainId, |
| 8 | + ibcChannelsFamily, |
| 9 | + namadaRegistryChainAssetsMapAtom, |
| 10 | +} from "atoms/integrations"; |
| 11 | +import { SwapResponse, SwapResponseError, SwapResponseOk } from "atoms/swaps"; |
| 12 | +import { createOsmosisSwapTxAtom } from "atoms/transfer/atoms"; |
15 | 13 | import BigNumber from "bignumber.js"; |
16 | | -import { useRequiresNewShieldedSync } from "hooks/useRequiresNewShieldedSync"; |
17 | | -import { useTransactionActions } from "hooks/useTransactionActions"; |
18 | | -import { useTransfer } from "hooks/useTransfer"; |
19 | | -import { useUrlState } from "hooks/useUrlState"; |
20 | | -import { wallets } from "integrations"; |
21 | | -import { useAtom, useAtomValue } from "jotai"; |
22 | | -import { useState } from "react"; |
| 14 | +import { useTransactionFee } from "hooks"; |
| 15 | +import invariant from "invariant"; |
| 16 | +import { useAtomValue } from "jotai"; |
| 17 | +import { useEffect, useState } from "react"; |
| 18 | +import { Asset, NamadaAssetWithAmount } from "types"; |
23 | 19 |
|
24 | 20 | const SLIPPAGE = 0.005; |
25 | 21 | const SWAP_CONTRACT_ADDRESS = |
26 | 22 | "osmo14q5zmg3fp774kpz2j8c52q7gqjn0dnm3vcj3guqpj4p9xylqpc7s2ezh0h"; |
27 | 23 |
|
28 | 24 | export const OsmosisSwap: React.FC = () => { |
29 | | - //const { mutateAsync: performOsmosisSwap } = useAtomValue( |
30 | | - // createOsmosisSwapTxAtom |
31 | | - //); |
32 | | - //const { data: availableAssets, isLoading: _isLoadingAssets } = useAtomValue( |
33 | | - // namadaShieldedAssetsAtom |
34 | | - //); |
| 25 | + const { mutateAsync: performOsmosisSwap } = useAtomValue( |
| 26 | + createOsmosisSwapTxAtom |
| 27 | + ); |
| 28 | + const { data: availableAssets, isLoading: _isLoadingAssets } = useAtomValue( |
| 29 | + namadaShieldedAssetsAtom |
| 30 | + ); |
35 | 31 |
|
36 | | - //const chainAssetsMapAtom = useAtomValue(namadaRegistryChainAssetsMapAtom); |
37 | | - //const namadaAssets = |
38 | | - // chainAssetsMapAtom.isSuccess ? Object.values(chainAssetsMapAtom.data) : []; |
| 32 | + const chainAssetsMapAtom = useAtomValue(namadaRegistryChainAssetsMapAtom); |
| 33 | + const namadaAssets = |
| 34 | + chainAssetsMapAtom.isSuccess ? Object.values(chainAssetsMapAtom.data) : []; |
39 | 35 |
|
40 | | - //const osmosisAssets = |
41 | | - // getChainRegistryByChainId("osmosis-1")?.assets.assets || []; |
| 36 | + const osmosisAssets = |
| 37 | + getChainRegistryByChainId("osmosis-1")?.assets.assets || []; |
42 | 38 |
|
43 | | - //const [from, setFrom] = useState<NamadaAssetWithAmount | undefined>(); |
44 | | - //const [to, setTo] = useState<Asset | undefined>(); |
45 | | - //const [amount, setAmount] = useState<string>(""); |
46 | | - //const [recipient, setRecipient] = useState<string>( |
47 | | - // "znam17drxewzvge966gzcl0u6tr4j90traepujm2vd8ptwwkgrftnhs2hdtnyzgl5freyjsdnchn4ddy" |
48 | | - //); |
49 | | - //const [localRecoveryAddr, setLocalRecoveryAddress] = useState<string>( |
50 | | - // "osmo18st0wqx84av8y6xdlss9d6m2nepyqwj6n3q7js" |
51 | | - //); |
52 | | - //const [quote, setQuote] = useState< |
53 | | - // (SwapResponseOk & { minAmount: string }) | null |
54 | | - //>(null); |
| 39 | + const [from, setFrom] = useState<NamadaAssetWithAmount | undefined>(); |
| 40 | + const [to, setTo] = useState<Asset | undefined>(); |
| 41 | + const [amount, setAmount] = useState<string>(""); |
| 42 | + const [recipient, setRecipient] = useState<string>( |
| 43 | + "znam17drxewzvge966gzcl0u6tr4j90traepujm2vd8ptwwkgrftnhs2hdtnyzgl5freyjsdnchn4ddy" |
| 44 | + ); |
| 45 | + const [localRecoveryAddr, setLocalRecoveryAddress] = useState<string>( |
| 46 | + "osmo18st0wqx84av8y6xdlss9d6m2nepyqwj6n3q7js" |
| 47 | + ); |
| 48 | + const [quote, setQuote] = useState< |
| 49 | + (SwapResponseOk & { minAmount: string }) | null |
| 50 | + >(null); |
55 | 51 |
|
56 | | - //const { data: ibcChannels } = useAtomValue(ibcChannelsFamily("osmosis")); |
| 52 | + const { data: ibcChannels } = useAtomValue(ibcChannelsFamily("osmosis")); |
57 | 53 |
|
58 | | - //const feeProps = useTransactionFee(["IbcTransfer"], true); |
| 54 | + const feeProps = useTransactionFee(["IbcTransfer"], true); |
59 | 55 |
|
60 | | - //useEffect(() => { |
61 | | - // const call = async (): Promise<void> => { |
62 | | - // invariant(from, "No from asset selected"); |
63 | | - // invariant(to, "No to asset selected"); |
64 | | - // // We have to map namada assets to osmosis assets to get correct base |
65 | | - // const fromOsmosis = osmosisAssets.find( |
66 | | - // (assets) => assets.symbol === from.asset.symbol |
67 | | - // ); |
68 | | - // const toOsmosis = osmosisAssets.find( |
69 | | - // (assets) => assets.symbol === to.symbol |
70 | | - // ); |
| 56 | + useEffect(() => { |
| 57 | + const call = async (): Promise<void> => { |
| 58 | + invariant(from, "No from asset selected"); |
| 59 | + invariant(to, "No to asset selected"); |
| 60 | + // We have to map namada assets to osmosis assets to get correct base |
| 61 | + const fromOsmosis = osmosisAssets.find( |
| 62 | + (assets) => assets.symbol === from.asset.symbol |
| 63 | + ); |
| 64 | + const toOsmosis = osmosisAssets.find( |
| 65 | + (assets) => assets.symbol === to.symbol |
| 66 | + ); |
71 | 67 |
|
72 | | - // invariant(fromOsmosis, "From asset is not found in Osmosis assets"); |
73 | | - // invariant(toOsmosis, "To asset is not found in Osmosis assets"); |
| 68 | + invariant(fromOsmosis, "From asset is not found in Osmosis assets"); |
| 69 | + invariant(toOsmosis, "To asset is not found in Osmosis assets"); |
74 | 70 |
|
75 | | - // const quote = await fetch( |
76 | | - // "https://sqs.osmosis.zone/router/quote?" + |
77 | | - // new URLSearchParams({ |
78 | | - // tokenIn: `${amount}${fromOsmosis.base}`, |
79 | | - // tokenOutDenom: toOsmosis.base, |
80 | | - // humanDenoms: "false", |
81 | | - // }).toString() |
82 | | - // ); |
83 | | - // const response: SwapResponse = await quote.json(); |
| 71 | + const quote = await fetch( |
| 72 | + "https://sqs.osmosis.zone/router/quote?" + |
| 73 | + new URLSearchParams({ |
| 74 | + tokenIn: `${amount}${fromOsmosis.base}`, |
| 75 | + tokenOutDenom: toOsmosis.base, |
| 76 | + humanDenoms: "false", |
| 77 | + }).toString() |
| 78 | + ); |
| 79 | + const response: SwapResponse = await quote.json(); |
84 | 80 |
|
85 | | - // if (!(response as SwapResponseError).message) { |
86 | | - // const r = response as SwapResponseOk; |
87 | | - // const minAmount = BigNumber(r.amount_out) |
88 | | - // .times(BigNumber(1).minus(SLIPPAGE)) |
89 | | - // .toString(); |
90 | | - // setQuote({ ...(response as SwapResponseOk), minAmount }); |
91 | | - // } else { |
92 | | - // setQuote(null); |
93 | | - // } |
94 | | - // }; |
95 | | - // if (from && to && amount) { |
96 | | - // call(); |
97 | | - // } |
98 | | - //}, [from?.asset.address, to?.address, amount]); |
| 81 | + if (!(response as SwapResponseError).message) { |
| 82 | + const r = response as SwapResponseOk; |
| 83 | + const minAmount = BigNumber(r.amount_out) |
| 84 | + .times(BigNumber(1).minus(SLIPPAGE)) |
| 85 | + .toString(); |
| 86 | + setQuote({ ...(response as SwapResponseOk), minAmount }); |
| 87 | + } else { |
| 88 | + setQuote(null); |
| 89 | + } |
| 90 | + }; |
| 91 | + if (from && to && amount) { |
| 92 | + call(); |
| 93 | + } |
| 94 | + }, [from?.asset.address, to?.address, amount]); |
99 | 95 |
|
100 | | - //const defaultAccounts = useAtomValue(allDefaultAccountsAtom); |
101 | | - //const shieldedAccount = defaultAccounts.data?.find( |
102 | | - // (account) => account.type === AccountType.ShieldedKeys |
103 | | - //); |
104 | | - //const transparentAccount = defaultAccounts.data?.find( |
105 | | - // (account) => account.type !== AccountType.ShieldedKeys |
106 | | - //); |
| 96 | + const defaultAccounts = useAtomValue(allDefaultAccountsAtom); |
| 97 | + const shieldedAccount = defaultAccounts.data?.find( |
| 98 | + (account) => account.type === AccountType.ShieldedKeys |
| 99 | + ); |
107 | 100 |
|
108 | 101 | //const handleOsmosisSwap = useCallback(async () => { |
109 | 102 | // invariant(transparentAccount, "No transparent account is found"); |
@@ -189,127 +182,81 @@ export const OsmosisSwap: React.FC = () => { |
189 | 182 | // } |
190 | 183 | //}, [transparentAccount, shieldedAccount, quote]); |
191 | 184 |
|
192 | | - /// New shiz below |
193 | | - |
194 | | - const [displayAmount, setDisplayAmount] = useState<BigNumber | undefined>(); |
195 | | - const [generalErrorMessage, setGeneralErrorMessage] = useState(""); |
196 | | - const [currentStatus, setCurrentStatus] = useState(""); |
197 | | - const [currentStatusExplanation, setCurrentStatusExplanation] = useState(""); |
198 | | - const requiresNewSync = useRequiresNewShieldedSync(); |
199 | | - |
200 | | - const rpcUrl = useAtomValue(rpcUrlAtom); |
201 | | - const chainParameters = useAtomValue(chainParametersAtom); |
202 | | - const defaultAccounts = useAtomValue(allDefaultAccountsAtom); |
203 | | - const [ledgerStatus, setLedgerStatusStop] = useAtom(ledgerStatusDataAtom); |
204 | | - const { data: availableAssets, isLoading: isLoadingAssets } = useAtomValue( |
205 | | - namadaShieldedAssetsAtom |
206 | | - ); |
207 | | - const namadaChainRegistry = useAtomValue(namadaChainRegistryAtom); |
208 | | - const chain = namadaChainRegistry.data?.chain; |
209 | | - |
210 | | - const { storeTransaction } = useTransactionActions(); |
211 | | - |
212 | | - const ledgerAccountInfo = ledgerStatus && { |
213 | | - deviceConnected: ledgerStatus.connected, |
214 | | - errorMessage: ledgerStatus.errorMessage, |
215 | | - }; |
216 | | - const chainId = chainParameters.data?.chainId; |
217 | | - const account = defaultAccounts.data?.find( |
218 | | - (account) => account.type === AccountType.ShieldedKeys |
219 | | - ); |
220 | | - const sourceAddress = account?.address; |
221 | | - const destinationAddress = defaultAccounts.data?.find( |
222 | | - (account) => account.type !== AccountType.ShieldedKeys |
223 | | - )?.address; |
224 | | - |
225 | | - const [selectedAssetAddress, setSelectedAssetAddress] = useUrlState( |
226 | | - params.asset |
227 | | - ); |
228 | | - const lastSync = useAtomValue(lastCompletedShieldedSyncAtom); |
229 | | - const selectedAsset = |
230 | | - selectedAssetAddress ? availableAssets?.[selectedAssetAddress] : undefined; |
231 | | - |
232 | | - const { |
233 | | - execute: performTransfer, |
234 | | - isPending: isPerformingTransfer, |
235 | | - isSuccess, |
236 | | - txKind, |
237 | | - feeProps, |
238 | | - completedAt, |
239 | | - redirectToTransactionPage, |
240 | | - } = useTransfer({ |
241 | | - source: sourceAddress ?? "", |
242 | | - target: destinationAddress ?? "", |
243 | | - token: selectedAsset?.asset.address ?? "", |
244 | | - displayAmount: displayAmount ?? new BigNumber(0), |
245 | | - onBeforeBuildTx: () => { |
246 | | - setCurrentStatus("Generating MASP Parameters..."); |
247 | | - setCurrentStatusExplanation( |
248 | | - "Generating MASP parameters can take a few seconds. Please wait..." |
249 | | - ); |
250 | | - }, |
251 | | - onBeforeSign: () => { |
252 | | - setCurrentStatus("Waiting for signature..."); |
253 | | - }, |
254 | | - onBeforeBroadcast: async () => { |
255 | | - setCurrentStatus("Broadcasting unshielding transaction..."); |
256 | | - }, |
257 | | - onError: async (originalError) => { |
258 | | - setCurrentStatus(""); |
259 | | - setCurrentStatusExplanation(""); |
260 | | - setGeneralErrorMessage((originalError as Error).message); |
261 | | - }, |
262 | | - asset: selectedAsset?.asset, |
263 | | - }); |
264 | | - |
265 | | - // We stop the ledger status check when the transfer is in progress |
266 | | - setLedgerStatusStop(isPerformingTransfer); |
| 185 | + //TODO: sucks |
| 186 | + const toAmount = quote ? BigNumber(quote.amount_out) : undefined; |
267 | 187 |
|
268 | 188 | return ( |
269 | 189 | <Panel className="relative rounded-sm flex flex-col flex-1 pt-9"> |
270 | 190 | <header className="flex flex-col items-center text-center mb-8 gap-6"> |
271 | 191 | <h1 className="text-yellow"> Shielded Swaps </h1> |
272 | 192 | </header> |
273 | | - |
274 | | - <TransferModule |
| 193 | + <SwapModule |
| 194 | + feeProps={feeProps} |
| 195 | + walletAddress={shieldedAccount?.address} |
275 | 196 | source={{ |
276 | | - isLoadingAssets: isLoadingAssets, |
| 197 | + amount: amount ? BigNumber(amount) : undefined, |
| 198 | + availableAmount: from?.amount, |
| 199 | + selectedAssetAddress: from?.asset.address, |
277 | 200 | availableAssets, |
278 | | - selectedAssetAddress, |
279 | | - availableAmount: selectedAsset?.amount, |
280 | | - chain, |
281 | | - availableWallets: [wallets.namada], |
282 | | - wallet: wallets.namada, |
283 | | - walletAddress: sourceAddress, |
284 | | - isShieldedAddress: true, |
285 | | - onChangeSelectedAsset: setSelectedAssetAddress, |
286 | | - amount: displayAmount, |
287 | | - onChangeAmount: setDisplayAmount, |
288 | | - ledgerAccountInfo, |
289 | | - hideChainSelector: true, |
290 | | - label: "Sell", |
| 201 | + onChangeAmount: (a) => setAmount(a ? a.toString() : ""), |
| 202 | + onChangeSellSelectedAsset: (address) => { |
| 203 | + setFrom(address ? availableAssets?.[address] : undefined); |
| 204 | + setQuote(null); |
| 205 | + }, |
291 | 206 | }} |
292 | | - destination={{ |
293 | | - chain, |
294 | | - availableWallets: [wallets.namada], |
295 | | - wallet: wallets.namada, |
296 | | - walletAddress: destinationAddress, |
297 | | - isShieldedAddress: false, |
298 | | - }} |
299 | | - feeProps={feeProps} |
300 | | - isShieldedTx={true} |
301 | | - isSubmitting={isPerformingTransfer || isSuccess} |
302 | | - errorMessage={generalErrorMessage} |
303 | | - onSubmitTransfer={() => {}} |
304 | | - currentStatus={currentStatus} |
305 | | - currentStatusExplanation={currentStatusExplanation} |
306 | | - completedAt={completedAt} |
307 | | - onComplete={redirectToTransactionPage} |
308 | | - buttonTextErrors={{ |
309 | | - NoAmount: "Define an amount to unshield", |
| 207 | + target={{ |
| 208 | + amount: toAmount, |
| 209 | + selectedAssetAddress: to?.address, |
| 210 | + availableAssets, |
| 211 | + onChangeBuySelectedAsset: (address) => { |
| 212 | + setTo(address ? availableAssets?.[address].asset : undefined); |
| 213 | + setQuote(null); |
| 214 | + }, |
310 | 215 | }} |
311 | 216 | /> |
312 | | - {requiresNewSync && <MaspSyncCover longSync={lastSync === undefined} />} |
| 217 | + |
| 218 | + {quote && ( |
| 219 | + <div> |
| 220 | + <div> |
| 221 | + Amount in: {quote.amount_in.amount} |
| 222 | + {from?.asset.denom_units[0].aliases?.[0]} |
| 223 | + </div> |
| 224 | + <div> |
| 225 | + Amount out: {quote.amount_out} |
| 226 | + {to?.denom_units[0].aliases?.[0]} |
| 227 | + </div> |
| 228 | + <div> |
| 229 | + Min amount out: {quote.minAmount} |
| 230 | + {to?.denom_units[0].aliases?.[0]} |
| 231 | + </div> |
| 232 | + <div>Slippage: {SLIPPAGE * 100}%</div> |
| 233 | + <div>Routes: </div> |
| 234 | + <div>Effective fee: {BigNumber(quote.effective_fee).toString()}</div> |
| 235 | + <div> |
| 236 | + Price: 1 {from?.asset.symbol} ≈{" "} |
| 237 | + {BigNumber(quote.amount_out).div(BigNumber(amount)).toString()}{" "} |
| 238 | + {to?.symbol} |
| 239 | + </div> |
| 240 | + <div> |
| 241 | + Price impact: {BigNumber(quote.price_impact).dp(3).toString()} |
| 242 | + </div> |
| 243 | + <ul className="list-disc list-inside"> |
| 244 | + {quote.route.map((r, i) => ( |
| 245 | + <li key={i}> |
| 246 | + Route{i + 1} |
| 247 | + <ul className="list-disc list-inside pl-4"> |
| 248 | + {r.pools.map((p, i) => ( |
| 249 | + <li key={i}> |
| 250 | + {p.id}: {p.token_out_denom} |
| 251 | + (Fee: {BigNumber(p.taker_fee).toString()}) |
| 252 | + </li> |
| 253 | + ))} |
| 254 | + </ul> |
| 255 | + </li> |
| 256 | + ))} |
| 257 | + </ul> |
| 258 | + </div> |
| 259 | + )} |
313 | 260 | </Panel> |
314 | 261 | // <div className="text-white"> |
315 | 262 | // <div>From:</div> |
|
0 commit comments