@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.
- package/dist/esm/components/Card/InputCard.d.ts +1 -1
- package/dist/esm/components/Header/WalletHeader.js +4 -4
- package/dist/esm/components/Header/WalletHeader.js.map +1 -1
- package/dist/esm/components/Skeleton/WidgetSkeleton.style.d.ts +1 -1
- package/dist/esm/components/TransactionDetails.js +3 -0
- package/dist/esm/components/TransactionDetails.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/RelayAdapter.js +3 -9
- package/dist/esm/cross/adapters/RelayAdapter.js.map +1 -1
- package/dist/esm/cross/crossChainQuote.js +55 -2
- package/dist/esm/cross/crossChainQuote.js.map +1 -1
- package/dist/esm/hooks/useSwapOnly.js +8 -2
- package/dist/esm/hooks/useSwapOnly.js.map +1 -1
- package/dist/esm/i18n/en.json +2 -2
- package/dist/esm/pages/SendToWallet/SendToWalletPage.style.d.ts +1 -1
- package/dist/esm/services/ExecuteRoute.d.ts +1 -0
- package/dist/esm/services/ExecuteRoute.js +127 -53
- package/dist/esm/services/ExecuteRoute.js.map +1 -1
- package/dist/esm/types/widget.d.ts +1 -1
- package/dist/esm/types/widget.js.map +1 -1
- package/package.json +3 -3
- package/src/components/Header/WalletHeader.tsx +4 -4
- package/src/components/TransactionDetails.tsx +4 -0
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/RelayAdapter.ts +4 -12
- package/src/cross/crossChainQuote.ts +54 -2
- package/src/hooks/useSwapOnly.ts +9 -2
- package/src/i18n/en.json +2 -2
- package/src/services/ExecuteRoute.ts +165 -54
- 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
|
|
869
|
+
const connector = options.account.connector;
|
|
804
870
|
const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
|
|
805
|
-
switch (
|
|
806
|
-
case '
|
|
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 '
|
|
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 '
|
|
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: ${
|
|
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
|
|
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 (
|
|
854
|
-
case '
|
|
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 '
|
|
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 '
|
|
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: ${
|
|
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
|
}
|
package/src/types/widget.ts
CHANGED
|
@@ -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
|
-
|
|
280
|
+
Partial<Record<'internal', string[]>>
|
|
280
281
|
poweredBy?: PoweredByType
|
|
281
282
|
isDefaultValueEnabled?: boolean
|
|
282
283
|
/**
|