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.
Files changed (32) hide show
  1. package/dist/{ccip-B4OF5VSU.js → ccip-CbJrlK-L.js} +1 -1
  2. package/dist/{index-Bja6TsJC.js → index-w7_dK4c5.js} +20068 -19732
  3. package/dist/index.js +2 -2
  4. package/dist/prepareSend.d.ts.map +1 -1
  5. package/dist/relaySdk.d.ts.map +1 -1
  6. package/dist/tokens.d.ts.map +1 -1
  7. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  8. package/dist/utils.d.ts +8 -0
  9. package/dist/utils.d.ts.map +1 -1
  10. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  11. package/dist/widget/components/FeeOption.d.ts.map +1 -1
  12. package/dist/widget/components/Fund.d.ts.map +1 -1
  13. package/dist/widget/components/Pay.d.ts.map +1 -1
  14. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  15. package/dist/widget/css/compiled.css +1 -1
  16. package/dist/widget/hooks/useSendForm.d.ts +0 -1
  17. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  18. package/dist/widget/index.js +1 -1
  19. package/package.json +2 -2
  20. package/src/prepareSend.ts +20 -5
  21. package/src/relaySdk.ts +2 -0
  22. package/src/tokens.ts +52 -4
  23. package/src/transactionIntent/deposits/gaslessDeposit.ts +2 -2
  24. package/src/transactionIntent/handlers/sameChainSameToken.ts +312 -1
  25. package/src/utils.ts +15 -0
  26. package/src/widget/compiled.css +1 -1
  27. package/src/widget/components/ClassicSwap.tsx +88 -49
  28. package/src/widget/components/FeeOption.tsx +3 -0
  29. package/src/widget/components/Fund.tsx +37 -38
  30. package/src/widget/components/Pay.tsx +45 -42
  31. package/src/widget/components/PoolDeposit.tsx +32 -29
  32. package/src/widget/hooks/useSendForm.ts +92 -39
@@ -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 (isNativeToken(recipient)) {
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
- if (tokenSymbol === chainInfo?.nativeCurrency.symbol) {
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
- const token = tokens.find(
557
- (t) => t.symbol === tokenSymbol && t.chainId === chainId,
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 || isNativeToken(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 { trackPaymentCompleted, trackPaymentError } from "../../analytics.js"
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()