@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.
- package/dist/esm/components/Header/WalletHeader.js +4 -4
- package/dist/esm/components/Header/WalletHeader.js.map +1 -1
- package/dist/esm/components/TokenList/VirtualizedTokenList.js +1 -0
- package/dist/esm/components/TokenList/VirtualizedTokenList.js.map +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/LifiAdapter.d.ts +3 -0
- package/dist/esm/cross/adapters/LifiAdapter.js +270 -44
- package/dist/esm/cross/adapters/LifiAdapter.js.map +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/adapters/index.d.ts +1 -0
- package/dist/esm/cross/adapters/index.js +1 -1
- package/dist/esm/cross/adapters/index.js.map +1 -1
- package/dist/esm/cross/crossChainQuote.js +61 -2
- 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 -14
- package/dist/esm/cross/factory.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 +5 -3
- package/dist/esm/i18n/ja.json +3 -1
- package/dist/esm/i18n/zh.json +3 -1
- package/dist/esm/services/ExecuteRoute.d.ts +1 -0
- package/dist/esm/services/ExecuteRoute.js +128 -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 +4 -4
- package/src/components/Header/WalletHeader.tsx +4 -4
- package/src/components/TokenList/VirtualizedTokenList.tsx +15 -15
- package/src/components/TransactionDetails.tsx +4 -0
- package/src/config/version.ts +1 -1
- package/src/cross/adapters/LifiAdapter.ts +303 -51
- package/src/cross/adapters/RelayAdapter.ts +4 -12
- package/src/cross/adapters/index.ts +1 -1
- package/src/cross/crossChainQuote.ts +60 -2
- package/src/cross/factory.ts +12 -11
- package/src/hooks/useSwapOnly.ts +9 -2
- package/src/i18n/en.json +5 -3
- package/src/i18n/ja.json +3 -1
- package/src/i18n/zh.json +3 -1
- package/src/services/ExecuteRoute.ts +166 -54
- 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
|
|
870
|
+
const connector = options.account.connector;
|
|
804
871
|
const anyWindow = typeof window !== 'undefined' ? (window as any) : undefined
|
|
805
|
-
switch (
|
|
806
|
-
case '
|
|
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 '
|
|
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 '
|
|
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: ${
|
|
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
|
|
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 (
|
|
854
|
-
case '
|
|
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 '
|
|
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 '
|
|
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: ${
|
|
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
|
}
|
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
|
/**
|