@shapeshiftoss/swap-widget 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +18 -11
- package/package.json +77 -28
- package/Dockerfile +0 -87
- package/dist/assets/PhArrowCircleDown-B305t-Sh.js +0 -14
- package/dist/assets/PhArrowClockwise-CJPxQhr_.js +0 -14
- package/dist/assets/PhArrowDown-C4hT9BVu.js +0 -14
- package/dist/assets/PhArrowLeft-Way4Cu-r.js +0 -14
- package/dist/assets/PhArrowRight-Cp5ZME0F.js +0 -14
- package/dist/assets/PhArrowSquareOut-NnrK69c5.js +0 -14
- package/dist/assets/PhArrowUp-Dph6j8rm.js +0 -14
- package/dist/assets/PhArrowUpRight-BpJjRRRh.js +0 -14
- package/dist/assets/PhArrowsClockwise-CFUT8X7R.js +0 -14
- package/dist/assets/PhArrowsDownUp-BjBI4ckC.js +0 -14
- package/dist/assets/PhArrowsLeftRight-CuLpbKRk.js +0 -14
- package/dist/assets/PhBank-Dchr4XQJ.js +0 -14
- package/dist/assets/PhBrowser-dUu2TrC1.js +0 -14
- package/dist/assets/PhCaretDown-78IuKU3V.js +0 -14
- package/dist/assets/PhCaretLeft-_aiyH7n1.js +0 -14
- package/dist/assets/PhCaretRight-8zJQEuDX.js +0 -14
- package/dist/assets/PhCaretUp-CiCxKWwN.js +0 -14
- package/dist/assets/PhCheck-CcADx8br.js +0 -14
- package/dist/assets/PhCircleHalf-BYzSzd2h.js +0 -14
- package/dist/assets/PhClock-UItiXeJq.js +0 -14
- package/dist/assets/PhCompass-CUy3uA7t.js +0 -14
- package/dist/assets/PhCopy-C-PDgSlZ.js +0 -14
- package/dist/assets/PhCreditCard-CWRaUT6F.js +0 -14
- package/dist/assets/PhCurrencyDollar-Cqla3AXT.js +0 -14
- package/dist/assets/PhDesktop-BBIPrYLl.js +0 -14
- package/dist/assets/PhDeviceMobile-BdsPaATg.js +0 -14
- package/dist/assets/PhDotsThree-BMXCQl-0.js +0 -14
- package/dist/assets/PhEnvelope-yUsis6yU.js +0 -14
- package/dist/assets/PhFunnelSimple-Ba7BbWTp.js +0 -14
- package/dist/assets/PhGlobe-DgLxBk5U.js +0 -14
- package/dist/assets/PhIdentificationCard-Czjmq9Z3.js +0 -14
- package/dist/assets/PhImage-BAJ_erz6.js +0 -14
- package/dist/assets/PhInfo-Cr4DhVUE.js +0 -14
- package/dist/assets/PhLightbulb-BX_7duTr.js +0 -14
- package/dist/assets/PhMagnifyingGlass-Mygfryvz.js +0 -14
- package/dist/assets/PhPaperPlaneRight-BeuCA660.js +0 -14
- package/dist/assets/PhPlus-BMjcHjsT.js +0 -14
- package/dist/assets/PhPower-CGffGmdb.js +0 -14
- package/dist/assets/PhPuzzlePiece-wm_GpVSi.js +0 -14
- package/dist/assets/PhQrCode-DRwRzXCH.js +0 -14
- package/dist/assets/PhQuestion-M09j2U0T.js +0 -14
- package/dist/assets/PhQuestionMark-BP9EMB6h.js +0 -14
- package/dist/assets/PhSealCheck-BfG80yKn.js +0 -14
- package/dist/assets/PhSignOut-C5YOWlPT.js +0 -14
- package/dist/assets/PhSpinner-BXooDYzU.js +0 -14
- package/dist/assets/PhTrash-Biq6rmmL.js +0 -14
- package/dist/assets/PhUser-CAgZSpGe.js +0 -14
- package/dist/assets/PhVault-l2PHF38a.js +0 -14
- package/dist/assets/PhWallet-w25D16It.js +0 -14
- package/dist/assets/PhWarning-DPEydIPw.js +0 -14
- package/dist/assets/PhWarningCircle-BqjT745H.js +0 -14
- package/dist/assets/PhX-s-SmVExa.js +0 -14
- package/dist/assets/SwapController-If0bFrNc.js +0 -1
- package/dist/assets/ccip-BBB7uCdL.js +0 -1
- package/dist/assets/ccip-CkkdfKKI.js +0 -1
- package/dist/assets/data-capture-D4gAy-Yc.js +0 -242
- package/dist/assets/email-B1AJehis.js +0 -90
- package/dist/assets/embedded-wallet-Q6DQopDk.js +0 -410
- package/dist/assets/features-B6JAPAFb.js +0 -5
- package/dist/assets/index-5jd8RPN9.js +0 -7428
- package/dist/assets/index-B4kNtSTk.js +0 -1080
- package/dist/assets/index-B753eAob.js +0 -1
- package/dist/assets/index-BZoLTv77.js +0 -3
- package/dist/assets/index-Bcw9UURX.js +0 -3
- package/dist/assets/index-BkrV-ahK.js +0 -2
- package/dist/assets/index-CRQzQ_up.js +0 -9
- package/dist/assets/index-CSW0Gt5y.js +0 -132
- package/dist/assets/index-CZ4jTIsk.js +0 -138
- package/dist/assets/index-Cwa8SB3h.js +0 -61
- package/dist/assets/index-DEwQgT4r.js +0 -78
- package/dist/assets/index-DVfQiQ1d.js +0 -1
- package/dist/assets/index-cV3UdaMs.css +0 -1
- package/dist/assets/index-hAmu622O.js +0 -43
- package/dist/assets/onramp-XcPNCLRU.js +0 -422
- package/dist/assets/pay-with-exchange-BeHt4OF4.js +0 -280
- package/dist/assets/property-C9JG5tag.js +0 -27
- package/dist/assets/receive-7ff4he2S.js +0 -131
- package/dist/assets/send-CkMBIRXT.js +0 -554
- package/dist/assets/socials-DmmxuLrP.js +0 -250
- package/dist/assets/swaps-IHAYRQ0x.js +0 -1050
- package/dist/assets/transactions-9Cxdk7o9.js +0 -16
- package/dist/assets/v4-BOvFkHkt.js +0 -1
- package/dist/assets/w3m-modal-kJzFnBfB.js +0 -891
- package/dist/assets/wallet-standard-Dv8LMoUB.js +0 -1
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +0 -11
- package/dist/icon-192x192.png +0 -0
- package/dist/icon-512x512.png +0 -0
- package/dist/index.html +0 -33
- package/index.html +0 -32
- package/public/favicon.png +0 -0
- package/public/favicon.svg +0 -11
- package/public/icon-192x192.png +0 -0
- package/public/icon-512x512.png +0 -0
- package/railway.toml +0 -10
- package/src/api/client.ts +0 -95
- package/src/components/AddressInputModal.css +0 -173
- package/src/components/AddressInputModal.tsx +0 -250
- package/src/components/ApprovalStep.tsx +0 -92
- package/src/components/ExecutionStep.tsx +0 -27
- package/src/components/InputStep.tsx +0 -431
- package/src/components/QuoteSelector.css +0 -146
- package/src/components/QuoteSelector.tsx +0 -137
- package/src/components/QuotesModal.css +0 -250
- package/src/components/QuotesModal.tsx +0 -182
- package/src/components/SettingsModal.css +0 -145
- package/src/components/SettingsModal.tsx +0 -180
- package/src/components/StatusStep.tsx +0 -139
- package/src/components/SwapWidget.css +0 -713
- package/src/components/SwapWidget.tsx +0 -607
- package/src/components/TokenSelectModal.css +0 -438
- package/src/components/TokenSelectModal.tsx +0 -487
- package/src/components/WalletProvider.tsx +0 -92
- package/src/config/appkit.ts +0 -104
- package/src/config/wagmi.ts +0 -27
- package/src/constants/chains.ts +0 -72
- package/src/constants/defaults.ts +0 -28
- package/src/constants/swappers.ts +0 -48
- package/src/constants/viemChains.ts +0 -69
- package/src/contexts/SwapWalletContext.tsx +0 -25
- package/src/demo/App.css +0 -458
- package/src/demo/App.tsx +0 -481
- package/src/demo/main.tsx +0 -25
- package/src/hooks/useAssets.ts +0 -180
- package/src/hooks/useBalances.ts +0 -822
- package/src/hooks/useBitcoinSigning.ts +0 -276
- package/src/hooks/useMarketData.ts +0 -169
- package/src/hooks/useSolanaSigning.ts +0 -275
- package/src/hooks/useStatusPolling.ts +0 -137
- package/src/hooks/useSwapApproval.ts +0 -106
- package/src/hooks/useSwapDisplayValues.ts +0 -157
- package/src/hooks/useSwapExecution.ts +0 -199
- package/src/hooks/useSwapHandlers.ts +0 -119
- package/src/hooks/useSwapQuote.ts +0 -71
- package/src/hooks/useSwapQuoting.ts +0 -102
- package/src/hooks/useSwapRates.ts +0 -61
- package/src/index.ts +0 -43
- package/src/machines/SwapMachineContext.ts +0 -5
- package/src/machines/__tests__/guards.test.ts +0 -228
- package/src/machines/__tests__/setup.test.ts +0 -7
- package/src/machines/__tests__/swapMachine.test.ts +0 -544
- package/src/machines/__tests__/types.test.ts +0 -124
- package/src/machines/guards.ts +0 -33
- package/src/machines/swapMachine.ts +0 -288
- package/src/machines/types.ts +0 -54
- package/src/services/transactionStatus.ts +0 -322
- package/src/types/index.ts +0 -361
- package/src/utils/addressValidation.ts +0 -201
- package/src/utils/redirect.ts +0 -45
- package/src/vite-env.d.ts +0 -10
- package/tsconfig.json +0 -23
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -101
- package/vitest.config.ts +0 -18
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
|
|
3
|
-
import { useSwapWallet } from '../contexts/SwapWalletContext'
|
|
4
|
-
import { SwapMachineCtx } from '../machines/SwapMachineContext'
|
|
5
|
-
import type { CheckStatusParams } from '../services/transactionStatus'
|
|
6
|
-
import { checkTransactionStatus } from '../services/transactionStatus'
|
|
7
|
-
import { getEvmNetworkId } from '../types'
|
|
8
|
-
|
|
9
|
-
const POLL_INTERVAL_MS = 5000
|
|
10
|
-
|
|
11
|
-
type UseStatusPollingParams = {
|
|
12
|
-
onSwapSuccess?: (txHash: string) => void
|
|
13
|
-
onSwapError?: (error: Error) => void
|
|
14
|
-
refetchSellBalance?: () => void
|
|
15
|
-
refetchBuyBalance?: () => void
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const useStatusPolling = ({
|
|
19
|
-
onSwapSuccess,
|
|
20
|
-
onSwapError,
|
|
21
|
-
refetchSellBalance,
|
|
22
|
-
refetchBuyBalance,
|
|
23
|
-
}: UseStatusPollingParams) => {
|
|
24
|
-
const stateValue = SwapMachineCtx.useSelector(s => s.value)
|
|
25
|
-
const context = SwapMachineCtx.useSelector(s => s.context)
|
|
26
|
-
const actorRef = SwapMachineCtx.useActorRef()
|
|
27
|
-
|
|
28
|
-
const { solana } = useSwapWallet()
|
|
29
|
-
const solanaConnection = solana.connection
|
|
30
|
-
|
|
31
|
-
const pollingRef = useRef(false)
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
const snap = actorRef.getSnapshot()
|
|
34
|
-
if (!snap.matches('polling_status')) {
|
|
35
|
-
pollingRef.current = false
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
if (pollingRef.current) return
|
|
39
|
-
pollingRef.current = true
|
|
40
|
-
|
|
41
|
-
let stopped = false
|
|
42
|
-
|
|
43
|
-
const poll = async () => {
|
|
44
|
-
if (stopped || !context.txHash) return
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
let statusParams: CheckStatusParams
|
|
48
|
-
|
|
49
|
-
if (context.isSellAssetEvm) {
|
|
50
|
-
statusParams = {
|
|
51
|
-
txHash: context.txHash,
|
|
52
|
-
chainType: 'evm',
|
|
53
|
-
chainId: getEvmNetworkId(context.sellAsset.chainId),
|
|
54
|
-
}
|
|
55
|
-
} else if (context.isSellAssetUtxo) {
|
|
56
|
-
statusParams = {
|
|
57
|
-
txHash: context.txHash,
|
|
58
|
-
chainType: 'utxo',
|
|
59
|
-
}
|
|
60
|
-
} else if (context.isSellAssetSolana) {
|
|
61
|
-
statusParams = {
|
|
62
|
-
txHash: context.txHash,
|
|
63
|
-
chainType: 'solana',
|
|
64
|
-
connection: solanaConnection as CheckStatusParams['connection'],
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
console.warn(
|
|
68
|
-
`[swap-widget] Status polling not implemented for chainType "${context.chainType}". Auto-confirming.`,
|
|
69
|
-
)
|
|
70
|
-
actorRef.send({ type: 'STATUS_CONFIRMED' })
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = await checkTransactionStatus(statusParams)
|
|
75
|
-
|
|
76
|
-
if (stopped) return
|
|
77
|
-
|
|
78
|
-
if (result.status === 'confirmed') {
|
|
79
|
-
actorRef.send({ type: 'STATUS_CONFIRMED' })
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (result.status === 'failed') {
|
|
84
|
-
actorRef.send({ type: 'STATUS_FAILED', error: result.error ?? 'Transaction failed' })
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
setTimeout(poll, POLL_INTERVAL_MS)
|
|
89
|
-
} catch (err) {
|
|
90
|
-
if (stopped) return
|
|
91
|
-
const errorMessage = err instanceof Error ? err.message : 'Unknown polling error'
|
|
92
|
-
actorRef.send({ type: 'STATUS_FAILED', error: errorMessage })
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
poll()
|
|
97
|
-
|
|
98
|
-
return () => {
|
|
99
|
-
stopped = true
|
|
100
|
-
pollingRef.current = false
|
|
101
|
-
}
|
|
102
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateValue is the sole trigger; other deps are stable refs or read from snapshot
|
|
103
|
-
}, [stateValue])
|
|
104
|
-
|
|
105
|
-
const completionRef = useRef(false)
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
const snap = actorRef.getSnapshot()
|
|
108
|
-
if (!snap.matches('complete')) {
|
|
109
|
-
completionRef.current = false
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
if (completionRef.current) return
|
|
113
|
-
completionRef.current = true
|
|
114
|
-
|
|
115
|
-
if (context.txHash) {
|
|
116
|
-
onSwapSuccess?.(context.txHash)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
refetchSellBalance?.()
|
|
120
|
-
refetchBuyBalance?.()
|
|
121
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateValue is the sole trigger; callbacks are stable
|
|
122
|
-
}, [stateValue])
|
|
123
|
-
|
|
124
|
-
const errorRef = useRef(false)
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
const snap = actorRef.getSnapshot()
|
|
127
|
-
if (!snap.matches('error')) {
|
|
128
|
-
errorRef.current = false
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
if (errorRef.current) return
|
|
132
|
-
errorRef.current = true
|
|
133
|
-
|
|
134
|
-
onSwapError?.(new Error(context.error ?? 'Unknown error'))
|
|
135
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateValue is the sole trigger; callbacks are stable
|
|
136
|
-
}, [stateValue])
|
|
137
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
import type { WalletClient } from 'viem'
|
|
3
|
-
import { createPublicClient, encodeFunctionData, erc20Abi, http } from 'viem'
|
|
4
|
-
|
|
5
|
-
import { getBaseAsset } from '../constants/chains'
|
|
6
|
-
import { switchOrAddChain, VIEM_CHAINS_BY_ID } from '../constants/viemChains'
|
|
7
|
-
import { useSwapWallet } from '../contexts/SwapWalletContext'
|
|
8
|
-
import { SwapMachineCtx } from '../machines/SwapMachineContext'
|
|
9
|
-
import { getEvmNetworkId } from '../types'
|
|
10
|
-
|
|
11
|
-
export const useSwapApproval = () => {
|
|
12
|
-
const stateValue = SwapMachineCtx.useSelector(s => s.value)
|
|
13
|
-
const context = SwapMachineCtx.useSelector(s => s.context)
|
|
14
|
-
const actorRef = SwapMachineCtx.useActorRef()
|
|
15
|
-
|
|
16
|
-
const { walletClient, walletAddress } = useSwapWallet()
|
|
17
|
-
|
|
18
|
-
const approvingRef = useRef(false)
|
|
19
|
-
|
|
20
|
-
useEffect(() => {
|
|
21
|
-
const snap = actorRef.getSnapshot()
|
|
22
|
-
if (!snap.matches('approving') || approvingRef.current) return
|
|
23
|
-
approvingRef.current = true
|
|
24
|
-
|
|
25
|
-
const executeApproval = async () => {
|
|
26
|
-
try {
|
|
27
|
-
if (!walletClient || !walletAddress) {
|
|
28
|
-
actorRef.send({ type: 'APPROVAL_ERROR', error: 'No wallet connected' })
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const quote = context.quote
|
|
33
|
-
if (!quote?.approval?.spender) {
|
|
34
|
-
actorRef.send({ type: 'APPROVAL_ERROR', error: 'No approval data in quote' })
|
|
35
|
-
return
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const sellAssetAddress = context.sellAsset.assetId.split('/')[1]?.split(':')[1]
|
|
39
|
-
if (!sellAssetAddress || !/^0x[a-fA-F0-9]{40}$/.test(sellAssetAddress)) {
|
|
40
|
-
actorRef.send({
|
|
41
|
-
type: 'APPROVAL_ERROR',
|
|
42
|
-
error: 'Approval not applicable for native assets',
|
|
43
|
-
})
|
|
44
|
-
return
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const requiredChainId = getEvmNetworkId(context.sellAsset.chainId)
|
|
48
|
-
const client = walletClient as WalletClient
|
|
49
|
-
|
|
50
|
-
const currentChainId = await client.getChainId()
|
|
51
|
-
if (currentChainId !== requiredChainId) {
|
|
52
|
-
await switchOrAddChain(client, requiredChainId)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const baseAsset = getBaseAsset(context.sellAsset.chainId)
|
|
56
|
-
const nativeCurrency = baseAsset
|
|
57
|
-
? { name: baseAsset.name, symbol: baseAsset.symbol, decimals: baseAsset.precision }
|
|
58
|
-
: { name: 'ETH', symbol: 'ETH', decimals: 18 }
|
|
59
|
-
|
|
60
|
-
const viemChain = VIEM_CHAINS_BY_ID[requiredChainId]
|
|
61
|
-
const chain = viemChain ?? {
|
|
62
|
-
id: requiredChainId,
|
|
63
|
-
name: baseAsset?.networkName ?? baseAsset?.name ?? 'Chain',
|
|
64
|
-
nativeCurrency,
|
|
65
|
-
rpcUrls: { default: { http: [] } },
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!context.sellAmountBaseUnit || context.sellAmountBaseUnit === '0') {
|
|
69
|
-
actorRef.send({ type: 'APPROVAL_ERROR', error: 'No sell amount specified' })
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const approvalData = encodeFunctionData({
|
|
74
|
-
abi: erc20Abi,
|
|
75
|
-
functionName: 'approve',
|
|
76
|
-
args: [quote.approval.spender as `0x${string}`, BigInt(context.sellAmountBaseUnit)],
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const approvalHash = await client.sendTransaction({
|
|
80
|
-
to: sellAssetAddress as `0x${string}`,
|
|
81
|
-
data: approvalData,
|
|
82
|
-
value: BigInt(0),
|
|
83
|
-
chain,
|
|
84
|
-
account: walletAddress as `0x${string}`,
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
const rpcUrl = chain.rpcUrls?.default?.http?.[0]
|
|
88
|
-
const publicClient = createPublicClient({
|
|
89
|
-
chain,
|
|
90
|
-
transport: rpcUrl ? http(rpcUrl) : http(),
|
|
91
|
-
})
|
|
92
|
-
await publicClient.waitForTransactionReceipt({ hash: approvalHash })
|
|
93
|
-
|
|
94
|
-
actorRef.send({ type: 'APPROVAL_SUCCESS', txHash: approvalHash })
|
|
95
|
-
} catch (error) {
|
|
96
|
-
const errorMessage = error instanceof Error ? error.message : 'Approval failed'
|
|
97
|
-
actorRef.send({ type: 'APPROVAL_ERROR', error: errorMessage })
|
|
98
|
-
} finally {
|
|
99
|
-
approvingRef.current = false
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
executeApproval()
|
|
104
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateValue is the sole trigger; other deps are stable refs read from snapshot
|
|
105
|
-
}, [stateValue])
|
|
106
|
-
}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
2
|
-
|
|
3
|
-
import type { ApiClient } from '../api/client'
|
|
4
|
-
import { getBaseAsset } from '../constants/chains'
|
|
5
|
-
import { useSwapWallet } from '../contexts/SwapWalletContext'
|
|
6
|
-
import { SwapMachineCtx } from '../machines/SwapMachineContext'
|
|
7
|
-
import { formatAmount, getChainType } from '../types'
|
|
8
|
-
import { useChainInfo } from './useAssets'
|
|
9
|
-
import { useMultiChainBalance } from './useBalances'
|
|
10
|
-
import { formatUsdValue, useMarketData } from './useMarketData'
|
|
11
|
-
import { useSwapRates } from './useSwapRates'
|
|
12
|
-
|
|
13
|
-
type UseSwapDisplayValuesParams = {
|
|
14
|
-
apiClient: ApiClient
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export const useSwapDisplayValues = ({ apiClient }: UseSwapDisplayValuesParams) => {
|
|
18
|
-
const sellAsset = SwapMachineCtx.useSelector(s => s.context.sellAsset)
|
|
19
|
-
const buyAsset = SwapMachineCtx.useSelector(s => s.context.buyAsset)
|
|
20
|
-
const sellAmountBaseUnit = SwapMachineCtx.useSelector(s => s.context.sellAmountBaseUnit)
|
|
21
|
-
const isSellAssetEvm = SwapMachineCtx.useSelector(s => s.context.isSellAssetEvm)
|
|
22
|
-
const isSellAssetUtxo = SwapMachineCtx.useSelector(s => s.context.isSellAssetUtxo)
|
|
23
|
-
const isSellAssetSolana = SwapMachineCtx.useSelector(s => s.context.isSellAssetSolana)
|
|
24
|
-
const selectedRate = SwapMachineCtx.useSelector(s => s.context.selectedRate)
|
|
25
|
-
|
|
26
|
-
const { walletAddress, effectiveReceiveAddress, bitcoin, solana } = useSwapWallet()
|
|
27
|
-
const bitcoinAddress = bitcoin.address
|
|
28
|
-
const solanaAddress = solana.address
|
|
29
|
-
|
|
30
|
-
const buyChainType = getChainType(buyAsset.chainId)
|
|
31
|
-
|
|
32
|
-
const {
|
|
33
|
-
data: rates,
|
|
34
|
-
isLoading: isLoadingRates,
|
|
35
|
-
error: ratesError,
|
|
36
|
-
} = useSwapRates(apiClient, {
|
|
37
|
-
sellAssetId: sellAsset.assetId,
|
|
38
|
-
buyAssetId: buyAsset.assetId,
|
|
39
|
-
sellAmountCryptoBaseUnit: sellAmountBaseUnit,
|
|
40
|
-
enabled:
|
|
41
|
-
!!sellAmountBaseUnit &&
|
|
42
|
-
sellAmountBaseUnit !== '0' &&
|
|
43
|
-
(isSellAssetEvm || isSellAssetUtxo || isSellAssetSolana),
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const {
|
|
47
|
-
data: sellAssetBalance,
|
|
48
|
-
isLoading: isSellBalanceLoading,
|
|
49
|
-
refetch: refetchSellBalance,
|
|
50
|
-
} = useMultiChainBalance(
|
|
51
|
-
walletAddress,
|
|
52
|
-
bitcoinAddress,
|
|
53
|
-
solanaAddress,
|
|
54
|
-
sellAsset.assetId,
|
|
55
|
-
sellAsset.precision,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
const buyAssetAddressForBalance = useMemo(() => {
|
|
59
|
-
if (buyChainType === 'evm') return effectiveReceiveAddress || walletAddress
|
|
60
|
-
if (buyChainType === 'utxo') return effectiveReceiveAddress || bitcoinAddress
|
|
61
|
-
if (buyChainType === 'solana') return effectiveReceiveAddress || solanaAddress
|
|
62
|
-
return effectiveReceiveAddress
|
|
63
|
-
}, [buyChainType, effectiveReceiveAddress, walletAddress, bitcoinAddress, solanaAddress])
|
|
64
|
-
|
|
65
|
-
const {
|
|
66
|
-
data: buyAssetBalance,
|
|
67
|
-
isLoading: isBuyBalanceLoading,
|
|
68
|
-
refetch: refetchBuyBalance,
|
|
69
|
-
} = useMultiChainBalance(
|
|
70
|
-
buyChainType === 'evm' ? buyAssetAddressForBalance : walletAddress,
|
|
71
|
-
buyChainType === 'utxo' ? buyAssetAddressForBalance : bitcoinAddress,
|
|
72
|
-
buyChainType === 'solana' ? buyAssetAddressForBalance : solanaAddress,
|
|
73
|
-
buyAsset.assetId,
|
|
74
|
-
buyAsset.precision,
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
const { data: sellChainInfo } = useChainInfo(sellAsset.chainId)
|
|
78
|
-
const { data: buyChainInfo } = useChainInfo(buyAsset.chainId)
|
|
79
|
-
|
|
80
|
-
const displayRate = useMemo(() => selectedRate ?? rates?.[0], [selectedRate, rates])
|
|
81
|
-
const buyAmount = displayRate?.buyAmountCryptoBaseUnit
|
|
82
|
-
|
|
83
|
-
const sellChainNativeAsset = useMemo(() => getBaseAsset(sellAsset.chainId), [sellAsset.chainId])
|
|
84
|
-
|
|
85
|
-
const assetIdsForPrices = useMemo(() => {
|
|
86
|
-
const ids = [sellAsset.assetId, buyAsset.assetId]
|
|
87
|
-
if (sellChainNativeAsset && sellChainNativeAsset.assetId !== sellAsset.assetId) {
|
|
88
|
-
ids.push(sellChainNativeAsset.assetId)
|
|
89
|
-
}
|
|
90
|
-
return ids
|
|
91
|
-
}, [sellAsset.assetId, buyAsset.assetId, sellChainNativeAsset])
|
|
92
|
-
|
|
93
|
-
const { data: marketData } = useMarketData(assetIdsForPrices)
|
|
94
|
-
const sellAssetUsdPrice = marketData?.[sellAsset.assetId]?.price
|
|
95
|
-
const buyAssetUsdPrice = marketData?.[buyAsset.assetId]?.price
|
|
96
|
-
const nativeAssetUsdPrice = sellChainNativeAsset
|
|
97
|
-
? marketData?.[sellChainNativeAsset.assetId]?.price
|
|
98
|
-
: undefined
|
|
99
|
-
|
|
100
|
-
const networkFeeDisplay = useMemo(() => {
|
|
101
|
-
const feeBaseUnit = displayRate?.networkFeeCryptoBaseUnit
|
|
102
|
-
if (!feeBaseUnit || feeBaseUnit === '0' || !sellChainNativeAsset) return undefined
|
|
103
|
-
const formatted = formatAmount(feeBaseUnit, sellChainNativeAsset.precision, 6)
|
|
104
|
-
const cryptoPart = `${formatted} ${sellChainNativeAsset.symbol}`
|
|
105
|
-
if (!nativeAssetUsdPrice) return cryptoPart
|
|
106
|
-
const fiatValue = formatUsdValue(
|
|
107
|
-
feeBaseUnit,
|
|
108
|
-
sellChainNativeAsset.precision,
|
|
109
|
-
nativeAssetUsdPrice,
|
|
110
|
-
)
|
|
111
|
-
return `${cryptoPart} (${fiatValue})`
|
|
112
|
-
}, [displayRate?.networkFeeCryptoBaseUnit, sellChainNativeAsset, nativeAssetUsdPrice])
|
|
113
|
-
|
|
114
|
-
const sellUsdValue = useMemo(() => {
|
|
115
|
-
if (!sellAmountBaseUnit || !sellAssetUsdPrice) return '$0.00'
|
|
116
|
-
return formatUsdValue(sellAmountBaseUnit, sellAsset.precision, sellAssetUsdPrice)
|
|
117
|
-
}, [sellAmountBaseUnit, sellAsset.precision, sellAssetUsdPrice])
|
|
118
|
-
|
|
119
|
-
const buyUsdValue = useMemo(() => {
|
|
120
|
-
if (!buyAmount || !buyAssetUsdPrice) return '$0.00'
|
|
121
|
-
return formatUsdValue(buyAmount, buyAsset.precision, buyAssetUsdPrice)
|
|
122
|
-
}, [buyAmount, buyAsset.precision, buyAssetUsdPrice])
|
|
123
|
-
|
|
124
|
-
const sellBalanceFiatValue = useMemo(() => {
|
|
125
|
-
if (!sellAssetBalance?.balance || !sellAssetUsdPrice) return undefined
|
|
126
|
-
return formatUsdValue(sellAssetBalance.balance, sellAsset.precision, sellAssetUsdPrice)
|
|
127
|
-
}, [sellAssetBalance?.balance, sellAsset.precision, sellAssetUsdPrice])
|
|
128
|
-
|
|
129
|
-
const buyBalanceFiatValue = useMemo(() => {
|
|
130
|
-
if (!buyAssetBalance?.balance || !buyAssetUsdPrice) return undefined
|
|
131
|
-
return formatUsdValue(buyAssetBalance.balance, buyAsset.precision, buyAssetUsdPrice)
|
|
132
|
-
}, [buyAssetBalance?.balance, buyAsset.precision, buyAssetUsdPrice])
|
|
133
|
-
|
|
134
|
-
return {
|
|
135
|
-
rates,
|
|
136
|
-
isLoadingRates,
|
|
137
|
-
ratesError,
|
|
138
|
-
sellAssetBalance,
|
|
139
|
-
isSellBalanceLoading,
|
|
140
|
-
refetchSellBalance,
|
|
141
|
-
buyAssetBalance,
|
|
142
|
-
isBuyBalanceLoading,
|
|
143
|
-
refetchBuyBalance,
|
|
144
|
-
sellChainInfo,
|
|
145
|
-
buyChainInfo,
|
|
146
|
-
displayRate,
|
|
147
|
-
buyAmount,
|
|
148
|
-
sellChainNativeAsset,
|
|
149
|
-
networkFeeDisplay,
|
|
150
|
-
sellUsdValue,
|
|
151
|
-
buyUsdValue,
|
|
152
|
-
sellAssetUsdPrice,
|
|
153
|
-
buyAssetUsdPrice,
|
|
154
|
-
sellBalanceFiatValue,
|
|
155
|
-
buyBalanceFiatValue,
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
2
|
-
import type { WalletClient } from 'viem'
|
|
3
|
-
|
|
4
|
-
import { switchOrAddChain, VIEM_CHAINS_BY_ID } from '../constants/viemChains'
|
|
5
|
-
import { useSwapWallet } from '../contexts/SwapWalletContext'
|
|
6
|
-
import { SwapMachineCtx } from '../machines/SwapMachineContext'
|
|
7
|
-
import { getEvmNetworkId } from '../types'
|
|
8
|
-
|
|
9
|
-
export const useSwapExecution = () => {
|
|
10
|
-
const stateValue = SwapMachineCtx.useSelector(s => s.value)
|
|
11
|
-
const context = SwapMachineCtx.useSelector(s => s.context)
|
|
12
|
-
const actorRef = SwapMachineCtx.useActorRef()
|
|
13
|
-
|
|
14
|
-
const { walletClient, walletAddress, bitcoin, solana } = useSwapWallet()
|
|
15
|
-
|
|
16
|
-
const executingRef = useRef(false)
|
|
17
|
-
|
|
18
|
-
useEffect(() => {
|
|
19
|
-
const snap = actorRef.getSnapshot()
|
|
20
|
-
if (!snap.matches('executing') || executingRef.current) return
|
|
21
|
-
executingRef.current = true
|
|
22
|
-
|
|
23
|
-
const executeSwap = async () => {
|
|
24
|
-
try {
|
|
25
|
-
const quote = context.quote
|
|
26
|
-
if (!quote) {
|
|
27
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: 'No quote available' })
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (context.isSellAssetEvm) {
|
|
32
|
-
if (!walletClient || !walletAddress) {
|
|
33
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: 'No wallet connected' })
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const requiredChainId = getEvmNetworkId(context.sellAsset.chainId)
|
|
38
|
-
const client = walletClient as WalletClient
|
|
39
|
-
|
|
40
|
-
const currentChainId = await client.getChainId()
|
|
41
|
-
if (currentChainId !== requiredChainId) {
|
|
42
|
-
await switchOrAddChain(client, requiredChainId)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const viemChain = VIEM_CHAINS_BY_ID[requiredChainId]
|
|
46
|
-
if (!viemChain) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`Unsupported EVM chain ID ${requiredChainId}. No RPC configuration available.`,
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
const chain = viemChain
|
|
52
|
-
|
|
53
|
-
const outerStep = quote.steps?.[0]
|
|
54
|
-
const innerStep = quote.quote?.steps?.[0]
|
|
55
|
-
|
|
56
|
-
const transactionData =
|
|
57
|
-
quote.transactionData ??
|
|
58
|
-
outerStep?.transactionData ??
|
|
59
|
-
outerStep?.relayTransactionMetadata ??
|
|
60
|
-
outerStep?.butterSwapTransactionMetadata ??
|
|
61
|
-
innerStep?.transactionData ??
|
|
62
|
-
innerStep?.relayTransactionMetadata ??
|
|
63
|
-
innerStep?.butterSwapTransactionMetadata
|
|
64
|
-
|
|
65
|
-
if (!transactionData) {
|
|
66
|
-
throw new Error(
|
|
67
|
-
`No transaction data returned. Response keys: ${Object.keys(quote).join(', ')}`,
|
|
68
|
-
)
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const txData = transactionData as {
|
|
72
|
-
type?: string
|
|
73
|
-
to: string
|
|
74
|
-
data: string
|
|
75
|
-
value?: string
|
|
76
|
-
gasLimit?: string
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (txData.type && txData.type !== 'evm') {
|
|
80
|
-
throw new Error(
|
|
81
|
-
`Expected EVM transaction data but got type '${txData.type}'. Check swapper configuration.`,
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (!txData.to || !txData.data) {
|
|
86
|
-
throw new Error(`Invalid EVM transaction data: missing 'to' or 'data' fields.`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const txHash = await client.sendTransaction({
|
|
90
|
-
to: txData.to as `0x${string}`,
|
|
91
|
-
data: txData.data as `0x${string}`,
|
|
92
|
-
value: BigInt(txData.value ?? '0'),
|
|
93
|
-
gas: txData.gasLimit ? BigInt(txData.gasLimit) : undefined,
|
|
94
|
-
chain,
|
|
95
|
-
account: walletAddress as `0x${string}`,
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
actorRef.send({ type: 'EXECUTE_SUCCESS', txHash })
|
|
99
|
-
} else if (context.isSellAssetUtxo) {
|
|
100
|
-
if (!bitcoin.isConnected || !bitcoin.address) {
|
|
101
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: 'Bitcoin wallet not connected' })
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
bitcoin.reset()
|
|
106
|
-
|
|
107
|
-
const outerStep = quote.steps?.[0]
|
|
108
|
-
const innerStep = quote.quote?.steps?.[0]
|
|
109
|
-
const transactionData =
|
|
110
|
-
quote.transactionData ?? outerStep?.transactionData ?? innerStep?.transactionData
|
|
111
|
-
|
|
112
|
-
if (!transactionData) {
|
|
113
|
-
throw new Error(
|
|
114
|
-
`No transaction data returned. Response keys: ${Object.keys(quote).join(', ')}`,
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let txid: string
|
|
119
|
-
|
|
120
|
-
if (transactionData.type === 'utxo_deposit') {
|
|
121
|
-
txid = await bitcoin.sendTransfer({
|
|
122
|
-
recipientAddress: transactionData.depositAddress,
|
|
123
|
-
amount: transactionData.value,
|
|
124
|
-
memo: transactionData.memo,
|
|
125
|
-
})
|
|
126
|
-
} else {
|
|
127
|
-
throw new Error(
|
|
128
|
-
`Unsupported UTXO transaction type: ${transactionData.type}. Only utxo_deposit is supported.`,
|
|
129
|
-
)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
actorRef.send({ type: 'EXECUTE_SUCCESS', txHash: txid })
|
|
133
|
-
} else if (context.isSellAssetSolana) {
|
|
134
|
-
if (!solana.isConnected || !solana.address || !solana.connection) {
|
|
135
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: 'Solana wallet not connected' })
|
|
136
|
-
return
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
solana.reset()
|
|
140
|
-
|
|
141
|
-
const outerStep = quote.steps?.[0]
|
|
142
|
-
const innerStep = quote.quote?.steps?.[0]
|
|
143
|
-
|
|
144
|
-
const txData = outerStep?.transactionData ?? innerStep?.transactionData
|
|
145
|
-
|
|
146
|
-
if (!txData || txData.type !== 'solana') {
|
|
147
|
-
throw new Error(
|
|
148
|
-
`No Solana transaction metadata returned. Response keys: ${Object.keys(quote).join(
|
|
149
|
-
', ',
|
|
150
|
-
)}`,
|
|
151
|
-
)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const { Transaction, PublicKey, TransactionInstruction } = await import('@solana/web3.js')
|
|
155
|
-
|
|
156
|
-
const instructions = txData.instructions.map(ix => {
|
|
157
|
-
const keys = ix.keys.map(key => ({
|
|
158
|
-
pubkey: new PublicKey(key.pubkey),
|
|
159
|
-
isSigner: key.isSigner,
|
|
160
|
-
isWritable: key.isWritable,
|
|
161
|
-
}))
|
|
162
|
-
|
|
163
|
-
return new TransactionInstruction({
|
|
164
|
-
keys,
|
|
165
|
-
programId: new PublicKey(ix.programId),
|
|
166
|
-
data: Buffer.from(ix.data, 'base64'),
|
|
167
|
-
})
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
const transaction = new Transaction().add(...instructions)
|
|
171
|
-
const conn = solana.connection as {
|
|
172
|
-
getLatestBlockhash: (commitment: string) => Promise<{ blockhash: string }>
|
|
173
|
-
}
|
|
174
|
-
const { blockhash } = await conn.getLatestBlockhash('confirmed')
|
|
175
|
-
transaction.recentBlockhash = blockhash
|
|
176
|
-
transaction.feePayer = new PublicKey(solana.address)
|
|
177
|
-
|
|
178
|
-
const signature = await solana.sendTransaction({ transaction })
|
|
179
|
-
actorRef.send({ type: 'EXECUTE_SUCCESS', txHash: signature })
|
|
180
|
-
} else {
|
|
181
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: 'Unsupported chain type' })
|
|
182
|
-
}
|
|
183
|
-
} catch (error) {
|
|
184
|
-
const errorMessage =
|
|
185
|
-
error instanceof Error
|
|
186
|
-
? error.message
|
|
187
|
-
: typeof error === 'string'
|
|
188
|
-
? error
|
|
189
|
-
: JSON.stringify(error) ?? 'Transaction failed'
|
|
190
|
-
actorRef.send({ type: 'EXECUTE_ERROR', error: errorMessage })
|
|
191
|
-
} finally {
|
|
192
|
-
executingRef.current = false
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
executeSwap()
|
|
197
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- stateValue is the sole trigger; other deps are stable refs read from snapshot
|
|
198
|
-
}, [stateValue])
|
|
199
|
-
}
|