0xtrails 0.5.0 → 0.6.0

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 (189) hide show
  1. package/dist/analytics.d.ts +8 -3
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-DhEkQ6QC.js → ccip-Dw5AN7oU.js} +1 -1
  4. package/dist/cctp.d.ts +0 -149
  5. package/dist/cctp.d.ts.map +1 -1
  6. package/dist/chains.d.ts +28 -3
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/constants.d.ts +1 -1
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/contractUtils.d.ts.map +1 -1
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/fees.d.ts.map +1 -1
  15. package/dist/gasless.d.ts +12 -0
  16. package/dist/gasless.d.ts.map +1 -1
  17. package/dist/{index-MhD2DA7_.js → index-BtVUTbEZ.js} +30984 -38945
  18. package/dist/index.d.ts +7 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +108 -107
  21. package/dist/indexerClient.d.ts +2 -2
  22. package/dist/intents.d.ts +0 -17
  23. package/dist/intents.d.ts.map +1 -1
  24. package/dist/mutations.d.ts.map +1 -1
  25. package/dist/paymasterSend.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +1 -1
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/sendUserOp.d.ts +0 -18
  29. package/dist/sendUserOp.d.ts.map +1 -1
  30. package/dist/tokenBalances.d.ts.map +1 -1
  31. package/dist/tokens.d.ts +10 -8
  32. package/dist/tokens.d.ts.map +1 -1
  33. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +4 -5
  34. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  35. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +4 -5
  36. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  37. package/dist/transactionIntent/deposits/standardDeposit.d.ts +2 -2
  38. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  39. package/dist/transactionIntent/execution/transactionState.d.ts +2 -2
  40. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -1
  41. package/dist/transactionIntent/handlers/crossChain.d.ts +4 -4
  42. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  43. package/dist/transactionIntent/handlers/index.d.ts +0 -1
  44. package/dist/transactionIntent/handlers/index.d.ts.map +1 -1
  45. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +4 -34
  46. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  47. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  48. package/dist/transactionIntent/quote/quoteHelpers.d.ts +2 -1
  49. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  50. package/dist/transactionIntent/types.d.ts +6 -19
  51. package/dist/transactionIntent/types.d.ts.map +1 -1
  52. package/dist/transactionIntent/utils/index.d.ts +0 -1
  53. package/dist/transactionIntent/utils/index.d.ts.map +1 -1
  54. package/dist/transactions.d.ts +2 -20
  55. package/dist/transactions.d.ts.map +1 -1
  56. package/dist/utils.d.ts +8 -2
  57. package/dist/utils.d.ts.map +1 -1
  58. package/dist/walletUtils.d.ts +21 -0
  59. package/dist/walletUtils.d.ts.map +1 -0
  60. package/dist/wallets.d.ts +33 -240
  61. package/dist/wallets.d.ts.map +1 -1
  62. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  63. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  64. package/dist/widget/components/FeeOption.d.ts +8 -13
  65. package/dist/widget/components/FeeOption.d.ts.map +1 -1
  66. package/dist/widget/components/FeeOptions.d.ts +11 -5
  67. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  68. package/dist/widget/components/NativeGasOption.d.ts.map +1 -1
  69. package/dist/widget/components/Pay.d.ts.map +1 -1
  70. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  71. package/dist/widget/components/QRCodeDeposit.d.ts +5 -0
  72. package/dist/widget/components/QRCodeDeposit.d.ts.map +1 -1
  73. package/dist/widget/components/QRCodeWalletSelect.d.ts +13 -0
  74. package/dist/widget/components/QRCodeWalletSelect.d.ts.map +1 -0
  75. package/dist/widget/components/QrCode.d.ts.map +1 -1
  76. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  77. package/dist/widget/components/Receipt.d.ts.map +1 -1
  78. package/dist/widget/components/ScreenHeader.d.ts +1 -1
  79. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  80. package/dist/widget/components/Toast.d.ts.map +1 -1
  81. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  82. package/dist/widget/css/compiled.css +1 -1
  83. package/dist/widget/hooks/useCheckout.d.ts +15 -1
  84. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  85. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  86. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  87. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  88. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  89. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  90. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts +7 -0
  91. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts.map +1 -0
  92. package/dist/widget/hooks/useIsSequenceWallet.d.ts +6 -0
  93. package/dist/widget/hooks/useIsSequenceWallet.d.ts.map +1 -0
  94. package/dist/widget/hooks/useQuote.d.ts +5 -8
  95. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  96. package/dist/widget/hooks/useRecentTokens.d.ts.map +1 -1
  97. package/dist/widget/hooks/useSelectedFeeOption.d.ts +30 -0
  98. package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -0
  99. package/dist/widget/hooks/useSendForm.d.ts +6 -15
  100. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  101. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  102. package/dist/widget/index.js +1 -1
  103. package/dist/widget/providers/TrailsProvider.d.ts +23 -12
  104. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
  105. package/dist/widget/widget.d.ts +11 -0
  106. package/dist/widget/widget.d.ts.map +1 -1
  107. package/package.json +8 -8
  108. package/src/analytics.ts +53 -21
  109. package/src/cctp.ts +0 -1016
  110. package/src/chains.ts +93 -39
  111. package/src/config.ts +24 -6
  112. package/src/constants.ts +1 -4
  113. package/src/contractUtils.ts +6 -6
  114. package/src/estimate.ts +3 -6
  115. package/src/fees.ts +5 -10
  116. package/src/gasless.ts +45 -0
  117. package/src/index.ts +7 -6
  118. package/src/indexerClient.ts +2 -2
  119. package/src/intents.ts +52 -206
  120. package/src/mutations.ts +3 -2
  121. package/src/paymasterSend.ts +2 -5
  122. package/src/prepareSend.ts +9 -12
  123. package/src/sendUserOp.ts +3 -64
  124. package/src/tokenBalances.ts +2 -1
  125. package/src/tokens.ts +62 -133
  126. package/src/trailsClient.ts +1 -1
  127. package/src/transactionIntent/deposits/depositOrchestrator.ts +14 -15
  128. package/src/transactionIntent/deposits/gaslessDeposit.ts +70 -100
  129. package/src/transactionIntent/deposits/standardDeposit.ts +22 -28
  130. package/src/transactionIntent/execution/transactionState.ts +2 -2
  131. package/src/transactionIntent/handlers/crossChain.ts +165 -385
  132. package/src/transactionIntent/handlers/index.ts +0 -1
  133. package/src/transactionIntent/handlers/sameChainSameToken.ts +228 -94
  134. package/src/transactionIntent/quote/normalizeQuote.ts +4 -6
  135. package/src/transactionIntent/quote/quoteHelpers.ts +35 -3
  136. package/src/transactionIntent/types.ts +6 -27
  137. package/src/transactionIntent/utils/index.ts +0 -1
  138. package/src/transactions.ts +6 -203
  139. package/src/umd.tsx +1 -3
  140. package/src/utils.ts +28 -8
  141. package/src/walletUtils.ts +42 -0
  142. package/src/wallets.ts +361 -203
  143. package/src/widget/compiled.css +1 -1
  144. package/src/widget/components/AccountIntentTransactionHistory.tsx +73 -4
  145. package/src/widget/components/AccountSettings.tsx +17 -17
  146. package/src/widget/components/ChainList.tsx +3 -3
  147. package/src/widget/components/ClassicSwap.tsx +19 -10
  148. package/src/widget/components/ConfigDisplay.tsx +1 -1
  149. package/src/widget/components/FeeOption.tsx +63 -20
  150. package/src/widget/components/FeeOptions.tsx +54 -123
  151. package/src/widget/components/NativeGasOption.tsx +3 -1
  152. package/src/widget/components/Pay.tsx +18 -11
  153. package/src/widget/components/PoolDeposit.tsx +23 -10
  154. package/src/widget/components/QRCodeDeposit.tsx +50 -30
  155. package/src/widget/components/QRCodeWalletSelect.tsx +77 -0
  156. package/src/widget/components/QrCode.tsx +188 -233
  157. package/src/widget/components/QuoteDetails.tsx +48 -2
  158. package/src/widget/components/Receipt.tsx +5 -2
  159. package/src/widget/components/ScreenHeader.tsx +10 -8
  160. package/src/widget/components/Toast.tsx +10 -0
  161. package/src/widget/components/TokenImage.tsx +56 -13
  162. package/src/widget/hooks/useCheckout.ts +71 -0
  163. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  164. package/src/widget/hooks/useDebugScreens.ts +5 -0
  165. package/src/widget/hooks/useIntentTransactionHistory.ts +788 -418
  166. package/src/widget/hooks/useIsConnectedWalletSmartContract.ts +43 -0
  167. package/src/widget/hooks/useIsSequenceWallet.ts +17 -0
  168. package/src/widget/hooks/useQuote.ts +16 -17
  169. package/src/widget/hooks/useRecentTokens.ts +2 -1
  170. package/src/widget/hooks/useSelectedFeeOption.tsx +257 -0
  171. package/src/widget/hooks/useSendForm.ts +172 -47
  172. package/src/widget/hooks/useTokenList.ts +15 -2
  173. package/src/widget/providers/TrailsProvider.tsx +53 -25
  174. package/src/widget/widget.tsx +119 -48
  175. package/dist/cctpqueue.d.ts +0 -18
  176. package/dist/cctpqueue.d.ts.map +0 -1
  177. package/dist/preconditions.d.ts +0 -12
  178. package/dist/preconditions.d.ts.map +0 -1
  179. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +0 -62
  180. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +0 -1
  181. package/dist/transactionIntent/utils/lifiHelpers.d.ts +0 -10
  182. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +0 -1
  183. package/dist/widget/hooks/useSelectedFeeToken.d.ts +0 -33
  184. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +0 -1
  185. package/src/cctpqueue.ts +0 -69
  186. package/src/preconditions.ts +0 -47
  187. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +0 -323
  188. package/src/transactionIntent/utils/lifiHelpers.ts +0 -68
  189. package/src/widget/hooks/useSelectedFeeToken.tsx +0 -288
package/src/cctp.ts CHANGED
@@ -1,12 +1,3 @@
1
- import type { Chain, PublicClient, WalletClient } from "viem"
2
- import {
3
- createPublicClient,
4
- encodeFunctionData,
5
- erc20Abi,
6
- getAddress,
7
- http,
8
- maxUint256,
9
- } from "viem"
10
1
  import {
11
2
  mainnet,
12
3
  sepolia,
@@ -27,30 +18,6 @@ import {
27
18
  worldchain,
28
19
  worldchainSepolia,
29
20
  } from "viem/chains"
30
- import { attemptSwitchChain } from "./chainSwitch.js"
31
- import { getIsTestnetChainId } from "./chains.js"
32
- import { logger } from "./logger.js"
33
-
34
- const domains: Record<number, number> = {
35
- [mainnet.id]: 0,
36
- [sepolia.id]: 0,
37
- [avalanche.id]: 1,
38
- [avalancheFuji.id]: 1,
39
- [optimism.id]: 2,
40
- [optimismSepolia.id]: 2,
41
- [arbitrum.id]: 3,
42
- [arbitrumSepolia.id]: 3,
43
- [base.id]: 6,
44
- [baseSepolia.id]: 6,
45
- [polygon.id]: 7,
46
- [polygonAmoy.id]: 7,
47
- [unichain.id]: 10,
48
- [unichainSepolia.id]: 10,
49
- [linea.id]: 11,
50
- [lineaSepolia.id]: 11,
51
- [worldchain.id]: 14,
52
- [worldchainSepolia.id]: 14,
53
- }
54
21
 
55
22
  const tokenAddresses: Record<number, string> = {
56
23
  // Mainnet USDC addresses from Circle CCTP documentation
@@ -78,993 +45,10 @@ const tokenAddresses: Record<number, string> = {
78
45
  [worldchain.id]: "0x79A02482A880bCe3F13E09da970dC34dB4cD24D1", // World Chain
79
46
  }
80
47
 
81
- const tokenMessengers: Record<number, string> = {
82
- [mainnet.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
83
- [sepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
84
- [avalanche.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
85
- [avalancheFuji.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
86
- [optimism.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
87
- [optimismSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
88
- [arbitrum.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
89
- [arbitrumSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
90
- [base.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
91
- [baseSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
92
- [polygon.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
93
- [polygonAmoy.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
94
- [unichain.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
95
- [unichainSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
96
- [linea.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
97
- [lineaSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
98
- [worldchain.id]: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
99
- [worldchainSepolia.id]: "0x8FE6B999Dc680CcFDD5Bf7EB0974218be2542DAA",
100
- }
101
-
102
- const messageTransmitters: Record<number, string> = {
103
- [mainnet.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
104
- [sepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
105
- [avalanche.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
106
- [avalancheFuji.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
107
- [optimism.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
108
- [optimismSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
109
- [arbitrum.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
110
- [arbitrumSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
111
- [base.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
112
- [baseSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
113
- [polygon.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
114
- [polygonAmoy.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
115
- [unichain.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
116
- [unichainSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
117
- [linea.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
118
- [lineaSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
119
- [worldchain.id]: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
120
- [worldchainSepolia.id]: "0xE737e5cEBEEBa77EFE34D4aa090756590b1CE275",
121
- }
122
-
123
- const customCctpRelayerAddress: Record<number, string> = {
124
- //[chains.arbitrumSepolia.id]: "0x58FEFe8A057E736CD361272BA2283DAfD8646198",
125
- [arbitrumSepolia.id]: "0x05F3AcC7a7BB0e888Bb1bDE014bD61AfAfaC6943",
126
- }
127
-
128
- export type Attestation = {
129
- attestation: `0x${string}`
130
- message: `0x${string}`
131
- }
132
-
133
- export function getDomain(chainId: number): number | null {
134
- return domains[chainId] ?? null
135
- }
136
-
137
48
  export function getUSDCTokenAddress(chainId: number): string | null {
138
49
  return tokenAddresses[chainId] ?? null
139
50
  }
140
51
 
141
- export function getTokenMessenger(chainId: number): string | null {
142
- return tokenMessengers[chainId] ?? null
143
- }
144
-
145
- export function getMessageTransmitter(chainId: number): string | null {
146
- return messageTransmitters[chainId] ?? null
147
- }
148
-
149
- export async function cctpTransfer({
150
- walletClient,
151
- originChain,
152
- destinationChain,
153
- amount,
154
- }: {
155
- walletClient: WalletClient
156
- originChain: Chain
157
- destinationChain: Chain
158
- amount: bigint
159
- }): Promise<{
160
- waitForAttestation: () => Promise<Attestation>
161
- txHash: `0x${string}`
162
- }> {
163
- const originToken = getUSDCTokenAddress(originChain.id)
164
- const originDomain = getDomain(originChain.id)
165
- const destinationDomain = getDomain(destinationChain.id)
166
- const originTokenMessenger = getTokenMessenger(originChain.id)
167
- const destinationAddress = walletClient.account?.address
168
-
169
- if (
170
- !originToken ||
171
- originDomain === null ||
172
- !originTokenMessenger ||
173
- destinationDomain === null ||
174
- !destinationAddress
175
- ) {
176
- logger.console.error(
177
- "[trails-sdk] cctpTransfer: Invalid origin chain config",
178
- {
179
- originToken,
180
- originDomain,
181
- originTokenMessenger,
182
- destinationDomain,
183
- destinationAddress,
184
- },
185
- )
186
- throw new Error("Invalid origin chain config")
187
- }
188
-
189
- const originClient = createPublicClient({
190
- chain: originChain,
191
- transport: http(),
192
- })
193
-
194
- await attemptSwitchChain({
195
- walletClient,
196
- desiredChainId: originChain.id,
197
- })
198
-
199
- const account = walletClient.account?.address
200
- if (!account) {
201
- throw new Error("No account found")
202
- }
203
-
204
- const needsApproval = await getNeedsApproval({
205
- publicClient: originClient,
206
- token: originToken,
207
- account,
208
- spender: originTokenMessenger,
209
- amount: maxUint256,
210
- })
211
-
212
- if (needsApproval) {
213
- const txHash = await approveERC20({
214
- walletClient,
215
- tokenAddress: originToken,
216
- spender: originTokenMessenger,
217
- amount: maxUint256,
218
- chain: originChain,
219
- })
220
-
221
- logger.console.log("waiting for approve", txHash)
222
- await originClient.waitForTransactionReceipt({
223
- hash: txHash,
224
- })
225
- logger.console.log("approve done")
226
- }
227
-
228
- const maxFee = getMaxFee()
229
-
230
- const txHash = await burnUSDC({
231
- walletClient,
232
- tokenMessenger: originTokenMessenger,
233
- destinationDomain,
234
- destinationAddress,
235
- amount,
236
- burnToken: originToken,
237
- maxFee,
238
- chain: originChain,
239
- })
240
-
241
- return {
242
- waitForAttestation: async () => {
243
- await originClient.waitForTransactionReceipt({
244
- hash: txHash,
245
- })
246
-
247
- const testnet = getIsTestnetChainId(originChain.id)
248
-
249
- const attestation = await waitForAttestation({
250
- domain: originDomain,
251
- transactionHash: txHash,
252
- testnet,
253
- })
254
-
255
- if (!attestation) {
256
- throw new Error("Failed to retrieve attestation")
257
- }
258
-
259
- return attestation
260
- },
261
- txHash: txHash,
262
- }
263
- }
264
-
265
- export async function cctpDestinationTx({
266
- relayerClient,
267
- destinationChain,
268
- attestation,
269
- }: {
270
- relayerClient: WalletClient
271
- destinationChain: Chain
272
- attestation: Attestation
273
- }): Promise<`0x${string}`> {
274
- const destinationTokenMessenger = getMessageTransmitter(destinationChain.id)
275
-
276
- if (!destinationTokenMessenger) {
277
- throw new Error("Invalid destination chain")
278
- }
279
-
280
- await attemptSwitchChain({
281
- walletClient: relayerClient,
282
- desiredChainId: destinationChain.id,
283
- })
284
-
285
- const txHash = await mintUSDC({
286
- walletClient: relayerClient,
287
- tokenMessenger: destinationTokenMessenger,
288
- attestation,
289
- chain: destinationChain,
290
- })
291
-
292
- logger.console.log("[trails-sdk] minted USDC")
293
- return txHash
294
- }
295
-
296
- export function getMaxFee(): bigint {
297
- return 500n // Set fast transfer max fee in 10^6 subunits (0.0005 USDC; change as needed)
298
- }
299
-
300
- export async function getNeedsApproval({
301
- publicClient,
302
- token,
303
- account,
304
- spender,
305
- amount,
306
- }: {
307
- publicClient: PublicClient
308
- token: string
309
- account: string
310
- spender: string
311
- amount: bigint
312
- }): Promise<boolean> {
313
- if (account?.toLowerCase() === spender.toLowerCase()) {
314
- return false
315
- }
316
-
317
- const allowance = await publicClient.readContract({
318
- address: token as `0x${string}`,
319
- abi: erc20Abi,
320
- functionName: "allowance",
321
- args: [getAddress(account), getAddress(spender)],
322
- })
323
-
324
- return allowance < amount
325
- }
326
-
327
- export async function approveERC20({
328
- walletClient,
329
- tokenAddress,
330
- spender,
331
- amount,
332
- chain,
333
- }: {
334
- walletClient: WalletClient
335
- tokenAddress: string
336
- spender: string
337
- amount: bigint
338
- chain: Chain
339
- }): Promise<`0x${string}`> {
340
- const approvalData = await getApproveERC20Data({
341
- tokenAddress,
342
- spender,
343
- amount,
344
- })
345
-
346
- logger.console.log("[trails-sdk] approving ERC20 transfer", approvalData)
347
-
348
- await attemptSwitchChain({
349
- walletClient,
350
- desiredChainId: chain.id,
351
- })
352
-
353
- const account = walletClient.account?.address
354
- if (!account) {
355
- throw new Error("No account found")
356
- }
357
-
358
- const txHash = await walletClient.sendTransaction({
359
- ...approvalData,
360
- account: account as `0x${string}`,
361
- chain,
362
- })
363
-
364
- if (!txHash) {
365
- throw new Error(
366
- "No transaction hash returned from walletClient.sendTransaction",
367
- )
368
- }
369
-
370
- return txHash
371
- }
372
-
373
- async function getApproveERC20Data({
374
- tokenAddress,
375
- spender,
376
- amount,
377
- }: {
378
- tokenAddress: string
379
- spender: string
380
- amount: bigint
381
- }): Promise<{ to: `0x${string}`; data: `0x${string}`; value: bigint }> {
382
- logger.console.log("[trails-sdk] get approve ERC20 transfer data", {
383
- tokenAddress,
384
- spender,
385
- amount,
386
- })
387
- return {
388
- to: tokenAddress as `0x${string}`,
389
- value: BigInt(0),
390
- data: encodeFunctionData({
391
- abi: [
392
- {
393
- type: "function",
394
- name: "approve",
395
- stateMutability: "nonpayable",
396
- inputs: [
397
- { name: "spender", type: "address" },
398
- { name: "amount", type: "uint256" },
399
- ],
400
- outputs: [{ name: "", type: "bool" }],
401
- },
402
- ],
403
- functionName: "approve",
404
- args: [spender as `0x${string}`, amount],
405
- }),
406
- }
407
- }
408
-
409
- export async function burnUSDC({
410
- walletClient,
411
- tokenMessenger,
412
- destinationDomain,
413
- destinationAddress,
414
- amount,
415
- burnToken,
416
- maxFee,
417
- chain,
418
- }: {
419
- walletClient: WalletClient
420
- tokenMessenger: string
421
- destinationDomain: number
422
- destinationAddress: string
423
- amount: bigint
424
- burnToken: string
425
- maxFee: bigint
426
- chain: Chain
427
- }): Promise<`0x${string}`> {
428
- const burnData = await getBurnUSDCData({
429
- tokenMessenger,
430
- destinationDomain,
431
- destinationAddress,
432
- amount,
433
- burnToken,
434
- maxFee: maxFee,
435
- })
436
-
437
- await attemptSwitchChain({
438
- walletClient,
439
- desiredChainId: chain.id,
440
- })
441
-
442
- const account = walletClient.account?.address
443
- if (!account) {
444
- throw new Error("No account found")
445
- }
446
-
447
- return walletClient.sendTransaction({
448
- ...burnData,
449
- account: account as `0x${string}`,
450
- chain,
451
- })
452
- }
453
-
454
- export async function getBurnUSDCData({
455
- tokenMessenger,
456
- destinationDomain,
457
- destinationAddress,
458
- amount,
459
- burnToken,
460
- maxFee,
461
- }: {
462
- tokenMessenger: string
463
- destinationDomain: number
464
- destinationAddress: string
465
- amount: bigint
466
- burnToken: string
467
- maxFee: bigint
468
- }): Promise<{ to: `0x${string}`; data: `0x${string}`; value: bigint }> {
469
- logger.console.log("[trails-sdk] get burn USDC data", {
470
- tokenMessenger,
471
- destinationDomain,
472
- destinationAddress,
473
- amount,
474
- burnToken,
475
- maxFee,
476
- })
477
- // Bytes32 Formatted Parameters
478
- const DESTINATION_ADDRESS_BYTES32 = `0x000000000000000000000000${destinationAddress.slice(2)}` // Destination address in bytes32 format
479
- const DESTINATION_CALLER_BYTES32 =
480
- "0x0000000000000000000000000000000000000000000000000000000000000000" // Empty bytes32 allows any address to call MessageTransmitterV2.receiveMessage()
481
-
482
- return {
483
- to: tokenMessenger as `0x${string}`,
484
- value: BigInt(0),
485
- data: encodeFunctionData({
486
- abi: [
487
- {
488
- type: "function",
489
- name: "depositForBurn",
490
- stateMutability: "nonpayable",
491
- inputs: [
492
- { name: "amount", type: "uint256" },
493
- { name: "destinationDomain", type: "uint32" },
494
- { name: "mintRecipient", type: "bytes32" },
495
- { name: "burnToken", type: "address" },
496
- { name: "destinationCaller", type: "bytes32" },
497
- { name: "maxFee", type: "uint256" },
498
- { name: "minFinalityThreshold", type: "uint32" },
499
- ],
500
- outputs: [],
501
- },
502
- ],
503
- functionName: "depositForBurn",
504
- args: [
505
- amount,
506
- destinationDomain,
507
- DESTINATION_ADDRESS_BYTES32 as `0x${string}`,
508
- burnToken as `0x${string}`,
509
- DESTINATION_CALLER_BYTES32 as `0x${string}`,
510
- maxFee,
511
- 1000, // minFinalityThreshold (1000 or less for Fast Transfer)
512
- ],
513
- }),
514
- }
515
- }
516
-
517
- export async function retrieveAttestation({
518
- domain,
519
- transactionHash,
520
- testnet,
521
- }: {
522
- domain: number
523
- transactionHash: `0x${string}`
524
- testnet: boolean
525
- }): Promise<Attestation | null> {
526
- logger.console.log("[trails-sdk] retrieving attestation", {
527
- domain,
528
- transactionHash,
529
- })
530
- const url = `https://iris-api${testnet ? "-sandbox" : ""}.circle.com/v2/messages/${domain}?transactionHash=${transactionHash}`
531
- while (true) {
532
- try {
533
- const response = await fetch(url)
534
- const data = await response.json()
535
- if (response.status === 404) {
536
- logger.console.log("[trails-sdk] waiting for attestation...")
537
- }
538
- if (data?.messages?.[0]?.status === "complete") {
539
- logger.console.log("[trails-sdk] attestation retrieved successfully!")
540
- return data.messages[0]
541
- }
542
- logger.console.log("[trails-sdk] waiting for attestation...")
543
- await new Promise((resolve) => setTimeout(resolve, 5000))
544
- } catch (error: unknown) {
545
- logger.console.error(
546
- "[trails-sdk] error fetching attestation:",
547
- error instanceof Error ? error.message : String(error),
548
- )
549
- await new Promise((resolve) => setTimeout(resolve, 5000))
550
- }
551
- }
552
- }
553
-
554
- export async function mintUSDC({
555
- walletClient,
556
- tokenMessenger,
557
- attestation,
558
- chain,
559
- }: {
560
- walletClient: WalletClient
561
- tokenMessenger: string
562
- attestation: Attestation
563
- chain: Chain
564
- }): Promise<`0x${string}`> {
565
- const mintData = await getMintUSDCData({
566
- tokenMessenger,
567
- attestation,
568
- })
569
-
570
- await attemptSwitchChain({
571
- walletClient,
572
- desiredChainId: chain.id,
573
- })
574
-
575
- const account = walletClient.account?.address
576
- if (!account) {
577
- throw new Error("No account found")
578
- }
579
-
580
- return walletClient.sendTransaction({
581
- ...mintData,
582
- account: account as `0x${string}`,
583
- chain,
584
- })
585
- }
586
-
587
- export async function getMintUSDCData({
588
- tokenMessenger,
589
- attestation,
590
- }: {
591
- tokenMessenger: string
592
- attestation: Attestation
593
- }): Promise<{ to: `0x${string}`; data: `0x${string}`; value: bigint }> {
594
- logger.console.log("[trails-sdk] get mint USDC data", {
595
- tokenMessenger,
596
- attestation,
597
- })
598
- return {
599
- to: tokenMessenger as `0x${string}`,
600
- value: BigInt(0),
601
- data: encodeFunctionData({
602
- abi: [
603
- {
604
- type: "function",
605
- name: "receiveMessage",
606
- stateMutability: "nonpayable",
607
- inputs: [
608
- { name: "message", type: "bytes" },
609
- { name: "attestation", type: "bytes" },
610
- ],
611
- outputs: [],
612
- },
613
- ],
614
- functionName: "receiveMessage",
615
- args: [attestation.message, attestation.attestation],
616
- }),
617
- }
618
- }
619
-
620
- export async function waitForAttestation({
621
- domain,
622
- transactionHash,
623
- testnet,
624
- }: {
625
- domain: number
626
- transactionHash: `0x${string}`
627
- testnet: boolean
628
- }): Promise<Attestation | null> {
629
- while (true) {
630
- const attestation = await retrieveAttestation({
631
- domain,
632
- transactionHash,
633
- testnet,
634
- })
635
- if (attestation) {
636
- return attestation
637
- }
638
- logger.console.log("[trails-sdk] waiting for attestation...")
639
- await new Promise((resolve) => setTimeout(resolve, 1000))
640
- }
641
- }
642
-
643
52
  export function getIsUsdcAddress(address: string, chainId: number): boolean {
644
53
  return address?.toLowerCase() === tokenAddresses[chainId]?.toLowerCase()
645
54
  }
646
-
647
- export async function cctpTransferWithCustomCall({
648
- walletClient,
649
- originChain,
650
- destinationChain,
651
- amount,
652
- }: {
653
- walletClient: WalletClient
654
- originChain: Chain
655
- destinationChain: Chain
656
- amount: bigint
657
- }): Promise<{
658
- waitForAttestation: () => Promise<Attestation>
659
- txHash: `0x${string}`
660
- }> {
661
- const destinationContract = customCctpRelayerAddress[destinationChain.id]
662
- if (!destinationContract) {
663
- logger.console.error(
664
- "[trails-sdk] cctpTransferWithCustomCall: No custom CCTP relayer address found for this chain",
665
- {
666
- originChain,
667
- destinationChain,
668
- },
669
- )
670
- throw new Error("No custom CCTP relayer address found for this chain")
671
- }
672
-
673
- const originToken = getUSDCTokenAddress(originChain.id)
674
- const originDomain = getDomain(originChain.id)
675
- const destinationDomain = getDomain(destinationChain.id)
676
- const originTokenMessenger = getTokenMessenger(originChain.id)
677
-
678
- if (
679
- !originToken ||
680
- originDomain === null ||
681
- !originTokenMessenger ||
682
- destinationDomain === null
683
- ) {
684
- logger.console.error(
685
- "[trails-sdk] cctpTransferWithCustomCall: Invalid origin chain config",
686
- )
687
- throw new Error("Invalid origin chain config")
688
- }
689
-
690
- const originClient = createPublicClient({
691
- chain: originChain,
692
- transport: http(),
693
- })
694
-
695
- await attemptSwitchChain({
696
- walletClient,
697
- desiredChainId: originChain.id,
698
- })
699
-
700
- const account = walletClient.account?.address
701
- if (!account) {
702
- throw new Error("No account found")
703
- }
704
-
705
- const needsApproval = await getNeedsApproval({
706
- publicClient: originClient,
707
- token: originToken,
708
- account,
709
- spender: originTokenMessenger,
710
- amount: maxUint256,
711
- })
712
-
713
- if (needsApproval) {
714
- const txHash = await approveERC20({
715
- walletClient,
716
- tokenAddress: originToken,
717
- spender: originTokenMessenger,
718
- amount: maxUint256,
719
- chain: originChain,
720
- })
721
-
722
- logger.console.log("waiting for approve", txHash)
723
- await originClient.waitForTransactionReceipt({
724
- hash: txHash,
725
- })
726
- logger.console.log("approve done")
727
- }
728
-
729
- const maxFee = getMaxFee()
730
-
731
- // Send USDC to your CCTPRelayer contract instead of user wallet
732
- const txHash = await burnUSDCToContract({
733
- walletClient,
734
- tokenMessenger: originTokenMessenger,
735
- destinationDomain,
736
- destinationContract, // CCTPRelayer contract address
737
- amount,
738
- burnToken: originToken,
739
- maxFee,
740
- chain: originChain,
741
- })
742
-
743
- return {
744
- waitForAttestation: async () => {
745
- await originClient.waitForTransactionReceipt({
746
- hash: txHash,
747
- })
748
-
749
- const testnet = getIsTestnetChainId(originChain.id)
750
-
751
- const attestation = await waitForAttestation({
752
- domain: originDomain,
753
- transactionHash: txHash,
754
- testnet,
755
- })
756
-
757
- if (!attestation) {
758
- throw new Error("Failed to retrieve attestation")
759
- }
760
-
761
- return attestation
762
- },
763
- txHash: txHash,
764
- }
765
- }
766
-
767
- export async function burnUSDCToContract({
768
- walletClient,
769
- tokenMessenger,
770
- destinationDomain,
771
- destinationContract,
772
- amount,
773
- burnToken,
774
- maxFee,
775
- chain,
776
- }: {
777
- walletClient: WalletClient
778
- tokenMessenger: string
779
- destinationDomain: number
780
- destinationContract: string
781
- amount: bigint
782
- burnToken: string
783
- maxFee: bigint
784
- chain: Chain
785
- }): Promise<`0x${string}`> {
786
- const burnData = await getBurnUSDCToContractData({
787
- tokenMessenger,
788
- destinationDomain,
789
- destinationContract,
790
- amount,
791
- burnToken,
792
- maxFee: maxFee,
793
- })
794
-
795
- await attemptSwitchChain({
796
- walletClient,
797
- desiredChainId: chain.id,
798
- })
799
-
800
- const account = walletClient.account?.address
801
- if (!account) {
802
- throw new Error("No account found")
803
- }
804
-
805
- return walletClient.sendTransaction({
806
- ...burnData,
807
- account: account as `0x${string}`,
808
- chain,
809
- })
810
- }
811
-
812
- export async function getBurnUSDCToContractData({
813
- tokenMessenger,
814
- destinationDomain,
815
- destinationContract,
816
- amount,
817
- burnToken,
818
- maxFee,
819
- }: {
820
- tokenMessenger: string
821
- destinationDomain: number
822
- destinationContract: string
823
- amount: bigint
824
- burnToken: string
825
- maxFee: bigint
826
- }): Promise<{ to: `0x${string}`; data: `0x${string}`; value: bigint }> {
827
- logger.console.log("[trails-sdk] get burn USDC to contract data", {
828
- tokenMessenger,
829
- destinationDomain,
830
- destinationContract,
831
- amount,
832
- burnToken,
833
- maxFee,
834
- })
835
-
836
- // Format destination contract address as bytes32
837
- const DESTINATION_CONTRACT_BYTES32 = `0x000000000000000000000000${destinationContract.slice(2)}`
838
- const DESTINATION_CALLER_BYTES32 =
839
- "0x0000000000000000000000000000000000000000000000000000000000000000" // Empty bytes32 allows any address to call
840
-
841
- return {
842
- to: tokenMessenger as `0x${string}`,
843
- value: BigInt(0),
844
- data: encodeFunctionData({
845
- abi: [
846
- {
847
- type: "function",
848
- name: "depositForBurn",
849
- stateMutability: "nonpayable",
850
- inputs: [
851
- { name: "amount", type: "uint256" },
852
- { name: "destinationDomain", type: "uint32" },
853
- { name: "mintRecipient", type: "bytes32" },
854
- { name: "burnToken", type: "address" },
855
- { name: "destinationCaller", type: "bytes32" },
856
- { name: "maxFee", type: "uint256" },
857
- { name: "minFinalityThreshold", type: "uint32" },
858
- ],
859
- outputs: [],
860
- },
861
- ],
862
- functionName: "depositForBurn",
863
- args: [
864
- amount,
865
- destinationDomain,
866
- DESTINATION_CONTRACT_BYTES32 as `0x${string}`,
867
- burnToken as `0x${string}`,
868
- DESTINATION_CALLER_BYTES32 as `0x${string}`,
869
- maxFee,
870
- 1000, // minFinalityThreshold (1000 or less for Fast Transfer)
871
- ],
872
- }),
873
- }
874
- }
875
-
876
- export async function executeCustomCallWithCCTP({
877
- relayerClient,
878
- destinationChain,
879
- attestation,
880
- targetContract,
881
- calldata,
882
- gasLimit = 500000n,
883
- }: {
884
- relayerClient: WalletClient
885
- destinationChain: Chain
886
- attestation: Attestation
887
- targetContract: string
888
- calldata: `0x${string}`
889
- gasLimit?: bigint
890
- }): Promise<`0x${string}`> {
891
- await attemptSwitchChain({
892
- walletClient: relayerClient,
893
- desiredChainId: destinationChain.id,
894
- })
895
-
896
- const account = relayerClient.account?.address
897
- if (!account) {
898
- throw new Error("No account found")
899
- }
900
-
901
- const cctpRelayerAddress = customCctpRelayerAddress[destinationChain.id]
902
- if (!cctpRelayerAddress) {
903
- throw new Error("No custom CCTP relayer address found for this chain")
904
- }
905
-
906
- const relayData = encodeFunctionData({
907
- abi: [
908
- {
909
- type: "function",
910
- name: "relayWithCustomCall",
911
- inputs: [
912
- {
913
- type: "tuple",
914
- name: "request",
915
- components: [
916
- { name: "message", type: "bytes" },
917
- { name: "attestation", type: "bytes" },
918
- { name: "targetContract", type: "address" },
919
- { name: "data", type: "bytes" },
920
- { name: "gasLimit", type: "uint256" },
921
- ],
922
- },
923
- ],
924
- },
925
- ],
926
- functionName: "relayWithCustomCall",
927
- args: [
928
- {
929
- message: attestation.message,
930
- attestation: attestation.attestation,
931
- targetContract: targetContract as `0x${string}`,
932
- data: calldata,
933
- gasLimit,
934
- },
935
- ],
936
- })
937
-
938
- return relayerClient.sendTransaction({
939
- to: cctpRelayerAddress as `0x${string}`,
940
- data: relayData,
941
- account: account as `0x${string}`,
942
- chain: destinationChain,
943
- })
944
- }
945
-
946
- // Complete flow function
947
- export async function cctpTransferCaller({
948
- walletClient,
949
- relayerClient, // Can be same as walletClient or different
950
- originChain,
951
- destinationChain,
952
- amount,
953
- targetContract, // The contract you want to call
954
- calldata, // The function call data
955
- gasLimit = 500000n,
956
- }: {
957
- walletClient: WalletClient
958
- relayerClient?: WalletClient
959
- originChain: Chain
960
- destinationChain: Chain
961
- amount: bigint
962
- targetContract: string
963
- calldata: `0x${string}`
964
- gasLimit?: bigint
965
- }): Promise<{
966
- burnTxHash: `0x${string}`
967
- executeTxHash: `0x${string}`
968
- }> {
969
- // Use walletClient as relayerClient if not provided
970
- const actualRelayerClient = relayerClient || walletClient
971
-
972
- logger.console.log("[trails-sdk] Starting CCTP transfer with custom call")
973
-
974
- // Step 1: Burn USDC on origin chain (send to CCTPRelayer)
975
- const { waitForAttestation, txHash: burnTxHash } =
976
- await cctpTransferWithCustomCall({
977
- walletClient,
978
- originChain,
979
- destinationChain,
980
- amount,
981
- })
982
-
983
- logger.console.log("[trails-sdk] Burn transaction sent:", burnTxHash)
984
-
985
- // Step 2: Wait for attestation
986
- logger.console.log("[trails-sdk] Waiting for attestation...")
987
- const attestation = await waitForAttestation()
988
-
989
- logger.console.log("[trails-sdk] Attestation received, executing custom call")
990
-
991
- // Step 3: Execute the relayed call on destination chain
992
- const executeTxHash = await executeCustomCallWithCCTP({
993
- relayerClient: actualRelayerClient,
994
- destinationChain,
995
- attestation,
996
- targetContract,
997
- calldata,
998
- gasLimit,
999
- })
1000
-
1001
- logger.console.log("[trails-sdk] Custom call executed:", executeTxHash)
1002
-
1003
- return {
1004
- burnTxHash,
1005
- executeTxHash,
1006
- }
1007
- }
1008
-
1009
- export async function getCCTPRelayerCallData({
1010
- attestation,
1011
- targetContract,
1012
- calldata,
1013
- gasLimit = 500000n,
1014
- destinationChain,
1015
- }: {
1016
- attestation: Attestation
1017
- targetContract: string
1018
- calldata: `0x${string}`
1019
- gasLimit?: bigint
1020
- destinationChain: Chain
1021
- }): Promise<{ to: `0x${string}`; data: `0x${string}`; value: bigint }> {
1022
- const cctpRelayerAddress = customCctpRelayerAddress[destinationChain.id]
1023
- if (!cctpRelayerAddress) {
1024
- throw new Error("No custom CCTP relayer address found for this chain")
1025
- }
1026
-
1027
- logger.console.log("[trails-sdk] get CCTP relayer call data", {
1028
- cctpRelayerAddress,
1029
- targetContract,
1030
- gasLimit,
1031
- })
1032
-
1033
- const relayData = encodeFunctionData({
1034
- abi: [
1035
- {
1036
- type: "function",
1037
- name: "relayWithCustomCall",
1038
- inputs: [
1039
- {
1040
- type: "tuple",
1041
- name: "request",
1042
- components: [
1043
- { name: "message", type: "bytes" },
1044
- { name: "attestation", type: "bytes" },
1045
- { name: "targetContract", type: "address" },
1046
- { name: "data", type: "bytes" },
1047
- { name: "gasLimit", type: "uint256" },
1048
- ],
1049
- },
1050
- ],
1051
- },
1052
- ],
1053
- functionName: "relayWithCustomCall",
1054
- args: [
1055
- {
1056
- message: attestation.message,
1057
- attestation: attestation.attestation,
1058
- targetContract: targetContract as `0x${string}`,
1059
- data: calldata,
1060
- gasLimit,
1061
- },
1062
- ],
1063
- })
1064
-
1065
- return {
1066
- to: cctpRelayerAddress as `0x${string}`,
1067
- data: relayData,
1068
- value: BigInt(0),
1069
- }
1070
- }