@silentswap/react 0.1.50 → 0.1.52
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/AssetsContext.js +1 -0
- package/dist/contexts/BalancesContext.d.ts +3 -1
- package/dist/contexts/BalancesContext.js +235 -7
- package/dist/contexts/SilentSwapContext.d.ts +6 -1
- package/dist/contexts/SilentSwapContext.js +7 -4
- package/dist/hooks/silent/tron-transaction.d.ts +34 -4
- package/dist/hooks/silent/tron-transaction.js +45 -1
- package/dist/hooks/silent/useAuth.js +14 -0
- package/dist/hooks/silent/useBridgeExecution.d.ts +12 -2
- package/dist/hooks/silent/useBridgeExecution.js +341 -114
- package/dist/hooks/silent/useQuoteCalculation.d.ts +3 -1
- package/dist/hooks/silent/useQuoteCalculation.js +72 -5
- package/dist/hooks/silent/useSilentQuote.d.ts +8 -1
- package/dist/hooks/silent/useSilentQuote.js +77 -7
- package/dist/hooks/useContacts.d.ts +2 -2
- package/dist/hooks/useOrderEstimates.d.ts +3 -1
- package/dist/hooks/useOrderEstimates.js +23 -11
- package/dist/hooks/useQuote.js +53 -15
- package/dist/hooks/useTransaction.d.ts +6 -1
- package/dist/hooks/useTransaction.js +89 -8
- package/dist/hooks/useTransactionAddress.d.ts +3 -2
- package/dist/hooks/useTransactionAddress.js +10 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/package.json +3 -3
|
@@ -1,10 +1,9 @@
|
|
|
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, 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';
|
|
3
|
+
import { ensureChain, waitForTransactionConfirmation, createPublicClientWithRpc, NI_CHAIN_ID_AVALANCHE, S0X_ADDR_USDC_AVALANCHE, S0X_ADDR_EVM_ZERO, UINT256_MAX, X_MAX_IMPACT_PERCENT, getRelayStatus, getDebridgeStatus, fetchDebridgeOrder, fetchRelayQuote, createPhonyDepositCalldata, isSolanaAsset, isBitcoinAsset, isTronAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, N_RELAY_CHAIN_ID_SOLANA, N_RELAY_CHAIN_ID_BITCOIN, N_RELAY_CHAIN_ID_TRON, N_DEBRIDGE_CHAIN_ID_SOLANA, N_DEBRIDGE_CHAIN_ID_TRON, SB58_ADDR_SOL_PROGRAM_SYSTEM, getChainById, SBTC_ADDR_BITCOIN_NATIVE, S0X_ADDR_TRON_NATIVE, } from '@silentswap/sdk';
|
|
4
4
|
import { createSolanaTransactionExecutor, convertRelaySolanaStepToTransaction } from './solana-transaction.js';
|
|
5
5
|
import { createBitcoinTransactionExecutor, convertRelayBitcoinStepToTransaction } from './bitcoin-transaction.js';
|
|
6
|
-
|
|
7
|
-
const XG_UINT256_MAX = (1n << 256n) - 1n;
|
|
6
|
+
import { createTronTransactionExecutor, convertRelayTronStepToTransaction, isRelayTronStepData } from './tron-transaction.js';
|
|
8
7
|
/** Delay after approve tx so RPC/nodes confirm before next tx (avoids MetaMask RPC errors) */
|
|
9
8
|
export const APPROVE_POST_DELAY_MS = 7000;
|
|
10
9
|
/**
|
|
@@ -160,6 +159,15 @@ function getRelayOriginAssetFromCaip19(caip19) {
|
|
|
160
159
|
originCurrency: SBTC_ADDR_BITCOIN_NATIVE, // Bitcoin native token (BTC) - relay.link requires this specific address
|
|
161
160
|
};
|
|
162
161
|
}
|
|
162
|
+
if (isTronAsset(caip19)) {
|
|
163
|
+
// Tron CAIP-19: native = tron:chainRef/slip44:195, TRC-20 = tron:chainRef/trc20:ADDRESS
|
|
164
|
+
const trc20Match = /^tron:[^/]+\/(?:trc20|token):([A-Za-z0-9]+)$/i.exec(caip19);
|
|
165
|
+
const originCurrency = trc20Match ? trc20Match[1] : S0X_ADDR_TRON_NATIVE;
|
|
166
|
+
return {
|
|
167
|
+
originChainId: N_RELAY_CHAIN_ID_TRON,
|
|
168
|
+
originCurrency,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
163
171
|
throw new Error(`Unsupported asset type: ${caip19}`);
|
|
164
172
|
}
|
|
165
173
|
// Depositor ABI for encoding deposit calldata
|
|
@@ -211,11 +219,13 @@ const DEPOSITOR_ABI = [
|
|
|
211
219
|
* @param solanaRpcUrl - Optional Solana RPC URL
|
|
212
220
|
* @param bitcoinConnector - Bitcoin wallet connector (required for Bitcoin swaps)
|
|
213
221
|
* @param bitcoinConnection - Bitcoin connection (optional, for consistency)
|
|
222
|
+
* @param tronConnector - Tron wallet connector (required for Tron swaps)
|
|
223
|
+
* @param tronConnection - Tron connection (optional, for consistency)
|
|
214
224
|
* @param setCurrentStep - Callback to set current step message
|
|
215
225
|
* @param onStatus - Optional status update callback
|
|
216
226
|
* @returns Functions for executing bridge transactions
|
|
217
227
|
*/
|
|
218
|
-
export function useBridgeExecution(walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, depositorAddress, onStatus, bitcoinConnector, bitcoinConnection) {
|
|
228
|
+
export function useBridgeExecution(walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, depositorAddress, onStatus, bitcoinConnector, bitcoinConnection, tronConnector, tronConnection) {
|
|
219
229
|
/**
|
|
220
230
|
* Execute Solana bridge transaction
|
|
221
231
|
*
|
|
@@ -234,11 +244,13 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
234
244
|
* @param evmSignerAddress - EVM signer address for deposit operations
|
|
235
245
|
* @returns Promise resolving to bridge execution result
|
|
236
246
|
*/
|
|
237
|
-
const executeSolanaBridge = useCallback(async (sourceAsset, sourceAmount, usdcAmount, solanaSenderAddress, evmSignerAddress,
|
|
247
|
+
const executeSolanaBridge = useCallback(async (sourceAsset, sourceAmount, usdcAmount, solanaSenderAddress, evmSignerAddress, options) => {
|
|
238
248
|
try {
|
|
239
249
|
if (!solanaConnector || !solanaConnection) {
|
|
240
250
|
throw new Error('Solana connector and connection are required for Solana swaps');
|
|
241
251
|
}
|
|
252
|
+
const depositParams = options?.depositParams;
|
|
253
|
+
const provider = options?.provider;
|
|
242
254
|
// Get relay origin asset parameters
|
|
243
255
|
const { originChainId, originCurrency } = getRelayOriginAssetFromCaip19(sourceAsset);
|
|
244
256
|
console.log('[SilentSwap:SolanaBridge] Start', {
|
|
@@ -264,7 +276,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
264
276
|
const approveUsdcCalldata = encodeFunctionData({
|
|
265
277
|
abi: erc20Abi,
|
|
266
278
|
functionName: 'approve',
|
|
267
|
-
args: [depositorAddress,
|
|
279
|
+
args: [depositorAddress, UINT256_MAX],
|
|
268
280
|
});
|
|
269
281
|
// Encode depositProxy2 calldata from depositParams (matches Svelte line 914-925)
|
|
270
282
|
// CRITICAL: The signer must match EXACTLY (including checksum) the one used in the SilentSwap quote request
|
|
@@ -297,61 +309,71 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
297
309
|
console.warn('DepositParams not provided, using phony calldata for bridge quote');
|
|
298
310
|
depositCalldataForExecution = createPhonyDepositCalldata(evmSignerAddress);
|
|
299
311
|
}
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
data:
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
amount:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
312
|
+
const selectedProvider = provider === 'debridge' ? 'debridge' : 'relay';
|
|
313
|
+
let rawResponse;
|
|
314
|
+
if (selectedProvider === 'debridge') {
|
|
315
|
+
const debridgeQuote = await fetchDebridgeOrder({
|
|
316
|
+
srcChainId: N_DEBRIDGE_CHAIN_ID_SOLANA,
|
|
317
|
+
srcChainTokenIn: originCurrency,
|
|
318
|
+
srcChainTokenInAmount: 'auto',
|
|
319
|
+
dstChainId: NI_CHAIN_ID_AVALANCHE,
|
|
320
|
+
dstChainTokenOut: S0X_ADDR_USDC_AVALANCHE,
|
|
321
|
+
dstChainTokenOutAmount: `${BigInt(bridgeUsdcAmount)}`,
|
|
322
|
+
dstChainTokenOutRecipient: depositorAddress,
|
|
323
|
+
prependOperatingExpenses: true,
|
|
324
|
+
additionalTakerRewardBps: 0,
|
|
325
|
+
account: solanaSenderAddress,
|
|
326
|
+
srcChainOrderAuthorityAddress: solanaSenderAddress,
|
|
327
|
+
srcChainRefundAddress: solanaSenderAddress,
|
|
328
|
+
dstChainOrderAuthorityAddress: depositorAddress,
|
|
329
|
+
dlnHook: JSON.stringify({
|
|
330
|
+
type: 'evm_transaction_call',
|
|
331
|
+
data: {
|
|
332
|
+
to: depositorAddress,
|
|
333
|
+
calldata: depositCalldataForExecution,
|
|
334
|
+
gas: 500_000,
|
|
335
|
+
},
|
|
336
|
+
}),
|
|
337
|
+
});
|
|
338
|
+
const usdIn = debridgeQuote.estimation?.srcChainTokenIn?.approximateUsdValue || 0;
|
|
339
|
+
const usdOut = debridgeQuote.estimation?.dstChainTokenOut?.approximateUsdValue || 0;
|
|
340
|
+
const impactPercent = usdIn > 0 ? 100 * (1 - usdOut / usdIn) : 0;
|
|
341
|
+
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
342
|
+
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
343
|
+
}
|
|
344
|
+
rawResponse = debridgeQuote;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const relayQuoteRequest = {
|
|
348
|
+
user: solanaSenderAddress,
|
|
349
|
+
originChainId,
|
|
350
|
+
originCurrency,
|
|
351
|
+
destinationChainId: NI_CHAIN_ID_AVALANCHE,
|
|
352
|
+
destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
|
|
353
|
+
amount: bridgeUsdcAmount,
|
|
354
|
+
recipient: depositorAddress,
|
|
355
|
+
tradeType: 'EXACT_OUTPUT',
|
|
356
|
+
txsGasLimit: 600_000,
|
|
357
|
+
txs: [
|
|
358
|
+
{
|
|
359
|
+
to: S0X_ADDR_USDC_AVALANCHE,
|
|
360
|
+
value: '0',
|
|
361
|
+
data: approveUsdcCalldata,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
to: depositorAddress,
|
|
365
|
+
value: '0',
|
|
366
|
+
data: depositCalldataForExecution,
|
|
367
|
+
},
|
|
368
|
+
],
|
|
369
|
+
};
|
|
370
|
+
const relayQuote = await fetchRelayQuote(relayQuoteRequest);
|
|
371
|
+
const impactPercent = Number(relayQuote.details.totalImpact.percent);
|
|
372
|
+
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
373
|
+
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
374
|
+
}
|
|
375
|
+
rawResponse = relayQuote;
|
|
352
376
|
}
|
|
353
|
-
const rawResponse = relayQuote;
|
|
354
|
-
const selectedProvider = 'relay';
|
|
355
377
|
// Create Solana transaction executor
|
|
356
378
|
const solanaExecutor = createSolanaTransactionExecutor(solanaConnector, solanaConnection);
|
|
357
379
|
setCurrentStep('Executing bridge transaction');
|
|
@@ -424,8 +446,21 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
424
446
|
};
|
|
425
447
|
}
|
|
426
448
|
else if (selectedProvider === 'debridge') {
|
|
427
|
-
|
|
428
|
-
|
|
449
|
+
const debridgeQuote = rawResponse;
|
|
450
|
+
if (!debridgeQuote.tx?.data) {
|
|
451
|
+
throw new Error('No Solana transaction data in deBridge response');
|
|
452
|
+
}
|
|
453
|
+
setCurrentStep('Requesting bridge...');
|
|
454
|
+
onStatus?.('Requesting bridge...');
|
|
455
|
+
await solanaExecutor({
|
|
456
|
+
chainId: N_DEBRIDGE_CHAIN_ID_SOLANA,
|
|
457
|
+
data: debridgeQuote.tx.data,
|
|
458
|
+
});
|
|
459
|
+
const debridgeOrderId = debridgeQuote?.order?.orderId;
|
|
460
|
+
if (!debridgeOrderId) {
|
|
461
|
+
throw new Error('Missing deBridge order ID');
|
|
462
|
+
}
|
|
463
|
+
return await monitorDebridgeBridgeStatus(debridgeOrderId, setCurrentStep, onStatus);
|
|
429
464
|
}
|
|
430
465
|
else {
|
|
431
466
|
throw new Error(`Unsupported bridge provider: ${selectedProvider}`);
|
|
@@ -485,7 +520,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
485
520
|
const approveUsdcCalldata = encodeFunctionData({
|
|
486
521
|
abi: erc20Abi,
|
|
487
522
|
functionName: 'approve',
|
|
488
|
-
args: [depositorAddress,
|
|
523
|
+
args: [depositorAddress, UINT256_MAX],
|
|
489
524
|
});
|
|
490
525
|
// Encode depositProxy2 calldata from depositParams
|
|
491
526
|
let depositCalldataForExecution;
|
|
@@ -609,6 +644,195 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
609
644
|
throw error;
|
|
610
645
|
}
|
|
611
646
|
}, [bitcoinConnector, bitcoinConnection, setCurrentStep, onStatus, depositorAddress]);
|
|
647
|
+
/**
|
|
648
|
+
* Execute Tron bridge transaction
|
|
649
|
+
*
|
|
650
|
+
* Handles bridge execution for Tron source assets:
|
|
651
|
+
* 1. Solves for optimal USDC amount (if not provided)
|
|
652
|
+
* 2. Gets quotes from relay.link or deBridge
|
|
653
|
+
* 3. Executes Tron transactions (EVM-style to/value/data)
|
|
654
|
+
* 4. Monitors bridge status
|
|
655
|
+
*/
|
|
656
|
+
const executeTronBridge = useCallback(async (sourceAsset, sourceAmount, usdcAmount, tronSenderAddress, evmSignerAddress, options) => {
|
|
657
|
+
try {
|
|
658
|
+
if (!tronConnector) {
|
|
659
|
+
throw new Error('Tron connector required for Tron swaps');
|
|
660
|
+
}
|
|
661
|
+
const depositParams = options?.depositParams;
|
|
662
|
+
const provider = options?.provider;
|
|
663
|
+
const { originChainId, originCurrency } = getRelayOriginAssetFromCaip19(sourceAsset);
|
|
664
|
+
if (!usdcAmount) {
|
|
665
|
+
throw new Error('USDC amount is required for Tron bridge execution. ' +
|
|
666
|
+
'It should be provided from the initial solveOptimalUsdcAmount call in handleGetQuote.');
|
|
667
|
+
}
|
|
668
|
+
const bridgeUsdcAmount = usdcAmount;
|
|
669
|
+
setCurrentStep('Fetching bridge quote');
|
|
670
|
+
onStatus?.('Fetching bridge quote');
|
|
671
|
+
const approveUsdcCalldata = encodeFunctionData({
|
|
672
|
+
abi: erc20Abi,
|
|
673
|
+
functionName: 'approve',
|
|
674
|
+
args: [depositorAddress, UINT256_MAX],
|
|
675
|
+
});
|
|
676
|
+
let depositCalldataForExecution;
|
|
677
|
+
if (depositParams) {
|
|
678
|
+
const checksummedSigner = getAddress(evmSignerAddress);
|
|
679
|
+
const finalDepositParams = {
|
|
680
|
+
...depositParams,
|
|
681
|
+
signer: checksummedSigner,
|
|
682
|
+
approvalExpiration: typeof depositParams.approvalExpiration === 'bigint'
|
|
683
|
+
? depositParams.approvalExpiration
|
|
684
|
+
: BigInt(String(depositParams.approvalExpiration)),
|
|
685
|
+
duration: typeof depositParams.duration === 'bigint'
|
|
686
|
+
? depositParams.duration
|
|
687
|
+
: BigInt(String(depositParams.duration)),
|
|
688
|
+
};
|
|
689
|
+
depositCalldataForExecution = encodeFunctionData({
|
|
690
|
+
abi: DEPOSITOR_ABI,
|
|
691
|
+
functionName: 'depositProxy2',
|
|
692
|
+
args: [finalDepositParams],
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
depositCalldataForExecution = createPhonyDepositCalldata(evmSignerAddress);
|
|
697
|
+
}
|
|
698
|
+
const selectedProvider = provider;
|
|
699
|
+
let rawResponse;
|
|
700
|
+
if (selectedProvider === 'debridge') {
|
|
701
|
+
const debridgeQuote = await fetchDebridgeOrder({
|
|
702
|
+
srcChainId: N_DEBRIDGE_CHAIN_ID_TRON,
|
|
703
|
+
srcChainTokenIn: originCurrency,
|
|
704
|
+
srcChainTokenInAmount: 'auto',
|
|
705
|
+
dstChainId: NI_CHAIN_ID_AVALANCHE,
|
|
706
|
+
dstChainTokenOut: S0X_ADDR_USDC_AVALANCHE,
|
|
707
|
+
dstChainTokenOutAmount: `${BigInt(bridgeUsdcAmount)}`,
|
|
708
|
+
dstChainTokenOutRecipient: depositorAddress,
|
|
709
|
+
prependOperatingExpenses: true,
|
|
710
|
+
additionalTakerRewardBps: 0,
|
|
711
|
+
account: tronSenderAddress,
|
|
712
|
+
srcChainOrderAuthorityAddress: tronSenderAddress,
|
|
713
|
+
srcChainRefundAddress: tronSenderAddress,
|
|
714
|
+
dstChainOrderAuthorityAddress: depositorAddress,
|
|
715
|
+
enableEstimate: false,
|
|
716
|
+
dlnHook: JSON.stringify({
|
|
717
|
+
type: 'evm_transaction_call',
|
|
718
|
+
data: {
|
|
719
|
+
to: depositorAddress,
|
|
720
|
+
calldata: depositCalldataForExecution,
|
|
721
|
+
gas: 500_000,
|
|
722
|
+
},
|
|
723
|
+
}),
|
|
724
|
+
});
|
|
725
|
+
const usdIn = debridgeQuote.estimation?.srcChainTokenIn?.approximateUsdValue || 0;
|
|
726
|
+
const usdOut = debridgeQuote.estimation?.dstChainTokenOut?.approximateUsdValue || 0;
|
|
727
|
+
const impactPercent = usdIn > 0 ? 100 * (1 - usdOut / usdIn) : 0;
|
|
728
|
+
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
729
|
+
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
730
|
+
}
|
|
731
|
+
rawResponse = debridgeQuote;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const relayQuote = await fetchRelayQuote({
|
|
735
|
+
user: tronSenderAddress,
|
|
736
|
+
originChainId,
|
|
737
|
+
originCurrency,
|
|
738
|
+
destinationChainId: NI_CHAIN_ID_AVALANCHE,
|
|
739
|
+
destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
|
|
740
|
+
amount: bridgeUsdcAmount,
|
|
741
|
+
recipient: depositorAddress,
|
|
742
|
+
tradeType: 'EXACT_OUTPUT',
|
|
743
|
+
txsGasLimit: 600_000,
|
|
744
|
+
txs: [
|
|
745
|
+
{ to: S0X_ADDR_USDC_AVALANCHE, value: '0', data: approveUsdcCalldata },
|
|
746
|
+
{ to: depositorAddress, value: '0', data: depositCalldataForExecution },
|
|
747
|
+
],
|
|
748
|
+
});
|
|
749
|
+
const impactPercent = Number(relayQuote.details.totalImpact.percent);
|
|
750
|
+
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
751
|
+
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
752
|
+
}
|
|
753
|
+
rawResponse = relayQuote;
|
|
754
|
+
}
|
|
755
|
+
const tronExecutor = createTronTransactionExecutor(tronConnector, tronConnection);
|
|
756
|
+
setCurrentStep('Executing bridge transaction');
|
|
757
|
+
onStatus?.('Executing bridge transaction');
|
|
758
|
+
if (selectedProvider === 'relay') {
|
|
759
|
+
const relayQuoteTyped = rawResponse;
|
|
760
|
+
if (!relayQuoteTyped.steps) {
|
|
761
|
+
throw new Error('No steps in relay quote response');
|
|
762
|
+
}
|
|
763
|
+
for (const step of relayQuoteTyped.steps) {
|
|
764
|
+
if (step.kind !== 'transaction') {
|
|
765
|
+
throw new Error(`Unsupported relay step kind: ${step.kind}`);
|
|
766
|
+
}
|
|
767
|
+
if (step.items.length > 1) {
|
|
768
|
+
throw new Error('Multiple items in transaction step not implemented');
|
|
769
|
+
}
|
|
770
|
+
const item = step.items[0];
|
|
771
|
+
const itemData = item.data;
|
|
772
|
+
// Relay returns Tron steps as nested TriggerSmartContract payloads
|
|
773
|
+
// (no top-level chainId), so we discriminate by shape. Non-Tron
|
|
774
|
+
// steps in a Tron bridge response are unexpected — throw instead
|
|
775
|
+
// of silently skipping so failures surface loudly.
|
|
776
|
+
if (!isRelayTronStepData(itemData)) {
|
|
777
|
+
throw new Error(`Unexpected relay step data in Tron bridge flow (step id: ${step.id}). ` +
|
|
778
|
+
`Expected TriggerSmartContract payload with 'parameter' field.`);
|
|
779
|
+
}
|
|
780
|
+
const label = step.id === 'approve'
|
|
781
|
+
? 'Requesting approval...'
|
|
782
|
+
: step.id === 'deposit'
|
|
783
|
+
? 'Requesting deposit...'
|
|
784
|
+
: 'Requesting bridge...';
|
|
785
|
+
setCurrentStep(label);
|
|
786
|
+
onStatus?.(label);
|
|
787
|
+
const tronTx = convertRelayTronStepToTransaction(itemData, N_RELAY_CHAIN_ID_TRON);
|
|
788
|
+
await tronExecutor(tronTx);
|
|
789
|
+
}
|
|
790
|
+
const requestId = relayQuoteTyped.steps.find((s) => s.requestId)?.requestId;
|
|
791
|
+
if (!requestId) {
|
|
792
|
+
throw new Error('Missing relay.link request ID');
|
|
793
|
+
}
|
|
794
|
+
const depositTxHash = await monitorRelayBridgeStatus(requestId, setCurrentStep, onStatus);
|
|
795
|
+
return {
|
|
796
|
+
depositTxHash: depositTxHash,
|
|
797
|
+
provider: 'relay',
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
else {
|
|
801
|
+
// DeBridge execution for Tron
|
|
802
|
+
const debridgeQuote = rawResponse;
|
|
803
|
+
const debridgeTx = debridgeQuote.tx;
|
|
804
|
+
if (!debridgeTx) {
|
|
805
|
+
throw new Error('No transaction in DeBridge order response');
|
|
806
|
+
}
|
|
807
|
+
setCurrentStep('Requesting bridge...');
|
|
808
|
+
onStatus?.('Requesting bridge...');
|
|
809
|
+
const tronTx = convertRelayTronStepToTransaction({
|
|
810
|
+
to: debridgeTx.to,
|
|
811
|
+
value: debridgeTx.value || '0',
|
|
812
|
+
data: debridgeTx.data,
|
|
813
|
+
chainId: N_RELAY_CHAIN_ID_TRON,
|
|
814
|
+
}, N_RELAY_CHAIN_ID_TRON);
|
|
815
|
+
await tronExecutor(tronTx);
|
|
816
|
+
const orderId = debridgeQuote.orderId;
|
|
817
|
+
if (!orderId) {
|
|
818
|
+
throw new Error('Missing DeBridge order ID');
|
|
819
|
+
}
|
|
820
|
+
return await monitorDebridgeBridgeStatus(orderId, setCurrentStep, onStatus);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
catch (error) {
|
|
824
|
+
console.error('Tron bridge execution failed:', {
|
|
825
|
+
sourceAsset,
|
|
826
|
+
sourceAmount,
|
|
827
|
+
usdcAmount,
|
|
828
|
+
tronSenderAddress,
|
|
829
|
+
evmSignerAddress,
|
|
830
|
+
error: error instanceof Error ? error.message : String(error),
|
|
831
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
832
|
+
});
|
|
833
|
+
throw error;
|
|
834
|
+
}
|
|
835
|
+
}, [tronConnector, tronConnection, setCurrentStep, onStatus, depositorAddress]);
|
|
612
836
|
/**
|
|
613
837
|
* Execute EVM bridge transaction
|
|
614
838
|
*
|
|
@@ -655,7 +879,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
655
879
|
const approveUsdcCalldata = encodeFunctionData({
|
|
656
880
|
abi: erc20Abi,
|
|
657
881
|
functionName: 'approve',
|
|
658
|
-
args: [depositorAddress,
|
|
882
|
+
args: [depositorAddress, UINT256_MAX],
|
|
659
883
|
});
|
|
660
884
|
// Match Svelte implementation exactly: spread g_params, then override signer, approvalExpiration, and duration
|
|
661
885
|
// CRITICAL: The signer must match EXACTLY (including checksum) the one used in the SilentSwap quote request
|
|
@@ -751,6 +975,7 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
751
975
|
return {
|
|
752
976
|
executeSolanaBridge,
|
|
753
977
|
executeBitcoinBridge,
|
|
978
|
+
executeTronBridge,
|
|
754
979
|
executeEvmBridge,
|
|
755
980
|
};
|
|
756
981
|
}
|
|
@@ -952,7 +1177,7 @@ walletClient, connector, setCurrentStep, onStatus) {
|
|
|
952
1177
|
address: sourceTokenAddress,
|
|
953
1178
|
abi: erc20Abi,
|
|
954
1179
|
functionName: 'approve',
|
|
955
|
-
args: [targetAllowance,
|
|
1180
|
+
args: [targetAllowance, UINT256_MAX],
|
|
956
1181
|
account: walletClient.account,
|
|
957
1182
|
chain: null,
|
|
958
1183
|
});
|
|
@@ -993,60 +1218,12 @@ walletClient, connector, setCurrentStep, onStatus) {
|
|
|
993
1218
|
throw new Error(`DeBridge transaction failed: ${receipt.transactionHash}`);
|
|
994
1219
|
}
|
|
995
1220
|
console.log('DeBridge transaction confirmed:', { hash: receipt.transactionHash });
|
|
996
|
-
setCurrentStep('Waiting for bridge');
|
|
997
|
-
onStatus?.('Waiting for bridge');
|
|
998
1221
|
// Monitor deBridge status
|
|
999
1222
|
const debridgeOrderId = debridgeResponse.orderId;
|
|
1000
1223
|
if (!debridgeOrderId) {
|
|
1001
1224
|
throw new Error('Missing deBridge order ID');
|
|
1002
1225
|
}
|
|
1003
|
-
|
|
1004
|
-
let depositTxHash;
|
|
1005
|
-
const startTime = Date.now();
|
|
1006
|
-
const timeout = 10 * 60 * 1000; // 10 minutes (bridges can take several minutes)
|
|
1007
|
-
let pollInterval = 2_000; // Start with 2 seconds
|
|
1008
|
-
const maxPollInterval = 10_000; // Max 10 seconds between polls
|
|
1009
|
-
let attemptCount = 0;
|
|
1010
|
-
while (Date.now() - startTime < timeout) {
|
|
1011
|
-
attemptCount++;
|
|
1012
|
-
try {
|
|
1013
|
-
const status = await getDebridgeStatus(debridgeOrderId);
|
|
1014
|
-
if (status.status === 'success') {
|
|
1015
|
-
depositTxHash = status.txHashes?.[0] || '0x';
|
|
1016
|
-
break;
|
|
1017
|
-
}
|
|
1018
|
-
else if (status.status === 'failed' || status.status === 'refund') {
|
|
1019
|
-
throw new Error(`DeBridge transaction failed: ${status.details || 'Unknown error'}`);
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
catch (error) {
|
|
1023
|
-
// If error is thrown from status check, re-throw it
|
|
1024
|
-
if (error instanceof Error && error.message.includes('DeBridge transaction failed')) {
|
|
1025
|
-
throw error;
|
|
1026
|
-
}
|
|
1027
|
-
// Otherwise log and continue polling
|
|
1028
|
-
console.error('Failed to fetch deBridge status:', {
|
|
1029
|
-
debridgeOrderId,
|
|
1030
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
// Update status message with elapsed time
|
|
1034
|
-
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
|
|
1035
|
-
setCurrentStep(`Waiting for bridge (${elapsedSeconds}s)...`);
|
|
1036
|
-
onStatus?.(`Waiting for bridge (${elapsedSeconds}s)...`);
|
|
1037
|
-
// Exponential backoff: increase interval after every 3 attempts
|
|
1038
|
-
if (attemptCount % 3 === 0) {
|
|
1039
|
-
pollInterval = Math.min(pollInterval * 1.5, maxPollInterval);
|
|
1040
|
-
}
|
|
1041
|
-
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1042
|
-
}
|
|
1043
|
-
if (!depositTxHash) {
|
|
1044
|
-
throw new Error('DeBridge transaction timed out after 10 minutes');
|
|
1045
|
-
}
|
|
1046
|
-
return {
|
|
1047
|
-
depositTxHash: depositTxHash,
|
|
1048
|
-
provider: 'debridge',
|
|
1049
|
-
};
|
|
1226
|
+
return await monitorDebridgeBridgeStatus(debridgeOrderId, setCurrentStep, onStatus);
|
|
1050
1227
|
}
|
|
1051
1228
|
/**
|
|
1052
1229
|
* Monitor relay.link bridge status until deposit arrives
|
|
@@ -1129,3 +1306,53 @@ async function monitorRelayBridgeStatus(requestId, setCurrentStep, onStatus) {
|
|
|
1129
1306
|
}
|
|
1130
1307
|
return depositTxHash;
|
|
1131
1308
|
}
|
|
1309
|
+
/**
|
|
1310
|
+
* Monitor deBridge bridge status until deposit arrives
|
|
1311
|
+
* With exponential backoff and timeout
|
|
1312
|
+
*/
|
|
1313
|
+
async function monitorDebridgeBridgeStatus(debridgeOrderId, setCurrentStep, onStatus) {
|
|
1314
|
+
setCurrentStep('Waiting for bridge');
|
|
1315
|
+
onStatus?.('Waiting for bridge');
|
|
1316
|
+
let depositTxHash;
|
|
1317
|
+
const startTime = Date.now();
|
|
1318
|
+
const timeout = 10 * 60 * 1000;
|
|
1319
|
+
let pollInterval = 2_000;
|
|
1320
|
+
const maxPollInterval = 10_000;
|
|
1321
|
+
let attemptCount = 0;
|
|
1322
|
+
while (Date.now() - startTime < timeout) {
|
|
1323
|
+
attemptCount++;
|
|
1324
|
+
try {
|
|
1325
|
+
const status = await getDebridgeStatus(debridgeOrderId);
|
|
1326
|
+
if (status.status === 'success') {
|
|
1327
|
+
depositTxHash = status.txHashes?.[0] || '0x';
|
|
1328
|
+
break;
|
|
1329
|
+
}
|
|
1330
|
+
if (status.status === 'failed' || status.status === 'refund') {
|
|
1331
|
+
throw new Error(`DeBridge transaction failed: ${status.details || 'Unknown error'}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
catch (error) {
|
|
1335
|
+
if (error instanceof Error && error.message.includes('DeBridge transaction failed')) {
|
|
1336
|
+
throw error;
|
|
1337
|
+
}
|
|
1338
|
+
console.error('Failed to fetch deBridge status:', {
|
|
1339
|
+
debridgeOrderId,
|
|
1340
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
const elapsedSeconds = Math.floor((Date.now() - startTime) / 1000);
|
|
1344
|
+
setCurrentStep(`Waiting for bridge (${elapsedSeconds}s)...`);
|
|
1345
|
+
onStatus?.(`Waiting for bridge (${elapsedSeconds}s)...`);
|
|
1346
|
+
if (attemptCount % 3 === 0) {
|
|
1347
|
+
pollInterval = Math.min(pollInterval * 1.5, maxPollInterval);
|
|
1348
|
+
}
|
|
1349
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
1350
|
+
}
|
|
1351
|
+
if (!depositTxHash) {
|
|
1352
|
+
throw new Error('DeBridge transaction timed out after 10 minutes');
|
|
1353
|
+
}
|
|
1354
|
+
return {
|
|
1355
|
+
depositTxHash: depositTxHash,
|
|
1356
|
+
provider: 'debridge',
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
@@ -32,6 +32,8 @@ export interface UseQuoteCalculationOptions {
|
|
|
32
32
|
setDestinations: (updater: Destination[] | ((prev: Destination[]) => Destination[])) => void;
|
|
33
33
|
/** Pro user ID for volume tracking (sent with quote request) */
|
|
34
34
|
proId?: string;
|
|
35
|
+
/** User's Tron address (TVM base58). Used as recipient fallback for Tron destinations. */
|
|
36
|
+
tronAddress?: string;
|
|
35
37
|
/** Optional: force a single bridge provider for testing. */
|
|
36
38
|
forceBridgeProvider?: 'relay' | 'debridge';
|
|
37
39
|
}
|
|
@@ -53,4 +55,4 @@ export interface UseQuoteCalculationReturn {
|
|
|
53
55
|
* - Quote request construction and fetching
|
|
54
56
|
* - Destination amount updates
|
|
55
57
|
*/
|
|
56
|
-
export declare function useQuoteCalculation({ address, evmAddress, wallet, depositorAddress, getQuote, getPrice, setDestinations, proId, forceBridgeProvider, }: UseQuoteCalculationOptions): UseQuoteCalculationReturn;
|
|
58
|
+
export declare function useQuoteCalculation({ address, evmAddress, wallet, depositorAddress, getQuote, getPrice, setDestinations, proId, tronAddress, forceBridgeProvider, }: UseQuoteCalculationOptions): UseQuoteCalculationReturn;
|