@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.
Files changed (158) hide show
  1. package/dist/esm/App.js.map +1 -1
  2. package/dist/esm/AppDrawer.style.d.ts +1 -1
  3. package/dist/esm/components/ActiveTransactions/ActiveTransactions.style.d.ts +2 -2
  4. package/dist/esm/components/AmountInput/AmountInput.style.d.ts +1 -1
  5. package/dist/esm/components/AmountInput/AmountInputAdornment.style.d.ts +1 -1
  6. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js +37 -33
  7. package/dist/esm/components/AmountInput/AmountInputEndAdornment.js.map +1 -1
  8. package/dist/esm/components/Avatar/Avatar.style.d.ts +1 -1
  9. package/dist/esm/components/Avatar/SmallAvatar.d.ts +1 -1
  10. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js +1 -1
  11. package/dist/esm/components/BaseTransactionButton/BaseTransactionButton.js.map +1 -1
  12. package/dist/esm/components/ButtonTertiary.d.ts +1 -1
  13. package/dist/esm/components/Card/CardHeader.d.ts +1 -1
  14. package/dist/esm/components/Card/CardIconButton.d.ts +1 -1
  15. package/dist/esm/components/Card/InputCard.d.ts +1 -1
  16. package/dist/esm/components/ContractComponent/NFT/NFT.style.d.ts +1 -1
  17. package/dist/esm/components/Header/Header.style.d.ts +2 -2
  18. package/dist/esm/components/Header/SettingsButton.style.d.ts +2 -2
  19. package/dist/esm/components/Header/WalletHeader.js +4 -4
  20. package/dist/esm/components/Header/WalletHeader.js.map +1 -1
  21. package/dist/esm/components/ListItem/ListItem.d.ts +1 -1
  22. package/dist/esm/components/Messages/WarningMessages.js +1 -0
  23. package/dist/esm/components/Messages/WarningMessages.js.map +1 -1
  24. package/dist/esm/components/Messages/useMessageQueue.js +1 -1
  25. package/dist/esm/components/Messages/useMessageQueue.js.map +1 -1
  26. package/dist/esm/components/Search/SearchInput.style.d.ts +1 -1
  27. package/dist/esm/components/SelectTokenButton/SelectTokenButton.style.d.ts +1 -1
  28. package/dist/esm/components/SendToWallet/SendToWallet.style.d.ts +1 -1
  29. package/dist/esm/components/Skeleton/WidgetSkeleton.style.d.ts +3 -3
  30. package/dist/esm/components/StepActions/StepActions.style.d.ts +1 -1
  31. package/dist/esm/components/Tabs/Tabs.style.d.ts +2 -2
  32. package/dist/esm/components/TokenList/TokenList.style.d.ts +2 -2
  33. package/dist/esm/components/TransactionDetails.js +4 -1
  34. package/dist/esm/components/TransactionDetails.js.map +1 -1
  35. package/dist/esm/config/defaultChainIds.js +3 -0
  36. package/dist/esm/config/defaultChainIds.js.map +1 -1
  37. package/dist/esm/config/version.d.ts +1 -1
  38. package/dist/esm/config/version.js +1 -1
  39. package/dist/esm/cross/adapters/AcrossAdapter.js +21 -22
  40. package/dist/esm/cross/adapters/AcrossAdapter.js.map +1 -1
  41. package/dist/esm/cross/adapters/BaseSwapAdapter.d.ts +17 -9
  42. package/dist/esm/cross/adapters/BaseSwapAdapter.js +15 -4
  43. package/dist/esm/cross/adapters/BaseSwapAdapter.js.map +1 -1
  44. package/dist/esm/cross/adapters/DebridgeAdapter.js +8 -7
  45. package/dist/esm/cross/adapters/DebridgeAdapter.js.map +1 -1
  46. package/dist/esm/cross/adapters/LifiAdapter.js +7 -6
  47. package/dist/esm/cross/adapters/LifiAdapter.js.map +1 -1
  48. package/dist/esm/cross/adapters/MayanAdapter.js +1 -2
  49. package/dist/esm/cross/adapters/MayanAdapter.js.map +1 -1
  50. package/dist/esm/cross/adapters/NearIntentsAdapter.d.ts +13 -6
  51. package/dist/esm/cross/adapters/NearIntentsAdapter.js +252 -75
  52. package/dist/esm/cross/adapters/NearIntentsAdapter.js.map +1 -1
  53. package/dist/esm/cross/adapters/OptimexAdapter.js +2 -1
  54. package/dist/esm/cross/adapters/OptimexAdapter.js.map +1 -1
  55. package/dist/esm/cross/adapters/OrbiterAdapter.js +9 -8
  56. package/dist/esm/cross/adapters/OrbiterAdapter.js.map +1 -1
  57. package/dist/esm/cross/adapters/RelayAdapter.js +40 -15
  58. package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
  59. package/dist/esm/cross/adapters/SymbiosisAdapter.js +13 -14
  60. package/dist/esm/cross/adapters/SymbiosisAdapter.js.map +1 -1
  61. package/dist/esm/cross/adapters/XYFinanceAdapter.js +13 -17
  62. package/dist/esm/cross/adapters/XYFinanceAdapter.js.map +1 -1
  63. package/dist/esm/cross/adapters/index.d.ts +2 -0
  64. package/dist/esm/cross/adapters/index.js +2 -1
  65. package/dist/esm/cross/adapters/index.js.map +1 -1
  66. package/dist/esm/cross/constants/index.d.ts +3 -1
  67. package/dist/esm/cross/constants/index.js +2 -0
  68. package/dist/esm/cross/constants/index.js.map +1 -1
  69. package/dist/esm/cross/crossChainQuote.d.ts +1 -1
  70. package/dist/esm/cross/crossChainQuote.js +75 -12
  71. package/dist/esm/cross/crossChainQuote.js.map +1 -1
  72. package/dist/esm/cross/factory.d.ts +3 -1
  73. package/dist/esm/cross/factory.js +10 -12
  74. package/dist/esm/cross/factory.js.map +1 -1
  75. package/dist/esm/cross/registry.d.ts +1 -1
  76. package/dist/esm/hooks/useAvailableChains.js +47 -40
  77. package/dist/esm/hooks/useAvailableChains.js.map +1 -1
  78. package/dist/esm/hooks/useChains.js.map +1 -1
  79. package/dist/esm/hooks/useRoutes.js +11 -3
  80. package/dist/esm/hooks/useRoutes.js.map +1 -1
  81. package/dist/esm/hooks/useSwapOnly.js +8 -2
  82. package/dist/esm/hooks/useSwapOnly.js.map +1 -1
  83. package/dist/esm/hooks/useTokenAddressBalance.js.map +1 -1
  84. package/dist/esm/hooks/useTokens.d.ts +1 -0
  85. package/dist/esm/hooks/useTokens.js +14 -0
  86. package/dist/esm/hooks/useTokens.js.map +1 -1
  87. package/dist/esm/i18n/en.json +2 -2
  88. package/dist/esm/index.js.map +1 -1
  89. package/dist/esm/pages/MainPage/MainWarningMessages.js.map +1 -1
  90. package/dist/esm/pages/SendToWallet/BookmarksPage.js +1 -1
  91. package/dist/esm/pages/SendToWallet/BookmarksPage.js.map +1 -1
  92. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js +2 -2
  93. package/dist/esm/pages/SendToWallet/RecentWalletsPage.js.map +1 -1
  94. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js +2 -2
  95. package/dist/esm/pages/SendToWallet/SendToConfiguredWalletPage.js.map +1 -1
  96. package/dist/esm/pages/SendToWallet/SendToWalletPage.style.d.ts +5 -5
  97. package/dist/esm/pages/SettingsPage/SettingsCard/SettingCard.style.d.ts +1 -1
  98. package/dist/esm/providers/WidgetProvider/WidgetProvider.js.map +1 -1
  99. package/dist/esm/services/ExecuteRoute.d.ts +1 -0
  100. package/dist/esm/services/ExecuteRoute.js +292 -9
  101. package/dist/esm/services/ExecuteRoute.js.map +1 -1
  102. package/dist/esm/services/OpenOceanService.js +30 -4
  103. package/dist/esm/services/OpenOceanService.js.map +1 -1
  104. package/dist/esm/stores/bookmarks/createBookmarkStore.js.map +1 -1
  105. package/dist/esm/types/widget.d.ts +1 -1
  106. package/dist/esm/types/widget.js.map +1 -1
  107. package/dist/esm/utils/chainType.d.ts +1 -0
  108. package/dist/esm/utils/chainType.js +15 -0
  109. package/dist/esm/utils/chainType.js.map +1 -1
  110. package/dist/esm/utils/getPriceImpact.js +3 -3
  111. package/dist/esm/utils/getPriceImpact.js.map +1 -1
  112. package/package.json +19 -7
  113. package/src/App.tsx +0 -1
  114. package/src/components/AmountInput/AmountInputEndAdornment.tsx +39 -34
  115. package/src/components/BaseTransactionButton/BaseTransactionButton.tsx +3 -2
  116. package/src/components/Header/WalletHeader.tsx +4 -4
  117. package/src/components/Messages/WarningMessages.tsx +1 -0
  118. package/src/components/Messages/useMessageQueue.ts +1 -1
  119. package/src/components/TransactionDetails.tsx +8 -4
  120. package/src/config/defaultChainIds.ts +3 -0
  121. package/src/config/version.ts +1 -1
  122. package/src/cross/adapters/AcrossAdapter.ts +21 -22
  123. package/src/cross/adapters/BaseSwapAdapter.ts +24 -8
  124. package/src/cross/adapters/DebridgeAdapter.ts +11 -11
  125. package/src/cross/adapters/LifiAdapter.ts +11 -10
  126. package/src/cross/adapters/MayanAdapter.ts +1 -2
  127. package/src/cross/adapters/NearIntentsAdapter.ts +303 -129
  128. package/src/cross/adapters/OptimexAdapter.ts +12 -11
  129. package/src/cross/adapters/OrbiterAdapter.ts +17 -16
  130. package/src/cross/adapters/RelayAdapter.ts +42 -17
  131. package/src/cross/adapters/SymbiosisAdapter.ts +13 -14
  132. package/src/cross/adapters/XYFinanceAdapter.ts +15 -19
  133. package/src/cross/adapters/index.ts +2 -1
  134. package/src/cross/constants/index.ts +4 -0
  135. package/src/cross/crossChainQuote.ts +79 -21
  136. package/src/cross/factory.ts +12 -12
  137. package/src/cross/registry.ts +1 -1
  138. package/src/hooks/useAvailableChains.ts +50 -43
  139. package/src/hooks/useChains.ts +0 -1
  140. package/src/hooks/useExplorer.ts +6 -6
  141. package/src/hooks/useRoutes.ts +12 -4
  142. package/src/hooks/useSwapOnly.ts +9 -2
  143. package/src/hooks/useTokenAddressBalance.ts +1 -1
  144. package/src/hooks/useTokens.ts +20 -5
  145. package/src/i18n/en.json +2 -2
  146. package/src/index.ts +0 -1
  147. package/src/pages/MainPage/MainWarningMessages.tsx +0 -1
  148. package/src/pages/SendToWallet/BookmarksPage.tsx +1 -1
  149. package/src/pages/SendToWallet/RecentWalletsPage.tsx +2 -2
  150. package/src/pages/SendToWallet/SendToConfiguredWalletPage.tsx +2 -2
  151. package/src/providers/WidgetProvider/WidgetProvider.tsx +0 -1
  152. package/src/services/ExecuteRoute.ts +426 -64
  153. package/src/services/OpenOceanService.ts +31 -7
  154. package/src/stores/bookmarks/createBookmarkStore.ts +15 -15
  155. package/src/types/widget.ts +2 -1
  156. package/src/utils/chainType.ts +25 -1
  157. package/src/utils/getPriceImpact.ts +3 -3
  158. 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.interface.getFunction('swap').selector
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
- const adaptedWallet: any = adaptSolanaWallet(
295
- options.account.address?.toString() ||
296
- '1nc1nerator11111111111111111111111111111111',
297
- 792703809, //chain id that Relay uses to identify solana
298
- connection,
299
- async (transaction) => {
300
- try {
301
- // Ensure transaction is properly formatted
302
- if (
303
- transaction instanceof VersionedTransaction ||
304
- transaction instanceof 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
- throw new Error('Invalid transaction type')
326
- } catch (error) {
327
- console.error('Transaction sending failed:', error)
328
- throw error
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
- code: 'EXECUTION_ERROR',
419
- message: error.message,
420
- htmlMessage: error.message,
421
- }
437
+ code: 'EXECUTION_ERROR',
438
+ message: error.message,
439
+ htmlMessage: error.message,
440
+ }
422
441
  : {
423
- code: 'UNKNOWN_ERROR',
424
- message: 'Unknown error occurred',
425
- htmlMessage: 'Unknown error occurred',
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
- '0x0000000000000000000000000000000000000000'
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
- route?.data?.prependedOperatingExpenseCost ||
550
- '0'
566
+ route?.data?.prependedOperatingExpenseCost ||
567
+ '0'
551
568
  )
552
569
  if (allowance < BigInt(amount)) {
553
570
  const approvalAmount = options.infiniteApproval
554
571
  ? BigInt(
555
- '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
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.sourceTxHash
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
- code: 'EXECUTION_ERROR',
676
- message: error.message,
677
- htmlMessage: error.message,
678
- }
692
+ code: 'EXECUTION_ERROR',
693
+ message: error.message,
694
+ htmlMessage: error.message,
695
+ }
679
696
  : {
680
- code: 'UNKNOWN_ERROR',
681
- message: 'Unknown error occurred',
682
- htmlMessage: 'Unknown error occurred',
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
- throw new Error('Failed to fetch token list')
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
- const chainName = this.getChainName(chain)
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
- acc[token.address.toLowerCase()] = token.usd
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
  }