@openocean.finance/widget 1.0.44 → 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 (31) hide show
  1. package/dist/esm/components/Card/InputCard.d.ts +1 -1
  2. package/dist/esm/components/Header/WalletHeader.js +4 -4
  3. package/dist/esm/components/Header/WalletHeader.js.map +1 -1
  4. package/dist/esm/components/Skeleton/WidgetSkeleton.style.d.ts +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/RelayAdapter.js +3 -9
  10. package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
  11. package/dist/esm/cross/crossChainQuote.js +55 -2
  12. package/dist/esm/cross/crossChainQuote.js.map +1 -1
  13. package/dist/esm/hooks/useSwapOnly.js +8 -2
  14. package/dist/esm/hooks/useSwapOnly.js.map +1 -1
  15. package/dist/esm/i18n/en.json +2 -2
  16. package/dist/esm/pages/SendToWallet/SendToWalletPage.style.d.ts +1 -1
  17. package/dist/esm/services/ExecuteRoute.d.ts +1 -0
  18. package/dist/esm/services/ExecuteRoute.js +127 -53
  19. package/dist/esm/services/ExecuteRoute.js.map +1 -1
  20. package/dist/esm/types/widget.d.ts +1 -1
  21. package/dist/esm/types/widget.js.map +1 -1
  22. package/package.json +3 -3
  23. package/src/components/Header/WalletHeader.tsx +4 -4
  24. package/src/components/TransactionDetails.tsx +4 -0
  25. package/src/config/version.ts +1 -1
  26. package/src/cross/adapters/RelayAdapter.ts +4 -12
  27. package/src/cross/crossChainQuote.ts +54 -2
  28. package/src/hooks/useSwapOnly.ts +9 -2
  29. package/src/i18n/en.json +2 -2
  30. package/src/services/ExecuteRoute.ts +165 -54
  31. package/src/types/widget.ts +2 -1
@@ -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;
@@ -528,7 +529,7 @@ async function executeEvmSwap(
528
529
  ) {
529
530
  let allowance = 0n
530
531
  try {
531
- allowance = (await publicClient.readContract({
532
+ allowance = (await (publicClient as any).readContract({
532
533
  address: step.action.fromToken.address as `0x${string}`,
533
534
  abi: [
534
535
  {
@@ -702,44 +703,6 @@ async function executeEvmSwap(
702
703
  throw error
703
704
  }
704
705
  }
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
706
 
744
707
  const createPsbtOptions = (_: any, request: any) => {
745
708
  var _a;
@@ -785,6 +748,110 @@ function extractSignedPsbt(response: any): string | null {
785
748
  return response.signedPsbtHex || response.signedPsbtBase64 || response.signedPsbt || null;
786
749
  }
787
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
+
788
855
  async function executeBitcoinSwap(
789
856
  step: ExtendedOpenOceanStep,
790
857
  options: ExecuteRouteOptions,
@@ -794,16 +861,15 @@ async function executeBitcoinSwap(
794
861
  try {
795
862
 
796
863
  const { quoteData } = step || {}
797
-
798
864
  const adaptedWallet: any = adaptBitcoinWallet(
799
865
  options.account.address?.toString() || '',
800
866
  async (_: any, __: any, dynamicParams: DynamicSignPsbtParams) => {
801
867
  const psbtFromBase64 = bitcoin.Psbt.fromBase64(dynamicParams.unsignedPsbtBase64);
802
868
  const psbtHex = psbtFromBase64.toHex();
803
- const walletType = detectBtcWallet();
869
+ const connector = options.account.connector;
804
870
  const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
805
- switch (walletType) {
806
- case 'okx': {
871
+ switch (connector?.name) {
872
+ case 'OKX Wallet': {
807
873
  const response = await anyWindow.okxwallet?.bitcoin?.signPsbt(
808
874
  psbtHex,
809
875
  createPsbtOptions(psbtFromBase64, dynamicParams)
@@ -815,7 +881,7 @@ async function executeBitcoinSwap(
815
881
  return convertHexToBase64(signedPsbt);
816
882
  }
817
883
 
818
- case 'unisat': {
884
+ case 'Unisat': {
819
885
  const response = await anyWindow.unisat?.signPsbt(
820
886
  psbtHex,
821
887
  createPsbtOptions(psbtFromBase64, dynamicParams)
@@ -827,7 +893,7 @@ async function executeBitcoinSwap(
827
893
  return convertHexToBase64(signedPsbt);
828
894
  }
829
895
 
830
- case 'xverse': {
896
+ case 'Xverse': {
831
897
  const response = await anyWindow.BitcoinProvider?.request('signPsbt', {
832
898
  psbt: psbtHex,
833
899
  finalize: true,
@@ -839,19 +905,55 @@ async function executeBitcoinSwap(
839
905
  return convertHexToBase64(signedPsbt);
840
906
  }
841
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
+
842
944
  default:
843
- throw new Error(`Unsupported wallet: ${walletType}`);
945
+ throw new Error(`Unsupported wallet: ${connector.name}`);
844
946
  }
845
947
  }
846
948
  );
847
949
  adaptedWallet.sendTransaction = async (params: { recipient: string; amount: string | number }) => {
848
- const walletType = detectBtcWallet();
950
+ const connector = options.account.connector;
849
951
  const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
850
952
 
851
953
  // Convert amount to satoshis (BTC amount * 100000000)
852
954
  const amountInSatoshis = Number(params.amount)
853
- switch (walletType) {
854
- case 'okx': {
955
+ switch (connector?.name) {
956
+ case 'OKX Wallet': {
855
957
  // OKX wallet sendBitcoin method
856
958
  if (anyWindow.okxwallet?.bitcoin?.sendBitcoin) {
857
959
  const txid = await anyWindow.okxwallet.bitcoin.sendBitcoin(
@@ -863,7 +965,7 @@ async function executeBitcoinSwap(
863
965
  throw new Error('OKX wallet does not support sendBitcoin');
864
966
  }
865
967
 
866
- case 'unisat': {
968
+ case 'Unisat': {
867
969
  // Unisat wallet sendBitcoin method
868
970
  if (anyWindow.unisat?.sendBitcoin) {
869
971
  const txid = await anyWindow.unisat.sendBitcoin(
@@ -875,7 +977,7 @@ async function executeBitcoinSwap(
875
977
  throw new Error('Unisat wallet does not support sendBitcoin');
876
978
  }
877
979
 
878
- case 'xverse': {
980
+ case 'Xverse': {
879
981
  // Xverse wallet sendBitcoin method
880
982
  if (anyWindow.BitcoinProvider?.request) {
881
983
  const response = await anyWindow.BitcoinProvider.request('sendBitcoin', {
@@ -887,8 +989,18 @@ async function executeBitcoinSwap(
887
989
  throw new Error('Xverse wallet does not support sendBitcoin');
888
990
  }
889
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
+
890
1002
  default:
891
- throw new Error(`Unsupported wallet: ${walletType}`);
1003
+ throw new Error(`Unsupported wallet: ${connector?.name}`);
892
1004
  }
893
1005
  }
894
1006
 
@@ -916,6 +1028,7 @@ async function executeBitcoinSwap(
916
1028
 
917
1029
  } catch (error) {
918
1030
  console.error('Bitcoin swap execution failed:', error)
1031
+ debugger
919
1032
  process.status = 'FAILED'
920
1033
  process.error =
921
1034
  error instanceof Error || (error && (error as any)?.message)
@@ -979,8 +1092,6 @@ async function executeSwap(
979
1092
  await executeSolanaSwap(currentStep, options, process, updatedRoute)
980
1093
  } else if (currentStep.action?.fromChainId === 20000000000001) {
981
1094
  await executeBitcoinSwap(currentStep, options, process, updatedRoute)
982
- } else if (currentStep.action?.fromChainId === 20000000000001) {
983
- await executeBitcoinSwap(currentStep, options, process, updatedRoute)
984
1095
  } else {
985
1096
  await executeEvmSwap(currentStep, options, process, updatedRoute)
986
1097
  }
@@ -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
  /**