@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,276 +0,0 @@
|
|
|
1
|
-
import { useAppKitAccount, useAppKitProvider } from '@reown/appkit/react'
|
|
2
|
-
import type { BitcoinConnector } from '@reown/appkit-adapter-bitcoin'
|
|
3
|
-
import { useCallback, useMemo, useState } from 'react'
|
|
4
|
-
|
|
5
|
-
import { checkBitcoinStatus } from '../services/transactionStatus'
|
|
6
|
-
|
|
7
|
-
type AccountAddress = string | { address?: string; purpose?: string | number }
|
|
8
|
-
|
|
9
|
-
type AccountAddressArray = AccountAddress[]
|
|
10
|
-
|
|
11
|
-
export type SignInput = {
|
|
12
|
-
address: string
|
|
13
|
-
index: number
|
|
14
|
-
sighashTypes?: number[]
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type BitcoinConnectorExtended = BitcoinConnector & {
|
|
18
|
-
getAccountAddresses?: () => Promise<AccountAddressArray> | AccountAddressArray
|
|
19
|
-
signPSBT?: (params: {
|
|
20
|
-
psbt: string
|
|
21
|
-
signInputs: SignInput[]
|
|
22
|
-
broadcast?: boolean
|
|
23
|
-
}) => Promise<string | { txid?: string; psbt?: string }>
|
|
24
|
-
signMessage?: (params: {
|
|
25
|
-
message: string
|
|
26
|
-
address: string
|
|
27
|
-
}) => Promise<string | { signature?: string }>
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type SendTransferParams = {
|
|
31
|
-
recipientAddress: string
|
|
32
|
-
amount: string
|
|
33
|
-
memo?: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type SignPsbtParams = {
|
|
37
|
-
psbt: string
|
|
38
|
-
signInputs: SignInput[]
|
|
39
|
-
broadcast?: boolean
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export type BitcoinSigningState = {
|
|
43
|
-
isLoading: boolean
|
|
44
|
-
error: string | undefined
|
|
45
|
-
txid: string | undefined
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export type UseBitcoinSigningResult = {
|
|
49
|
-
isConnected: boolean
|
|
50
|
-
address: string | undefined
|
|
51
|
-
sendTransfer: (params: SendTransferParams) => Promise<string>
|
|
52
|
-
signPsbt: (params: SignPsbtParams) => Promise<string>
|
|
53
|
-
signMessage: (message: string) => Promise<string>
|
|
54
|
-
getAccountAddresses: () => Promise<string[]>
|
|
55
|
-
state: BitcoinSigningState
|
|
56
|
-
reset: () => void
|
|
57
|
-
checkTxStatus: (
|
|
58
|
-
txid: string,
|
|
59
|
-
network?: 'mainnet' | 'testnet',
|
|
60
|
-
) => ReturnType<typeof checkBitcoinStatus>
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const useBitcoinSigning = (): UseBitcoinSigningResult => {
|
|
64
|
-
const { walletProvider } = useAppKitProvider<BitcoinConnector>('bip122')
|
|
65
|
-
const provider = walletProvider as BitcoinConnectorExtended | undefined
|
|
66
|
-
|
|
67
|
-
const [state, setState] = useState<BitcoinSigningState>({
|
|
68
|
-
isLoading: false,
|
|
69
|
-
error: undefined,
|
|
70
|
-
txid: undefined,
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
const {
|
|
74
|
-
address: appKitBtcAddress,
|
|
75
|
-
allAccounts: btcAccounts,
|
|
76
|
-
isConnected: isBtcConnected,
|
|
77
|
-
} = useAppKitAccount({ namespace: 'bip122' })
|
|
78
|
-
|
|
79
|
-
const address = useMemo(() => {
|
|
80
|
-
if (!provider || !isBtcConnected) return undefined
|
|
81
|
-
return appKitBtcAddress ?? btcAccounts[0]?.address
|
|
82
|
-
}, [provider, isBtcConnected, appKitBtcAddress, btcAccounts])
|
|
83
|
-
|
|
84
|
-
const sendTransfer = useCallback(
|
|
85
|
-
async (params: SendTransferParams): Promise<string> => {
|
|
86
|
-
if (!provider?.sendTransfer) {
|
|
87
|
-
throw new Error('Bitcoin wallet not connected')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined, txid: undefined }))
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
const txid = await provider.sendTransfer({
|
|
94
|
-
recipient: params.recipientAddress,
|
|
95
|
-
amount: params.amount,
|
|
96
|
-
...(params.memo && { memo: params.memo }),
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
if (!txid) {
|
|
100
|
-
throw new Error('Transaction submitted but no txid returned')
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
setState(prev => ({ ...prev, isLoading: false, txid }))
|
|
104
|
-
return txid
|
|
105
|
-
} catch (error) {
|
|
106
|
-
const errorMessage =
|
|
107
|
-
error instanceof Error ? error.message : 'Failed to send Bitcoin transfer'
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
errorMessage.toLowerCase().includes('rejected') ||
|
|
111
|
-
errorMessage.toLowerCase().includes('denied') ||
|
|
112
|
-
errorMessage.toLowerCase().includes('cancelled') ||
|
|
113
|
-
errorMessage.toLowerCase().includes('user refused')
|
|
114
|
-
) {
|
|
115
|
-
setState(prev => ({ ...prev, isLoading: false, error: 'Transaction rejected by user' }))
|
|
116
|
-
throw new Error('Transaction rejected by user')
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
120
|
-
throw error
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
[provider],
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
const signPsbt = useCallback(
|
|
127
|
-
async (params: SignPsbtParams): Promise<string> => {
|
|
128
|
-
if (!provider?.signPSBT) {
|
|
129
|
-
throw new Error('Bitcoin wallet not connected')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined, txid: undefined }))
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const result = await provider.signPSBT({
|
|
136
|
-
psbt: params.psbt,
|
|
137
|
-
signInputs: params.signInputs,
|
|
138
|
-
broadcast: params.broadcast ?? true,
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
const txidOrPsbt = typeof result === 'string' ? result : result?.txid ?? result?.psbt ?? ''
|
|
142
|
-
|
|
143
|
-
if (params.broadcast !== false) {
|
|
144
|
-
setState(prev => ({ ...prev, isLoading: false, txid: txidOrPsbt }))
|
|
145
|
-
} else {
|
|
146
|
-
setState(prev => ({ ...prev, isLoading: false }))
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return txidOrPsbt
|
|
150
|
-
} catch (error) {
|
|
151
|
-
const errorMessage = error instanceof Error ? error.message : 'Failed to sign PSBT'
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
errorMessage.toLowerCase().includes('rejected') ||
|
|
155
|
-
errorMessage.toLowerCase().includes('denied') ||
|
|
156
|
-
errorMessage.toLowerCase().includes('cancelled') ||
|
|
157
|
-
errorMessage.toLowerCase().includes('user refused')
|
|
158
|
-
) {
|
|
159
|
-
setState(prev => ({ ...prev, isLoading: false, error: 'Signing rejected by user' }))
|
|
160
|
-
throw new Error('Signing rejected by user')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
164
|
-
throw error
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
[provider],
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
const signMessage = useCallback(
|
|
171
|
-
async (message: string): Promise<string> => {
|
|
172
|
-
if (!provider?.signMessage) {
|
|
173
|
-
throw new Error('Bitcoin wallet not connected')
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (!address) {
|
|
177
|
-
const errorMessage = 'No Bitcoin address available to sign with'
|
|
178
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
179
|
-
throw new Error(errorMessage)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined }))
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
const signature = await provider.signMessage({
|
|
186
|
-
message,
|
|
187
|
-
address,
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
const signatureObj = signature as string | { signature?: string }
|
|
191
|
-
const signatureStr =
|
|
192
|
-
typeof signatureObj === 'string'
|
|
193
|
-
? signatureObj
|
|
194
|
-
: (signatureObj && typeof signatureObj === 'object' && 'signature' in signatureObj
|
|
195
|
-
? signatureObj.signature
|
|
196
|
-
: '') ?? ''
|
|
197
|
-
|
|
198
|
-
if (!signatureStr) {
|
|
199
|
-
throw new Error('Signing succeeded but no signature returned')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
setState(prev => ({ ...prev, isLoading: false }))
|
|
203
|
-
return signatureStr
|
|
204
|
-
} catch (error) {
|
|
205
|
-
const errorMessage = error instanceof Error ? error.message : 'Failed to sign message'
|
|
206
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
207
|
-
throw error
|
|
208
|
-
}
|
|
209
|
-
},
|
|
210
|
-
[provider, address],
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
const getAccountAddresses = useCallback(async (): Promise<string[]> => {
|
|
214
|
-
if (!provider) {
|
|
215
|
-
return []
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
const accountsResult = provider.getAccountAddresses?.()
|
|
220
|
-
const accounts: AccountAddressArray | undefined =
|
|
221
|
-
accountsResult instanceof Promise
|
|
222
|
-
? await accountsResult
|
|
223
|
-
: (accountsResult as AccountAddressArray | undefined)
|
|
224
|
-
|
|
225
|
-
if (!accounts || !Array.isArray(accounts)) return []
|
|
226
|
-
|
|
227
|
-
return accounts
|
|
228
|
-
.map((account: AccountAddress) => {
|
|
229
|
-
if (typeof account === 'string') return account
|
|
230
|
-
return account?.address ?? ''
|
|
231
|
-
})
|
|
232
|
-
.filter((addr: string | undefined): addr is string => Boolean(addr))
|
|
233
|
-
} catch {
|
|
234
|
-
return []
|
|
235
|
-
}
|
|
236
|
-
}, [provider])
|
|
237
|
-
|
|
238
|
-
const reset = useCallback(() => {
|
|
239
|
-
setState({
|
|
240
|
-
isLoading: false,
|
|
241
|
-
error: undefined,
|
|
242
|
-
txid: undefined,
|
|
243
|
-
})
|
|
244
|
-
}, [])
|
|
245
|
-
|
|
246
|
-
const checkTxStatus = useCallback((txid: string, network: 'mainnet' | 'testnet' = 'mainnet') => {
|
|
247
|
-
return checkBitcoinStatus(txid, network)
|
|
248
|
-
}, [])
|
|
249
|
-
|
|
250
|
-
const isConnected = !!provider && !!address
|
|
251
|
-
|
|
252
|
-
return useMemo(
|
|
253
|
-
() => ({
|
|
254
|
-
isConnected,
|
|
255
|
-
address,
|
|
256
|
-
sendTransfer,
|
|
257
|
-
signPsbt,
|
|
258
|
-
signMessage,
|
|
259
|
-
getAccountAddresses,
|
|
260
|
-
state,
|
|
261
|
-
reset,
|
|
262
|
-
checkTxStatus,
|
|
263
|
-
}),
|
|
264
|
-
[
|
|
265
|
-
isConnected,
|
|
266
|
-
address,
|
|
267
|
-
sendTransfer,
|
|
268
|
-
signPsbt,
|
|
269
|
-
signMessage,
|
|
270
|
-
getAccountAddresses,
|
|
271
|
-
state,
|
|
272
|
-
reset,
|
|
273
|
-
checkTxStatus,
|
|
274
|
-
],
|
|
275
|
-
)
|
|
276
|
-
}
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { adapters } from '@shapeshiftoss/caip'
|
|
2
|
-
import { BigAmount, bn } from '@shapeshiftoss/utils'
|
|
3
|
-
import { useQuery } from '@tanstack/react-query'
|
|
4
|
-
|
|
5
|
-
import type { AssetId } from '../types'
|
|
6
|
-
|
|
7
|
-
const MARKET_DATA_STALE_TIME = 10 * 60 * 1000
|
|
8
|
-
const MARKET_DATA_GC_TIME = 60 * 60 * 1000
|
|
9
|
-
|
|
10
|
-
type MarketData = {
|
|
11
|
-
price: string
|
|
12
|
-
marketCap: string
|
|
13
|
-
volume: string
|
|
14
|
-
changePercent24Hr: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export type MarketDataById = Record<AssetId, MarketData>
|
|
18
|
-
|
|
19
|
-
type CoinGeckoMarketCap = {
|
|
20
|
-
id: string
|
|
21
|
-
symbol: string
|
|
22
|
-
current_price: number
|
|
23
|
-
market_cap: number
|
|
24
|
-
total_volume: number
|
|
25
|
-
price_change_percentage_24h: number
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const COINGECKO_PROXY_URL = 'https://api.proxy.shapeshift.com/api/v1/markets'
|
|
29
|
-
const COINGECKO_DIRECT_URL = 'https://api.coingecko.com/api/v3'
|
|
30
|
-
|
|
31
|
-
const fetchWithRetry = async (url: string, retries = 2, delay = 1000): Promise<Response> => {
|
|
32
|
-
for (let i = 0; i <= retries; i++) {
|
|
33
|
-
try {
|
|
34
|
-
const response = await fetch(url)
|
|
35
|
-
if (response.ok) return response
|
|
36
|
-
if (response.status === 429 && i < retries) {
|
|
37
|
-
await new Promise(r => setTimeout(r, delay * (i + 1)))
|
|
38
|
-
continue
|
|
39
|
-
}
|
|
40
|
-
return response
|
|
41
|
-
} catch (error) {
|
|
42
|
-
if (i === retries) throw error
|
|
43
|
-
await new Promise(r => setTimeout(r, delay * (i + 1)))
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
throw new Error('Failed after retries')
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const fetchMarketsPage = async (
|
|
50
|
-
baseUrl: string,
|
|
51
|
-
page: number,
|
|
52
|
-
perPage: number,
|
|
53
|
-
): Promise<CoinGeckoMarketCap[]> => {
|
|
54
|
-
const url = `${baseUrl}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=${perPage}&page=${page}&sparkline=false`
|
|
55
|
-
const response = await fetchWithRetry(url)
|
|
56
|
-
if (!response.ok) return []
|
|
57
|
-
return response.json()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const fetchAllMarketData = async (): Promise<MarketDataById> => {
|
|
61
|
-
const result: MarketDataById = {}
|
|
62
|
-
const perPage = 250
|
|
63
|
-
const totalPages = 2
|
|
64
|
-
|
|
65
|
-
let baseUrl = COINGECKO_PROXY_URL
|
|
66
|
-
|
|
67
|
-
const controller = new AbortController()
|
|
68
|
-
const timeoutId = setTimeout(() => controller.abort(), 5000)
|
|
69
|
-
|
|
70
|
-
const testResponse = await fetch(
|
|
71
|
-
`${COINGECKO_PROXY_URL}/coins/markets?vs_currency=usd&per_page=1&page=1`,
|
|
72
|
-
{ signal: controller.signal },
|
|
73
|
-
).catch(() => null)
|
|
74
|
-
|
|
75
|
-
clearTimeout(timeoutId)
|
|
76
|
-
|
|
77
|
-
if (!testResponse?.ok) {
|
|
78
|
-
baseUrl = COINGECKO_DIRECT_URL
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const allData: CoinGeckoMarketCap[] = []
|
|
83
|
-
|
|
84
|
-
for (let page = 1; page <= totalPages; page++) {
|
|
85
|
-
const pageData = await fetchMarketsPage(baseUrl, page, perPage)
|
|
86
|
-
allData.push(...pageData)
|
|
87
|
-
|
|
88
|
-
if (page < totalPages) {
|
|
89
|
-
await new Promise(r => setTimeout(r, 500))
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
for (const asset of allData) {
|
|
94
|
-
const assetIds = adapters.coingeckoToAssetIds(asset.id)
|
|
95
|
-
if (!assetIds?.length) continue
|
|
96
|
-
|
|
97
|
-
const marketData: MarketData = {
|
|
98
|
-
price: asset.current_price?.toString() ?? '0',
|
|
99
|
-
marketCap: asset.market_cap?.toString() ?? '0',
|
|
100
|
-
volume: asset.total_volume?.toString() ?? '0',
|
|
101
|
-
changePercent24Hr: asset.price_change_percentage_24h ?? 0,
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
for (const assetId of assetIds) {
|
|
105
|
-
result[assetId] = marketData
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error('Failed to fetch market data:', error)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return result
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export const useAllMarketData = () => {
|
|
116
|
-
return useQuery({
|
|
117
|
-
queryKey: ['allMarketData'],
|
|
118
|
-
queryFn: fetchAllMarketData,
|
|
119
|
-
staleTime: MARKET_DATA_STALE_TIME,
|
|
120
|
-
gcTime: MARKET_DATA_GC_TIME,
|
|
121
|
-
retry: 1,
|
|
122
|
-
retryDelay: 5000,
|
|
123
|
-
})
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export const useMarketData = (assetIds: AssetId[]) => {
|
|
127
|
-
const { data: allMarketData, ...rest } = useAllMarketData()
|
|
128
|
-
|
|
129
|
-
const filteredData = (() => {
|
|
130
|
-
if (!allMarketData) return {}
|
|
131
|
-
|
|
132
|
-
const result: MarketDataById = {}
|
|
133
|
-
for (const assetId of assetIds) {
|
|
134
|
-
if (allMarketData[assetId]) {
|
|
135
|
-
result[assetId] = allMarketData[assetId]
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return result
|
|
139
|
-
})()
|
|
140
|
-
|
|
141
|
-
return { data: filteredData, ...rest }
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export const useAssetPrice = (assetId: AssetId | undefined) => {
|
|
145
|
-
const { data: allMarketData, ...rest } = useAllMarketData()
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
data: assetId ? allMarketData?.[assetId]?.price : undefined,
|
|
149
|
-
...rest,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
export const formatUsdValue = (
|
|
154
|
-
cryptoAmount: string,
|
|
155
|
-
precision: number,
|
|
156
|
-
usdPrice: string | undefined,
|
|
157
|
-
): string => {
|
|
158
|
-
if (!usdPrice || usdPrice === '0') return '$0.00'
|
|
159
|
-
|
|
160
|
-
const amount = BigAmount.fromBaseUnit({ value: cryptoAmount, precision }).toPrecision()
|
|
161
|
-
const usdValue = bn(amount).times(usdPrice).toNumber()
|
|
162
|
-
|
|
163
|
-
if (usdValue < 0.01) return '< $0.01'
|
|
164
|
-
if (usdValue < 1000) return `$${usdValue.toFixed(2)}`
|
|
165
|
-
if (usdValue < 1000000) return `$${(usdValue / 1000).toFixed(2)}K`
|
|
166
|
-
return `$${(usdValue / 1000000).toFixed(2)}M`
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export type { MarketData }
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import { useAppKitProvider } from '@reown/appkit/react'
|
|
2
|
-
import type { Provider } from '@reown/appkit-adapter-solana/react'
|
|
3
|
-
import { useAppKitConnection } from '@reown/appkit-adapter-solana/react'
|
|
4
|
-
import type { Transaction, VersionedTransaction } from '@solana/web3.js'
|
|
5
|
-
import { useCallback, useMemo, useState } from 'react'
|
|
6
|
-
|
|
7
|
-
import { checkSolanaStatus, waitForSolanaConfirmation } from '../services/transactionStatus'
|
|
8
|
-
|
|
9
|
-
type AnyTransaction = Transaction | VersionedTransaction
|
|
10
|
-
type TransactionSignature = string | { toString: () => string }
|
|
11
|
-
type MessageSignature = Uint8Array
|
|
12
|
-
|
|
13
|
-
type SolanaProviderExtended = Provider & {
|
|
14
|
-
sendTransaction?: (
|
|
15
|
-
transaction: AnyTransaction,
|
|
16
|
-
connection: unknown,
|
|
17
|
-
) => Promise<TransactionSignature>
|
|
18
|
-
signTransaction?: <T extends AnyTransaction>(transaction: T) => Promise<T>
|
|
19
|
-
signMessage?: (message: Uint8Array) => Promise<MessageSignature>
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type SendTransactionParams = {
|
|
23
|
-
transaction: Transaction | VersionedTransaction
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export type SignMessageParams = {
|
|
27
|
-
message: Uint8Array | string
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type SolanaSigningState = {
|
|
31
|
-
isLoading: boolean
|
|
32
|
-
error: string | undefined
|
|
33
|
-
signature: string | undefined
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type UseSolanaSigningResult = {
|
|
37
|
-
isConnected: boolean
|
|
38
|
-
address: string | undefined
|
|
39
|
-
connection: ReturnType<typeof useAppKitConnection>['connection']
|
|
40
|
-
sendTransaction: (params: SendTransactionParams) => Promise<string>
|
|
41
|
-
signMessage: (params: SignMessageParams) => Promise<Uint8Array>
|
|
42
|
-
signTransaction: <T extends Transaction | VersionedTransaction>(transaction: T) => Promise<T>
|
|
43
|
-
state: SolanaSigningState
|
|
44
|
-
reset: () => void
|
|
45
|
-
checkTxStatus: (signature: string) => ReturnType<typeof checkSolanaStatus>
|
|
46
|
-
waitForConfirmation: (
|
|
47
|
-
signature: string,
|
|
48
|
-
commitment?: 'confirmed' | 'finalized',
|
|
49
|
-
) => ReturnType<typeof waitForSolanaConfirmation>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const useSolanaSigning = (): UseSolanaSigningResult => {
|
|
53
|
-
const { walletProvider } = useAppKitProvider<Provider>('solana')
|
|
54
|
-
const provider = walletProvider as SolanaProviderExtended | undefined
|
|
55
|
-
const { connection } = useAppKitConnection()
|
|
56
|
-
|
|
57
|
-
const [state, setState] = useState<SolanaSigningState>({
|
|
58
|
-
isLoading: false,
|
|
59
|
-
error: undefined,
|
|
60
|
-
signature: undefined,
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const address = useMemo(() => {
|
|
64
|
-
if (!provider) {
|
|
65
|
-
return undefined
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const publicKey = provider.publicKey
|
|
70
|
-
if (!publicKey) {
|
|
71
|
-
return undefined
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const addressStr = publicKey?.toBase58?.() ?? publicKey?.toString?.() ?? undefined
|
|
75
|
-
return addressStr
|
|
76
|
-
} catch {
|
|
77
|
-
return undefined
|
|
78
|
-
}
|
|
79
|
-
}, [provider])
|
|
80
|
-
|
|
81
|
-
const sendTransaction = useCallback(
|
|
82
|
-
async (params: SendTransactionParams): Promise<string> => {
|
|
83
|
-
if (!provider?.sendTransaction) {
|
|
84
|
-
throw new Error('Solana wallet not connected')
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
if (!connection) {
|
|
88
|
-
throw new Error('Solana connection not available')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined, signature: undefined }))
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
const signature: TransactionSignature = await provider.sendTransaction(
|
|
95
|
-
params.transaction,
|
|
96
|
-
connection,
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
const signatureStr = typeof signature === 'string' ? signature : signature.toString()
|
|
100
|
-
|
|
101
|
-
if (!signatureStr) {
|
|
102
|
-
throw new Error('Transaction submitted but no signature returned')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
setState(prev => ({ ...prev, isLoading: false, signature: signatureStr }))
|
|
106
|
-
return signatureStr
|
|
107
|
-
} catch (error) {
|
|
108
|
-
const errorMessage =
|
|
109
|
-
error instanceof Error ? error.message : 'Failed to send Solana transaction'
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
errorMessage.toLowerCase().includes('rejected') ||
|
|
113
|
-
errorMessage.toLowerCase().includes('denied') ||
|
|
114
|
-
errorMessage.toLowerCase().includes('cancelled') ||
|
|
115
|
-
errorMessage.toLowerCase().includes('user refused') ||
|
|
116
|
-
errorMessage.toLowerCase().includes('user rejected')
|
|
117
|
-
) {
|
|
118
|
-
setState(prev => ({ ...prev, isLoading: false, error: 'Transaction rejected by user' }))
|
|
119
|
-
throw new Error('Transaction rejected by user')
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
123
|
-
throw error
|
|
124
|
-
}
|
|
125
|
-
},
|
|
126
|
-
[provider, connection],
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
const signTransaction = useCallback(
|
|
130
|
-
async <T extends Transaction | VersionedTransaction>(transaction: T): Promise<T> => {
|
|
131
|
-
if (!provider?.signTransaction) {
|
|
132
|
-
throw new Error('Solana wallet not connected or does not support signTransaction')
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined }))
|
|
136
|
-
|
|
137
|
-
try {
|
|
138
|
-
const signedTransaction = await provider.signTransaction(transaction)
|
|
139
|
-
|
|
140
|
-
setState(prev => ({ ...prev, isLoading: false }))
|
|
141
|
-
return signedTransaction as T
|
|
142
|
-
} catch (error) {
|
|
143
|
-
const errorMessage = error instanceof Error ? error.message : 'Failed to sign transaction'
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
errorMessage.toLowerCase().includes('rejected') ||
|
|
147
|
-
errorMessage.toLowerCase().includes('denied') ||
|
|
148
|
-
errorMessage.toLowerCase().includes('cancelled') ||
|
|
149
|
-
errorMessage.toLowerCase().includes('user refused') ||
|
|
150
|
-
errorMessage.toLowerCase().includes('user rejected')
|
|
151
|
-
) {
|
|
152
|
-
setState(prev => ({ ...prev, isLoading: false, error: 'Signing rejected by user' }))
|
|
153
|
-
throw new Error('Signing rejected by user')
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
157
|
-
throw error
|
|
158
|
-
}
|
|
159
|
-
},
|
|
160
|
-
[provider],
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
const signMessage = useCallback(
|
|
164
|
-
async (params: SignMessageParams): Promise<Uint8Array> => {
|
|
165
|
-
if (!provider?.signMessage) {
|
|
166
|
-
throw new Error('Solana wallet not connected or does not support signMessage')
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
setState(prev => ({ ...prev, isLoading: true, error: undefined }))
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const messageBytes =
|
|
173
|
-
typeof params.message === 'string'
|
|
174
|
-
? new TextEncoder().encode(params.message)
|
|
175
|
-
: params.message
|
|
176
|
-
|
|
177
|
-
const signature = await provider.signMessage(messageBytes)
|
|
178
|
-
|
|
179
|
-
setState(prev => ({ ...prev, isLoading: false }))
|
|
180
|
-
return signature
|
|
181
|
-
} catch (error) {
|
|
182
|
-
const errorMessage = error instanceof Error ? error.message : 'Failed to sign message'
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
errorMessage.toLowerCase().includes('rejected') ||
|
|
186
|
-
errorMessage.toLowerCase().includes('denied') ||
|
|
187
|
-
errorMessage.toLowerCase().includes('cancelled') ||
|
|
188
|
-
errorMessage.toLowerCase().includes('user refused') ||
|
|
189
|
-
errorMessage.toLowerCase().includes('user rejected')
|
|
190
|
-
) {
|
|
191
|
-
setState(prev => ({
|
|
192
|
-
...prev,
|
|
193
|
-
isLoading: false,
|
|
194
|
-
error: 'Message signing rejected by user',
|
|
195
|
-
}))
|
|
196
|
-
throw new Error('Message signing rejected by user')
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
setState(prev => ({ ...prev, isLoading: false, error: errorMessage }))
|
|
200
|
-
throw error
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
[provider],
|
|
204
|
-
)
|
|
205
|
-
|
|
206
|
-
const reset = useCallback(() => {
|
|
207
|
-
setState({
|
|
208
|
-
isLoading: false,
|
|
209
|
-
error: undefined,
|
|
210
|
-
signature: undefined,
|
|
211
|
-
})
|
|
212
|
-
}, [])
|
|
213
|
-
|
|
214
|
-
const checkTxStatus = useCallback(
|
|
215
|
-
(signature: string) => {
|
|
216
|
-
if (!connection) {
|
|
217
|
-
return Promise.resolve({
|
|
218
|
-
status: 'pending' as const,
|
|
219
|
-
error: 'Solana connection not available',
|
|
220
|
-
})
|
|
221
|
-
}
|
|
222
|
-
return checkSolanaStatus(
|
|
223
|
-
signature,
|
|
224
|
-
connection as unknown as Parameters<typeof checkSolanaStatus>[1],
|
|
225
|
-
)
|
|
226
|
-
},
|
|
227
|
-
[connection],
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
const waitForConfirmation = useCallback(
|
|
231
|
-
(signature: string, commitment: 'confirmed' | 'finalized' = 'confirmed') => {
|
|
232
|
-
if (!connection) {
|
|
233
|
-
return Promise.resolve({
|
|
234
|
-
status: 'failed' as const,
|
|
235
|
-
error: 'Solana connection not available',
|
|
236
|
-
})
|
|
237
|
-
}
|
|
238
|
-
return waitForSolanaConfirmation(
|
|
239
|
-
signature,
|
|
240
|
-
connection as unknown as Parameters<typeof waitForSolanaConfirmation>[1],
|
|
241
|
-
commitment,
|
|
242
|
-
)
|
|
243
|
-
},
|
|
244
|
-
[connection],
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
const actuallyConnected = !!address && !!connection
|
|
248
|
-
|
|
249
|
-
return useMemo(
|
|
250
|
-
() => ({
|
|
251
|
-
isConnected: actuallyConnected,
|
|
252
|
-
address,
|
|
253
|
-
connection,
|
|
254
|
-
sendTransaction,
|
|
255
|
-
signTransaction,
|
|
256
|
-
signMessage,
|
|
257
|
-
state,
|
|
258
|
-
reset,
|
|
259
|
-
checkTxStatus,
|
|
260
|
-
waitForConfirmation,
|
|
261
|
-
}),
|
|
262
|
-
[
|
|
263
|
-
actuallyConnected,
|
|
264
|
-
address,
|
|
265
|
-
connection,
|
|
266
|
-
sendTransaction,
|
|
267
|
-
signTransaction,
|
|
268
|
-
signMessage,
|
|
269
|
-
state,
|
|
270
|
-
reset,
|
|
271
|
-
checkTxStatus,
|
|
272
|
-
waitForConfirmation,
|
|
273
|
-
],
|
|
274
|
-
)
|
|
275
|
-
}
|