@openocean.finance/widget 1.0.44 → 1.0.46

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 (47) hide show
  1. package/dist/esm/components/Header/WalletHeader.js +4 -4
  2. package/dist/esm/components/Header/WalletHeader.js.map +1 -1
  3. package/dist/esm/components/TokenList/VirtualizedTokenList.js +1 -0
  4. package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +1 -1
  5. package/dist/esm/components/TransactionDetails.js +3 -0
  6. package/dist/esm/components/TransactionDetails.js.map +1 -1
  7. package/dist/esm/config/version.d.ts +1 -1
  8. package/dist/esm/config/version.js +1 -1
  9. package/dist/esm/cross/adapters/LifiAdapter.d.ts +3 -0
  10. package/dist/esm/cross/adapters/LifiAdapter.js +270 -44
  11. package/dist/esm/cross/adapters/LifiAdapter.js.map +1 -1
  12. package/dist/esm/cross/adapters/RelayAdapter.js +3 -9
  13. package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
  14. package/dist/esm/cross/adapters/index.d.ts +1 -0
  15. package/dist/esm/cross/adapters/index.js +1 -1
  16. package/dist/esm/cross/adapters/index.js.map +1 -1
  17. package/dist/esm/cross/crossChainQuote.js +61 -2
  18. package/dist/esm/cross/crossChainQuote.js.map +1 -1
  19. package/dist/esm/cross/factory.d.ts +3 -1
  20. package/dist/esm/cross/factory.js +10 -14
  21. package/dist/esm/cross/factory.js.map +1 -1
  22. package/dist/esm/hooks/useSwapOnly.js +8 -2
  23. package/dist/esm/hooks/useSwapOnly.js.map +1 -1
  24. package/dist/esm/i18n/en.json +5 -3
  25. package/dist/esm/i18n/ja.json +3 -1
  26. package/dist/esm/i18n/zh.json +3 -1
  27. package/dist/esm/services/ExecuteRoute.d.ts +1 -0
  28. package/dist/esm/services/ExecuteRoute.js +128 -53
  29. package/dist/esm/services/ExecuteRoute.js.map +1 -1
  30. package/dist/esm/types/widget.d.ts +1 -1
  31. package/dist/esm/types/widget.js.map +1 -1
  32. package/package.json +4 -4
  33. package/src/components/Header/WalletHeader.tsx +4 -4
  34. package/src/components/TokenList/VirtualizedTokenList.tsx +15 -15
  35. package/src/components/TransactionDetails.tsx +4 -0
  36. package/src/config/version.ts +1 -1
  37. package/src/cross/adapters/LifiAdapter.ts +303 -51
  38. package/src/cross/adapters/RelayAdapter.ts +4 -12
  39. package/src/cross/adapters/index.ts +1 -1
  40. package/src/cross/crossChainQuote.ts +60 -2
  41. package/src/cross/factory.ts +12 -11
  42. package/src/hooks/useSwapOnly.ts +9 -2
  43. package/src/i18n/en.json +5 -3
  44. package/src/i18n/ja.json +3 -1
  45. package/src/i18n/zh.json +3 -1
  46. package/src/services/ExecuteRoute.ts +166 -54
  47. package/src/types/widget.ts +2 -1
package/src/i18n/zh.json CHANGED
@@ -302,7 +302,9 @@
302
302
  "routePriority": "路由优先级",
303
303
  "slippage": "最大滑点",
304
304
  "custom": "自定义",
305
- "resetSettings": "您正在使用自定义设置来过滤可用路由。"
305
+ "resetSettings": "您正在使用自定义设置来过滤可用路由。",
306
+ "dynamicMode": "动态模式",
307
+ "dynamicModeTooltip": "自动最小化滑点以增强 MEV 保护通过运行模拟。"
306
308
  },
307
309
  "sendToWallet": {
308
310
  "addBookmark": "添加到收藏",
@@ -14,6 +14,7 @@ import { useSettingsStore } from '../stores/settings/useSettingsStore.js'
14
14
  import { sendAndConfirmSolanaTransaction } from './SendAndConfirmSolanaTransaction.js'
15
15
  import { adaptBitcoinWallet } from '@relayprotocol/relay-bitcoin-wallet-adapter'
16
16
  import * as bitcoin from 'bitcoinjs-lib';
17
+
17
18
  type DynamicSignPsbtParams = {
18
19
  allowedSighash: number[];
19
20
  unsignedPsbtBase64: string;
@@ -287,6 +288,7 @@ async function executeSolanaSwap(
287
288
  process: Process,
288
289
  route: ExtendedRoute
289
290
  ): Promise<void> {
291
+ debugger
290
292
  try {
291
293
  // Check wallet connection status
292
294
  if (!options.account || !options.account.connector) {
@@ -528,7 +530,7 @@ async function executeEvmSwap(
528
530
  ) {
529
531
  let allowance = 0n
530
532
  try {
531
- allowance = (await publicClient.readContract({
533
+ allowance = (await (publicClient as any).readContract({
532
534
  address: step.action.fromToken.address as `0x${string}`,
533
535
  abi: [
534
536
  {
@@ -702,44 +704,6 @@ async function executeEvmSwap(
702
704
  throw error
703
705
  }
704
706
  }
705
- function detectBtcWallet() {
706
- const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
707
- if (anyWindow.okxwallet?.bitcoin && anyWindow.okxwallet?.bitcoin?.signPsbt) return 'okx';
708
- if (anyWindow.unisat && anyWindow.unisat?.signPsbt) return 'unisat';
709
- if (anyWindow.BitcoinProvider && anyWindow.BitcoinProvider.request) return 'xverse';
710
- return null;
711
- }
712
-
713
- // Get wallet type from connected account
714
- function getConnectedBtcWallet(account: Account): string | null {
715
- if (!account.connector) return null;
716
-
717
- const connectorId = (account.connector as any).id?.toLowerCase() || '';
718
- const connectorName = (account.connector as any).name?.toLowerCase() || '';
719
-
720
- // Match by connector ID or name
721
- if (connectorId.includes('okx') || connectorId.includes('com.okex.wallet')) {
722
- return 'okx';
723
- }
724
- if (connectorId.includes('unisat')) {
725
- return 'unisat';
726
- }
727
- if (connectorId.includes('xverse') || connectorId.includes('XverseProviders')) {
728
- return 'xverse';
729
- }
730
- if (connectorId.includes('phantom') || connectorName.includes('phantom')) {
731
- return 'phantom';
732
- }
733
- if (connectorId.includes('leather') || connectorName.includes('leather')) {
734
- return 'leather';
735
- }
736
- if (connectorId.includes('onekey') || connectorName.includes('onekey')) {
737
- return 'onekey';
738
- }
739
-
740
- // Fallback to detection if connector doesn't match
741
- return detectBtcWallet();
742
- }
743
707
 
744
708
  const createPsbtOptions = (_: any, request: any) => {
745
709
  var _a;
@@ -785,6 +749,110 @@ function extractSignedPsbt(response: any): string | null {
785
749
  return response.signedPsbtHex || response.signedPsbtBase64 || response.signedPsbt || null;
786
750
  }
787
751
 
752
+
753
+ // BTC 主网
754
+ const network = bitcoin.networks.bitcoin;
755
+
756
+ export async function sendBTCWithPhantom(sender: string, recipient: string, amount: number) {
757
+ const phantom = (window as any).phantom?.bitcoin;
758
+ if (!phantom) throw new Error("Phantom Bitcoin provider not found");
759
+ // 1. 获取 UTXO(这里使用 mempool.space API,你也可以换自己的)
760
+ const utxos = await fetch(
761
+ `https://mempool.space/api/address/${sender}/utxo`
762
+ ).then((r) => r.json());
763
+
764
+ if (!utxos.length) throw new Error("No UTXO available");
765
+
766
+ // ====== 费用参数 ======
767
+ // amount / fee 单位都按 satoshi 处理,调用方需要保证一致
768
+ const fee = 5000; // 手续费(可以根据当前网络费率动态调整)
769
+ const required = BigInt(amount + fee);
770
+
771
+ // 2. 简单的累加选币:从小到大选 UTXO,直到覆盖 amount + fee
772
+ const sortedUtxos = [...utxos].sort(
773
+ (a: any, b: any) => Number(a.value) - Number(b.value)
774
+ );
775
+
776
+ const selectedUtxos: any[] = [];
777
+ let totalInput = 0n;
778
+
779
+ for (const utxo of sortedUtxos) {
780
+ selectedUtxos.push(utxo);
781
+ totalInput += BigInt(utxo.value);
782
+ if (totalInput >= required) break;
783
+ }
784
+
785
+ if (totalInput < required) {
786
+ throw new Error("Insufficient balance: UTXOs do not cover amount + fee");
787
+ }
788
+
789
+ const changeValue = totalInput - required;
790
+
791
+ // 可选:避免输出过小(dust),这里简单判断 >0 即可,如需更严格可改成 > 546
792
+ if (changeValue <= 0n) {
793
+ throw new Error("UTXOs too small to cover amount and fee with change");
794
+ }
795
+
796
+ // 3. 构造 PSBT(多输入)
797
+ const psbt = new bitcoin.Psbt({ network });
798
+
799
+ selectedUtxos.forEach((utxo) => {
800
+ psbt.addInput({
801
+ hash: utxo.txid,
802
+ index: utxo.vout,
803
+ witnessUtxo: {
804
+ // bitcoinjs-lib 期望 bigint,这里将 mempool 返回的 number 转成 bigint
805
+ value: BigInt(utxo.value),
806
+ script: bitcoin.address.toOutputScript(sender, network),
807
+ },
808
+ });
809
+ });
810
+
811
+ // 目标地址 output
812
+ psbt.addOutput({
813
+ address: recipient,
814
+ value: BigInt(amount),
815
+ });
816
+
817
+ // 找零 output
818
+ psbt.addOutput({
819
+ address: sender,
820
+ value: changeValue,
821
+ });
822
+
823
+ // 4. 调用 Phantom.signPSBT
824
+ const psbtBase64 = psbt.toBase64();
825
+
826
+ const signOptions = {
827
+ autoFinalized: false,
828
+ toSignInputs: selectedUtxos.map((_, index) => ({
829
+ address: sender,
830
+ index,
831
+ // SIGHASH_ALL (0x01) 通常足够,如有需要可根据业务调整
832
+ sighashTypes: [0, 1], // 保持和原来一致
833
+ })),
834
+ };
835
+
836
+ const signed = await phantom.signPSBT(psbtBase64, signOptions);
837
+
838
+ // Phantom 返回 Base64
839
+ const signedPsbt = bitcoin.Psbt.fromBase64(signed);
840
+
841
+ // 5. Finalize + 提取原始交易
842
+ signedPsbt.finalizeAllInputs();
843
+ const rawTx = signedPsbt.extractTransaction().toHex();
844
+
845
+ // 6. 广播 Raw TX
846
+ const txid = await fetch("https://mempool.space/api/tx", {
847
+ method: "POST",
848
+ body: rawTx,
849
+ }).then((r) => r.text());
850
+
851
+ console.log("Broadcasted TXID:", txid);
852
+ return txid;
853
+ }
854
+
855
+
788
856
  async function executeBitcoinSwap(
789
857
  step: ExtendedOpenOceanStep,
790
858
  options: ExecuteRouteOptions,
@@ -794,16 +862,15 @@ async function executeBitcoinSwap(
794
862
  try {
795
863
 
796
864
  const { quoteData } = step || {}
797
-
798
865
  const adaptedWallet: any = adaptBitcoinWallet(
799
866
  options.account.address?.toString() || '',
800
867
  async (_: any, __: any, dynamicParams: DynamicSignPsbtParams) => {
801
868
  const psbtFromBase64 = bitcoin.Psbt.fromBase64(dynamicParams.unsignedPsbtBase64);
802
869
  const psbtHex = psbtFromBase64.toHex();
803
- const walletType = detectBtcWallet();
870
+ const connector = options.account.connector;
804
871
  const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
805
- switch (walletType) {
806
- case 'okx': {
872
+ switch (connector?.name) {
873
+ case 'OKX Wallet': {
807
874
  const response = await anyWindow.okxwallet?.bitcoin?.signPsbt(
808
875
  psbtHex,
809
876
  createPsbtOptions(psbtFromBase64, dynamicParams)
@@ -815,7 +882,7 @@ async function executeBitcoinSwap(
815
882
  return convertHexToBase64(signedPsbt);
816
883
  }
817
884
 
818
- case 'unisat': {
885
+ case 'Unisat': {
819
886
  const response = await anyWindow.unisat?.signPsbt(
820
887
  psbtHex,
821
888
  createPsbtOptions(psbtFromBase64, dynamicParams)
@@ -827,7 +894,7 @@ async function executeBitcoinSwap(
827
894
  return convertHexToBase64(signedPsbt);
828
895
  }
829
896
 
830
- case 'xverse': {
897
+ case 'Xverse': {
831
898
  const response = await anyWindow.BitcoinProvider?.request('signPsbt', {
832
899
  psbt: psbtHex,
833
900
  finalize: true,
@@ -839,19 +906,55 @@ async function executeBitcoinSwap(
839
906
  return convertHexToBase64(signedPsbt);
840
907
  }
841
908
 
909
+ case 'Phantom': {
910
+ const phantom = anyWindow.phantom?.bitcoin;
911
+ if (!phantom?.signPSBT) throw new Error('Phantom wallet does not support signPSBT');
912
+
913
+ const inputsToSign = [];
914
+ console.log("Phantom options = ", JSON.stringify(createPsbtOptions(psbtFromBase64, dynamicParams)));
915
+ console.log("psbtHex = ", psbtHex);
916
+ console.log("psbtBase64 = ", psbtFromBase64.toBase64());
917
+
918
+ debugger
919
+ for (const sig of dynamicParams.signature || []) {
920
+ for (const index of sig.signingIndexes || []) {
921
+ inputsToSign.push({
922
+ index,
923
+ address: sig.address,
924
+ sighashTypes: dynamicParams.allowedSighash,
925
+ });
926
+ }
927
+ }
928
+ debugger
929
+
930
+ const response = await phantom.signPSBT(
931
+ psbtFromBase64.toBase64(), // ✅ Phantom only accepts base64
932
+ {
933
+ autoFinalize: false, // ✅ correct name
934
+ inputsToSign, // ✅ correct name
935
+ }
936
+ );
937
+
938
+ const signedPsbt = extractSignedPsbt(response);
939
+ if (!signedPsbt) throw new Error('Missing psbt response from Phantom wallet');
940
+
941
+ return signedPsbt; // already base64
942
+ }
943
+
944
+
842
945
  default:
843
- throw new Error(`Unsupported wallet: ${walletType}`);
946
+ throw new Error(`Unsupported wallet: ${connector.name}`);
844
947
  }
845
948
  }
846
949
  );
847
950
  adaptedWallet.sendTransaction = async (params: { recipient: string; amount: string | number }) => {
848
- const walletType = detectBtcWallet();
951
+ const connector = options.account.connector;
849
952
  const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
850
953
 
851
954
  // Convert amount to satoshis (BTC amount * 100000000)
852
955
  const amountInSatoshis = Number(params.amount)
853
- switch (walletType) {
854
- case 'okx': {
956
+ switch (connector?.name) {
957
+ case 'OKX Wallet': {
855
958
  // OKX wallet sendBitcoin method
856
959
  if (anyWindow.okxwallet?.bitcoin?.sendBitcoin) {
857
960
  const txid = await anyWindow.okxwallet.bitcoin.sendBitcoin(
@@ -863,7 +966,7 @@ async function executeBitcoinSwap(
863
966
  throw new Error('OKX wallet does not support sendBitcoin');
864
967
  }
865
968
 
866
- case 'unisat': {
969
+ case 'Unisat': {
867
970
  // Unisat wallet sendBitcoin method
868
971
  if (anyWindow.unisat?.sendBitcoin) {
869
972
  const txid = await anyWindow.unisat.sendBitcoin(
@@ -875,7 +978,7 @@ async function executeBitcoinSwap(
875
978
  throw new Error('Unisat wallet does not support sendBitcoin');
876
979
  }
877
980
 
878
- case 'xverse': {
981
+ case 'Xverse': {
879
982
  // Xverse wallet sendBitcoin method
880
983
  if (anyWindow.BitcoinProvider?.request) {
881
984
  const response = await anyWindow.BitcoinProvider.request('sendBitcoin', {
@@ -887,8 +990,18 @@ async function executeBitcoinSwap(
887
990
  throw new Error('Xverse wallet does not support sendBitcoin');
888
991
  }
889
992
 
993
+ case 'Phantom': {
994
+ // Phantom wallet sendBitcoin method
995
+ const txid = await sendBTCWithPhantom(
996
+ options.account.address?.toString() || '',
997
+ params.recipient,
998
+ amountInSatoshis
999
+ );
1000
+ return txid;
1001
+ }
1002
+
890
1003
  default:
891
- throw new Error(`Unsupported wallet: ${walletType}`);
1004
+ throw new Error(`Unsupported wallet: ${connector?.name}`);
892
1005
  }
893
1006
  }
894
1007
 
@@ -916,6 +1029,7 @@ async function executeBitcoinSwap(
916
1029
 
917
1030
  } catch (error) {
918
1031
  console.error('Bitcoin swap execution failed:', error)
1032
+ debugger
919
1033
  process.status = 'FAILED'
920
1034
  process.error =
921
1035
  error instanceof Error || (error && (error as any)?.message)
@@ -979,8 +1093,6 @@ async function executeSwap(
979
1093
  await executeSolanaSwap(currentStep, options, process, updatedRoute)
980
1094
  } else if (currentStep.action?.fromChainId === 20000000000001) {
981
1095
  await executeBitcoinSwap(currentStep, options, process, updatedRoute)
982
- } else if (currentStep.action?.fromChainId === 20000000000001) {
983
- await executeBitcoinSwap(currentStep, options, process, updatedRoute)
984
1096
  } else {
985
1097
  await executeEvmSwap(currentStep, options, process, updatedRoute)
986
1098
  }
@@ -37,6 +37,7 @@ import type { DefaultFieldValues } from '../stores/form/types.js'
37
37
  export type WidgetVariant = 'compact' | 'wide' | 'drawer'
38
38
  export type WidgetSubvariant =
39
39
  | 'default'
40
+ | 'swap'
40
41
  | 'bridge'
41
42
  | 'split'
42
43
  | 'custom'
@@ -276,7 +277,7 @@ export interface WidgetConfig {
276
277
  languages?: WidgetLanguages
277
278
  languageResources?: LanguageResources
278
279
  explorerUrls?: Record<number, string[]> &
279
- Partial<Record<'internal', string[]>>
280
+ Partial<Record<'internal', string[]>>
280
281
  poweredBy?: PoweredByType
281
282
  isDefaultValueEnabled?: boolean
282
283
  /**