0xtrails 0.6.4 → 0.6.6
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-B4OF5VSU.js → ccip-CbJrlK-L.js} +1 -1
- package/dist/{index-Bja6TsJC.js → index-w7_dK4c5.js} +20068 -19732
- package/dist/index.js +2 -2
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/relaySdk.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +0 -1
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/package.json +2 -2
- package/src/prepareSend.ts +20 -5
- package/src/relaySdk.ts +2 -0
- package/src/tokens.ts +52 -4
- package/src/transactionIntent/deposits/gaslessDeposit.ts +2 -2
- package/src/transactionIntent/handlers/sameChainSameToken.ts +312 -1
- package/src/utils.ts +15 -0
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/ClassicSwap.tsx +88 -49
- package/src/widget/components/FeeOption.tsx +3 -0
- package/src/widget/components/Fund.tsx +37 -38
- package/src/widget/components/Pay.tsx +45 -42
- package/src/widget/components/PoolDeposit.tsx +32 -29
- package/src/widget/hooks/useSendForm.ts +92 -39
package/src/prepareSend.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { abortControllerRegistry } from "./abortController.js"
|
|
2
2
|
import { trackPaymentError, trackPaymentStarted } from "./analytics.js"
|
|
3
3
|
import { getSlippageToleranceValue } from "./widget/components/SlippageToleranceSettings.js"
|
|
4
|
-
import { isNativeToken } from "./utils.js"
|
|
4
|
+
import { isNativeToken, isZeroAccount } from "./utils.js"
|
|
5
5
|
import { getERC20TransferData } from "./encoders.js"
|
|
6
6
|
import { getTokenPrice } from "./prices.js"
|
|
7
7
|
import {
|
|
@@ -135,7 +135,7 @@ export async function prepareSend(
|
|
|
135
135
|
const transactionStates: TransactionState[] = []
|
|
136
136
|
|
|
137
137
|
// Validate recipient is not zero address
|
|
138
|
-
if (
|
|
138
|
+
if (isZeroAccount(recipient)) {
|
|
139
139
|
throw new Error("Recipient address cannot be zero address")
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -149,10 +149,16 @@ export async function prepareSend(
|
|
|
149
149
|
let effectiveDestinationAddress = recipient
|
|
150
150
|
let effectiveDestinationCalldata = destinationCalldata
|
|
151
151
|
|
|
152
|
+
// Check if this is a same-chain same-token transfer (before modifying effectiveDestinationAddress)
|
|
153
|
+
const isToSameChain = isSameChain(originChainId, destinationChainId)
|
|
154
|
+
const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
|
|
155
|
+
const isSameChainSameToken = isToSameChain && isToSameToken
|
|
156
|
+
|
|
152
157
|
if (
|
|
153
158
|
!hasCustomCalldata &&
|
|
154
159
|
tradeType === TradeType.EXACT_INPUT &&
|
|
155
|
-
!isNativeToken(destinationTokenAddress)
|
|
160
|
+
!isNativeToken(destinationTokenAddress) &&
|
|
161
|
+
!isSameChainSameToken // Don't override recipient for same-chain same-token transfers
|
|
156
162
|
) {
|
|
157
163
|
// we need to set custom calldata for the cctp transfer in order to have destination intent adddress execution needed for metatxn tracking
|
|
158
164
|
effectiveDestinationCalldata = getERC20TransferData({
|
|
@@ -292,8 +298,6 @@ export async function prepareSend(
|
|
|
292
298
|
}
|
|
293
299
|
const isToSelf =
|
|
294
300
|
account.address.toLowerCase() === effectiveDestinationAddress.toLowerCase()
|
|
295
|
-
const isToSameChain = isSameChain(originChainId, destinationChainId)
|
|
296
|
-
const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
|
|
297
301
|
|
|
298
302
|
logger.console.log("[trails-sdk] isToSameChain", isToSameChain)
|
|
299
303
|
logger.console.log("[trails-sdk] isToSameToken", isToSameToken)
|
|
@@ -381,6 +385,17 @@ export async function prepareSend(
|
|
|
381
385
|
// }
|
|
382
386
|
|
|
383
387
|
if (isToSameToken && isToSameChain) {
|
|
388
|
+
logger.console.log(
|
|
389
|
+
"[trails-sdk] Same-chain same-token detected, using handleSameChainSameToken:",
|
|
390
|
+
{
|
|
391
|
+
recipient: effectiveDestinationAddress,
|
|
392
|
+
originalRecipient: recipient,
|
|
393
|
+
accountAddress: account.address,
|
|
394
|
+
originTokenAddress,
|
|
395
|
+
destinationTokenAddress,
|
|
396
|
+
},
|
|
397
|
+
)
|
|
398
|
+
|
|
384
399
|
return await handleSameChainSameToken({
|
|
385
400
|
mainSignerAddress,
|
|
386
401
|
originTokenAddress,
|
package/src/relaySdk.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
berachain,
|
|
13
13
|
blast,
|
|
14
14
|
opBNB,
|
|
15
|
+
bsc,
|
|
15
16
|
bob,
|
|
16
17
|
boba,
|
|
17
18
|
celo,
|
|
@@ -90,6 +91,7 @@ export const relaySupportedChains: Record<number, Chain> = {
|
|
|
90
91
|
[base.id]: base,
|
|
91
92
|
[berachain.id]: berachain,
|
|
92
93
|
[blast.id]: blast,
|
|
94
|
+
[bsc.id]: bsc,
|
|
93
95
|
[opBNB.id]: opBNB,
|
|
94
96
|
[bob.id]: bob,
|
|
95
97
|
[boba.id]: boba,
|
package/src/tokens.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
linea,
|
|
17
17
|
unichain,
|
|
18
18
|
worldchain,
|
|
19
|
+
bsc,
|
|
19
20
|
} from "viem/chains"
|
|
20
21
|
import { getRelaySupportedTokens, type Chain } from "./relaySdk.js"
|
|
21
22
|
import { useReadContracts } from "wagmi"
|
|
@@ -49,13 +50,13 @@ export const commonTokenImages: Record<string, string> = {
|
|
|
49
50
|
ARB: "https://assets.sequence.info/images/tokens/large/42161/0x912ce59144191c1204e64559fe8253a0e49e6548.webp",
|
|
50
51
|
LINK: "https://assets.sequence.info/images/tokens/large/1/0x514910771af9ca656af840dff83e8264ecf986ca.webp",
|
|
51
52
|
XTZ: "https://assets.sequence.info/images/tokens/large/42793/0x0000000000000000000000000000000000000000.webp",
|
|
53
|
+
BNB: "https://assets.sequence.info/images/tokens/large/56/0x0000000000000000000000000000000000000000.webp",
|
|
52
54
|
WXTZ: "https://assets.coingecko.com/coins/images/976/standard/Tezos-logo.png?1696502091",
|
|
53
55
|
SOMI: "https://assets.sequence.info/images/tokens/large/5031/0x0000000000000000000000000000000000000000.webp",
|
|
54
56
|
XAI: "https://assets.coingecko.com/coins/images/34258/large/round_icon_2048_px.png?1719523838",
|
|
55
57
|
APE: "https://assets.sequence.info/images/tokens/large/33139/0x0000000000000000000000000000000000000000.webp",
|
|
56
58
|
ANIME:
|
|
57
59
|
"https://assets.coingecko.com/coins/images/53575/large/anime.jpg?1736748703",
|
|
58
|
-
BNB: "https://assets.sequence.info/images/tokens/large/56/0x0000000000000000000000000000000000000000.webp",
|
|
59
60
|
AVAX: "https://assets.sequence.info/images/tokens/large/43114/0x0000000000000000000000000000000000000000.webp",
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -546,16 +547,43 @@ export async function getTokenInfo(
|
|
|
546
547
|
}
|
|
547
548
|
}
|
|
548
549
|
|
|
550
|
+
// Mapping of hyphenated symbols to their canonical token symbols.
|
|
551
|
+
// The Sequence API returns BNB-USD, but we want to use USDT instead.
|
|
552
|
+
const HYPHENATED_SYMBOL_MAP: Record<string, string> = {
|
|
553
|
+
"BNB-USD": "USDT",
|
|
554
|
+
}
|
|
555
|
+
|
|
549
556
|
export async function getTokenAddress(chainId: number, tokenSymbol: string) {
|
|
550
557
|
const chainInfo = getChainInfo(chainId)
|
|
551
|
-
|
|
558
|
+
// Normalize token symbol: trim whitespace and convert to uppercase for comparison
|
|
559
|
+
const normalizedTokenSymbol = tokenSymbol.trim().toUpperCase()
|
|
560
|
+
|
|
561
|
+
const nativeSymbol = (chainInfo as any)?.nativeCurrency?.symbol
|
|
562
|
+
if (nativeSymbol && normalizedTokenSymbol === nativeSymbol.toUpperCase()) {
|
|
552
563
|
return zeroAddress
|
|
553
564
|
}
|
|
554
565
|
|
|
555
566
|
const tokens = await getSupportedTokens()
|
|
556
|
-
|
|
557
|
-
|
|
567
|
+
|
|
568
|
+
// First try exact match (normalized)
|
|
569
|
+
let token = tokens.find(
|
|
570
|
+
(t) =>
|
|
571
|
+
t.symbol.trim().toUpperCase() === normalizedTokenSymbol &&
|
|
572
|
+
t.chainId === chainId,
|
|
558
573
|
)
|
|
574
|
+
|
|
575
|
+
// If not found, check if it's a hyphenated symbol that maps to another symbol
|
|
576
|
+
if (!token) {
|
|
577
|
+
const mappedSymbol = HYPHENATED_SYMBOL_MAP[normalizedTokenSymbol]
|
|
578
|
+
if (mappedSymbol) {
|
|
579
|
+
token = tokens.find(
|
|
580
|
+
(t) =>
|
|
581
|
+
t.symbol.trim().toUpperCase() === mappedSymbol.toUpperCase() &&
|
|
582
|
+
t.chainId === chainId,
|
|
583
|
+
)
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
559
587
|
if (token?.contractAddress) {
|
|
560
588
|
return token.contractAddress
|
|
561
589
|
}
|
|
@@ -1159,6 +1187,26 @@ export const commonTokens: SupportedToken[] = [
|
|
|
1159
1187
|
chainName: etherlink.name,
|
|
1160
1188
|
imageUrl: commonTokenImages.USDT as string,
|
|
1161
1189
|
},
|
|
1190
|
+
{
|
|
1191
|
+
id: "USDT-bsc",
|
|
1192
|
+
symbol: "USDT",
|
|
1193
|
+
name: "Tether USD",
|
|
1194
|
+
contractAddress: "0x55d398326f99059ff775485246999027b3197955",
|
|
1195
|
+
decimals: 18,
|
|
1196
|
+
chainId: bsc.id,
|
|
1197
|
+
chainName: bsc.name,
|
|
1198
|
+
imageUrl: commonTokenImages.USDT as string,
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
id: "BNB-bsc",
|
|
1202
|
+
symbol: "BNB",
|
|
1203
|
+
name: "BNB",
|
|
1204
|
+
contractAddress: "0x0000000000000000000000000000000000000000",
|
|
1205
|
+
decimals: 18,
|
|
1206
|
+
chainId: bsc.id,
|
|
1207
|
+
chainName: bsc.name,
|
|
1208
|
+
imageUrl: commonTokenImages.BNB as string,
|
|
1209
|
+
},
|
|
1162
1210
|
|
|
1163
1211
|
// Somnia
|
|
1164
1212
|
{
|
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
import type { CheckoutOnHandlers } from "../../widget/hooks/useCheckout.js"
|
|
11
11
|
import type { TransactionState } from "../../transactions.js"
|
|
12
12
|
import { logger } from "../../logger.js"
|
|
13
|
-
import { isNativeToken } from "../../utils.js"
|
|
13
|
+
import { isNativeToken, isZeroAccount } from "../../utils.js"
|
|
14
14
|
import { attemptSwitchChain } from "../../chainSwitch.js"
|
|
15
15
|
import { getTransactionStateFromReceipt } from "../execution/transactionState.js"
|
|
16
16
|
import {
|
|
@@ -386,7 +386,7 @@ export async function attemptGaslessDeposit({
|
|
|
386
386
|
| undefined
|
|
387
387
|
|
|
388
388
|
// Validate that we have a valid fee collector address
|
|
389
|
-
if (!feeCollectorAddress ||
|
|
389
|
+
if (!feeCollectorAddress || isZeroAccount(feeCollectorAddress)) {
|
|
390
390
|
throw new Error(
|
|
391
391
|
"[trails-sdk] Fee collector address not provided by API. Cannot proceed with gasless deposit. " +
|
|
392
392
|
"Please ensure the API is returning feeCollectorAddress in the gasFeeOptions response.",
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
buildSameChainSameTokenTransactionParams,
|
|
25
25
|
quoteIntent,
|
|
26
26
|
commitIntent,
|
|
27
|
+
sendOriginTransaction,
|
|
27
28
|
} from "../../intents.js"
|
|
28
29
|
import { estimateGasLimit } from "../../estimate.js"
|
|
29
30
|
import { getNormalizedQuoteObject } from "../quote/normalizeQuote.js"
|
|
@@ -31,7 +32,11 @@ import { attemptSwitchChain } from "../../chainSwitch.js"
|
|
|
31
32
|
import { attemptUserDepositTx } from "../deposits/depositOrchestrator.js"
|
|
32
33
|
import { getIntentArgs } from "../quote/quoteHelpers.js"
|
|
33
34
|
import { TradeType } from "../types.js"
|
|
34
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
trackPaymentCompleted,
|
|
37
|
+
trackPaymentError,
|
|
38
|
+
trackTransactionConfirmed,
|
|
39
|
+
} from "../../analytics.js"
|
|
35
40
|
import { updatePersistentToast } from "../../toast.js"
|
|
36
41
|
import { getChainInfo } from "../../chains.js"
|
|
37
42
|
import { getTransactionStateFromReceipt } from "../execution/transactionState.js"
|
|
@@ -135,6 +140,301 @@ export async function handleSameChainSameToken({
|
|
|
135
140
|
|
|
136
141
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
137
142
|
|
|
143
|
+
// Check if this is a native token transfer - if so, skip intents and do direct transfer
|
|
144
|
+
const isNative = isNativeToken(effectiveOriginTokenAddress)
|
|
145
|
+
|
|
146
|
+
if (isNative) {
|
|
147
|
+
logger.console.log(
|
|
148
|
+
"[trails-sdk] Same-chain same-token native transfer detected, using direct transfer (no intents)",
|
|
149
|
+
{
|
|
150
|
+
originChainId: effectiveOriginChainId,
|
|
151
|
+
tokenAddress: effectiveOriginTokenAddress,
|
|
152
|
+
amount: swapAmount,
|
|
153
|
+
recipient,
|
|
154
|
+
hasCustomCalldata,
|
|
155
|
+
},
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// Build transaction params for direct native transfer
|
|
159
|
+
const originCallParamsBase = buildSameChainSameTokenTransactionParams({
|
|
160
|
+
hasCustomCalldata,
|
|
161
|
+
recipient,
|
|
162
|
+
effectiveOriginTokenAddress,
|
|
163
|
+
destinationCalldata,
|
|
164
|
+
swapAmount,
|
|
165
|
+
effectiveOriginChainId,
|
|
166
|
+
effectiveOriginChain,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// Estimate gas limit
|
|
170
|
+
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
171
|
+
effectivePublicClient,
|
|
172
|
+
{
|
|
173
|
+
account: account.address,
|
|
174
|
+
to: originCallParamsBase.to,
|
|
175
|
+
data: originCallParamsBase.data,
|
|
176
|
+
value: BigInt(originCallParamsBase.value),
|
|
177
|
+
},
|
|
178
|
+
"quote",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
logger.console.log(
|
|
182
|
+
"[trails-sdk][gas-estimation] Estimated gas limit for direct native transfer:",
|
|
183
|
+
estimatedGasLimitForQuote,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
// Create minimal quote without intent addresses (using recipient as both addresses)
|
|
187
|
+
const quote = await getNormalizedQuoteObject({
|
|
188
|
+
originDepositAddress: recipient, // Direct transfer, no intent address
|
|
189
|
+
destinationDepositAddress: recipient, // Direct transfer, no intent address
|
|
190
|
+
destinationAddress: recipient,
|
|
191
|
+
destinationCalldata,
|
|
192
|
+
originAmount: swapAmount,
|
|
193
|
+
destinationAmount: swapAmount,
|
|
194
|
+
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
195
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
196
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
197
|
+
destinationTokenAddress: effectiveOriginTokenAddress,
|
|
198
|
+
transactionStates,
|
|
199
|
+
originChainId: effectiveOriginChainId,
|
|
200
|
+
destinationChainId: effectiveOriginChainId,
|
|
201
|
+
originNativeTokenPriceUsd,
|
|
202
|
+
slippageTolerance,
|
|
203
|
+
quoteProvider: quoteProvider || "direct",
|
|
204
|
+
noSufficientBalance,
|
|
205
|
+
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
206
|
+
// No intent for direct transfers
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// Call onCheckoutQuote callback if provided
|
|
210
|
+
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
211
|
+
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Return PrepareSendReturn with direct send function
|
|
215
|
+
return {
|
|
216
|
+
quote,
|
|
217
|
+
feeOptions: undefined, // No fee options for direct native transfers
|
|
218
|
+
send: async ({
|
|
219
|
+
onOriginSend,
|
|
220
|
+
}: {
|
|
221
|
+
onOriginSend?: () => void
|
|
222
|
+
selectedFeeOption?: FeeOption | null
|
|
223
|
+
}): Promise<SendReturn> => {
|
|
224
|
+
try {
|
|
225
|
+
// Check balance before sending
|
|
226
|
+
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
227
|
+
account,
|
|
228
|
+
tokenAddress: effectiveOriginTokenAddress,
|
|
229
|
+
depositAmount: swapAmount,
|
|
230
|
+
publicClient: effectivePublicClient,
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
if (!hasEnoughBalance) {
|
|
234
|
+
throw balanceError
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const depositAmountFormatted = Number(
|
|
238
|
+
formatUnits(BigInt(swapAmount), originTokenDecimals),
|
|
239
|
+
)
|
|
240
|
+
const depositAmountUsd = calcAmountUsdPrice({
|
|
241
|
+
amount: depositAmountFormatted,
|
|
242
|
+
usdPrice: sourceTokenPriceUsd,
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Build transaction params
|
|
246
|
+
logger.console.log(
|
|
247
|
+
"[trails-sdk] Building same-chain same-token transaction params:",
|
|
248
|
+
{
|
|
249
|
+
recipient,
|
|
250
|
+
hasCustomCalldata,
|
|
251
|
+
effectiveOriginTokenAddress,
|
|
252
|
+
swapAmount,
|
|
253
|
+
accountAddress: account.address,
|
|
254
|
+
},
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
const originCallParams = buildSameChainSameTokenTransactionParams({
|
|
258
|
+
hasCustomCalldata,
|
|
259
|
+
recipient,
|
|
260
|
+
effectiveOriginTokenAddress,
|
|
261
|
+
destinationCalldata,
|
|
262
|
+
swapAmount,
|
|
263
|
+
effectiveOriginChainId,
|
|
264
|
+
effectiveOriginChain,
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
logger.console.log(
|
|
268
|
+
"[trails-sdk] origin call params",
|
|
269
|
+
originCallParams,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
let originUserTxReceipt: TransactionReceipt | null = null
|
|
273
|
+
|
|
274
|
+
if (!dryMode) {
|
|
275
|
+
// Update transaction state to pending
|
|
276
|
+
try {
|
|
277
|
+
onTransactionStateChange([
|
|
278
|
+
{
|
|
279
|
+
transactionHash: "",
|
|
280
|
+
explorerUrl: "",
|
|
281
|
+
chainId: effectiveOriginChainId,
|
|
282
|
+
state: "pending",
|
|
283
|
+
label: "Execute",
|
|
284
|
+
},
|
|
285
|
+
])
|
|
286
|
+
} catch (error) {
|
|
287
|
+
logger.console.error(
|
|
288
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
289
|
+
error,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Show persistent toast for checkout flow
|
|
294
|
+
updatePersistentToast(
|
|
295
|
+
"Payment Started",
|
|
296
|
+
"Waiting for wallet confirmation...",
|
|
297
|
+
"info",
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
logger.console.log(
|
|
301
|
+
"[trails-sdk] origin call params",
|
|
302
|
+
originCallParams,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
// Use sendOriginTransaction helper which handles gas estimation, fee boosting, and tracking
|
|
306
|
+
const txHash = await sendOriginTransaction(
|
|
307
|
+
account,
|
|
308
|
+
walletClient,
|
|
309
|
+
originCallParams as any,
|
|
310
|
+
{
|
|
311
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
logger.console.log("[trails-sdk] origin tx", txHash)
|
|
316
|
+
|
|
317
|
+
if (onOriginSend) {
|
|
318
|
+
onOriginSend()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Wait for transaction receipt
|
|
322
|
+
const receipt =
|
|
323
|
+
await effectivePublicClient.waitForTransactionReceipt({
|
|
324
|
+
hash: txHash,
|
|
325
|
+
})
|
|
326
|
+
logger.console.log("[trails-sdk] receipt", receipt)
|
|
327
|
+
originUserTxReceipt = receipt
|
|
328
|
+
|
|
329
|
+
// Track transaction confirmation
|
|
330
|
+
trackTransactionConfirmed({
|
|
331
|
+
transactionHash: txHash,
|
|
332
|
+
chainId: effectiveOriginChainId,
|
|
333
|
+
userAddress: account.address,
|
|
334
|
+
blockNumber: Number(receipt.blockNumber),
|
|
335
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
336
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
// Remove persistent toast and show success
|
|
340
|
+
const chainInfo = getChainInfo(effectiveOriginChainId)
|
|
341
|
+
updatePersistentToast(
|
|
342
|
+
"Transfer Confirmed",
|
|
343
|
+
`Your transaction on ${(chainInfo as any)?.name || "chain"} has been confirmed`,
|
|
344
|
+
"info",
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
// Update transaction state to completed
|
|
348
|
+
try {
|
|
349
|
+
onTransactionStateChange([
|
|
350
|
+
getTransactionStateFromReceipt(
|
|
351
|
+
originUserTxReceipt,
|
|
352
|
+
effectiveOriginChainId,
|
|
353
|
+
transactionStates[0]?.label || "Execute",
|
|
354
|
+
),
|
|
355
|
+
])
|
|
356
|
+
} catch (error) {
|
|
357
|
+
logger.console.error(
|
|
358
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
359
|
+
error,
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Track payment completion for same-chain same-token transaction
|
|
364
|
+
if (
|
|
365
|
+
originUserTxReceipt &&
|
|
366
|
+
originUserTxReceipt.status === "success"
|
|
367
|
+
) {
|
|
368
|
+
trackPaymentCompleted({
|
|
369
|
+
userAddress: account.address,
|
|
370
|
+
originTxHash: originUserTxReceipt.transactionHash,
|
|
371
|
+
originChainId: effectiveOriginChainId,
|
|
372
|
+
mode,
|
|
373
|
+
fundMethod,
|
|
374
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
375
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
376
|
+
destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// Call onCheckoutComplete callback if provided
|
|
380
|
+
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
381
|
+
checkoutOnHandlers.triggerCheckoutComplete(
|
|
382
|
+
"success",
|
|
383
|
+
account.address,
|
|
384
|
+
)
|
|
385
|
+
}
|
|
386
|
+
} else if (originUserTxReceipt) {
|
|
387
|
+
trackPaymentError({
|
|
388
|
+
error: "Transaction failed",
|
|
389
|
+
userAddress: account.address,
|
|
390
|
+
mode,
|
|
391
|
+
fundMethod,
|
|
392
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// Call onCheckoutError callback if provided
|
|
396
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
397
|
+
checkoutOnHandlers.triggerCheckoutError("Transaction failed")
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
depositUserTxnReceipt: originUserTxReceipt,
|
|
404
|
+
originIntentTransaction: null, // No intent for direct transfers
|
|
405
|
+
destinationIntentTransaction: null, // No intent for direct transfers
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
const errorMessage =
|
|
409
|
+
error instanceof Error
|
|
410
|
+
? error.message
|
|
411
|
+
: "Unknown error occurred during transaction"
|
|
412
|
+
logger.console.error(
|
|
413
|
+
"[trails-sdk] Error in direct native transfer:",
|
|
414
|
+
error,
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
// Track payment error
|
|
418
|
+
trackPaymentError({
|
|
419
|
+
error: errorMessage,
|
|
420
|
+
userAddress: account.address,
|
|
421
|
+
mode,
|
|
422
|
+
fundMethod,
|
|
423
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
// Call onCheckoutError callback if provided
|
|
427
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
428
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Re-throw the error so caller can handle if needed
|
|
432
|
+
throw error
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
138
438
|
// For same-chain transactions, use Intent flow to support gasless deposits
|
|
139
439
|
const salt = Date.now().toString()
|
|
140
440
|
const intentArgs = await getIntentArgs(
|
|
@@ -304,6 +604,17 @@ export async function handleSameChainSameToken({
|
|
|
304
604
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
305
605
|
|
|
306
606
|
// Build origin call params (reusing the same logic as quote estimation)
|
|
607
|
+
logger.console.log(
|
|
608
|
+
"[trails-sdk] Building same-chain same-token transaction params (intent flow):",
|
|
609
|
+
{
|
|
610
|
+
recipient,
|
|
611
|
+
hasCustomCalldata,
|
|
612
|
+
effectiveOriginTokenAddress,
|
|
613
|
+
swapAmount,
|
|
614
|
+
accountAddress: account.address,
|
|
615
|
+
},
|
|
616
|
+
)
|
|
617
|
+
|
|
307
618
|
const originCallParamsBase = buildSameChainSameTokenTransactionParams({
|
|
308
619
|
hasCustomCalldata,
|
|
309
620
|
recipient,
|
package/src/utils.ts
CHANGED
|
@@ -104,5 +104,20 @@ export function isNativeToken(
|
|
|
104
104
|
return addresses.includes(normalizedAddress)
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Checks if an address is the zero address (0x0000...0000)
|
|
109
|
+
* Use this for recipient/account addresses, not for token addresses.
|
|
110
|
+
* For token addresses, use isNativeToken() instead.
|
|
111
|
+
* @param address - The address to check
|
|
112
|
+
* @returns True if the address is the zero address
|
|
113
|
+
*/
|
|
114
|
+
export function isZeroAccount(address?: string | null): boolean {
|
|
115
|
+
if (!address) {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return address.toLowerCase() === zeroAddress.toLowerCase()
|
|
120
|
+
}
|
|
121
|
+
|
|
107
122
|
export const normalizeAddress = (address?: string | null): string =>
|
|
108
123
|
(address ?? "").toLowerCase()
|