@silentswap/react 0.0.53 → 0.0.55
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/contexts/BalancesContext.d.ts +2 -0
- package/dist/contexts/BalancesContext.js +106 -6
- package/dist/contexts/SilentSwapContext.d.ts +6 -1
- package/dist/contexts/SilentSwapContext.js +37 -13
- package/dist/hooks/silent/useBridgeExecution.d.ts +5 -1
- package/dist/hooks/silent/useBridgeExecution.js +203 -23
- package/dist/hooks/silent/useQuoteCalculation.js +135 -33
- package/dist/hooks/silent/useSilentQuote.d.ts +6 -1
- package/dist/hooks/silent/useSilentQuote.js +76 -4
- package/dist/hooks/useOrderEstimates.js +14 -6
- package/dist/hooks/useQuote.js +5 -1
- package/dist/hooks/useSwap.js +7 -1
- package/dist/hooks/useTransaction.d.ts +6 -1
- package/dist/hooks/useTransaction.js +105 -2
- package/dist/hooks/useTransactionAddress.d.ts +3 -2
- package/dist/hooks/useTransactionAddress.js +8 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +4 -3
|
@@ -8,7 +8,7 @@ import { getAssociatedTokenAddress } from '@solana/spl-token';
|
|
|
8
8
|
import { PublicKey } from '@solana/web3.js';
|
|
9
9
|
import { useAssetsContext } from './AssetsContext.js';
|
|
10
10
|
import { usePrices } from '../hooks/usePrices.js';
|
|
11
|
-
import { isSolanaAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, isEvmNativeToken, SB58_CHAIN_ID_SOLANA_MAINNET, A_VIEM_CHAINS, } from '@silentswap/sdk';
|
|
11
|
+
import { isSolanaAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, isEvmNativeToken, isBitcoinAsset, SB58_CHAIN_ID_SOLANA_MAINNET, A_VIEM_CHAINS, BITCOIN_CHAIN_ID, } from '@silentswap/sdk';
|
|
12
12
|
const BalancesContext = createContext(undefined);
|
|
13
13
|
// Custom RPC endpoints from 0xrpc.io (fast, free, private public RPC)
|
|
14
14
|
// Reference: https://0xrpc.io
|
|
@@ -68,6 +68,18 @@ const separateSolanaAssets = (solanaAssets) => {
|
|
|
68
68
|
}
|
|
69
69
|
return { a_assets_native, a_assets_tokens };
|
|
70
70
|
};
|
|
71
|
+
// Utility: Separate Bitcoin assets (currently only native BTC is supported)
|
|
72
|
+
const separateBitcoinAssets = (bitcoinAssets) => {
|
|
73
|
+
const a_assets_native = [];
|
|
74
|
+
for (const asset of bitcoinAssets) {
|
|
75
|
+
// Bitcoin native token uses slip44:0 or similar
|
|
76
|
+
// For now, we'll treat all Bitcoin assets as native (BTC)
|
|
77
|
+
if (isBitcoinAsset(asset.caip19)) {
|
|
78
|
+
a_assets_native.push(asset);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { a_assets_native };
|
|
82
|
+
};
|
|
71
83
|
// Utility: Fetch ERC-20 balance for a single token (fallback when multicall not available)
|
|
72
84
|
const fetchSingleErc20Balance = async (y_client, s0x_account, k_asset, getUnitValueUsd) => {
|
|
73
85
|
try {
|
|
@@ -354,10 +366,80 @@ const fetchSolanaBalances = async (solAddress, solanaRpcUrl, assets, getUnitValu
|
|
|
354
366
|
return { balances: {}, error: errorMsg };
|
|
355
367
|
}
|
|
356
368
|
};
|
|
369
|
+
// Utility: Fetch Bitcoin native balances using Mempool.space API
|
|
370
|
+
const fetchBitcoinNativeBalances = async (bitcoinAddress, a_assets_native, getUnitValueUsd) => {
|
|
371
|
+
const updatedBalances = {};
|
|
372
|
+
if (a_assets_native.length === 0)
|
|
373
|
+
return updatedBalances;
|
|
374
|
+
try {
|
|
375
|
+
// Fetch balance from Mempool.space API (free, no auth required)
|
|
376
|
+
// Returns balance in satoshis (smallest unit, 1 BTC = 100,000,000 satoshis)
|
|
377
|
+
const response = await fetch(`https://mempool.space/api/address/${bitcoinAddress}`);
|
|
378
|
+
if (!response.ok) {
|
|
379
|
+
throw new Error(`Failed to fetch Bitcoin balance: ${response.status} ${response.statusText}`);
|
|
380
|
+
}
|
|
381
|
+
const data = await response.json();
|
|
382
|
+
// Mempool.space returns chain_stats with funded_txo_sum and spent_txo_sum
|
|
383
|
+
// Balance = funded - spent
|
|
384
|
+
const funded = BigInt(data.chain_stats?.funded_txo_sum || 0);
|
|
385
|
+
const spent = BigInt(data.chain_stats?.spent_txo_sum || 0);
|
|
386
|
+
const xg_balance = funded - spent;
|
|
387
|
+
await Promise.all(a_assets_native.map(async (k_asset) => {
|
|
388
|
+
let x_value_usd = 0;
|
|
389
|
+
try {
|
|
390
|
+
x_value_usd = await getUnitValueUsd(k_asset, xg_balance);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
// console.warn(`Failed to calculate USD value for native ${k_asset.caip19}:`, error);
|
|
394
|
+
}
|
|
395
|
+
updatedBalances[k_asset.caip19] = {
|
|
396
|
+
asset: k_asset,
|
|
397
|
+
balance: xg_balance,
|
|
398
|
+
usdValue: x_value_usd,
|
|
399
|
+
};
|
|
400
|
+
}));
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
// Log error for debugging but don't throw - allow other operations to continue
|
|
404
|
+
if (process.env.NODE_ENV === 'development') {
|
|
405
|
+
console.warn(`Failed to fetch native BTC balance:`, error);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return updatedBalances;
|
|
409
|
+
};
|
|
410
|
+
// Utility: Fetch all Bitcoin balances using direct API calls
|
|
411
|
+
const fetchBitcoinBalances = async (bitcoinAddress, bitcoinRpcUrl, // Not used currently, but kept for potential future RPC usage
|
|
412
|
+
assets, getUnitValueUsd) => {
|
|
413
|
+
try {
|
|
414
|
+
const bitcoinAssets = Object.values(assets).filter((asset) => isBitcoinAsset(asset.caip19));
|
|
415
|
+
const { a_assets_native } = separateBitcoinAssets(bitcoinAssets);
|
|
416
|
+
// Fetch native Bitcoin balances using Mempool.space API
|
|
417
|
+
const nativeResult = await Promise.allSettled([
|
|
418
|
+
fetchBitcoinNativeBalances(bitcoinAddress, a_assets_native, getUnitValueUsd),
|
|
419
|
+
]);
|
|
420
|
+
const nativeBalances = nativeResult[0]?.status === 'fulfilled' ? nativeResult[0].value : {};
|
|
421
|
+
// Collect errors if any
|
|
422
|
+
const errors = [];
|
|
423
|
+
if (nativeResult[0]?.status === 'rejected') {
|
|
424
|
+
errors.push(`Native: ${nativeResult[0].reason?.message || 'Unknown error'}`);
|
|
425
|
+
}
|
|
426
|
+
return {
|
|
427
|
+
balances: nativeBalances,
|
|
428
|
+
error: errors.length > 0 ? errors.join('; ') : undefined,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
const errorMsg = `Failed to fetch Bitcoin balances: ${error instanceof Error ? error.message : String(error)}`;
|
|
433
|
+
if (process.env.NODE_ENV === 'development') {
|
|
434
|
+
console.error(errorMsg, error);
|
|
435
|
+
}
|
|
436
|
+
return { balances: {}, error: errorMsg };
|
|
437
|
+
}
|
|
438
|
+
};
|
|
357
439
|
/**
|
|
358
440
|
* Provider for user balances across all supported chains
|
|
359
441
|
*/
|
|
360
|
-
export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUrl }) => {
|
|
442
|
+
export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUrl, bitcoinAddress, bitcoinRpcUrl }) => {
|
|
361
443
|
const { assets, loading: assetsLoading } = useAssetsContext();
|
|
362
444
|
const { getUnitValueUsd } = usePrices();
|
|
363
445
|
const [balances, setBalances] = useState({});
|
|
@@ -388,6 +470,15 @@ export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUr
|
|
|
388
470
|
promise: fetchSolanaBalances(solAddress, solanaRpcUrl, assets, getUnitValueUsd),
|
|
389
471
|
});
|
|
390
472
|
}
|
|
473
|
+
console.log('[BalancesContext] bitcoinAddress', bitcoinAddress);
|
|
474
|
+
console.log('[BalancesContext] bitcoinRpcUrl', bitcoinRpcUrl);
|
|
475
|
+
// Process Bitcoin balances if Bitcoin address is available
|
|
476
|
+
if (bitcoinAddress && bitcoinRpcUrl) {
|
|
477
|
+
balanceTasks.push({
|
|
478
|
+
chainId: BITCOIN_CHAIN_ID,
|
|
479
|
+
promise: fetchBitcoinBalances(bitcoinAddress, bitcoinRpcUrl, assets, getUnitValueUsd),
|
|
480
|
+
});
|
|
481
|
+
}
|
|
391
482
|
// Use Promise.allSettled to handle partial failures gracefully
|
|
392
483
|
// This ensures one chain failure doesn't prevent others from updating
|
|
393
484
|
const results = await Promise.allSettled(balanceTasks.map((task) => task.promise));
|
|
@@ -454,6 +545,15 @@ export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUr
|
|
|
454
545
|
promise: fetchSolanaBalances(solAddress, solanaRpcUrl, assets, getUnitValueUsd),
|
|
455
546
|
});
|
|
456
547
|
}
|
|
548
|
+
// Process Bitcoin balances if requested
|
|
549
|
+
const shouldRefetchBitcoin = chainIds.includes(BITCOIN_CHAIN_ID);
|
|
550
|
+
console.log('[BalancesContext] shouldRefetchBitcoin', shouldRefetchBitcoin);
|
|
551
|
+
if (bitcoinAddress && bitcoinRpcUrl && shouldRefetchBitcoin) {
|
|
552
|
+
balanceTasks.push({
|
|
553
|
+
chainId: BITCOIN_CHAIN_ID,
|
|
554
|
+
promise: fetchBitcoinBalances(bitcoinAddress, bitcoinRpcUrl, assets, getUnitValueUsd),
|
|
555
|
+
});
|
|
556
|
+
}
|
|
457
557
|
// Use Promise.allSettled to handle partial failures gracefully
|
|
458
558
|
const results = await Promise.allSettled(balanceTasks.map((task) => task.promise));
|
|
459
559
|
const newBalances = {};
|
|
@@ -500,17 +600,17 @@ export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUr
|
|
|
500
600
|
finally {
|
|
501
601
|
setLoading(false);
|
|
502
602
|
}
|
|
503
|
-
}, [evmAddress, solAddress, solanaRpcUrl, assets, assetsLoading, getUnitValueUsd]);
|
|
603
|
+
}, [evmAddress, solAddress, solanaRpcUrl, bitcoinAddress, bitcoinRpcUrl, assets, assetsLoading, getUnitValueUsd]);
|
|
504
604
|
// Calculate total USD value
|
|
505
605
|
const totalUsdValue = useMemo(() => {
|
|
506
606
|
return Object.values(balances).reduce((total, balance) => total + balance.usdValue, 0);
|
|
507
607
|
}, [balances]);
|
|
508
|
-
// Auto-update balances when address, solAddress, or assets change
|
|
608
|
+
// Auto-update balances when address, solAddress, bitcoinAddress, or assets change
|
|
509
609
|
useEffect(() => {
|
|
510
|
-
if ((evmAddress || solAddress) && !assetsLoading) {
|
|
610
|
+
if ((evmAddress || solAddress || bitcoinAddress) && !assetsLoading) {
|
|
511
611
|
updateBalances();
|
|
512
612
|
}
|
|
513
|
-
}, [evmAddress, solAddress, solanaRpcUrl, assetsLoading, updateBalances]);
|
|
613
|
+
}, [evmAddress, solAddress, solanaRpcUrl, bitcoinAddress, bitcoinRpcUrl, assetsLoading, updateBalances]);
|
|
514
614
|
const value = useMemo(() => ({
|
|
515
615
|
balances,
|
|
516
616
|
loading: loading || assetsLoading,
|
|
@@ -5,6 +5,7 @@ import { ENVIRONMENT, type SilentSwapClient, type SilentSwapClientConfig, type A
|
|
|
5
5
|
import { type ExecuteSwapParams, type SwapResult } from '../hooks/silent/useSilentQuote.js';
|
|
6
6
|
import { type OutputStatus } from '../hooks/silent/useOrderTracking.js';
|
|
7
7
|
import { type SolanaWalletConnector, type SolanaConnection } from '../hooks/silent/solana-transaction.js';
|
|
8
|
+
import { type BitcoinWalletConnector, type BitcoinConnection } from '../hooks/silent/bitcoin-transaction.js';
|
|
8
9
|
import { type SilentSwapWallet } from '../hooks/silent/useWallet.js';
|
|
9
10
|
export interface SilentSwapContextType {
|
|
10
11
|
client: SilentSwapClient;
|
|
@@ -42,7 +43,7 @@ export interface SilentSwapContextType {
|
|
|
42
43
|
solanaRpcUrl?: string;
|
|
43
44
|
}
|
|
44
45
|
export declare function useSilentSwap(): SilentSwapContextType;
|
|
45
|
-
export declare function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, environment, baseUrl, solanaRpcUrl, walletClient, }: {
|
|
46
|
+
export declare function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, bitcoinConnector, bitcoinConnection, environment, baseUrl, solanaRpcUrl, walletClient, bitcoinAddress, bitcoinRpcUrl, }: {
|
|
46
47
|
children: React.ReactNode;
|
|
47
48
|
client: SilentSwapClient;
|
|
48
49
|
evmAddress?: string;
|
|
@@ -52,7 +53,11 @@ export declare function SilentSwapProvider({ children, client, evmAddress, solAd
|
|
|
52
53
|
walletClient?: WalletClient;
|
|
53
54
|
solanaConnector?: SolanaWalletConnector;
|
|
54
55
|
solanaConnection?: SolanaConnection;
|
|
56
|
+
bitcoinConnector?: BitcoinWalletConnector;
|
|
57
|
+
bitcoinConnection?: BitcoinConnection;
|
|
55
58
|
environment?: ENVIRONMENT;
|
|
56
59
|
baseUrl?: string;
|
|
57
60
|
solanaRpcUrl?: string;
|
|
61
|
+
bitcoinAddress?: string;
|
|
62
|
+
bitcoinRpcUrl?: string;
|
|
58
63
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { createContext, useContext, useEffect, useMemo } from 'react';
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo, useState, useCallback } from 'react';
|
|
4
4
|
import { useAuth } from '../hooks/silent/useAuth.js';
|
|
5
5
|
import { useWallet } from '../hooks/silent/useWallet.js';
|
|
6
6
|
import { useSilentQuote } from '../hooks/silent/useSilentQuote.js';
|
|
@@ -63,7 +63,7 @@ export function useSilentSwap() {
|
|
|
63
63
|
}
|
|
64
64
|
return context;
|
|
65
65
|
}
|
|
66
|
-
function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, solanaConnector, solanaConnection, environment, config, solanaRpcUrl, connector, isConnected = false, walletClient, }) {
|
|
66
|
+
function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, bitcoinAddress, solanaConnector, solanaConnection, bitcoinConnector, bitcoinConnection, environment, config, solanaRpcUrl, connector, isConnected = false, walletClient, }) {
|
|
67
67
|
// Authentication hook - only for EVM
|
|
68
68
|
const { auth, isLoading: authLoading } = useAuth({
|
|
69
69
|
client,
|
|
@@ -90,7 +90,7 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, sol
|
|
|
90
90
|
const setDestinations = useSwap((state) => state.setDestinations);
|
|
91
91
|
const updateDestinationAmount = useSwap((state) => state.updateDestinationAmount);
|
|
92
92
|
const splits = useSwap((state) => state.splits);
|
|
93
|
-
const transactionAddress = useTransactionAddress(tokenIn, evmAddress, solAddress);
|
|
93
|
+
const transactionAddress = useTransactionAddress(tokenIn, evmAddress, solAddress, bitcoinAddress);
|
|
94
94
|
const effectiveQuoteAddress = transactionAddress;
|
|
95
95
|
const { getPrice } = usePrices();
|
|
96
96
|
const { serviceFeeRate, overheadUsd } = useStatus();
|
|
@@ -127,19 +127,28 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, sol
|
|
|
127
127
|
connector,
|
|
128
128
|
solanaConnector,
|
|
129
129
|
solanaConnection,
|
|
130
|
+
bitcoinConnector,
|
|
131
|
+
bitcoinConnection,
|
|
130
132
|
getPrice,
|
|
131
133
|
});
|
|
132
|
-
const
|
|
134
|
+
const effectiveDepositAmountUsd = useMemo(() => {
|
|
135
|
+
return depositAmountUsdFromEstimates > 0 ? depositAmountUsdFromEstimates : depositAmountUsdc;
|
|
136
|
+
}, [depositAmountUsdFromEstimates, depositAmountUsdc]);
|
|
137
|
+
const usdcPrice = usdcPriceFromEstimates || 1;
|
|
138
|
+
// Preserve last calculated fees to persist across component unmounts (e.g., when navigating to order tracking)
|
|
139
|
+
const [preservedFees, setPreservedFees] = useState(null);
|
|
140
|
+
const { handleNewSwap: handleNewSwapBase } = useResetSwapForm({
|
|
133
141
|
clearQuote,
|
|
134
142
|
isConnected,
|
|
135
143
|
wallet,
|
|
136
144
|
setFacilitatorGroups,
|
|
137
145
|
refreshWallet,
|
|
138
146
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
// Wrap handleNewSwap to also clear preserved fees
|
|
148
|
+
const handleNewSwap = useCallback(() => {
|
|
149
|
+
setPreservedFees(null);
|
|
150
|
+
handleNewSwapBase();
|
|
151
|
+
}, [handleNewSwapBase]);
|
|
143
152
|
const { serviceFeeUsd, bridgeFeeIngressUsd, bridgeFeeEgressUsd } = useHiddenSwapFees({
|
|
144
153
|
depositAmountUsdc: effectiveDepositAmountUsd,
|
|
145
154
|
bridgeProvider: bridgeProviderFromQuote,
|
|
@@ -150,6 +159,21 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, sol
|
|
|
150
159
|
ingressQuote: ingressQuoteFromEstimates,
|
|
151
160
|
egressQuotes: egressQuotesFromEstimates,
|
|
152
161
|
});
|
|
162
|
+
// Preserve fees when they're calculated (non-zero values indicate valid fees)
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (serviceFeeUsd > 0 || bridgeFeeIngressUsd > 0 || bridgeFeeEgressUsd > 0) {
|
|
165
|
+
setPreservedFees({
|
|
166
|
+
serviceFeeUsd,
|
|
167
|
+
bridgeFeeIngressUsd,
|
|
168
|
+
bridgeFeeEgressUsd,
|
|
169
|
+
depositAmountUsdc: effectiveDepositAmountUsd,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}, [serviceFeeUsd, bridgeFeeIngressUsd, bridgeFeeEgressUsd, effectiveDepositAmountUsd]);
|
|
173
|
+
// Use preserved fees if current fees are zero (component was unmounted/remounted)
|
|
174
|
+
const effectiveServiceFeeUsd = serviceFeeUsd > 0 ? serviceFeeUsd : (preservedFees?.serviceFeeUsd ?? 0);
|
|
175
|
+
const effectiveBridgeFeeIngressUsd = bridgeFeeIngressUsd > 0 ? bridgeFeeIngressUsd : (preservedFees?.bridgeFeeIngressUsd ?? 0);
|
|
176
|
+
const effectiveBridgeFeeEgressUsd = bridgeFeeEgressUsd > 0 ? bridgeFeeEgressUsd : (preservedFees?.bridgeFeeEgressUsd ?? 0);
|
|
153
177
|
const slippageUsd = useSlippageUsd({
|
|
154
178
|
inputUsdValue: effectiveDepositAmountUsd,
|
|
155
179
|
slippage,
|
|
@@ -185,9 +209,9 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, sol
|
|
|
185
209
|
orderComplete,
|
|
186
210
|
orderTrackingError,
|
|
187
211
|
orderOutputs,
|
|
188
|
-
serviceFeeUsd,
|
|
189
|
-
bridgeFeeIngressUsd,
|
|
190
|
-
bridgeFeeEgressUsd,
|
|
212
|
+
serviceFeeUsd: effectiveServiceFeeUsd,
|
|
213
|
+
bridgeFeeIngressUsd: effectiveBridgeFeeIngressUsd,
|
|
214
|
+
bridgeFeeEgressUsd: effectiveBridgeFeeEgressUsd,
|
|
191
215
|
slippageUsd,
|
|
192
216
|
serviceFeeRate,
|
|
193
217
|
overheadUsd,
|
|
@@ -197,7 +221,7 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, sol
|
|
|
197
221
|
solanaRpcUrl,
|
|
198
222
|
}, children: children });
|
|
199
223
|
}
|
|
200
|
-
export function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, environment = ENVIRONMENT.STAGING, baseUrl, solanaRpcUrl, walletClient, }) {
|
|
224
|
+
export function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, bitcoinConnector, bitcoinConnection, environment = ENVIRONMENT.STAGING, baseUrl, solanaRpcUrl, walletClient, bitcoinAddress, bitcoinRpcUrl, }) {
|
|
201
225
|
const config = useMemo(() => {
|
|
202
226
|
const computedBaseUrl = baseUrl ?? ENVIRONMENT_CONFIGS[environment].baseUrl;
|
|
203
227
|
console.log('[SilentSwapProvider] Creating config:', { environment, baseUrl, computedBaseUrl });
|
|
@@ -206,5 +230,5 @@ export function SilentSwapProvider({ children, client, evmAddress, solAddress, c
|
|
|
206
230
|
baseUrl: computedBaseUrl,
|
|
207
231
|
};
|
|
208
232
|
}, [environment, baseUrl]);
|
|
209
|
-
return (_jsx(AssetsProvider, { children: _jsx(PricesProvider, { children: _jsx(BalancesProvider, { evmAddress: evmAddress, solAddress: solAddress, solanaRpcUrl: solanaRpcUrl, children: _jsx(OrdersProvider, { baseUrl: config.baseUrl, children: _jsx(SilentSwapInnerProvider, { client: client, connector: connector, isConnected: isConnected, evmAddress: evmAddress, solAddress: solAddress, solanaConnector: solanaConnector, solanaConnection: solanaConnection, environment: environment, config: config, solanaRpcUrl: solanaRpcUrl, walletClient: walletClient, children: children }) }) }) }) }));
|
|
233
|
+
return (_jsx(AssetsProvider, { children: _jsx(PricesProvider, { children: _jsx(BalancesProvider, { evmAddress: evmAddress, solAddress: solAddress, solanaRpcUrl: solanaRpcUrl, bitcoinAddress: bitcoinAddress, bitcoinRpcUrl: bitcoinRpcUrl, children: _jsx(OrdersProvider, { baseUrl: config.baseUrl, children: _jsx(SilentSwapInnerProvider, { client: client, connector: connector, isConnected: isConnected, evmAddress: evmAddress, solAddress: solAddress, bitcoinAddress: bitcoinAddress, solanaConnector: solanaConnector, solanaConnection: solanaConnection, bitcoinConnector: bitcoinConnector, bitcoinConnection: bitcoinConnection, environment: environment, config: config, solanaRpcUrl: solanaRpcUrl, walletClient: walletClient, children: children }) }) }) }) }));
|
|
210
234
|
}
|
|
@@ -2,6 +2,7 @@ import type { Hex, WalletClient } from 'viem';
|
|
|
2
2
|
import type { Connector } from 'wagmi';
|
|
3
3
|
import type { DepositParams } from '@silentswap/sdk';
|
|
4
4
|
import type { SolanaWalletConnector, SolanaConnection } from './solana-transaction.js';
|
|
5
|
+
import type { BitcoinWalletConnector, BitcoinConnection } from './bitcoin-transaction.js';
|
|
5
6
|
/**
|
|
6
7
|
* Result from executing a bridge transaction
|
|
7
8
|
*/
|
|
@@ -29,11 +30,14 @@ export interface BridgeExecutionResult {
|
|
|
29
30
|
* @param solanaConnector - Solana wallet connector (required for Solana swaps)
|
|
30
31
|
* @param solanaConnection - Solana RPC connection (required for Solana swaps)
|
|
31
32
|
* @param solanaRpcUrl - Optional Solana RPC URL
|
|
33
|
+
* @param bitcoinConnector - Bitcoin wallet connector (required for Bitcoin swaps)
|
|
34
|
+
* @param bitcoinConnection - Bitcoin connection (optional, for consistency)
|
|
32
35
|
* @param setCurrentStep - Callback to set current step message
|
|
33
36
|
* @param onStatus - Optional status update callback
|
|
34
37
|
* @returns Functions for executing bridge transactions
|
|
35
38
|
*/
|
|
36
|
-
export declare function useBridgeExecution(walletClient: WalletClient | undefined, connector: Connector | undefined, solanaConnector: SolanaWalletConnector | undefined, solanaConnection: SolanaConnection | undefined, solanaRpcUrl: string | undefined, setCurrentStep: (step: string) => void, depositorAddress: Hex, onStatus?: (status: string) => void): {
|
|
39
|
+
export declare function useBridgeExecution(walletClient: WalletClient | undefined, connector: Connector | undefined, solanaConnector: SolanaWalletConnector | undefined, solanaConnection: SolanaConnection | undefined, solanaRpcUrl: string | undefined, setCurrentStep: (step: string) => void, depositorAddress: Hex, onStatus?: (status: string) => void, bitcoinConnector?: BitcoinWalletConnector, bitcoinConnection?: BitcoinConnection): {
|
|
37
40
|
executeSolanaBridge: (sourceAsset: string, sourceAmount: string, usdcAmount: string | undefined, solanaSenderAddress: string, evmSignerAddress: `0x${string}`, depositParams?: DepositParams<`${bigint}`>) => Promise<BridgeExecutionResult>;
|
|
41
|
+
executeBitcoinBridge: (sourceAsset: string, sourceAmount: string, usdcAmount: string | undefined, bitcoinSenderAddress: string, evmSignerAddress: `0x${string}`, depositParams?: DepositParams<`${bigint}`>) => Promise<BridgeExecutionResult>;
|
|
38
42
|
executeEvmBridge: (sourceChainId: number, sourceTokenAddress: string, sourceAmount: string, usdcAmount: string | undefined, depositParams: DepositParams<`${bigint}`>, evmSignerAddress: `0x${string}`, evmSenderAddress?: `0x${string}`, provider?: "relay" | "debridge") => Promise<BridgeExecutionResult>;
|
|
39
43
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { encodeFunctionData, erc20Abi, getAddress } from 'viem';
|
|
3
|
-
import { ensureChain, waitForTransactionConfirmation, createPublicClientWithRpc, NI_CHAIN_ID_AVALANCHE, S0X_ADDR_USDC_AVALANCHE, X_MAX_IMPACT_PERCENT, getRelayStatus, getDebridgeStatus, fetchDebridgeOrder, fetchRelayQuote, createPhonyDepositCalldata, isSolanaAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, N_RELAY_CHAIN_ID_SOLANA, SB58_ADDR_SOL_PROGRAM_SYSTEM, getChainById, } from '@silentswap/sdk';
|
|
3
|
+
import { ensureChain, waitForTransactionConfirmation, createPublicClientWithRpc, NI_CHAIN_ID_AVALANCHE, S0X_ADDR_USDC_AVALANCHE, X_MAX_IMPACT_PERCENT, getRelayStatus, getDebridgeStatus, fetchDebridgeOrder, fetchRelayQuote, createPhonyDepositCalldata, isSolanaAsset, isBitcoinAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, N_RELAY_CHAIN_ID_SOLANA, N_RELAY_CHAIN_ID_BITCOIN, SB58_ADDR_SOL_PROGRAM_SYSTEM, getChainById, SBTC_ADDR_BITCOIN_NATIVE, } from '@silentswap/sdk';
|
|
4
4
|
import { createSolanaTransactionExecutor, convertRelaySolanaStepToTransaction } from './solana-transaction.js';
|
|
5
|
+
import { createBitcoinTransactionExecutor, convertRelayBitcoinStepToTransaction } from './bitcoin-transaction.js';
|
|
5
6
|
const S0X_ADDR_EVM_ZERO = '0x0000000000000000000000000000000000000000';
|
|
6
7
|
const XG_UINT256_MAX = (1n << 256n) - 1n;
|
|
7
8
|
/**
|
|
@@ -124,29 +125,40 @@ async function sendTransactionAndWait(walletClient, connector, chainId, transact
|
|
|
124
125
|
}
|
|
125
126
|
/**
|
|
126
127
|
* Get relay.link origin asset parameters from CAIP-19
|
|
128
|
+
* Supports both Solana and Bitcoin assets
|
|
127
129
|
*/
|
|
128
|
-
function
|
|
129
|
-
if (
|
|
130
|
-
|
|
130
|
+
function getRelayOriginAssetFromCaip19(caip19) {
|
|
131
|
+
if (isSolanaAsset(caip19)) {
|
|
132
|
+
const parsed = parseSolanaCaip19(caip19);
|
|
133
|
+
if (!parsed) {
|
|
134
|
+
throw new Error(`Invalid Solana CAIP-19: ${caip19}`);
|
|
135
|
+
}
|
|
136
|
+
const originCurrency = isSolanaNativeToken(caip19)
|
|
137
|
+
? SB58_ADDR_SOL_PROGRAM_SYSTEM
|
|
138
|
+
: isSplToken(caip19)
|
|
139
|
+
? parsed.tokenAddress ||
|
|
140
|
+
(() => {
|
|
141
|
+
throw new Error(`Missing token address in Solana CAIP-19: ${caip19}`);
|
|
142
|
+
})()
|
|
143
|
+
: (() => {
|
|
144
|
+
throw new Error(`Unsupported Solana asset type: ${caip19}`);
|
|
145
|
+
})();
|
|
146
|
+
return {
|
|
147
|
+
originChainId: N_RELAY_CHAIN_ID_SOLANA,
|
|
148
|
+
originCurrency,
|
|
149
|
+
};
|
|
131
150
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
if (isBitcoinAsset(caip19)) {
|
|
152
|
+
// For Bitcoin, use the relay chain ID and pass the asset reference as currency
|
|
153
|
+
// Bitcoin CAIP-19 format: bip122:chainId/slip44:0 (for native BTC)
|
|
154
|
+
// For Bitcoin, we typically only support native BTC
|
|
155
|
+
// relay.link requires SBTC_ADDR_BITCOIN_NATIVE for native Bitcoin token
|
|
156
|
+
return {
|
|
157
|
+
originChainId: N_RELAY_CHAIN_ID_BITCOIN,
|
|
158
|
+
originCurrency: SBTC_ADDR_BITCOIN_NATIVE, // Bitcoin native token (BTC) - relay.link requires this specific address
|
|
159
|
+
};
|
|
135
160
|
}
|
|
136
|
-
|
|
137
|
-
? SB58_ADDR_SOL_PROGRAM_SYSTEM
|
|
138
|
-
: isSplToken(caip19)
|
|
139
|
-
? parsed.tokenAddress ||
|
|
140
|
-
(() => {
|
|
141
|
-
throw new Error(`Missing token address in Solana CAIP-19: ${caip19}`);
|
|
142
|
-
})()
|
|
143
|
-
: (() => {
|
|
144
|
-
throw new Error(`Unsupported Solana asset type: ${caip19}`);
|
|
145
|
-
})();
|
|
146
|
-
return {
|
|
147
|
-
originChainId: N_RELAY_CHAIN_ID_SOLANA,
|
|
148
|
-
originCurrency,
|
|
149
|
-
};
|
|
161
|
+
throw new Error(`Unsupported asset type: ${caip19}`);
|
|
150
162
|
}
|
|
151
163
|
// Depositor ABI for encoding deposit calldata
|
|
152
164
|
const DEPOSITOR_ABI = [
|
|
@@ -195,11 +207,13 @@ const DEPOSITOR_ABI = [
|
|
|
195
207
|
* @param solanaConnector - Solana wallet connector (required for Solana swaps)
|
|
196
208
|
* @param solanaConnection - Solana RPC connection (required for Solana swaps)
|
|
197
209
|
* @param solanaRpcUrl - Optional Solana RPC URL
|
|
210
|
+
* @param bitcoinConnector - Bitcoin wallet connector (required for Bitcoin swaps)
|
|
211
|
+
* @param bitcoinConnection - Bitcoin connection (optional, for consistency)
|
|
198
212
|
* @param setCurrentStep - Callback to set current step message
|
|
199
213
|
* @param onStatus - Optional status update callback
|
|
200
214
|
* @returns Functions for executing bridge transactions
|
|
201
215
|
*/
|
|
202
|
-
export function useBridgeExecution(walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, depositorAddress, onStatus) {
|
|
216
|
+
export function useBridgeExecution(walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, depositorAddress, onStatus, bitcoinConnector, bitcoinConnection) {
|
|
203
217
|
/**
|
|
204
218
|
* Execute Solana bridge transaction
|
|
205
219
|
*
|
|
@@ -224,7 +238,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
224
238
|
throw new Error('Solana connector and connection are required for Solana swaps');
|
|
225
239
|
}
|
|
226
240
|
// Get relay origin asset parameters
|
|
227
|
-
const { originChainId, originCurrency } =
|
|
241
|
+
const { originChainId, originCurrency } = getRelayOriginAssetFromCaip19(sourceAsset);
|
|
228
242
|
// CRITICAL: In Svelte, solve_uusdc_amount is called ONCE before order creation,
|
|
229
243
|
// and the result (usdcAmount) is used directly in bridge execution.
|
|
230
244
|
// We should NEVER solve again here - the usdcAmount must be provided
|
|
@@ -381,6 +395,171 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
381
395
|
throw error;
|
|
382
396
|
}
|
|
383
397
|
}, [solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, onStatus, depositorAddress]);
|
|
398
|
+
/**
|
|
399
|
+
* Execute Bitcoin bridge transaction
|
|
400
|
+
*
|
|
401
|
+
* Handles bridge execution for Bitcoin source assets:
|
|
402
|
+
* 1. Solves for optimal USDC amount (if not provided)
|
|
403
|
+
* 2. Gets quotes from relay.link (Bitcoin only supports relay)
|
|
404
|
+
* 3. Executes Bitcoin transactions
|
|
405
|
+
* 4. Monitors bridge status
|
|
406
|
+
*
|
|
407
|
+
* @param sourceAsset - Source asset CAIP-19 (Bitcoin)
|
|
408
|
+
* @param sourceAmount - Source amount in token units
|
|
409
|
+
* @param usdcAmount - Optional USDC amount (will be solved if not provided)
|
|
410
|
+
* @param depositParams - Deposit parameters from order response
|
|
411
|
+
* @param bitcoinSenderAddress - Bitcoin sender address
|
|
412
|
+
* @param evmSignerAddress - EVM signer address for deposit operations
|
|
413
|
+
* @returns Promise resolving to bridge execution result
|
|
414
|
+
*/
|
|
415
|
+
const executeBitcoinBridge = useCallback(async (sourceAsset, sourceAmount, usdcAmount, bitcoinSenderAddress, evmSignerAddress, depositParams) => {
|
|
416
|
+
try {
|
|
417
|
+
if (!bitcoinConnector) {
|
|
418
|
+
throw new Error('Bitcoin connector required for Bitcoin swaps');
|
|
419
|
+
}
|
|
420
|
+
// Get relay origin asset parameters
|
|
421
|
+
const { originChainId, originCurrency } = getRelayOriginAssetFromCaip19(sourceAsset);
|
|
422
|
+
// CRITICAL: In Svelte, solve_uusdc_amount is called ONCE before order creation,
|
|
423
|
+
// and the result (usdcAmount) is used directly in bridge execution.
|
|
424
|
+
// We should NEVER solve again here - the usdcAmount must be provided
|
|
425
|
+
// from the initial solve in handleGetQuote (matching Svelte Form.svelte line 2535 and 2576-2584).
|
|
426
|
+
if (!usdcAmount) {
|
|
427
|
+
throw new Error('USDC amount is required for Bitcoin bridge execution. ' +
|
|
428
|
+
'It should be provided from the initial solveOptimalUsdcAmount call in handleGetQuote. ' +
|
|
429
|
+
'This matches Svelte behavior where solve_uusdc_amount is called once before order creation.');
|
|
430
|
+
}
|
|
431
|
+
// Use the provided usdcAmount directly (matches Svelte behavior)
|
|
432
|
+
const bridgeUsdcAmount = usdcAmount;
|
|
433
|
+
setCurrentStep('Fetching bridge quote');
|
|
434
|
+
onStatus?.('Fetching bridge quote');
|
|
435
|
+
// Encode USDC on Avalanche approval calldata
|
|
436
|
+
const approveUsdcCalldata = encodeFunctionData({
|
|
437
|
+
abi: erc20Abi,
|
|
438
|
+
functionName: 'approve',
|
|
439
|
+
args: [depositorAddress, XG_UINT256_MAX],
|
|
440
|
+
});
|
|
441
|
+
// Encode depositProxy2 calldata from depositParams
|
|
442
|
+
let depositCalldataForExecution;
|
|
443
|
+
if (depositParams) {
|
|
444
|
+
const checksummedSigner = getAddress(evmSignerAddress);
|
|
445
|
+
const finalDepositParams = {
|
|
446
|
+
...depositParams,
|
|
447
|
+
signer: checksummedSigner,
|
|
448
|
+
approvalExpiration: typeof depositParams.approvalExpiration === 'bigint'
|
|
449
|
+
? depositParams.approvalExpiration
|
|
450
|
+
: BigInt(String(depositParams.approvalExpiration)),
|
|
451
|
+
duration: typeof depositParams.duration === 'bigint'
|
|
452
|
+
? depositParams.duration
|
|
453
|
+
: BigInt(String(depositParams.duration)),
|
|
454
|
+
};
|
|
455
|
+
depositCalldataForExecution = encodeFunctionData({
|
|
456
|
+
abi: DEPOSITOR_ABI,
|
|
457
|
+
functionName: 'depositProxy2',
|
|
458
|
+
args: [finalDepositParams],
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
console.warn('DepositParams not provided, using phony calldata for bridge quote');
|
|
463
|
+
depositCalldataForExecution = createPhonyDepositCalldata(evmSignerAddress);
|
|
464
|
+
}
|
|
465
|
+
// Request bridge quote for Bitcoin → USDC Avalanche using EXACT_OUTPUT
|
|
466
|
+
const relayQuote = await fetchRelayQuote({
|
|
467
|
+
user: bitcoinSenderAddress, // Bitcoin address
|
|
468
|
+
originChainId, // Bitcoin relay chain ID
|
|
469
|
+
originCurrency, // Bitcoin currency (SBTC_ADDR_BITCOIN_NATIVE for native BTC)
|
|
470
|
+
destinationChainId: NI_CHAIN_ID_AVALANCHE,
|
|
471
|
+
destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
|
|
472
|
+
amount: bridgeUsdcAmount, // USDC amount in micro units (EXACT_OUTPUT target)
|
|
473
|
+
recipient: depositorAddress, // EVM depositor address
|
|
474
|
+
tradeType: 'EXACT_OUTPUT', // CRITICAL: Must use EXACT_OUTPUT for execution
|
|
475
|
+
txsGasLimit: 600_000,
|
|
476
|
+
txs: [
|
|
477
|
+
{
|
|
478
|
+
to: S0X_ADDR_USDC_AVALANCHE,
|
|
479
|
+
value: '0',
|
|
480
|
+
data: approveUsdcCalldata, // USDC approval calldata
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
to: depositorAddress,
|
|
484
|
+
value: '0',
|
|
485
|
+
data: depositCalldataForExecution, // Deposit calldata
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
});
|
|
489
|
+
// Check price impact
|
|
490
|
+
const impactPercent = Number(relayQuote.details.totalImpact.percent);
|
|
491
|
+
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
492
|
+
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
493
|
+
}
|
|
494
|
+
const rawResponse = relayQuote;
|
|
495
|
+
const selectedProvider = 'relay'; // Bitcoin only supports relay
|
|
496
|
+
// Create Bitcoin transaction executor
|
|
497
|
+
const bitcoinExecutor = createBitcoinTransactionExecutor(bitcoinConnector, bitcoinConnection);
|
|
498
|
+
setCurrentStep('Executing bridge transaction');
|
|
499
|
+
onStatus?.('Executing bridge transaction');
|
|
500
|
+
// Execute transactions
|
|
501
|
+
if (selectedProvider === 'relay') {
|
|
502
|
+
const relayQuote = rawResponse;
|
|
503
|
+
if (!relayQuote.steps) {
|
|
504
|
+
throw new Error('No steps in relay quote response');
|
|
505
|
+
}
|
|
506
|
+
// Execute each step from relay.link
|
|
507
|
+
for (const step of relayQuote.steps) {
|
|
508
|
+
if (step.kind !== 'transaction') {
|
|
509
|
+
throw new Error(`Unsupported relay step kind: ${step.kind}`);
|
|
510
|
+
}
|
|
511
|
+
if (step.items.length > 1) {
|
|
512
|
+
throw new Error('Multiple items in transaction step not implemented');
|
|
513
|
+
}
|
|
514
|
+
const item = step.items[0];
|
|
515
|
+
const itemData = item.data;
|
|
516
|
+
// Bitcoin transaction (PSBT)
|
|
517
|
+
if ('psbt' in itemData || 'hex' in itemData || 'data' in itemData) {
|
|
518
|
+
setCurrentStep(step.id === 'approve'
|
|
519
|
+
? 'Requesting approval...'
|
|
520
|
+
: step.id === 'deposit'
|
|
521
|
+
? 'Requesting deposit...'
|
|
522
|
+
: 'Requesting bridge...');
|
|
523
|
+
onStatus?.(step.id === 'approve'
|
|
524
|
+
? 'Requesting approval...'
|
|
525
|
+
: step.id === 'deposit'
|
|
526
|
+
? 'Requesting deposit...'
|
|
527
|
+
: 'Requesting bridge...');
|
|
528
|
+
// Convert relay step to BridgeTransaction
|
|
529
|
+
const bitcoinTx = convertRelayBitcoinStepToTransaction(itemData, N_RELAY_CHAIN_ID_BITCOIN);
|
|
530
|
+
// Execute Bitcoin transaction
|
|
531
|
+
await bitcoinExecutor(bitcoinTx);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// Find request ID for status monitoring
|
|
535
|
+
const requestId = relayQuote.steps.find((s) => s.requestId)?.requestId;
|
|
536
|
+
if (!requestId) {
|
|
537
|
+
throw new Error('Missing relay.link request ID');
|
|
538
|
+
}
|
|
539
|
+
// Monitor bridge status
|
|
540
|
+
const depositTxHash = await monitorRelayBridgeStatus(requestId, setCurrentStep, onStatus);
|
|
541
|
+
return {
|
|
542
|
+
depositTxHash: depositTxHash,
|
|
543
|
+
provider: 'relay',
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
throw new Error(`Unsupported bridge provider: ${selectedProvider}`);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
console.error('Bitcoin bridge execution failed:', {
|
|
552
|
+
sourceAsset,
|
|
553
|
+
sourceAmount,
|
|
554
|
+
usdcAmount,
|
|
555
|
+
bitcoinSenderAddress,
|
|
556
|
+
evmSignerAddress,
|
|
557
|
+
error: error instanceof Error ? error.message : String(error),
|
|
558
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
559
|
+
});
|
|
560
|
+
throw error;
|
|
561
|
+
}
|
|
562
|
+
}, [bitcoinConnector, bitcoinConnection, setCurrentStep, onStatus, depositorAddress]);
|
|
384
563
|
/**
|
|
385
564
|
* Execute EVM bridge transaction
|
|
386
565
|
*
|
|
@@ -508,6 +687,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
508
687
|
}, [walletClient, connector, setCurrentStep, onStatus, depositorAddress]);
|
|
509
688
|
return {
|
|
510
689
|
executeSolanaBridge,
|
|
690
|
+
executeBitcoinBridge,
|
|
511
691
|
executeEvmBridge,
|
|
512
692
|
};
|
|
513
693
|
}
|