@openocean.finance/widget 1.0.43 → 1.0.45
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/dist/esm/App.js.map +1 -1
- package/dist/esm/AppDrawer.style.d.ts +1 -1
- package/dist/esm/components/ActiveTransactions/ActiveTransactions.style.d.ts +2 -2
- package/dist/esm/components/AmountInput/AmountInput.style.d.ts +1 -1
- package/dist/esm/components/AmountInput/AmountInputAdornment.style.d.ts +1 -1
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +37 -33
- package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
- package/dist/esm/components/Avatar/Avatar.style.d.ts +1 -1
- package/dist/esm/components/Avatar/SmallAvatar.d.ts +1 -1
- package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js +1 -1
- package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js.map +1 -1
- package/dist/esm/components/ButtonTertiary.d.ts +1 -1
- package/dist/esm/components/Card/CardHeader.d.ts +1 -1
- package/dist/esm/components/Card/CardIconButton.d.ts +1 -1
- package/dist/esm/components/Card/InputCard.d.ts +1 -1
- package/dist/esm/components/ContractComponent/NFT/NFT.style.d.ts +1 -1
- package/dist/esm/components/Header/Header.style.d.ts +2 -2
- package/dist/esm/components/Header/SettingsButton.style.d.ts +2 -2
- package/dist/esm/components/Header/WalletHeader.js +4 -4
- package/dist/esm/components/Header/WalletHeader.js.map +1 -1
- package/dist/esm/components/ListItem/ListItem.d.ts +1 -1
- package/dist/esm/components/Messages/WarningMessages.js +1 -0
- package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
- package/dist/esm/components/Messages/useMessageQueue.js +1 -1
- package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
- package/dist/esm/components/Search/SearchInput.style.d.ts +1 -1
- package/dist/esm/components/SelectTokenButton/SelectTokenButton.style.d.ts +1 -1
- package/dist/esm/components/SendToWallet/SendToWallet.style.d.ts +1 -1
- package/dist/esm/components/Skeleton/WidgetSkeleton.style.d.ts +3 -3
- package/dist/esm/components/StepActions/StepActions.style.d.ts +1 -1
- package/dist/esm/components/Tabs/Tabs.style.d.ts +2 -2
- package/dist/esm/components/TokenList/TokenList.style.d.ts +2 -2
- package/dist/esm/components/TransactionDetails.js +4 -1
- package/dist/esm/components/TransactionDetails.js.map +1 -1
- package/dist/esm/config/defaultChainIds.js +3 -0
- package/dist/esm/config/defaultChainIds.js.map +1 -1
- package/dist/esm/config/version.d.ts +1 -1
- package/dist/esm/config/version.js +1 -1
- package/dist/esm/cross/adapters/AcrossAdapter.js +21 -22
- package/dist/esm/cross/adapters/AcrossAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/BaseSwapAdapter.d.ts +17 -9
- package/dist/esm/cross/adapters/BaseSwapAdapter.js +15 -4
- package/dist/esm/cross/adapters/BaseSwapAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/DebridgeAdapter.js +8 -7
- package/dist/esm/cross/adapters/DebridgeAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/LifiAdapter.js +7 -6
- package/dist/esm/cross/adapters/LifiAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/MayanAdapter.js +1 -2
- package/dist/esm/cross/adapters/MayanAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/NearIntentsAdapter.d.ts +13 -6
- package/dist/esm/cross/adapters/NearIntentsAdapter.js +252 -75
- package/dist/esm/cross/adapters/NearIntentsAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/OptimexAdapter.js +2 -1
- package/dist/esm/cross/adapters/OptimexAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/OrbiterAdapter.js +9 -8
- package/dist/esm/cross/adapters/OrbiterAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/RelayAdapter.js +40 -15
- package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/SymbiosisAdapter.js +13 -14
- package/dist/esm/cross/adapters/SymbiosisAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/XYFinanceAdapter.js +13 -17
- package/dist/esm/cross/adapters/XYFinanceAdapter.js.map +1 -1
- package/dist/esm/cross/adapters/index.d.ts +2 -0
- package/dist/esm/cross/adapters/index.js +2 -1
- package/dist/esm/cross/adapters/index.js.map +1 -1
- package/dist/esm/cross/constants/index.d.ts +3 -1
- package/dist/esm/cross/constants/index.js +2 -0
- package/dist/esm/cross/constants/index.js.map +1 -1
- package/dist/esm/cross/crossChainQuote.d.ts +1 -1
- package/dist/esm/cross/crossChainQuote.js +75 -12
- package/dist/esm/cross/crossChainQuote.js.map +1 -1
- package/dist/esm/cross/factory.d.ts +3 -1
- package/dist/esm/cross/factory.js +10 -12
- package/dist/esm/cross/factory.js.map +1 -1
- package/dist/esm/cross/registry.d.ts +1 -1
- package/dist/esm/hooks/useAvailableChains.js +47 -40
- package/dist/esm/hooks/useAvailableChains.js.map +1 -1
- package/dist/esm/hooks/useChains.js.map +1 -1
- package/dist/esm/hooks/useRoutes.js +11 -3
- package/dist/esm/hooks/useRoutes.js.map +1 -1
- package/dist/esm/hooks/useSwapOnly.js +8 -2
- package/dist/esm/hooks/useSwapOnly.js.map +1 -1
- package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
- package/dist/esm/hooks/useTokens.d.ts +1 -0
- package/dist/esm/hooks/useTokens.js +14 -0
- package/dist/esm/hooks/useTokens.js.map +1 -1
- package/dist/esm/i18n/en.json +2 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pages/MainPage/MainWarningMessages.js.map +1 -1
- package/dist/esm/pages/SendToWallet/BookmarksPage.js +1 -1
- package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +2 -2
- package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +2 -2
- package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
- package/dist/esm/pages/SendToWallet/SendToWalletPage.style.d.ts +5 -5
- package/dist/esm/pages/SettingsPage/SettingsCard/SettingCard.style.d.ts +1 -1
- package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
- package/dist/esm/services/ExecuteRoute.d.ts +1 -0
- package/dist/esm/services/ExecuteRoute.js +292 -9
- package/dist/esm/services/ExecuteRoute.js.map +1 -1
- package/dist/esm/services/OpenOceanService.js +30 -4
- package/dist/esm/services/OpenOceanService.js.map +1 -1
- package/dist/esm/stores/bookmarks/createBookmarkStore.js.map +1 -1
- package/dist/esm/types/widget.d.ts +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/dist/esm/utils/chainType.d.ts +1 -0
- package/dist/esm/utils/chainType.js +15 -0
- package/dist/esm/utils/chainType.js.map +1 -1
- package/dist/esm/utils/getPriceImpact.js +3 -3
- package/dist/esm/utils/getPriceImpact.js.map +1 -1
- package/package.json +19 -7
- package/src/App.tsx +0 -1
- package/src/components/AmountInput/AmountInputEndAdornment.tsx +39 -34
- package/src/components/BaseTransactionButton/BaseTransactionButton.tsx +3 -2
- package/src/components/Header/WalletHeader.tsx +4 -4
- package/src/components/Messages/WarningMessages.tsx +1 -0
- package/src/components/Messages/useMessageQueue.ts +1 -1
- package/src/components/TransactionDetails.tsx +8 -4
- package/src/config/defaultChainIds.ts +3 -0
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/AcrossAdapter.ts +21 -22
- package/src/cross/adapters/BaseSwapAdapter.ts +24 -8
- package/src/cross/adapters/DebridgeAdapter.ts +11 -11
- package/src/cross/adapters/LifiAdapter.ts +11 -10
- package/src/cross/adapters/MayanAdapter.ts +1 -2
- package/src/cross/adapters/NearIntentsAdapter.ts +303 -129
- package/src/cross/adapters/OptimexAdapter.ts +12 -11
- package/src/cross/adapters/OrbiterAdapter.ts +17 -16
- package/src/cross/adapters/RelayAdapter.ts +42 -17
- package/src/cross/adapters/SymbiosisAdapter.ts +13 -14
- package/src/cross/adapters/XYFinanceAdapter.ts +15 -19
- package/src/cross/adapters/index.ts +2 -1
- package/src/cross/constants/index.ts +4 -0
- package/src/cross/crossChainQuote.ts +79 -21
- package/src/cross/factory.ts +12 -12
- package/src/cross/registry.ts +1 -1
- package/src/hooks/useAvailableChains.ts +50 -43
- package/src/hooks/useChains.ts +0 -1
- package/src/hooks/useExplorer.ts +6 -6
- package/src/hooks/useRoutes.ts +12 -4
- package/src/hooks/useSwapOnly.ts +9 -2
- package/src/hooks/useTokenAddressBalance.ts +1 -1
- package/src/hooks/useTokens.ts +20 -5
- package/src/i18n/en.json +2 -2
- package/src/index.ts +0 -1
- package/src/pages/MainPage/MainWarningMessages.tsx +0 -1
- package/src/pages/SendToWallet/BookmarksPage.tsx +1 -1
- package/src/pages/SendToWallet/RecentWalletsPage.tsx +2 -2
- package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +2 -2
- package/src/providers/WidgetProvider/WidgetProvider.tsx +0 -1
- package/src/services/ExecuteRoute.ts +426 -64
- package/src/services/OpenOceanService.ts +31 -7
- package/src/stores/bookmarks/createBookmarkStore.ts +15 -15
- package/src/types/widget.ts +2 -1
- package/src/utils/chainType.ts +25 -1
- package/src/utils/getPriceImpact.ts +3 -3
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -12,7 +12,17 @@ import { getPublicClient, getWalletClient } from 'wagmi/actions'
|
|
|
12
12
|
import { bridgeExecuteSwap } from '../cross/crossChainQuote.js'
|
|
13
13
|
import { useSettingsStore } from '../stores/settings/useSettingsStore.js'
|
|
14
14
|
import { sendAndConfirmSolanaTransaction } from './SendAndConfirmSolanaTransaction.js'
|
|
15
|
+
import { adaptBitcoinWallet } from '@relayprotocol/relay-bitcoin-wallet-adapter'
|
|
16
|
+
import * as bitcoin from 'bitcoinjs-lib';
|
|
15
17
|
|
|
18
|
+
type DynamicSignPsbtParams = {
|
|
19
|
+
allowedSighash: number[];
|
|
20
|
+
unsignedPsbtBase64: string;
|
|
21
|
+
signature: Array<{
|
|
22
|
+
address: string;
|
|
23
|
+
signingIndexes: number[];
|
|
24
|
+
}>;
|
|
25
|
+
};
|
|
16
26
|
const OpenOceanABI = [
|
|
17
27
|
{
|
|
18
28
|
inputs: [
|
|
@@ -164,24 +174,24 @@ async function swapQuoteMEV(
|
|
|
164
174
|
const inAmount = response?.inAmount
|
|
165
175
|
const inTokenDecimals = response?.inToken?.decimals || 18
|
|
166
176
|
const inTokenPrice = Number(response?.inToken?.priceUSD || 0)
|
|
167
|
-
const amount = decimals2Amount(inAmount, inTokenDecimals) * inTokenPrice
|
|
177
|
+
const amount = decimals2Amount(inAmount as string, inTokenDecimals) * inTokenPrice
|
|
168
178
|
if (amount < 1) {
|
|
169
179
|
return response
|
|
170
180
|
}
|
|
171
|
-
const { publicClient } = options
|
|
181
|
+
const { publicClient } = options || {}
|
|
172
182
|
const OPENOCEAN_CONTRACT = new ethers.Contract(
|
|
173
183
|
'0x6352a56caadC4F1E25CD6c75970Fa768A3304e64',
|
|
174
184
|
OpenOceanABI
|
|
175
185
|
)
|
|
176
186
|
if (
|
|
177
187
|
ethers.hexlify(ethers.getBytes(response?.data || '0x').slice(0, 4)) !==
|
|
178
|
-
OPENOCEAN_CONTRACT
|
|
188
|
+
OPENOCEAN_CONTRACT?.interface?.getFunction('swap')?.selector
|
|
179
189
|
) {
|
|
180
190
|
return response
|
|
181
191
|
}
|
|
182
192
|
const oldCallData = OPENOCEAN_CONTRACT.interface.decodeFunctionData(
|
|
183
193
|
'swap',
|
|
184
|
-
response?.data
|
|
194
|
+
response?.data as any
|
|
185
195
|
)
|
|
186
196
|
const callData = [...oldCallData]
|
|
187
197
|
callData[1] = [...oldCallData[1]]
|
|
@@ -291,44 +301,53 @@ async function executeSolanaSwap(
|
|
|
291
301
|
|
|
292
302
|
const { transactionRequest, type, quoteData } = step || {}
|
|
293
303
|
if ((type as any) === 'bridge') {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
transaction
|
|
305
|
-
) {
|
|
306
|
-
const connector = options.account.connector as any
|
|
307
|
-
if (typeof connector.signTransaction !== 'function') {
|
|
308
|
-
throw new Error('Wallet does not support transaction signing')
|
|
309
|
-
}
|
|
310
|
-
// Sign transaction
|
|
311
|
-
const signature = await connector.signTransaction(transaction)
|
|
312
|
-
const serializedTransaction = signature.serialize({
|
|
313
|
-
verifySignatures: false,
|
|
314
|
-
requireAllSignatures: false,
|
|
315
|
-
})
|
|
316
|
-
const txid = await connection.sendRawTransaction(
|
|
317
|
-
serializedTransaction,
|
|
318
|
-
{
|
|
319
|
-
skipPreflight: true,
|
|
320
|
-
}
|
|
321
|
-
)
|
|
322
|
-
return { signature: txid }
|
|
304
|
+
// Define signAndSendTransaction function
|
|
305
|
+
const signAndSendTransaction = async (transaction: Transaction | VersionedTransaction) => {
|
|
306
|
+
try {
|
|
307
|
+
// Ensure transaction is properly formatted
|
|
308
|
+
if (
|
|
309
|
+
transaction instanceof VersionedTransaction ||
|
|
310
|
+
transaction instanceof Transaction
|
|
311
|
+
) {
|
|
312
|
+
const connector = options.account.connector as any
|
|
313
|
+
if (typeof connector.signTransaction !== 'function') {
|
|
314
|
+
throw new Error('Wallet does not support transaction signing')
|
|
323
315
|
}
|
|
324
316
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
317
|
+
// Sign transaction
|
|
318
|
+
const signature = await connector.signTransaction(transaction)
|
|
319
|
+
const serializedTransaction = signature.serialize({
|
|
320
|
+
verifySignatures: false,
|
|
321
|
+
requireAllSignatures: false,
|
|
322
|
+
})
|
|
323
|
+
const txid = await connection.sendRawTransaction(
|
|
324
|
+
serializedTransaction,
|
|
325
|
+
{
|
|
326
|
+
skipPreflight: true,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
return { signature: txid }
|
|
329
330
|
}
|
|
331
|
+
|
|
332
|
+
throw new Error('Invalid transaction type')
|
|
333
|
+
} catch (error) {
|
|
334
|
+
console.error('Transaction sending failed:', error)
|
|
335
|
+
throw error
|
|
330
336
|
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const adaptedWallet: any = adaptSolanaWallet(
|
|
340
|
+
options.account.address?.toString() ||
|
|
341
|
+
'1nc1nerator11111111111111111111111111111111',
|
|
342
|
+
792703809, //chain id that Relay uses to identify solana
|
|
343
|
+
connection,
|
|
344
|
+
signAndSendTransaction
|
|
331
345
|
)
|
|
346
|
+
|
|
347
|
+
// Expose connection and sendTransaction method for adapters to use
|
|
348
|
+
adaptedWallet.connection = connection
|
|
349
|
+
adaptedWallet.sendTransaction = signAndSendTransaction
|
|
350
|
+
|
|
332
351
|
const signedTx = await bridgeExecuteSwap({
|
|
333
352
|
quoteData: quoteData,
|
|
334
353
|
walletClient: adaptedWallet,
|
|
@@ -352,7 +371,7 @@ async function executeSolanaSwap(
|
|
|
352
371
|
const txData: any = transactionRequest?.data || ''
|
|
353
372
|
const dexId = transactionRequest?.type || 0
|
|
354
373
|
if (step.action.fromChainId === step.action.toChainId) {
|
|
355
|
-
if (dexId === 6 || dexId === 7 || dexId === 9) {
|
|
374
|
+
if (dexId === 6 || dexId === 7 || dexId === 9 || dexId === 10) {
|
|
356
375
|
transaction = VersionedTransaction.deserialize(
|
|
357
376
|
hexToUint8Array(txData)
|
|
358
377
|
)
|
|
@@ -415,15 +434,15 @@ async function executeSolanaSwap(
|
|
|
415
434
|
process.error =
|
|
416
435
|
error instanceof Error
|
|
417
436
|
? {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
437
|
+
code: 'EXECUTION_ERROR',
|
|
438
|
+
message: error.message,
|
|
439
|
+
htmlMessage: error.message,
|
|
440
|
+
}
|
|
422
441
|
: {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
442
|
+
code: 'UNKNOWN_ERROR',
|
|
443
|
+
message: 'Unknown error occurred',
|
|
444
|
+
htmlMessage: 'Unknown error occurred',
|
|
445
|
+
}
|
|
427
446
|
step.execution!.status = 'FAILED'
|
|
428
447
|
options.updateRouteHook?.(route)
|
|
429
448
|
throw error
|
|
@@ -439,7 +458,6 @@ async function executeEvmSwap(
|
|
|
439
458
|
): Promise<void> {
|
|
440
459
|
try {
|
|
441
460
|
let walletClient = await getWalletClient(options.wagmiConfig)
|
|
442
|
-
|
|
443
461
|
// Check if wallet is connected
|
|
444
462
|
if (!walletClient) {
|
|
445
463
|
if (options.account?.connector && options.onDisconnect) {
|
|
@@ -460,8 +478,7 @@ async function executeEvmSwap(
|
|
|
460
478
|
)
|
|
461
479
|
const currentChainId = await walletClient.getChainId()
|
|
462
480
|
const targetChainId = step.action.fromChainId
|
|
463
|
-
|
|
464
|
-
if (currentChainId != targetChainId) {
|
|
481
|
+
if (currentChainId != targetChainId && targetChainId != 20000000000001) {
|
|
465
482
|
try {
|
|
466
483
|
// Try to switch to target chain
|
|
467
484
|
await walletClient.switchChain({ id: targetChainId })
|
|
@@ -508,11 +525,11 @@ async function executeEvmSwap(
|
|
|
508
525
|
'0x0000000000000000000000000000000000001010',
|
|
509
526
|
].indexOf(step.action.fromToken.address) === -1 &&
|
|
510
527
|
step.estimate.approvalAddress !==
|
|
511
|
-
|
|
528
|
+
'0x0000000000000000000000000000000000000000'
|
|
512
529
|
) {
|
|
513
530
|
let allowance = 0n
|
|
514
531
|
try {
|
|
515
|
-
allowance = (await publicClient.readContract({
|
|
532
|
+
allowance = (await (publicClient as any).readContract({
|
|
516
533
|
address: step.action.fromToken.address as `0x${string}`,
|
|
517
534
|
abi: [
|
|
518
535
|
{
|
|
@@ -546,14 +563,14 @@ async function executeEvmSwap(
|
|
|
546
563
|
BigInt(step.action.fromAmount) +
|
|
547
564
|
BigInt(
|
|
548
565
|
route?.prependedOperatingExpenseCost ||
|
|
549
|
-
|
|
550
|
-
|
|
566
|
+
route?.data?.prependedOperatingExpenseCost ||
|
|
567
|
+
'0'
|
|
551
568
|
)
|
|
552
569
|
if (allowance < BigInt(amount)) {
|
|
553
570
|
const approvalAmount = options.infiniteApproval
|
|
554
571
|
? BigInt(
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
'0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
|
|
573
|
+
)
|
|
557
574
|
: BigInt(amount)
|
|
558
575
|
|
|
559
576
|
const hash = await walletClient.writeContract({
|
|
@@ -590,7 +607,7 @@ async function executeEvmSwap(
|
|
|
590
607
|
quoteData: quoteData,
|
|
591
608
|
walletClient: walletClient,
|
|
592
609
|
})
|
|
593
|
-
hash = result
|
|
610
|
+
hash = result?.sourceTxHash || ''
|
|
594
611
|
} else {
|
|
595
612
|
const txRequest = {
|
|
596
613
|
chain: publicClient.chain,
|
|
@@ -672,21 +689,364 @@ async function executeEvmSwap(
|
|
|
672
689
|
process.error =
|
|
673
690
|
error instanceof Error
|
|
674
691
|
? {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
692
|
+
code: 'EXECUTION_ERROR',
|
|
693
|
+
message: error.message,
|
|
694
|
+
htmlMessage: error.message,
|
|
695
|
+
}
|
|
679
696
|
: {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
697
|
+
code: 'UNKNOWN_ERROR',
|
|
698
|
+
message: 'Unknown error occurred',
|
|
699
|
+
htmlMessage: 'Unknown error occurred',
|
|
700
|
+
}
|
|
684
701
|
step.execution!.status = 'FAILED'
|
|
685
702
|
options.updateRouteHook?.(route)
|
|
686
703
|
throw error
|
|
687
704
|
}
|
|
688
705
|
}
|
|
689
706
|
|
|
707
|
+
const createPsbtOptions = (_: any, request: any) => {
|
|
708
|
+
var _a;
|
|
709
|
+
const psbtSignOptions: any = {
|
|
710
|
+
autoFinalized: false,
|
|
711
|
+
};
|
|
712
|
+
if (request.signature) {
|
|
713
|
+
// validatePsbt(psbt, request.allowedSighash, request.signature);
|
|
714
|
+
|
|
715
|
+
const toSignInputs = [];
|
|
716
|
+
for (const signature of request.signature) {
|
|
717
|
+
if ((_a = signature.signingIndexes) === null || _a === void 0 ? void 0 : _a.length) {
|
|
718
|
+
for (const index of signature.signingIndexes) {
|
|
719
|
+
toSignInputs.push({
|
|
720
|
+
address: signature.address,
|
|
721
|
+
disableAddressValidation: signature.disableAddressValidation,
|
|
722
|
+
index,
|
|
723
|
+
sighashTypes: request.allowedSighash,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
psbtSignOptions.toSignInputs = toSignInputs;
|
|
729
|
+
}
|
|
730
|
+
return psbtSignOptions;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
// Helper function to convert hex response to base64
|
|
734
|
+
function convertHexToBase64(hexString: string): string {
|
|
735
|
+
try {
|
|
736
|
+
const buffer = Buffer.from(hexString, 'hex');
|
|
737
|
+
return buffer.toString('base64');
|
|
738
|
+
} catch (error) {
|
|
739
|
+
// If conversion fails, it might already be base64
|
|
740
|
+
return hexString;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Helper function to extract signed PSBT from wallet response
|
|
745
|
+
function extractSignedPsbt(response: any): string | null {
|
|
746
|
+
if (!response) return null;
|
|
747
|
+
if (typeof response === 'string') return response;
|
|
748
|
+
return response.signedPsbtHex || response.signedPsbtBase64 || response.signedPsbt || null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
// BTC 主网
|
|
753
|
+
const network = bitcoin.networks.bitcoin;
|
|
754
|
+
|
|
755
|
+
export async function sendBTCWithPhantom(sender: string, recipient: string, amount: number) {
|
|
756
|
+
const phantom = (window as any).phantom?.bitcoin;
|
|
757
|
+
if (!phantom) throw new Error("Phantom Bitcoin provider not found");
|
|
758
|
+
// 1. 获取 UTXO(这里使用 mempool.space API,你也可以换自己的)
|
|
759
|
+
const utxos = await fetch(
|
|
760
|
+
`https://mempool.space/api/address/${sender}/utxo`
|
|
761
|
+
).then((r) => r.json());
|
|
762
|
+
|
|
763
|
+
if (!utxos.length) throw new Error("No UTXO available");
|
|
764
|
+
|
|
765
|
+
// ====== 费用参数 ======
|
|
766
|
+
// amount / fee 单位都按 satoshi 处理,调用方需要保证一致
|
|
767
|
+
const fee = 5000; // 手续费(可以根据当前网络费率动态调整)
|
|
768
|
+
const required = BigInt(amount + fee);
|
|
769
|
+
|
|
770
|
+
// 2. 简单的累加选币:从小到大选 UTXO,直到覆盖 amount + fee
|
|
771
|
+
const sortedUtxos = [...utxos].sort(
|
|
772
|
+
(a: any, b: any) => Number(a.value) - Number(b.value)
|
|
773
|
+
);
|
|
774
|
+
|
|
775
|
+
const selectedUtxos: any[] = [];
|
|
776
|
+
let totalInput = 0n;
|
|
777
|
+
|
|
778
|
+
for (const utxo of sortedUtxos) {
|
|
779
|
+
selectedUtxos.push(utxo);
|
|
780
|
+
totalInput += BigInt(utxo.value);
|
|
781
|
+
if (totalInput >= required) break;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
if (totalInput < required) {
|
|
785
|
+
throw new Error("Insufficient balance: UTXOs do not cover amount + fee");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const changeValue = totalInput - required;
|
|
789
|
+
|
|
790
|
+
// 可选:避免输出过小(dust),这里简单判断 >0 即可,如需更严格可改成 > 546
|
|
791
|
+
if (changeValue <= 0n) {
|
|
792
|
+
throw new Error("UTXOs too small to cover amount and fee with change");
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// 3. 构造 PSBT(多输入)
|
|
796
|
+
const psbt = new bitcoin.Psbt({ network });
|
|
797
|
+
|
|
798
|
+
selectedUtxos.forEach((utxo) => {
|
|
799
|
+
psbt.addInput({
|
|
800
|
+
hash: utxo.txid,
|
|
801
|
+
index: utxo.vout,
|
|
802
|
+
witnessUtxo: {
|
|
803
|
+
// bitcoinjs-lib 期望 bigint,这里将 mempool 返回的 number 转成 bigint
|
|
804
|
+
value: BigInt(utxo.value),
|
|
805
|
+
script: bitcoin.address.toOutputScript(sender, network),
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
// 目标地址 output
|
|
811
|
+
psbt.addOutput({
|
|
812
|
+
address: recipient,
|
|
813
|
+
value: BigInt(amount),
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// 找零 output
|
|
817
|
+
psbt.addOutput({
|
|
818
|
+
address: sender,
|
|
819
|
+
value: changeValue,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// 4. 调用 Phantom.signPSBT
|
|
823
|
+
const psbtBase64 = psbt.toBase64();
|
|
824
|
+
|
|
825
|
+
const signOptions = {
|
|
826
|
+
autoFinalized: false,
|
|
827
|
+
toSignInputs: selectedUtxos.map((_, index) => ({
|
|
828
|
+
address: sender,
|
|
829
|
+
index,
|
|
830
|
+
// SIGHASH_ALL (0x01) 通常足够,如有需要可根据业务调整
|
|
831
|
+
sighashTypes: [0, 1], // 保持和原来一致
|
|
832
|
+
})),
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
const signed = await phantom.signPSBT(psbtBase64, signOptions);
|
|
836
|
+
|
|
837
|
+
// Phantom 返回 Base64
|
|
838
|
+
const signedPsbt = bitcoin.Psbt.fromBase64(signed);
|
|
839
|
+
|
|
840
|
+
// 5. Finalize + 提取原始交易
|
|
841
|
+
signedPsbt.finalizeAllInputs();
|
|
842
|
+
const rawTx = signedPsbt.extractTransaction().toHex();
|
|
843
|
+
|
|
844
|
+
// 6. 广播 Raw TX
|
|
845
|
+
const txid = await fetch("https://mempool.space/api/tx", {
|
|
846
|
+
method: "POST",
|
|
847
|
+
body: rawTx,
|
|
848
|
+
}).then((r) => r.text());
|
|
849
|
+
|
|
850
|
+
console.log("Broadcasted TXID:", txid);
|
|
851
|
+
return txid;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
async function executeBitcoinSwap(
|
|
856
|
+
step: ExtendedOpenOceanStep,
|
|
857
|
+
options: ExecuteRouteOptions,
|
|
858
|
+
process: Process,
|
|
859
|
+
route: ExtendedRoute
|
|
860
|
+
): Promise<void> {
|
|
861
|
+
try {
|
|
862
|
+
|
|
863
|
+
const { quoteData } = step || {}
|
|
864
|
+
const adaptedWallet: any = adaptBitcoinWallet(
|
|
865
|
+
options.account.address?.toString() || '',
|
|
866
|
+
async (_: any, __: any, dynamicParams: DynamicSignPsbtParams) => {
|
|
867
|
+
const psbtFromBase64 = bitcoin.Psbt.fromBase64(dynamicParams.unsignedPsbtBase64);
|
|
868
|
+
const psbtHex = psbtFromBase64.toHex();
|
|
869
|
+
const connector = options.account.connector;
|
|
870
|
+
const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
|
|
871
|
+
switch (connector?.name) {
|
|
872
|
+
case 'OKX Wallet': {
|
|
873
|
+
const response = await anyWindow.okxwallet?.bitcoin?.signPsbt(
|
|
874
|
+
psbtHex,
|
|
875
|
+
createPsbtOptions(psbtFromBase64, dynamicParams)
|
|
876
|
+
);
|
|
877
|
+
const signedPsbt = extractSignedPsbt(response);
|
|
878
|
+
if (!signedPsbt) {
|
|
879
|
+
throw new Error('Missing psbt response from OKX wallet');
|
|
880
|
+
}
|
|
881
|
+
return convertHexToBase64(signedPsbt);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
case 'Unisat': {
|
|
885
|
+
const response = await anyWindow.unisat?.signPsbt(
|
|
886
|
+
psbtHex,
|
|
887
|
+
createPsbtOptions(psbtFromBase64, dynamicParams)
|
|
888
|
+
);
|
|
889
|
+
const signedPsbt = extractSignedPsbt(response);
|
|
890
|
+
if (!signedPsbt) {
|
|
891
|
+
throw new Error('Missing psbt response from Unisat wallet');
|
|
892
|
+
}
|
|
893
|
+
return convertHexToBase64(signedPsbt);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
case 'Xverse': {
|
|
897
|
+
const response = await anyWindow.BitcoinProvider?.request('signPsbt', {
|
|
898
|
+
psbt: psbtHex,
|
|
899
|
+
finalize: true,
|
|
900
|
+
});
|
|
901
|
+
const signedPsbt = extractSignedPsbt(response);
|
|
902
|
+
if (!signedPsbt) {
|
|
903
|
+
throw new Error('Missing psbt response from Xverse wallet');
|
|
904
|
+
}
|
|
905
|
+
return convertHexToBase64(signedPsbt);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
case 'Phantom': {
|
|
909
|
+
const phantom = anyWindow.phantom?.bitcoin;
|
|
910
|
+
if (!phantom?.signPSBT) throw new Error('Phantom wallet does not support signPSBT');
|
|
911
|
+
|
|
912
|
+
const inputsToSign = [];
|
|
913
|
+
console.log("Phantom options = ", JSON.stringify(createPsbtOptions(psbtFromBase64, dynamicParams)));
|
|
914
|
+
console.log("psbtHex = ", psbtHex);
|
|
915
|
+
console.log("psbtBase64 = ", psbtFromBase64.toBase64());
|
|
916
|
+
|
|
917
|
+
debugger
|
|
918
|
+
for (const sig of dynamicParams.signature || []) {
|
|
919
|
+
for (const index of sig.signingIndexes || []) {
|
|
920
|
+
inputsToSign.push({
|
|
921
|
+
index,
|
|
922
|
+
address: sig.address,
|
|
923
|
+
sighashTypes: dynamicParams.allowedSighash,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
debugger
|
|
928
|
+
|
|
929
|
+
const response = await phantom.signPSBT(
|
|
930
|
+
psbtFromBase64.toBase64(), // ✅ Phantom only accepts base64
|
|
931
|
+
{
|
|
932
|
+
autoFinalize: false, // ✅ correct name
|
|
933
|
+
inputsToSign, // ✅ correct name
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
const signedPsbt = extractSignedPsbt(response);
|
|
938
|
+
if (!signedPsbt) throw new Error('Missing psbt response from Phantom wallet');
|
|
939
|
+
|
|
940
|
+
return signedPsbt; // already base64
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
default:
|
|
945
|
+
throw new Error(`Unsupported wallet: ${connector.name}`);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
);
|
|
949
|
+
adaptedWallet.sendTransaction = async (params: { recipient: string; amount: string | number }) => {
|
|
950
|
+
const connector = options.account.connector;
|
|
951
|
+
const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
|
|
952
|
+
|
|
953
|
+
// Convert amount to satoshis (BTC amount * 100000000)
|
|
954
|
+
const amountInSatoshis = Number(params.amount)
|
|
955
|
+
switch (connector?.name) {
|
|
956
|
+
case 'OKX Wallet': {
|
|
957
|
+
// OKX wallet sendBitcoin method
|
|
958
|
+
if (anyWindow.okxwallet?.bitcoin?.sendBitcoin) {
|
|
959
|
+
const txid = await anyWindow.okxwallet.bitcoin.sendBitcoin(
|
|
960
|
+
params.recipient,
|
|
961
|
+
amountInSatoshis
|
|
962
|
+
);
|
|
963
|
+
return txid;
|
|
964
|
+
}
|
|
965
|
+
throw new Error('OKX wallet does not support sendBitcoin');
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
case 'Unisat': {
|
|
969
|
+
// Unisat wallet sendBitcoin method
|
|
970
|
+
if (anyWindow.unisat?.sendBitcoin) {
|
|
971
|
+
const txid = await anyWindow.unisat.sendBitcoin(
|
|
972
|
+
params.recipient,
|
|
973
|
+
amountInSatoshis
|
|
974
|
+
);
|
|
975
|
+
return txid;
|
|
976
|
+
}
|
|
977
|
+
throw new Error('Unisat wallet does not support sendBitcoin');
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
case 'Xverse': {
|
|
981
|
+
// Xverse wallet sendBitcoin method
|
|
982
|
+
if (anyWindow.BitcoinProvider?.request) {
|
|
983
|
+
const response = await anyWindow.BitcoinProvider.request('sendBitcoin', {
|
|
984
|
+
address: params.recipient,
|
|
985
|
+
amount: amountInSatoshis,
|
|
986
|
+
});
|
|
987
|
+
return response.txid || response;
|
|
988
|
+
}
|
|
989
|
+
throw new Error('Xverse wallet does not support sendBitcoin');
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
case 'Phantom': {
|
|
993
|
+
// Phantom wallet sendBitcoin method
|
|
994
|
+
const txid = await sendBTCWithPhantom(
|
|
995
|
+
options.account.address?.toString() || '',
|
|
996
|
+
params.recipient,
|
|
997
|
+
amountInSatoshis
|
|
998
|
+
);
|
|
999
|
+
return txid;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
default:
|
|
1003
|
+
throw new Error(`Unsupported wallet: ${connector?.name}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
const signedTx = await bridgeExecuteSwap({
|
|
1008
|
+
quoteData: quoteData,
|
|
1009
|
+
walletClient: adaptedWallet,
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
if (!signedTx) {
|
|
1013
|
+
throw new Error('Failed to sign transaction')
|
|
1014
|
+
}
|
|
1015
|
+
const hash = signedTx.sourceTxHash
|
|
1016
|
+
|
|
1017
|
+
process.status = 'DONE'
|
|
1018
|
+
process.doneAt = Date.now()
|
|
1019
|
+
process.txHash = hash
|
|
1020
|
+
process.message = 'Transaction confirmed'
|
|
1021
|
+
options.updateRouteHook?.(route)
|
|
1022
|
+
|
|
1023
|
+
// process.status = 'PENDING'
|
|
1024
|
+
// process.txHash = hash
|
|
1025
|
+
// process.message = 'Transaction pending'
|
|
1026
|
+
// options.updateRouteHook?.(route)
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
console.error('Bitcoin swap execution failed:', error)
|
|
1031
|
+
debugger
|
|
1032
|
+
process.status = 'FAILED'
|
|
1033
|
+
process.error =
|
|
1034
|
+
error instanceof Error || (error && (error as any)?.message)
|
|
1035
|
+
? {
|
|
1036
|
+
code: 'EXECUTION_ERROR',
|
|
1037
|
+
message: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1038
|
+
htmlMessage: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
1039
|
+
}
|
|
1040
|
+
: {
|
|
1041
|
+
code: 'UNKNOWN_ERROR',
|
|
1042
|
+
message: 'Unknown error occurred',
|
|
1043
|
+
htmlMessage: 'Unknown error occurred',
|
|
1044
|
+
}
|
|
1045
|
+
step.execution!.status = 'FAILED'
|
|
1046
|
+
options.updateRouteHook?.(route)
|
|
1047
|
+
throw error
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
690
1050
|
// Execute transaction
|
|
691
1051
|
async function executeSwap(
|
|
692
1052
|
route: ExtendedRoute,
|
|
@@ -730,6 +1090,8 @@ async function executeSwap(
|
|
|
730
1090
|
const currentStep = route.steps[0]
|
|
731
1091
|
if (currentStep.action?.fromChainId === 1151111081099710) {
|
|
732
1092
|
await executeSolanaSwap(currentStep, options, process, updatedRoute)
|
|
1093
|
+
} else if (currentStep.action?.fromChainId === 20000000000001) {
|
|
1094
|
+
await executeBitcoinSwap(currentStep, options, process, updatedRoute)
|
|
733
1095
|
} else {
|
|
734
1096
|
await executeEvmSwap(currentStep, options, process, updatedRoute)
|
|
735
1097
|
}
|
|
@@ -17,6 +17,8 @@ export class OpenOceanService {
|
|
|
17
17
|
// Chain ID to OpenOcean chain name mapping
|
|
18
18
|
private static readonly CHAIN_ID_MAP: Record<string | number, string> = {
|
|
19
19
|
1151111081099710: 'solana', // Solana mainnet
|
|
20
|
+
20000000000001: 'bitcoin', // Bitcoin mainnet
|
|
21
|
+
20000000000006: 'near', // Near mainnet
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// Get OpenOcean supported chain name
|
|
@@ -125,7 +127,21 @@ export class OpenOceanService {
|
|
|
125
127
|
const response = await fetch(`${this.API_V4_URL}/${chainName}/tokenList`)
|
|
126
128
|
const data = await response.json()
|
|
127
129
|
if (data.code !== 200) {
|
|
128
|
-
|
|
130
|
+
if (chain === '20000000000001') {
|
|
131
|
+
data.data = [
|
|
132
|
+
{
|
|
133
|
+
address: 'bitcoin',
|
|
134
|
+
symbol: 'BTC',
|
|
135
|
+
decimals: 8,
|
|
136
|
+
isNative: true,
|
|
137
|
+
name: 'Bitcoin',
|
|
138
|
+
icon: 'https://assets.coingecko.com/coins/images/1/standard/bitcoin.png',
|
|
139
|
+
usd: '109559',
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error('Failed to fetch token list')
|
|
144
|
+
}
|
|
129
145
|
}
|
|
130
146
|
return data.data.map((token: OpenOceanToken) => {
|
|
131
147
|
let address = token.address
|
|
@@ -159,7 +175,7 @@ export class OpenOceanService {
|
|
|
159
175
|
}
|
|
160
176
|
|
|
161
177
|
static async getGasPrice(chain: string) {
|
|
162
|
-
if (!chain || chain === '1151111081099710') {
|
|
178
|
+
if (!chain || chain === '1151111081099710' || chain === '20000000000001') {
|
|
163
179
|
return {
|
|
164
180
|
data: {
|
|
165
181
|
gasPrice: '1000000000000000000',
|
|
@@ -177,7 +193,7 @@ export class OpenOceanService {
|
|
|
177
193
|
// Check if the address is valid
|
|
178
194
|
if (!tokenAddress || (!/^0x[a-fA-F0-9]{40}$/.test(tokenAddress) && !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(tokenAddress))) {
|
|
179
195
|
throw new Error('Invalid token address')
|
|
180
|
-
}
|
|
196
|
+
}
|
|
181
197
|
const response = await fetch(`${this.API_V4_URL}/${chainName}/getTokenInfo?tokenAddress=${tokenAddress}`)
|
|
182
198
|
const data = await response.json()
|
|
183
199
|
if (!data || !data.address || !data.symbol || !data.decimals) {
|
|
@@ -204,22 +220,30 @@ export class OpenOceanService {
|
|
|
204
220
|
* @returns Promise<Record<string, string>> returns an object where key is token address and value is price
|
|
205
221
|
*/
|
|
206
222
|
static async getTokensPrice(chain: string, tokenAddresses: string[]): Promise<Record<string, string>> {
|
|
207
|
-
|
|
223
|
+
let chainName = this.getChainName(chain)
|
|
224
|
+
if (chain === '20000000000001') {
|
|
225
|
+
tokenAddresses = ['0x2260fac5e5542a773aa44fbcfedf7c193bc2c599']
|
|
226
|
+
chainName = '1'
|
|
227
|
+
}
|
|
228
|
+
|
|
208
229
|
const tokenAddressesStr = tokenAddresses.join(',')
|
|
209
230
|
const response = await fetch(`${this.API_V3_URL}/${chainName}/designated_tokenList?tokens=${tokenAddressesStr}`)
|
|
210
231
|
const data = await response.json()
|
|
211
|
-
|
|
232
|
+
|
|
212
233
|
if (data.code !== 200) {
|
|
213
234
|
throw new Error('Failed to fetch token prices')
|
|
214
235
|
}
|
|
215
236
|
|
|
216
237
|
const prices = data.data.reduce((acc: Record<string, string>, token: OpenOceanToken) => {
|
|
217
238
|
if (token.address && token.usd) {
|
|
218
|
-
|
|
239
|
+
if (chain === '20000000000001') {
|
|
240
|
+
acc.bitcoin = token.usd
|
|
241
|
+
} else {
|
|
242
|
+
acc[token.address.toLowerCase()] = token.usd
|
|
243
|
+
}
|
|
219
244
|
}
|
|
220
245
|
return acc
|
|
221
246
|
}, {})
|
|
222
|
-
|
|
223
247
|
return prices
|
|
224
248
|
}
|
|
225
249
|
}
|