0xtrails 0.6.3 → 0.6.5
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-Ct6RMeeG.js → ccip-BlQRn0i5.js} +1 -1
- package/dist/{index-27ebsG0R.js → index-BsEaWwhF.js} +21694 -21333
- package/dist/index.js +118 -115
- 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/crossChain.d.ts +2 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +2 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +1 -0
- package/dist/transactionIntent/types.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 +6 -1
- package/dist/widget/components/FeeOption.d.ts.map +1 -1
- package/dist/widget/components/FeeOptions.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/useQuote.d.ts +2 -1
- package/dist/widget/hooks/useQuote.d.ts.map +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/dist/widget/widget.d.ts +1 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/prepareSend.ts +23 -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/crossChain.ts +3 -0
- package/src/transactionIntent/handlers/sameChainSameToken.ts +315 -1
- package/src/transactionIntent/quote/quoteHelpers.ts +7 -2
- package/src/transactionIntent/types.ts +1 -0
- package/src/utils.ts +15 -0
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/ClassicSwap.tsx +51 -6
- package/src/widget/components/FeeOption.tsx +55 -38
- package/src/widget/components/FeeOptions.tsx +57 -10
- package/src/widget/components/Fund.tsx +0 -4
- package/src/widget/components/Pay.tsx +23 -8
- package/src/widget/components/PoolDeposit.tsx +10 -1
- package/src/widget/hooks/useQuote.ts +4 -0
- package/src/widget/hooks/useSendForm.ts +71 -36
- package/src/widget/widget.tsx +1 -0
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 {
|
|
@@ -85,6 +85,7 @@ export async function prepareSend(
|
|
|
85
85
|
executeIntentFn,
|
|
86
86
|
sequenceProjectAccessKey,
|
|
87
87
|
sequenceIndexerUrl,
|
|
88
|
+
isSmartWallet,
|
|
88
89
|
} = options
|
|
89
90
|
let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
|
|
90
91
|
|
|
@@ -134,7 +135,7 @@ export async function prepareSend(
|
|
|
134
135
|
const transactionStates: TransactionState[] = []
|
|
135
136
|
|
|
136
137
|
// Validate recipient is not zero address
|
|
137
|
-
if (
|
|
138
|
+
if (isZeroAccount(recipient)) {
|
|
138
139
|
throw new Error("Recipient address cannot be zero address")
|
|
139
140
|
}
|
|
140
141
|
|
|
@@ -148,10 +149,16 @@ export async function prepareSend(
|
|
|
148
149
|
let effectiveDestinationAddress = recipient
|
|
149
150
|
let effectiveDestinationCalldata = destinationCalldata
|
|
150
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
|
+
|
|
151
157
|
if (
|
|
152
158
|
!hasCustomCalldata &&
|
|
153
159
|
tradeType === TradeType.EXACT_INPUT &&
|
|
154
|
-
!isNativeToken(destinationTokenAddress)
|
|
160
|
+
!isNativeToken(destinationTokenAddress) &&
|
|
161
|
+
!isSameChainSameToken // Don't override recipient for same-chain same-token transfers
|
|
155
162
|
) {
|
|
156
163
|
// we need to set custom calldata for the cctp transfer in order to have destination intent adddress execution needed for metatxn tracking
|
|
157
164
|
effectiveDestinationCalldata = getERC20TransferData({
|
|
@@ -291,8 +298,6 @@ export async function prepareSend(
|
|
|
291
298
|
}
|
|
292
299
|
const isToSelf =
|
|
293
300
|
account.address.toLowerCase() === effectiveDestinationAddress.toLowerCase()
|
|
294
|
-
const isToSameChain = isSameChain(originChainId, destinationChainId)
|
|
295
|
-
const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
|
|
296
301
|
|
|
297
302
|
logger.console.log("[trails-sdk] isToSameChain", isToSameChain)
|
|
298
303
|
logger.console.log("[trails-sdk] isToSameToken", isToSameToken)
|
|
@@ -380,6 +385,17 @@ export async function prepareSend(
|
|
|
380
385
|
// }
|
|
381
386
|
|
|
382
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
|
+
|
|
383
399
|
return await handleSameChainSameToken({
|
|
384
400
|
mainSignerAddress,
|
|
385
401
|
originTokenAddress,
|
|
@@ -412,6 +428,7 @@ export async function prepareSend(
|
|
|
412
428
|
executeIntentFn,
|
|
413
429
|
sequenceProjectAccessKey,
|
|
414
430
|
sequenceIndexerUrl,
|
|
431
|
+
isSmartWallet,
|
|
415
432
|
})
|
|
416
433
|
}
|
|
417
434
|
|
|
@@ -455,6 +472,7 @@ export async function prepareSend(
|
|
|
455
472
|
executeIntentFn,
|
|
456
473
|
sequenceProjectAccessKey,
|
|
457
474
|
sequenceIndexerUrl,
|
|
475
|
+
isSmartWallet,
|
|
458
476
|
})
|
|
459
477
|
}
|
|
460
478
|
|
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.",
|
|
@@ -130,6 +130,7 @@ export async function handleCrossChain({
|
|
|
130
130
|
executeIntentFn,
|
|
131
131
|
sequenceProjectAccessKey,
|
|
132
132
|
sequenceIndexerUrl,
|
|
133
|
+
isSmartWallet,
|
|
133
134
|
}: {
|
|
134
135
|
mainSignerAddress: string
|
|
135
136
|
originChainId: number
|
|
@@ -174,6 +175,7 @@ export async function handleCrossChain({
|
|
|
174
175
|
}) => Promise<ExecuteIntentResponse>
|
|
175
176
|
sequenceProjectAccessKey?: string
|
|
176
177
|
sequenceIndexerUrl?: string
|
|
178
|
+
isSmartWallet?: boolean
|
|
177
179
|
}): Promise<PrepareSendReturn> {
|
|
178
180
|
const salt = Date.now().toString()
|
|
179
181
|
|
|
@@ -193,6 +195,7 @@ export async function handleCrossChain({
|
|
|
193
195
|
quoteProvider,
|
|
194
196
|
undefined, // connector - not available in this context
|
|
195
197
|
walletId, // walletId - use this to check for Sequence wallets
|
|
198
|
+
isSmartWallet, // isSmartWallet - force onlyNativeGasFee when true
|
|
196
199
|
)
|
|
197
200
|
|
|
198
201
|
logger.console.log(
|
|
@@ -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"
|
|
@@ -72,6 +77,7 @@ export async function handleSameChainSameToken({
|
|
|
72
77
|
executeIntentFn,
|
|
73
78
|
sequenceProjectAccessKey,
|
|
74
79
|
sequenceIndexerUrl,
|
|
80
|
+
isSmartWallet,
|
|
75
81
|
}: {
|
|
76
82
|
mainSignerAddress: string
|
|
77
83
|
originTokenAddress: string
|
|
@@ -108,6 +114,7 @@ export async function handleSameChainSameToken({
|
|
|
108
114
|
}) => Promise<ExecuteIntentResponse>
|
|
109
115
|
sequenceProjectAccessKey?: string
|
|
110
116
|
sequenceIndexerUrl?: string
|
|
117
|
+
isSmartWallet?: boolean
|
|
111
118
|
}): Promise<PrepareSendReturn> {
|
|
112
119
|
logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
|
|
113
120
|
const testnet = isTestnetDebugMode()
|
|
@@ -133,6 +140,301 @@ export async function handleSameChainSameToken({
|
|
|
133
140
|
|
|
134
141
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
135
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
|
+
|
|
136
438
|
// For same-chain transactions, use Intent flow to support gasless deposits
|
|
137
439
|
const salt = Date.now().toString()
|
|
138
440
|
const intentArgs = await getIntentArgs(
|
|
@@ -151,6 +453,7 @@ export async function handleSameChainSameToken({
|
|
|
151
453
|
quoteProvider || "",
|
|
152
454
|
undefined, // connector - not available in this context
|
|
153
455
|
walletId, // walletId - use this to check for Sequence wallets
|
|
456
|
+
isSmartWallet, // isSmartWallet - force onlyNativeGasFee when true
|
|
154
457
|
)
|
|
155
458
|
|
|
156
459
|
logger.console.log(
|
|
@@ -301,6 +604,17 @@ export async function handleSameChainSameToken({
|
|
|
301
604
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
302
605
|
|
|
303
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
|
+
|
|
304
618
|
const originCallParamsBase = buildSameChainSameTokenTransactionParams({
|
|
305
619
|
hasCustomCalldata,
|
|
306
620
|
recipient,
|
|
@@ -25,6 +25,7 @@ export async function getIntentArgs(
|
|
|
25
25
|
provider?: string | null,
|
|
26
26
|
connector?: Connector | undefined,
|
|
27
27
|
walletId?: string | undefined,
|
|
28
|
+
isSmartWallet?: boolean,
|
|
28
29
|
): Promise<QuoteIntentRequest> {
|
|
29
30
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
30
31
|
|
|
@@ -44,12 +45,16 @@ export async function getIntentArgs(
|
|
|
44
45
|
originChainId,
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
// Set onlyNativeGasFee to true if
|
|
48
|
-
|
|
48
|
+
// Set onlyNativeGasFee to true if:
|
|
49
|
+
// 1. isSmartWallet prop is explicitly set to true, OR
|
|
50
|
+
// 2. wallet is Sequence or smart contract
|
|
51
|
+
const onlyNativeGasFee =
|
|
52
|
+
isSmartWallet === true || isSequence || isSmartContract
|
|
49
53
|
|
|
50
54
|
logger.console.log("[trails-sdk] Wallet check for onlyNativeGasFee:", {
|
|
51
55
|
isSequence,
|
|
52
56
|
isSmartContract,
|
|
57
|
+
isSmartWallet,
|
|
53
58
|
onlyNativeGasFee,
|
|
54
59
|
walletId,
|
|
55
60
|
connectorName: connector?.name,
|
|
@@ -64,6 +64,7 @@ export type PrepareSendOptions = {
|
|
|
64
64
|
selectedFeeOption?: FeeOption | null
|
|
65
65
|
walletId?: string
|
|
66
66
|
abortSignal?: AbortSignal
|
|
67
|
+
isSmartWallet?: boolean
|
|
67
68
|
// Optional mutation callbacks for React Query integration
|
|
68
69
|
commitIntentFn?: (intent: Intent) => Promise<CommitIntentResponse>
|
|
69
70
|
executeIntentFn?: (params: {
|
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()
|