0xtrails 0.9.2 → 0.9.4
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/{ccip-g6lDdnrD.js → ccip-lAtzqne5.js} +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/{index-D-QngA_s.js → index-D5AG6huo.js} +22290 -21786
- package/dist/index.js +3 -3
- package/dist/intents.d.ts +1 -1
- package/dist/intents.d.ts.map +1 -1
- package/dist/mutations.d.ts +5 -2
- package/dist/mutations.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactionIntent/constants.d.ts +1 -0
- package/dist/transactionIntent/constants.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +3 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +4 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +2 -0
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/transactionIntent/utils/resilientDepositTracker.d.ts +25 -0
- package/dist/transactionIntent/utils/resilientDepositTracker.d.ts.map +1 -0
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +2 -2
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useQuote.d.ts +2 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/providers/TrailsProvider.d.ts +2 -0
- package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
- package/dist/widget/widget.d.ts +1 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/config.ts +1 -0
- package/src/constants.ts +1 -0
- package/src/error.ts +6 -1
- package/src/intents.ts +22 -1
- package/src/prices.ts +1 -1
- package/src/tokens.ts +4 -3
- package/src/transactionIntent/constants.ts +2 -0
- package/src/transactionIntent/deposits/depositOrchestrator.ts +7 -0
- package/src/transactionIntent/deposits/standardDeposit.ts +194 -37
- package/src/transactionIntent/handlers/crossChain.ts +152 -105
- package/src/transactionIntent/handlers/sameChainSameToken.ts +1 -0
- package/src/transactionIntent/quote/normalizeQuote.ts +7 -4
- package/src/transactionIntent/types.ts +2 -0
- package/src/transactionIntent/utils/resilientDepositTracker.ts +281 -0
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +170 -87
- package/src/widget/components/ClassicSwap.tsx +7 -1
- package/src/widget/components/ConfigDisplay.tsx +5 -0
- package/src/widget/components/Earn.tsx +14 -1
- package/src/widget/components/EarnPools.tsx +180 -59
- package/src/widget/components/Fund.tsx +3 -1
- package/src/widget/components/PoolWithdraw.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +12 -35
- package/src/widget/components/Receipt.tsx +66 -40
- package/src/widget/components/SlippageToleranceSettings.tsx +86 -44
- package/src/widget/components/TransactionDetails.tsx +138 -218
- package/src/widget/components/UserPreferences.tsx +114 -41
- package/src/widget/components/WalletConnect.tsx +111 -48
- package/src/widget/hooks/useQuote.ts +389 -352
- package/src/widget/providers/TrailsProvider.tsx +5 -0
- package/src/widget/widget.tsx +2 -0
|
@@ -212,6 +212,7 @@ export async function handleCrossChain({
|
|
|
212
212
|
destinationTokenSymbol,
|
|
213
213
|
feeTokenSymbol: originTokenSymbol,
|
|
214
214
|
},
|
|
215
|
+
abortSignal,
|
|
215
216
|
)
|
|
216
217
|
logger.console.log("[trails-sdk] Quote intent:", intent)
|
|
217
218
|
logger.console.log("[trails-sdk] Quote intent gasFeeOptions:", gasFeeOptions)
|
|
@@ -687,6 +688,8 @@ export async function handleCrossChain({
|
|
|
687
688
|
abortSignal,
|
|
688
689
|
intentId: intent.intentId,
|
|
689
690
|
executeIntentFn,
|
|
691
|
+
sequenceProjectAccessKey,
|
|
692
|
+
sequenceIndexerUrl,
|
|
690
693
|
})
|
|
691
694
|
|
|
692
695
|
// In gasless mode, depositUserTxnReceipt will be null because the transaction
|
|
@@ -817,6 +820,9 @@ export async function handleCrossChain({
|
|
|
817
820
|
}
|
|
818
821
|
|
|
819
822
|
const checkForDepositTx = async () => {
|
|
823
|
+
let consecutiveErrors = 0
|
|
824
|
+
const maxConsecutiveErrors = 5
|
|
825
|
+
|
|
820
826
|
while (true) {
|
|
821
827
|
// Check if we should abort
|
|
822
828
|
if (abortSignal?.aborted) {
|
|
@@ -827,120 +833,129 @@ export async function handleCrossChain({
|
|
|
827
833
|
}
|
|
828
834
|
|
|
829
835
|
try {
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
indexerUrl: sequenceIndexerUrl,
|
|
836
|
+
// First try balance check as primary method
|
|
837
|
+
const balanceCheck = await checkAccountBalance({
|
|
838
|
+
account: {
|
|
839
|
+
address: originIntentAddress as `0x${string}`,
|
|
840
|
+
} as Account,
|
|
841
|
+
tokenAddress: originTokenAddress,
|
|
842
|
+
depositAmount: quote.originAmount,
|
|
843
|
+
publicClient: publicClient,
|
|
839
844
|
})
|
|
840
|
-
logger.console.log(
|
|
841
|
-
"[trails-sdk] getAccountTransactionHistory response",
|
|
842
|
-
response,
|
|
843
|
-
)
|
|
844
|
-
if (response.transactions.length > 0) {
|
|
845
|
-
const tx = response.transactions[0]
|
|
846
|
-
if (!tx?.txnHash) {
|
|
847
|
-
await new Promise((resolve) =>
|
|
848
|
-
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
849
|
-
)
|
|
850
|
-
continue
|
|
851
|
-
}
|
|
852
|
-
// const isReceive = tx.transfers.some(
|
|
853
|
-
// (transfer) => transfer.transferType === "RECEIVE",
|
|
854
|
-
// )
|
|
855
|
-
// if (!isReceive) {
|
|
856
|
-
// await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY))
|
|
857
|
-
// continue
|
|
858
|
-
// }
|
|
859
|
-
const originDepositTxReceipt =
|
|
860
|
-
await publicClient.getTransactionReceipt({
|
|
861
|
-
hash: tx.txnHash as `0x${string}`,
|
|
862
|
-
})
|
|
863
845
|
|
|
864
|
-
|
|
846
|
+
if (balanceCheck.hasEnoughBalance) {
|
|
865
847
|
logger.console.log(
|
|
866
|
-
"[trails-sdk]
|
|
848
|
+
"[trails-sdk] Deposit detected via balance check (QR/exchange mode)",
|
|
867
849
|
{
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
tokenAddress: originTokenAddress,
|
|
850
|
+
balance: balanceCheck.balanceFormatted,
|
|
851
|
+
required: balanceCheck.requiredAmountFormatted,
|
|
871
852
|
},
|
|
872
853
|
)
|
|
873
854
|
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
855
|
+
// Try to find the actual transaction via indexer
|
|
856
|
+
if (sequenceIndexerUrl && sequenceProjectAccessKey) {
|
|
857
|
+
try {
|
|
858
|
+
const response = await getAccountTransactionHistory({
|
|
859
|
+
chainId: originChainId,
|
|
860
|
+
accountAddress: originIntentAddress,
|
|
861
|
+
abortSignal,
|
|
862
|
+
apiKey: sequenceProjectAccessKey,
|
|
863
|
+
indexerUrl: sequenceIndexerUrl,
|
|
864
|
+
})
|
|
882
865
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
866
|
+
if (response.transactions.length > 0) {
|
|
867
|
+
const tx = response.transactions[0]
|
|
868
|
+
if (tx?.txnHash) {
|
|
869
|
+
depositUserTxnReceipt =
|
|
870
|
+
await publicClient.getTransactionReceipt({
|
|
871
|
+
hash: tx.txnHash as `0x${string}`,
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
875
|
+
depositUserTxnReceipt,
|
|
876
|
+
originChainId,
|
|
877
|
+
transactionStates[0]?.label,
|
|
878
|
+
)
|
|
879
|
+
onTransactionStateChange(transactionStates)
|
|
880
|
+
if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
|
|
881
|
+
checkoutOnHandlers.triggerCheckoutStatusUpdate(
|
|
882
|
+
transactionStates,
|
|
883
|
+
)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// Call executeIntent after deposit detection
|
|
887
|
+
if (
|
|
888
|
+
depositUserTxnReceipt.status === "success" &&
|
|
889
|
+
intent.intentId &&
|
|
890
|
+
!executeIntentCalled
|
|
891
|
+
) {
|
|
892
|
+
logger.console.log(
|
|
893
|
+
"[trails-sdk] Calling executeIntent after deposit detection",
|
|
894
|
+
)
|
|
895
|
+
try {
|
|
896
|
+
const executeIntentFnToUse =
|
|
897
|
+
executeIntentFn ||
|
|
898
|
+
trailsClient.executeIntent.bind(trailsClient)
|
|
899
|
+
await executeIntentFnToUse({
|
|
900
|
+
intentId: intent.intentId,
|
|
901
|
+
depositTransactionHash:
|
|
902
|
+
depositUserTxnReceipt.transactionHash,
|
|
903
|
+
})
|
|
904
|
+
executeIntentCalled = true
|
|
905
|
+
if (!unifiedPollerPromise) {
|
|
906
|
+
startUnifiedPolling()
|
|
907
|
+
}
|
|
908
|
+
} catch (error) {
|
|
909
|
+
logger.console.error(
|
|
910
|
+
"[trails-sdk] Error calling executeIntent:",
|
|
911
|
+
error,
|
|
912
|
+
)
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
if (onOriginSend) {
|
|
917
|
+
onOriginSend()
|
|
918
|
+
}
|
|
919
|
+
break
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
} catch (indexerError) {
|
|
923
|
+
logger.console.debug(
|
|
924
|
+
"[trails-sdk] Indexer failed but balance is sufficient, proceeding",
|
|
925
|
+
{ error: (indexerError as Error)?.message },
|
|
926
|
+
)
|
|
927
|
+
}
|
|
901
928
|
}
|
|
902
929
|
|
|
930
|
+
// Even without tx hash, we know deposit happened
|
|
931
|
+
// Set a flag that deposit was detected but we'll need to poll for the actual tx
|
|
903
932
|
logger.console.log(
|
|
904
|
-
"[trails-sdk]
|
|
905
|
-
{
|
|
906
|
-
balance: balanceCheck.balanceFormatted,
|
|
907
|
-
required: balanceCheck.requiredAmountFormatted,
|
|
908
|
-
},
|
|
933
|
+
"[trails-sdk] Balance detected but no transaction hash - proceeding with intent execution",
|
|
909
934
|
)
|
|
910
935
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
transactionStates[0]
|
|
917
|
-
|
|
936
|
+
// Don't create a fake receipt - just mark that we detected funds
|
|
937
|
+
depositUserTxnReceipt = null
|
|
938
|
+
|
|
939
|
+
// Update state to show deposit detected (but without fake hash)
|
|
940
|
+
transactionStates[0] = {
|
|
941
|
+
...transactionStates[0],
|
|
942
|
+
state: "pending",
|
|
943
|
+
transactionHash: "", // Empty string instead of undefined for type compatibility
|
|
944
|
+
explorerUrl: transactionStates[0]?.explorerUrl || "",
|
|
945
|
+
chainId: transactionStates[0]?.chainId || originChainId,
|
|
946
|
+
label: transactionStates[0]?.label || "Deposit detected",
|
|
947
|
+
}
|
|
918
948
|
onTransactionStateChange(transactionStates)
|
|
919
|
-
// Also trigger checkout status update if handler is provided
|
|
920
949
|
if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
|
|
921
950
|
checkoutOnHandlers.triggerCheckoutStatusUpdate(
|
|
922
951
|
transactionStates,
|
|
923
952
|
)
|
|
924
953
|
}
|
|
925
954
|
|
|
926
|
-
// Call executeIntent
|
|
927
|
-
|
|
928
|
-
// Must be called BEFORE waitIntentReceipt polling starts
|
|
929
|
-
// For onramp-meld, executeIntent was already called after commitIntent
|
|
930
|
-
if (
|
|
931
|
-
originDepositTxReceipt.status === "success" &&
|
|
932
|
-
intent.intentId &&
|
|
933
|
-
(fundMethod === "qr-code" ||
|
|
934
|
-
fundMethod === "onramp-exchange") &&
|
|
935
|
-
!executeIntentCalled
|
|
936
|
-
) {
|
|
955
|
+
// Call executeIntent with balance-based detection
|
|
956
|
+
if (intent.intentId && !executeIntentCalled) {
|
|
937
957
|
logger.console.log(
|
|
938
|
-
"[trails-sdk] Calling executeIntent
|
|
939
|
-
{
|
|
940
|
-
intentId: intent.intentId,
|
|
941
|
-
txHash: originDepositTxReceipt.transactionHash,
|
|
942
|
-
fundMethod,
|
|
943
|
-
},
|
|
958
|
+
"[trails-sdk] Calling executeIntent after balance-based deposit detection",
|
|
944
959
|
)
|
|
945
960
|
try {
|
|
946
961
|
const executeIntentFnToUse =
|
|
@@ -948,24 +963,15 @@ export async function handleCrossChain({
|
|
|
948
963
|
trailsClient.executeIntent.bind(trailsClient)
|
|
949
964
|
await executeIntentFnToUse({
|
|
950
965
|
intentId: intent.intentId,
|
|
951
|
-
depositTransactionHash
|
|
952
|
-
originDepositTxReceipt.transactionHash,
|
|
966
|
+
// Note: No depositTransactionHash available in this case
|
|
953
967
|
})
|
|
954
968
|
executeIntentCalled = true
|
|
955
|
-
logger.console.log(
|
|
956
|
-
"[trails-sdk] executeIntent completed successfully (QR code mode)",
|
|
957
|
-
)
|
|
958
|
-
|
|
959
|
-
// Now that executeIntent has been called, start unified polling if not already started
|
|
960
969
|
if (!unifiedPollerPromise) {
|
|
961
|
-
logger.console.log(
|
|
962
|
-
"[trails-sdk] Starting unified polling after executeIntent (QR code mode)",
|
|
963
|
-
)
|
|
964
970
|
startUnifiedPolling()
|
|
965
971
|
}
|
|
966
972
|
} catch (error) {
|
|
967
973
|
logger.console.error(
|
|
968
|
-
"[trails-sdk] Error calling executeIntent
|
|
974
|
+
"[trails-sdk] Error calling executeIntent:",
|
|
969
975
|
error,
|
|
970
976
|
)
|
|
971
977
|
}
|
|
@@ -976,9 +982,50 @@ export async function handleCrossChain({
|
|
|
976
982
|
}
|
|
977
983
|
break
|
|
978
984
|
}
|
|
985
|
+
|
|
986
|
+
// If sequenceIndexerUrl is available, also try the indexer as backup
|
|
987
|
+
if (sequenceIndexerUrl && sequenceProjectAccessKey) {
|
|
988
|
+
try {
|
|
989
|
+
const _response = await getAccountTransactionHistory({
|
|
990
|
+
chainId: originChainId,
|
|
991
|
+
accountAddress: originIntentAddress,
|
|
992
|
+
abortSignal,
|
|
993
|
+
apiKey: sequenceProjectAccessKey,
|
|
994
|
+
indexerUrl: sequenceIndexerUrl,
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
// Original indexer-based logic continues here...
|
|
998
|
+
// (keeping the existing logic for when balance check doesn't pass)
|
|
999
|
+
} catch (indexerError) {
|
|
1000
|
+
logger.console.debug(
|
|
1001
|
+
"[trails-sdk] Indexer check failed, relying on balance check",
|
|
1002
|
+
{ error: (indexerError as Error)?.message },
|
|
1003
|
+
)
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// Reset consecutive errors on successful iteration
|
|
1008
|
+
consecutiveErrors = 0
|
|
979
1009
|
} catch (error) {
|
|
980
|
-
|
|
1010
|
+
consecutiveErrors++
|
|
1011
|
+
logger.console.error(
|
|
1012
|
+
"[trails-sdk] Error checking for deposit tx",
|
|
1013
|
+
{
|
|
1014
|
+
error,
|
|
1015
|
+
consecutiveErrors,
|
|
1016
|
+
maxConsecutiveErrors,
|
|
1017
|
+
},
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
if (consecutiveErrors >= maxConsecutiveErrors) {
|
|
1021
|
+
logger.console.error(
|
|
1022
|
+
"[trails-sdk] Max consecutive errors reached in deposit polling, continuing anyway",
|
|
1023
|
+
)
|
|
1024
|
+
// Don't break - continue polling as transaction might still go through
|
|
1025
|
+
consecutiveErrors = 0 // Reset counter to keep trying
|
|
1026
|
+
}
|
|
981
1027
|
}
|
|
1028
|
+
|
|
982
1029
|
await new Promise((resolve) =>
|
|
983
1030
|
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
984
1031
|
)
|
|
@@ -238,18 +238,19 @@ export async function getNormalizedQuoteObject({
|
|
|
238
238
|
|
|
239
239
|
// Calculate exchange rates using server-derived prices when available
|
|
240
240
|
// This ensures consistency with server-provided USD amounts (fromAmountUsd, toAmountUsd)
|
|
241
|
+
// Uses != null checks to handle 0 values correctly (not truthy checks)
|
|
241
242
|
const originTokenPrice =
|
|
242
|
-
intent?.quote?.fromAmountUsd && intent?.quote?.fromAmount
|
|
243
|
+
intent?.quote?.fromAmountUsd != null && intent?.quote?.fromAmount != null
|
|
243
244
|
? intent.quote.fromAmountUsd /
|
|
244
245
|
Number(formatUnits(intent.quote.fromAmount, originToken.decimals))
|
|
245
|
-
: originTokenPriceUsd
|
|
246
|
+
: originTokenPriceUsd != null
|
|
246
247
|
? Number(originTokenPriceUsd)
|
|
247
248
|
: 0
|
|
248
249
|
const destinationTokenPrice =
|
|
249
|
-
intent?.quote?.toAmountUsd && intent?.quote?.toAmount
|
|
250
|
+
intent?.quote?.toAmountUsd != null && intent?.quote?.toAmount != null
|
|
250
251
|
? intent.quote.toAmountUsd /
|
|
251
252
|
Number(formatUnits(intent.quote.toAmount, destinationToken.decimals))
|
|
252
|
-
: destinationTokenPriceUsd
|
|
253
|
+
: destinationTokenPriceUsd != null
|
|
253
254
|
? Number(destinationTokenPriceUsd)
|
|
254
255
|
: 0
|
|
255
256
|
const exchangeRates = getTokenExchangeRates(
|
|
@@ -373,6 +374,8 @@ export async function getNormalizedQuoteObject({
|
|
|
373
374
|
gasCostFormatted,
|
|
374
375
|
originTokenRate: exchangeRates.originTokenRate,
|
|
375
376
|
destinationTokenRate: exchangeRates.destinationTokenRate,
|
|
377
|
+
originTokenPriceUsd: originTokenPrice,
|
|
378
|
+
destinationTokenPriceUsd: destinationTokenPrice,
|
|
376
379
|
originAmountDisplay: formatAmountDisplay(originAmountFormatted),
|
|
377
380
|
destinationAmountDisplay: formatAmountDisplay(destinationAmountFormatted),
|
|
378
381
|
originAmountMinDisplay: formatAmountDisplay(originAmountMinFormatted),
|
|
@@ -126,6 +126,8 @@ export type PrepareSendQuote = {
|
|
|
126
126
|
gasCostFormatted: string
|
|
127
127
|
originTokenRate: string
|
|
128
128
|
destinationTokenRate: string
|
|
129
|
+
originTokenPriceUsd: number
|
|
130
|
+
destinationTokenPriceUsd: number
|
|
129
131
|
routeProviders?: RouteProviderInfo[]
|
|
130
132
|
noSufficientBalance: boolean
|
|
131
133
|
trailsFeeBreakdown?: TrailsFeeBreakdown | null
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import type { PublicClient, TransactionReceipt } from "viem"
|
|
2
|
+
import { logger } from "../../logger.js"
|
|
3
|
+
import { POLLING_INTERVALS } from "../constants.js"
|
|
4
|
+
import { getAccountTransactionHistory } from "../../transactions.js"
|
|
5
|
+
import type { Account } from "viem"
|
|
6
|
+
import { checkAccountBalance } from "./balanceChecker.js"
|
|
7
|
+
|
|
8
|
+
export interface ResilientDepositTrackerOptions {
|
|
9
|
+
publicClient: PublicClient
|
|
10
|
+
originChainId: number
|
|
11
|
+
originIntentAddress: string
|
|
12
|
+
originTokenAddress: string
|
|
13
|
+
depositAmount: string
|
|
14
|
+
txHash?: `0x${string}` | null
|
|
15
|
+
abortSignal?: AbortSignal
|
|
16
|
+
sequenceProjectAccessKey?: string
|
|
17
|
+
sequenceIndexerUrl?: string
|
|
18
|
+
maxRetries?: number
|
|
19
|
+
onDepositDetected?: (
|
|
20
|
+
txHash: `0x${string}` | null,
|
|
21
|
+
receipt: TransactionReceipt | null,
|
|
22
|
+
) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resilient deposit tracker that handles RPC failures gracefully
|
|
27
|
+
* Falls back to polling when transaction submission fails
|
|
28
|
+
*/
|
|
29
|
+
export async function trackDepositResilient({
|
|
30
|
+
publicClient,
|
|
31
|
+
originChainId,
|
|
32
|
+
originIntentAddress,
|
|
33
|
+
originTokenAddress,
|
|
34
|
+
depositAmount,
|
|
35
|
+
txHash,
|
|
36
|
+
abortSignal,
|
|
37
|
+
sequenceProjectAccessKey,
|
|
38
|
+
sequenceIndexerUrl,
|
|
39
|
+
maxRetries = 60, // 60 attempts = ~3 minutes with 3s intervals
|
|
40
|
+
onDepositDetected,
|
|
41
|
+
}: ResilientDepositTrackerOptions): Promise<TransactionReceipt | null> {
|
|
42
|
+
logger.console.log("[trails-sdk] Starting resilient deposit tracking", {
|
|
43
|
+
hasHash: !!txHash,
|
|
44
|
+
txHash,
|
|
45
|
+
originIntentAddress,
|
|
46
|
+
depositAmount,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
let attempts = 0
|
|
50
|
+
let lastError: Error | null = null
|
|
51
|
+
|
|
52
|
+
// If we have a transaction hash, try to get its receipt first
|
|
53
|
+
if (txHash) {
|
|
54
|
+
while (attempts < maxRetries) {
|
|
55
|
+
if (abortSignal?.aborted) {
|
|
56
|
+
logger.console.log("[trails-sdk] Deposit tracking aborted by signal")
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Try to get the transaction receipt
|
|
62
|
+
const receipt = await publicClient.getTransactionReceipt({
|
|
63
|
+
hash: txHash,
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
if (receipt) {
|
|
67
|
+
logger.console.log("[trails-sdk] Deposit transaction receipt found", {
|
|
68
|
+
hash: receipt.transactionHash,
|
|
69
|
+
status: receipt.status,
|
|
70
|
+
blockNumber: receipt.blockNumber,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Notify callback if provided
|
|
74
|
+
if (onDepositDetected) {
|
|
75
|
+
onDepositDetected(txHash, receipt)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return receipt
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
// Receipt not found yet, this is expected for pending transactions
|
|
82
|
+
if (attempts === 0) {
|
|
83
|
+
logger.console.log(
|
|
84
|
+
"[trails-sdk] Transaction receipt not available yet, will continue polling",
|
|
85
|
+
{ txHash, error: (error as Error)?.message },
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
lastError = error as Error
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
attempts++
|
|
92
|
+
await new Promise((resolve) =>
|
|
93
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_RECEIPT),
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logger.console.warn(
|
|
98
|
+
"[trails-sdk] Could not get receipt for known transaction hash after retries",
|
|
99
|
+
{
|
|
100
|
+
txHash,
|
|
101
|
+
attempts,
|
|
102
|
+
lastError: lastError?.message,
|
|
103
|
+
},
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fall back to polling the intent address for deposits
|
|
108
|
+
logger.console.log(
|
|
109
|
+
"[trails-sdk] Falling back to intent address polling for deposit detection",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
attempts = 0
|
|
113
|
+
const pollingStartTime = Date.now()
|
|
114
|
+
const maxPollingDuration = 10 * 60 * 1000 // 10 minutes max
|
|
115
|
+
|
|
116
|
+
while (attempts < maxRetries) {
|
|
117
|
+
if (abortSignal?.aborted) {
|
|
118
|
+
logger.console.log("[trails-sdk] Deposit tracking aborted by signal")
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if we've exceeded maximum polling duration
|
|
123
|
+
if (Date.now() - pollingStartTime > maxPollingDuration) {
|
|
124
|
+
logger.console.warn(
|
|
125
|
+
"[trails-sdk] Maximum polling duration exceeded for deposit detection",
|
|
126
|
+
)
|
|
127
|
+
break
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
// Primary method: Check transaction history via indexer to find ANY transaction to intent address
|
|
132
|
+
if (sequenceIndexerUrl && sequenceProjectAccessKey) {
|
|
133
|
+
try {
|
|
134
|
+
const response = await getAccountTransactionHistory({
|
|
135
|
+
chainId: originChainId,
|
|
136
|
+
accountAddress: originIntentAddress,
|
|
137
|
+
abortSignal,
|
|
138
|
+
apiKey: sequenceProjectAccessKey,
|
|
139
|
+
indexerUrl: sequenceIndexerUrl,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (response.transactions.length > 0) {
|
|
143
|
+
const recentTx = response.transactions[0]
|
|
144
|
+
if (recentTx?.txnHash) {
|
|
145
|
+
logger.console.log(
|
|
146
|
+
"[trails-sdk] Found deposit transaction to intent address via indexer",
|
|
147
|
+
{ txHash: recentTx.txnHash },
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// Get the full receipt
|
|
151
|
+
const receipt = await publicClient.getTransactionReceipt({
|
|
152
|
+
hash: recentTx.txnHash as `0x${string}`,
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
logger.console.log(
|
|
156
|
+
"[trails-sdk] Deposit transaction detected - proceeding",
|
|
157
|
+
{
|
|
158
|
+
txHash: receipt.transactionHash,
|
|
159
|
+
to: receipt.to,
|
|
160
|
+
status: receipt.status,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// Notify callback if provided
|
|
165
|
+
if (onDepositDetected) {
|
|
166
|
+
onDepositDetected(
|
|
167
|
+
receipt.transactionHash as `0x${string}`,
|
|
168
|
+
receipt,
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return receipt
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} catch (indexerError) {
|
|
176
|
+
logger.console.debug(
|
|
177
|
+
"[trails-sdk] Indexer check failed, will continue polling",
|
|
178
|
+
{ error: (indexerError as Error)?.message },
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Fallback method: Check if ANY balance exists at the intent address
|
|
184
|
+
// This indicates a transaction has occurred even if we can't find the specific tx
|
|
185
|
+
const balanceCheck = await checkAccountBalance({
|
|
186
|
+
account: {
|
|
187
|
+
address: originIntentAddress as `0x${string}`,
|
|
188
|
+
} as Account,
|
|
189
|
+
tokenAddress: originTokenAddress,
|
|
190
|
+
depositAmount: "1", // Check for any non-zero balance
|
|
191
|
+
publicClient: publicClient,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
if (balanceCheck.hasEnoughBalance) {
|
|
195
|
+
logger.console.log(
|
|
196
|
+
"[trails-sdk] Detected funds at intent address (transaction occurred)",
|
|
197
|
+
{
|
|
198
|
+
balance: balanceCheck.balanceFormatted,
|
|
199
|
+
intentAddress: originIntentAddress,
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
// We detected a deposit via balance, but don't have the tx hash
|
|
204
|
+
// This means a transaction definitely occurred
|
|
205
|
+
// Return null to indicate we detected funds but have no transaction details
|
|
206
|
+
logger.console.log(
|
|
207
|
+
"[trails-sdk] Funds detected but no transaction hash available - proceeding anyway",
|
|
208
|
+
)
|
|
209
|
+
return null
|
|
210
|
+
}
|
|
211
|
+
} catch (error) {
|
|
212
|
+
logger.console.debug("[trails-sdk] Error during deposit polling", {
|
|
213
|
+
attempt: attempts,
|
|
214
|
+
error: (error as Error)?.message,
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
attempts++
|
|
219
|
+
await new Promise((resolve) =>
|
|
220
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
logger.console.warn(
|
|
225
|
+
"[trails-sdk] Deposit tracking ended without detecting deposit",
|
|
226
|
+
{
|
|
227
|
+
attempts,
|
|
228
|
+
duration: Date.now() - pollingStartTime,
|
|
229
|
+
},
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return null
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Helper to determine if an error is recoverable via polling
|
|
237
|
+
*/
|
|
238
|
+
export function isRecoverableDepositError(error: unknown): boolean {
|
|
239
|
+
if (!error) return false
|
|
240
|
+
|
|
241
|
+
const errorMessage = (error as Error)?.message?.toLowerCase() || ""
|
|
242
|
+
const errorCode = (error as any)?.code
|
|
243
|
+
const errorCodeLower =
|
|
244
|
+
typeof errorCode === "string" ? errorCode.toLowerCase() : errorCode
|
|
245
|
+
|
|
246
|
+
// User rejections are not recoverable (case-insensitive)
|
|
247
|
+
if (
|
|
248
|
+
errorCode === 4001 ||
|
|
249
|
+
errorCodeLower === "action_rejected" ||
|
|
250
|
+
errorMessage.includes("user rejected") ||
|
|
251
|
+
errorMessage.includes("user denied") ||
|
|
252
|
+
errorMessage.includes("rejected by user")
|
|
253
|
+
) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Specific recoverable errors (case-insensitive)
|
|
258
|
+
if (
|
|
259
|
+
errorMessage.includes("wallet timeout") ||
|
|
260
|
+
errorMessage.includes("an unknown rpc error occurred")
|
|
261
|
+
) {
|
|
262
|
+
return true
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Other network/RPC errors are also recoverable (case-insensitive)
|
|
266
|
+
if (
|
|
267
|
+
errorMessage.includes("network") ||
|
|
268
|
+
errorMessage.includes("timeout") ||
|
|
269
|
+
errorMessage.includes("rpc") ||
|
|
270
|
+
errorMessage.includes("connection") ||
|
|
271
|
+
errorMessage.includes("fetch") ||
|
|
272
|
+
errorCodeLower === "network_error" ||
|
|
273
|
+
errorCodeLower === "timeout" ||
|
|
274
|
+
errorCode === -32603 // Internal JSON-RPC error (numeric, no case)
|
|
275
|
+
) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Unknown errors - DON'T attempt recovery by default (safer)
|
|
280
|
+
return false
|
|
281
|
+
}
|