@fastxyz/allset-sdk 0.1.12 → 1.0.1

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 (44) hide show
  1. package/README.md +322 -266
  2. package/dist/index.d.ts +658 -5
  3. package/dist/index.js +927 -7
  4. package/package.json +21 -47
  5. package/dist/browser/index.d.ts +0 -2
  6. package/dist/browser/index.d.ts.map +0 -1
  7. package/dist/browser/index.js +0 -1
  8. package/dist/core/address.d.ts +0 -5
  9. package/dist/core/address.d.ts.map +0 -1
  10. package/dist/core/address.js +0 -29
  11. package/dist/core/deposit.d.ts +0 -59
  12. package/dist/core/deposit.d.ts.map +0 -1
  13. package/dist/core/deposit.js +0 -92
  14. package/dist/core/index.d.ts +0 -6
  15. package/dist/core/index.d.ts.map +0 -1
  16. package/dist/core/index.js +0 -3
  17. package/dist/default-config.d.ts +0 -78
  18. package/dist/default-config.d.ts.map +0 -1
  19. package/dist/default-config.js +0 -78
  20. package/dist/index.d.ts.map +0 -1
  21. package/dist/intents.d.ts +0 -94
  22. package/dist/intents.d.ts.map +0 -1
  23. package/dist/intents.js +0 -119
  24. package/dist/node/bridge.d.ts +0 -38
  25. package/dist/node/bridge.d.ts.map +0 -1
  26. package/dist/node/bridge.js +0 -519
  27. package/dist/node/config.d.ts +0 -45
  28. package/dist/node/config.d.ts.map +0 -1
  29. package/dist/node/config.js +0 -48
  30. package/dist/node/eip7702.d.ts +0 -54
  31. package/dist/node/eip7702.d.ts.map +0 -1
  32. package/dist/node/eip7702.js +0 -275
  33. package/dist/node/evm-executor.d.ts +0 -130
  34. package/dist/node/evm-executor.d.ts.map +0 -1
  35. package/dist/node/evm-executor.js +0 -160
  36. package/dist/node/index.d.ts +0 -15
  37. package/dist/node/index.d.ts.map +0 -1
  38. package/dist/node/index.js +0 -17
  39. package/dist/node/provider.d.ts +0 -162
  40. package/dist/node/provider.d.ts.map +0 -1
  41. package/dist/node/provider.js +0 -272
  42. package/dist/node/types.d.ts +0 -110
  43. package/dist/node/types.d.ts.map +0 -1
  44. package/dist/node/types.js +0 -4
package/dist/index.js CHANGED
@@ -1,7 +1,927 @@
1
- /**
2
- * @fastxyz/allset-sdk — browser-safe core helpers
3
- *
4
- * Root exports are pure helpers only. Use `@fastxyz/allset-sdk/node` for
5
- * provider, executor, wallet, bridge execution, and file-backed config APIs.
6
- */
7
- export * from './core/index.js';
1
+ // src/address.ts
2
+ import { fromFastAddress, toHex } from "@fastxyz/sdk";
3
+ function fastAddressToBytes(address) {
4
+ try {
5
+ return fromFastAddress(address);
6
+ } catch (err) {
7
+ throw new Error(`Invalid Fast address "${address}": ${err.message}`);
8
+ }
9
+ }
10
+ function fastAddressToBytes32(address) {
11
+ return toHex(fastAddressToBytes(address));
12
+ }
13
+
14
+ // src/claims.ts
15
+ import { encodeAbiParameters, keccak256 } from "viem";
16
+ function hexToUint8Array(hex) {
17
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
18
+ const bytes = new Uint8Array(clean.length / 2);
19
+ for (let i = 0; i < bytes.length; i++) {
20
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
21
+ }
22
+ return bytes;
23
+ }
24
+ var TRANSFER_CLAIM_ABI_PARAMS = [
25
+ {
26
+ type: "tuple",
27
+ components: [
28
+ { name: "from", type: "string" },
29
+ { name: "nonce", type: "uint256" },
30
+ { name: "asset", type: "string" },
31
+ { name: "amount", type: "uint256" },
32
+ { name: "to", type: "string" }
33
+ ]
34
+ }
35
+ ];
36
+ var INTENT_CLAIM_ABI_PARAMS = [
37
+ {
38
+ type: "tuple",
39
+ components: [
40
+ { name: "transferFastTxId", type: "bytes32" },
41
+ { name: "deadline", type: "uint256" },
42
+ {
43
+ name: "intents",
44
+ type: "tuple[]",
45
+ components: [
46
+ { name: "action", type: "uint8" },
47
+ { name: "payload", type: "bytes" },
48
+ { name: "value", type: "uint256" }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ ];
54
+ function encodeTransferClaim(params) {
55
+ return encodeAbiParameters(TRANSFER_CLAIM_ABI_PARAMS, [
56
+ {
57
+ from: params.from.toLowerCase(),
58
+ nonce: BigInt(params.nonce),
59
+ asset: params.asset,
60
+ amount: params.amount,
61
+ to: params.to
62
+ }
63
+ ]);
64
+ }
65
+ function hashTransferClaim(params) {
66
+ return keccak256(encodeTransferClaim(params));
67
+ }
68
+ function encodeIntentClaim(params) {
69
+ return encodeAbiParameters(INTENT_CLAIM_ABI_PARAMS, [
70
+ {
71
+ transferFastTxId: params.transferFastTxId,
72
+ deadline: params.deadline,
73
+ intents: params.intents.map((i) => ({
74
+ action: i.action,
75
+ payload: i.payload,
76
+ value: i.value
77
+ }))
78
+ }
79
+ ]);
80
+ }
81
+ function buildIntentClaimBytes(params) {
82
+ const deadline = params.deadline ?? BigInt(Math.floor(Date.now() / 1e3) + 3600);
83
+ const encoded = encodeIntentClaim({
84
+ transferFastTxId: params.transferFastTxId,
85
+ deadline,
86
+ intents: params.intents
87
+ });
88
+ return hexToUint8Array(encoded);
89
+ }
90
+ function extractClaimId(crossSignTransaction) {
91
+ const bytes = new Uint8Array(crossSignTransaction.slice(32, 64));
92
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
93
+ }
94
+
95
+ // src/deposit.ts
96
+ import { encodeFunctionData } from "viem";
97
+ var BRIDGE_DEPOSIT_ABI = [
98
+ {
99
+ type: "function",
100
+ name: "deposit",
101
+ inputs: [
102
+ { name: "token", type: "address" },
103
+ { name: "amount", type: "uint256" },
104
+ { name: "receiver", type: "bytes32" }
105
+ ],
106
+ outputs: [],
107
+ stateMutability: "payable"
108
+ }
109
+ ];
110
+ function encodeDepositCalldata(params) {
111
+ return encodeFunctionData({
112
+ abi: BRIDGE_DEPOSIT_ABI,
113
+ functionName: "deposit",
114
+ args: [params.tokenAddress, params.amount, params.receiverBytes32]
115
+ });
116
+ }
117
+ function buildDepositTransaction(params) {
118
+ const receiverBytes32 = fastAddressToBytes32(params.receiver);
119
+ return {
120
+ chainId: params.chainId,
121
+ to: params.bridgeContract,
122
+ data: encodeDepositCalldata({
123
+ tokenAddress: params.tokenAddress,
124
+ amount: params.amount,
125
+ receiverBytes32
126
+ }),
127
+ value: params.isNative ? params.amount : 0n,
128
+ receiverBytes32
129
+ };
130
+ }
131
+
132
+ // src/eip7702.ts
133
+ import {
134
+ createPublicClient,
135
+ encodeAbiParameters as encodeAbiParameters2,
136
+ http,
137
+ keccak256 as keccak2562,
138
+ parseAbi
139
+ } from "viem";
140
+ import { privateKeyToAccount } from "viem/accounts";
141
+ import { getUserOperationTypedData } from "viem/account-abstraction";
142
+ var ENTRY_POINT_V08 = "0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108";
143
+ var TRUSTED_DELEGATES = /* @__PURE__ */ new Set([
144
+ "0xe6cae83bde06e4c305530e199d7217f42808555b"
145
+ // Simple7702Account v0.8
146
+ ]);
147
+ var ERC20_BALANCEOF_ABI = parseAbi([
148
+ "function balanceOf(address account) view returns (uint256)"
149
+ ]);
150
+ function toEvenHex(n) {
151
+ let h = n.toString(16);
152
+ if (h.length % 2 !== 0) h = `0${h}`;
153
+ return `0x${h}`;
154
+ }
155
+ async function postJson(url, body, timeoutMs) {
156
+ const ac = new AbortController();
157
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
158
+ try {
159
+ const res = await fetch(url, {
160
+ method: "POST",
161
+ headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify(body),
163
+ signal: ac.signal
164
+ });
165
+ if (!res.ok) {
166
+ const err = await res.text();
167
+ throw new Error(`POST ${url} failed (${res.status}): ${err}`);
168
+ }
169
+ return await res.json();
170
+ } catch (e) {
171
+ if (e.name === "AbortError") {
172
+ throw new Error(`POST ${url} timed out after ${timeoutMs}ms`);
173
+ }
174
+ throw e;
175
+ } finally {
176
+ clearTimeout(timer);
177
+ }
178
+ }
179
+ var InsufficientBalanceError = class extends Error {
180
+ constructor(balance, required, tokenAddress) {
181
+ super(
182
+ `Insufficient token balance: have ${balance}, need ${required} (token ${tokenAddress})`
183
+ );
184
+ this.balance = balance;
185
+ this.required = required;
186
+ this.tokenAddress = tokenAddress;
187
+ this.name = "InsufficientBalanceError";
188
+ }
189
+ };
190
+ function parseUserOp(raw) {
191
+ return {
192
+ sender: raw.sender,
193
+ nonce: BigInt(raw.nonce),
194
+ callData: raw.callData,
195
+ callGasLimit: BigInt(raw.callGasLimit),
196
+ verificationGasLimit: BigInt(raw.verificationGasLimit),
197
+ preVerificationGas: BigInt(raw.preVerificationGas),
198
+ maxFeePerGas: BigInt(raw.maxFeePerGas),
199
+ maxPriorityFeePerGas: BigInt(raw.maxPriorityFeePerGas),
200
+ ...raw.paymaster && { paymaster: raw.paymaster },
201
+ ...raw.paymasterVerificationGasLimit && {
202
+ paymasterVerificationGasLimit: BigInt(raw.paymasterVerificationGasLimit)
203
+ },
204
+ ...raw.paymasterPostOpGasLimit && {
205
+ paymasterPostOpGasLimit: BigInt(raw.paymasterPostOpGasLimit)
206
+ },
207
+ ...raw.paymasterData && { paymasterData: raw.paymasterData },
208
+ ...raw.factory && { factory: raw.factory },
209
+ ...raw.factoryData && { factoryData: raw.factoryData },
210
+ signature: "0x"
211
+ };
212
+ }
213
+ function serializeUserOp(op) {
214
+ const toHex2 = (n) => `0x${n.toString(16)}`;
215
+ return {
216
+ sender: op.sender,
217
+ nonce: toHex2(op.nonce),
218
+ callData: op.callData,
219
+ callGasLimit: toHex2(op.callGasLimit),
220
+ verificationGasLimit: toHex2(op.verificationGasLimit),
221
+ preVerificationGas: toHex2(op.preVerificationGas),
222
+ maxFeePerGas: toHex2(op.maxFeePerGas),
223
+ maxPriorityFeePerGas: toHex2(op.maxPriorityFeePerGas),
224
+ ...op.paymaster && { paymaster: op.paymaster },
225
+ ...op.paymasterVerificationGasLimit !== void 0 && {
226
+ paymasterVerificationGasLimit: toHex2(op.paymasterVerificationGasLimit)
227
+ },
228
+ ...op.paymasterPostOpGasLimit !== void 0 && {
229
+ paymasterPostOpGasLimit: toHex2(op.paymasterPostOpGasLimit)
230
+ },
231
+ ...op.paymasterData && { paymasterData: op.paymasterData },
232
+ ...op.factory && { factory: op.factory },
233
+ ...op.factoryData && { factoryData: op.factoryData },
234
+ ...op.signature && { signature: op.signature }
235
+ };
236
+ }
237
+ async function smartDeposit(params) {
238
+ const {
239
+ privateKey,
240
+ rpcUrl,
241
+ allsetApiUrl,
242
+ tokenAddress,
243
+ amount,
244
+ bridgeAddress,
245
+ depositCalldata,
246
+ requestTimeoutMs = 6e4
247
+ } = params;
248
+ const eoa = privateKeyToAccount(privateKey);
249
+ const publicClient = createPublicClient({ transport: http(rpcUrl) });
250
+ const tokenBalance = await publicClient.readContract({
251
+ address: tokenAddress,
252
+ abi: ERC20_BALANCEOF_ABI,
253
+ functionName: "balanceOf",
254
+ args: [eoa.address]
255
+ });
256
+ if (tokenBalance < amount) {
257
+ throw new InsufficientBalanceError(tokenBalance, amount, tokenAddress);
258
+ }
259
+ const chainId = await publicClient.getChainId();
260
+ const timestamp = Math.floor(Date.now() / 1e3);
261
+ const nonceBytes = crypto.getRandomValues(new Uint8Array(32));
262
+ const nonce = `0x${Array.from(nonceBytes, (b) => b.toString(16).padStart(2, "0")).join("")}`;
263
+ const DOMAIN_TAG = "AllSet Portal authSig v1";
264
+ const msgHash = keccak2562(
265
+ encodeAbiParameters2(
266
+ [
267
+ { type: "string" },
268
+ { type: "uint256" },
269
+ { type: "bytes32" },
270
+ { type: "address" },
271
+ { type: "address" },
272
+ { type: "uint256" },
273
+ { type: "address" },
274
+ { type: "bytes" },
275
+ { type: "uint256" }
276
+ ],
277
+ [
278
+ DOMAIN_TAG,
279
+ BigInt(chainId),
280
+ nonce,
281
+ eoa.address,
282
+ tokenAddress,
283
+ amount,
284
+ bridgeAddress,
285
+ depositCalldata,
286
+ BigInt(timestamp)
287
+ ]
288
+ )
289
+ );
290
+ const authSig = await eoa.signMessage({ message: { raw: msgHash } });
291
+ const prepareReq = {
292
+ rpcUrl,
293
+ from: eoa.address,
294
+ tokenAddress,
295
+ amount: amount.toString(),
296
+ bridgeAddress,
297
+ depositCalldata,
298
+ chainId,
299
+ nonce,
300
+ timestamp,
301
+ authSig
302
+ };
303
+ const PREPARE_DELAYS = [0, 500, 1500];
304
+ let prepared;
305
+ for (let attempt = 0; attempt < PREPARE_DELAYS.length; attempt++) {
306
+ if (PREPARE_DELAYS[attempt] > 0) {
307
+ await new Promise((r) => setTimeout(r, PREPARE_DELAYS[attempt]));
308
+ }
309
+ try {
310
+ prepared = await postJson(
311
+ `${allsetApiUrl}/userop/prepare`,
312
+ prepareReq,
313
+ requestTimeoutMs
314
+ );
315
+ break;
316
+ } catch (e) {
317
+ const isLast = attempt === PREPARE_DELAYS.length - 1;
318
+ const msg = e.message ?? "";
319
+ const isRetryable = !msg.match(/POST .+ failed \([1-4][0-9]{2}\)/) || msg.includes("(429)") || msg.includes("(5");
320
+ if (isLast || !isRetryable) throw e;
321
+ }
322
+ }
323
+ if (!TRUSTED_DELEGATES.has(prepared.delegate7702Address.toLowerCase())) {
324
+ throw new Error(
325
+ `smartDeposit: untrusted delegate address returned by backend: ${prepared.delegate7702Address}`
326
+ );
327
+ }
328
+ let eip7702Auth;
329
+ if (prepared.needsAuthorization) {
330
+ const accountNonce = await publicClient.getTransactionCount({
331
+ address: eoa.address,
332
+ blockTag: "pending"
333
+ });
334
+ const signed = await eoa.signAuthorization({
335
+ address: prepared.delegate7702Address,
336
+ chainId,
337
+ nonce: accountNonce
338
+ });
339
+ const yParity = signed.yParity ?? 0;
340
+ eip7702Auth = {
341
+ address: prepared.delegate7702Address,
342
+ chainId: toEvenHex(chainId),
343
+ nonce: toEvenHex(accountNonce),
344
+ yParity: toEvenHex(yParity),
345
+ r: `0x${BigInt(signed.r).toString(16).padStart(64, "0")}`,
346
+ s: `0x${BigInt(signed.s).toString(16).padStart(64, "0")}`
347
+ };
348
+ }
349
+ const userOpToSign = parseUserOp(prepared.unsignedUserOp);
350
+ const typedData = getUserOperationTypedData({
351
+ chainId,
352
+ entryPointAddress: ENTRY_POINT_V08,
353
+ userOperation: { ...userOpToSign, signature: "0x" }
354
+ });
355
+ const signature = await eoa.signTypedData(typedData);
356
+ const signedUserOp = { ...userOpToSign, signature };
357
+ const serialized = serializeUserOp(signedUserOp);
358
+ if (eip7702Auth) {
359
+ serialized.eip7702Auth = eip7702Auth;
360
+ }
361
+ const submitReq = {
362
+ rpcUrl,
363
+ signedUserOp: serialized
364
+ };
365
+ const { txHash, userOpHash: returnedUserOpHash } = await postJson(
366
+ `${allsetApiUrl}/userop/submit`,
367
+ submitReq,
368
+ requestTimeoutMs
369
+ );
370
+ return {
371
+ txHash,
372
+ userOpHash: returnedUserOpHash,
373
+ userAddress: eoa.address
374
+ };
375
+ }
376
+
377
+ // src/evm.ts
378
+ import { createPublicClient as createPublicClient2, createWalletClient, http as http2, parseAbi as parseAbi2 } from "viem";
379
+ import { generatePrivateKey, privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
380
+ import { arbitrum, arbitrumSepolia, base, mainnet as ethereum, sepolia } from "viem/chains";
381
+ function normalizePrivateKey(privateKey) {
382
+ return privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
383
+ }
384
+ function createEvmWallet(privateKey) {
385
+ const key = privateKey ? normalizePrivateKey(privateKey) : generatePrivateKey();
386
+ return Object.assign(privateKeyToAccount2(key), { privateKey: key });
387
+ }
388
+ var ERC20_ABI = parseAbi2([
389
+ "function approve(address spender, uint256 amount) returns (bool)",
390
+ "function allowance(address owner, address spender) view returns (uint256)",
391
+ "function balanceOf(address owner) view returns (uint256)"
392
+ ]);
393
+ var CHAIN_MAP = {
394
+ 1: ethereum,
395
+ 11155111: sepolia,
396
+ 421614: arbitrumSepolia,
397
+ 42161: arbitrum,
398
+ 8453: base
399
+ };
400
+ function createEvmExecutor(account, rpcUrl, chainOrId) {
401
+ const chain = typeof chainOrId === "number" ? resolveChain(chainOrId) : chainOrId;
402
+ const walletClient = createWalletClient({
403
+ account,
404
+ chain,
405
+ transport: http2(rpcUrl)
406
+ });
407
+ const publicClient = createPublicClient2({
408
+ chain,
409
+ transport: http2(rpcUrl)
410
+ });
411
+ return { walletClient, publicClient };
412
+ }
413
+ function resolveChain(chainId) {
414
+ const chain = CHAIN_MAP[chainId];
415
+ if (!chain) {
416
+ throw new Error(
417
+ `Unsupported EVM chain ID: ${chainId}. Supported: ${Object.keys(CHAIN_MAP).join(", ")}. Pass a viem Chain object directly to avoid this restriction.`
418
+ );
419
+ }
420
+ return chain;
421
+ }
422
+ async function getEvmErc20Balance(rpcUrl, tokenAddress, ownerAddress) {
423
+ const client = createPublicClient2({ transport: http2(rpcUrl) });
424
+ return client.readContract({
425
+ address: tokenAddress,
426
+ abi: ERC20_ABI,
427
+ functionName: "balanceOf",
428
+ args: [ownerAddress]
429
+ });
430
+ }
431
+ async function getEvmNativeBalance(rpcUrl, address) {
432
+ const client = createPublicClient2({ transport: http2(rpcUrl) });
433
+ return client.getBalance({ address });
434
+ }
435
+
436
+ // src/intents.ts
437
+ import { encodeAbiParameters as encodeAbiParameters3 } from "viem";
438
+ var IntentAction = /* @__PURE__ */ ((IntentAction2) => {
439
+ IntentAction2[IntentAction2["Execute"] = 0] = "Execute";
440
+ IntentAction2[IntentAction2["DynamicTransfer"] = 1] = "DynamicTransfer";
441
+ IntentAction2[IntentAction2["DynamicDeposit"] = 2] = "DynamicDeposit";
442
+ IntentAction2[IntentAction2["Revoke"] = 3] = "Revoke";
443
+ return IntentAction2;
444
+ })(IntentAction || {});
445
+ function buildTransferIntent(token, receiver) {
446
+ const payload = encodeAbiParameters3([{ type: "address" }, { type: "address" }], [token, receiver]);
447
+ return {
448
+ action: 1 /* DynamicTransfer */,
449
+ payload,
450
+ value: 0n
451
+ };
452
+ }
453
+ function buildExecuteIntent(target, calldata, value = 0n) {
454
+ const payload = encodeAbiParameters3([{ type: "address" }, { type: "bytes" }], [target, calldata]);
455
+ return {
456
+ action: 0 /* Execute */,
457
+ payload,
458
+ value
459
+ };
460
+ }
461
+ function buildDepositBackIntent(token, fastReceiver) {
462
+ const receiverBytes = fastAddressToBytes32(fastReceiver);
463
+ const payload = encodeAbiParameters3([{ type: "address" }, { type: "bytes32" }], [token, receiverBytes]);
464
+ return {
465
+ action: 2 /* DynamicDeposit */,
466
+ payload,
467
+ value: 0n
468
+ };
469
+ }
470
+ function buildRevokeIntent() {
471
+ return {
472
+ action: 3 /* Revoke */,
473
+ payload: "0x",
474
+ value: 0n
475
+ };
476
+ }
477
+
478
+ // src/bridge.ts
479
+ import { TransactionCertificateFromRpc } from "@fastxyz/schema";
480
+ import { TransactionBuilder } from "@fastxyz/sdk";
481
+ import { Schema } from "effect";
482
+ import { decodeAbiParameters } from "viem";
483
+
484
+ // src/errors.ts
485
+ var FastError = class extends Error {
486
+ code;
487
+ note;
488
+ constructor(code, message, opts) {
489
+ super(message);
490
+ this.name = "FastError";
491
+ this.code = code;
492
+ this.note = opts?.note ?? "";
493
+ }
494
+ toJSON() {
495
+ return { name: this.name, code: this.code, message: this.message, note: this.note };
496
+ }
497
+ };
498
+
499
+ // src/relay.ts
500
+ async function relayExecute(params) {
501
+ const {
502
+ relayerUrl,
503
+ encodedTransferClaim,
504
+ transferProof,
505
+ transferFastTxId,
506
+ fastsetAddress,
507
+ externalAddress,
508
+ encodedIntentClaim,
509
+ intentProof,
510
+ intentFastTxId,
511
+ intentClaimId,
512
+ externalTokenAddress
513
+ } = params;
514
+ const body = {
515
+ encoded_transfer_claim: encodedTransferClaim,
516
+ transfer_proof: transferProof,
517
+ transfer_fast_tx_id: transferFastTxId,
518
+ transfer_claim_id: transferFastTxId,
519
+ fastset_address: fastsetAddress,
520
+ external_address: externalAddress
521
+ };
522
+ if (encodedIntentClaim && intentProof) {
523
+ body.encoded_intent_claim = encodedIntentClaim;
524
+ body.intent_proof = intentProof;
525
+ }
526
+ if (intentFastTxId) {
527
+ body.intent_fast_tx_id = intentFastTxId;
528
+ }
529
+ if (intentClaimId) {
530
+ body.intent_claim_id = intentClaimId;
531
+ }
532
+ if (externalTokenAddress) {
533
+ body.external_token_address = externalTokenAddress;
534
+ }
535
+ const relayRes = await fetch(`${relayerUrl.replace(/\/$/, "")}/relay`, {
536
+ method: "POST",
537
+ headers: { "Content-Type": "application/json" },
538
+ body: JSON.stringify(body)
539
+ });
540
+ if (!relayRes.ok) {
541
+ const text = await relayRes.text();
542
+ throw new FastError("TX_FAILED", `Relayer request failed (${relayRes.status}): ${text}`, {
543
+ note: "The intent was submitted to Fast network but the relayer rejected it. Try again."
544
+ });
545
+ }
546
+ return { success: true };
547
+ }
548
+
549
+ // src/bridge.ts
550
+ function hexToUint8Array2(hex) {
551
+ const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
552
+ const bytes = new Uint8Array(clean.length / 2);
553
+ for (let i = 0; i < bytes.length; i++) {
554
+ bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
555
+ }
556
+ return bytes;
557
+ }
558
+ function bigIntToNumber(obj) {
559
+ if (typeof obj === "bigint") return Number(obj);
560
+ if (obj instanceof Uint8Array) return Array.from(obj);
561
+ if (Array.isArray(obj)) return obj.map(bigIntToNumber);
562
+ if (obj !== null && typeof obj === "object") {
563
+ const result = {};
564
+ for (const [key, value] of Object.entries(obj)) {
565
+ result[key] = bigIntToNumber(value);
566
+ }
567
+ return result;
568
+ }
569
+ return obj;
570
+ }
571
+ function resolveExternalAddress(intents, externalAddressOverride) {
572
+ if (externalAddressOverride) return externalAddressOverride;
573
+ for (const intent of intents) {
574
+ if (intent.action === 1 /* DynamicTransfer */) {
575
+ try {
576
+ const [, receiver] = decodeAbiParameters(
577
+ [{ type: "address" }, { type: "address" }],
578
+ intent.payload
579
+ );
580
+ return receiver;
581
+ } catch {
582
+ continue;
583
+ }
584
+ }
585
+ if (intent.action === 0 /* Execute */) {
586
+ try {
587
+ const [target] = decodeAbiParameters(
588
+ [{ type: "address" }, { type: "bytes" }],
589
+ intent.payload
590
+ );
591
+ return target;
592
+ } catch {
593
+ }
594
+ }
595
+ }
596
+ return null;
597
+ }
598
+ async function sendTx(clients, tx) {
599
+ const walletClient = clients.walletClient;
600
+ const hash = await walletClient.sendTransaction({
601
+ to: tx.to,
602
+ data: tx.data,
603
+ value: BigInt(tx.value),
604
+ gas: tx.gas ? BigInt(tx.gas) : void 0
605
+ });
606
+ const receipt = await clients.publicClient.waitForTransactionReceipt({
607
+ hash
608
+ });
609
+ return {
610
+ txHash: hash,
611
+ status: receipt.status === "success" ? "success" : "reverted"
612
+ };
613
+ }
614
+ async function checkAllowance(clients, token, spender, owner) {
615
+ return clients.publicClient.readContract({
616
+ address: token,
617
+ abi: ERC20_ABI,
618
+ functionName: "allowance",
619
+ args: [owner, spender]
620
+ });
621
+ }
622
+ async function approveErc20(clients, token, spender, amount) {
623
+ const walletClient = clients.walletClient;
624
+ const hash = await walletClient.writeContract({
625
+ address: token,
626
+ abi: ERC20_ABI,
627
+ functionName: "approve",
628
+ args: [spender, BigInt(amount)]
629
+ });
630
+ const receipt = await clients.publicClient.waitForTransactionReceipt({ hash });
631
+ if (receipt.status === "reverted") {
632
+ throw new FastError(
633
+ "TX_FAILED",
634
+ `ERC-20 approve transaction reverted: ${hash}`,
635
+ {
636
+ note: "Check that you have sufficient ETH for gas fees."
637
+ }
638
+ );
639
+ }
640
+ const amountBig = BigInt(amount);
641
+ const walletAddress = walletClient.account?.address;
642
+ for (let attempt = 0; attempt < 10; attempt++) {
643
+ const current = await checkAllowance(clients, token, spender, walletAddress);
644
+ if (current >= amountBig) break;
645
+ await new Promise((resolve) => setTimeout(resolve, 500));
646
+ }
647
+ }
648
+ async function evmSign(certificate, crossSignUrl) {
649
+ const wireFormat = Schema.encodeSync(TransactionCertificateFromRpc)(
650
+ certificate
651
+ );
652
+ const serialized = bigIntToNumber(wireFormat);
653
+ const res = await fetch(crossSignUrl, {
654
+ method: "POST",
655
+ headers: { "Content-Type": "application/json" },
656
+ body: JSON.stringify({
657
+ jsonrpc: "2.0",
658
+ id: 1,
659
+ method: "crossSign_evmSignCertificate",
660
+ params: { certificate: serialized }
661
+ })
662
+ });
663
+ if (!res.ok) {
664
+ throw new FastError(
665
+ "TX_FAILED",
666
+ `Cross-sign request failed: ${res.status}`,
667
+ {
668
+ note: "The AllSet cross-sign service rejected the request."
669
+ }
670
+ );
671
+ }
672
+ const json = await res.json();
673
+ if (json.error) {
674
+ throw new FastError(
675
+ "TX_FAILED",
676
+ `Cross-sign error: ${json.error.message}`,
677
+ {
678
+ note: "The certificate could not be cross-signed."
679
+ }
680
+ );
681
+ }
682
+ if (!json.result?.transaction || !json.result?.signature) {
683
+ throw new FastError("TX_FAILED", "Cross-sign returned invalid response", {
684
+ note: "Missing transaction or signature in response."
685
+ });
686
+ }
687
+ return json.result;
688
+ }
689
+ async function executeDeposit(params) {
690
+ const {
691
+ chainId,
692
+ bridgeContract,
693
+ tokenAddress,
694
+ isNative = false,
695
+ amount,
696
+ receiverAddress,
697
+ evmClients
698
+ } = params;
699
+ let depositPlan;
700
+ try {
701
+ depositPlan = buildDepositTransaction({
702
+ chainId,
703
+ bridgeContract,
704
+ tokenAddress,
705
+ isNative,
706
+ amount: BigInt(amount),
707
+ receiver: receiverAddress
708
+ });
709
+ } catch (err) {
710
+ const msg = err instanceof Error ? err.message : String(err);
711
+ throw new FastError(
712
+ "INVALID_ADDRESS",
713
+ `Failed to decode Fast receiver address "${receiverAddress}": ${msg}`,
714
+ {
715
+ note: "The receiver address must be a valid Fast network bech32m address (fast1...)."
716
+ }
717
+ );
718
+ }
719
+ let txHash;
720
+ if (isNative) {
721
+ const receipt = await sendTx(evmClients, {
722
+ to: depositPlan.to,
723
+ data: depositPlan.data,
724
+ value: depositPlan.value.toString()
725
+ });
726
+ if (receipt.status === "reverted") {
727
+ throw new FastError(
728
+ "TX_FAILED",
729
+ `Deposit transaction reverted: ${receipt.txHash}`,
730
+ {
731
+ note: "Check that you have sufficient ETH balance."
732
+ }
733
+ );
734
+ }
735
+ txHash = receipt.txHash;
736
+ } else {
737
+ await approveErc20(evmClients, tokenAddress, bridgeContract, amount);
738
+ const receipt = await sendTx(evmClients, {
739
+ to: depositPlan.to,
740
+ data: depositPlan.data,
741
+ value: depositPlan.value.toString()
742
+ });
743
+ if (receipt.status === "reverted") {
744
+ throw new FastError(
745
+ "TX_FAILED",
746
+ `Deposit transaction reverted: ${receipt.txHash}`,
747
+ {
748
+ note: "Check that you have sufficient token balance and the approval succeeded."
749
+ }
750
+ );
751
+ }
752
+ txHash = receipt.txHash;
753
+ }
754
+ return { txHash, orderId: txHash, estimatedTime: "1-5 minutes" };
755
+ }
756
+ async function executeIntent(params) {
757
+ const {
758
+ fastBridgeAddress,
759
+ relayerUrl,
760
+ crossSignUrl,
761
+ tokenEvmAddress,
762
+ tokenFastTokenId,
763
+ amount,
764
+ intents,
765
+ externalAddress: externalAddressOverride,
766
+ deadlineSeconds = 3600,
767
+ signer,
768
+ provider,
769
+ networkId
770
+ } = params;
771
+ if (!intents || intents.length === 0) {
772
+ throw new FastError(
773
+ "INVALID_PARAMS",
774
+ "executeIntent requires at least one intent",
775
+ {
776
+ note: "Use intent builders like buildTransferIntent(), buildExecuteIntent(), etc."
777
+ }
778
+ );
779
+ }
780
+ if (externalAddressOverride && !externalAddressOverride.startsWith("0x")) {
781
+ throw new FastError(
782
+ "INVALID_PARAMS",
783
+ "executeIntent externalAddress must be an EVM address",
784
+ {
785
+ note: "Pass a 0x-prefixed address for the relayer target."
786
+ }
787
+ );
788
+ }
789
+ const tokenId = hexToUint8Array2(tokenFastTokenId);
790
+ const publicKey = await signer.getPublicKey();
791
+ const fastAddress = await signer.getFastAddress();
792
+ const accountInfo1 = await provider.getAccountInfo({
793
+ address: publicKey,
794
+ tokenBalancesFilter: null,
795
+ stateKeyFilter: null,
796
+ certificateByNonce: null
797
+ });
798
+ const transferEnvelope = await new TransactionBuilder({
799
+ networkId,
800
+ signer,
801
+ nonce: accountInfo1.nextNonce
802
+ }).addTokenTransfer({
803
+ tokenId,
804
+ recipient: fastAddressToBytes(fastBridgeAddress),
805
+ amount: BigInt(amount),
806
+ userData: null
807
+ }).sign();
808
+ const transferResult = await provider.submitTransaction(transferEnvelope);
809
+ if (transferResult.type !== "Success") {
810
+ throw new FastError(
811
+ "TX_FAILED",
812
+ `Token transfer submission incomplete: ${transferResult.type}`,
813
+ {
814
+ note: "The transfer transaction was not fully confirmed. Try again."
815
+ }
816
+ );
817
+ }
818
+ const transferCrossSign = await evmSign(transferResult.value, crossSignUrl);
819
+ const transferFastTxId = extractClaimId(transferCrossSign.transaction);
820
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + deadlineSeconds);
821
+ const intentClaimEncoded = encodeIntentClaim({
822
+ transferFastTxId,
823
+ deadline,
824
+ intents
825
+ });
826
+ const intentBytes = hexToUint8Array2(intentClaimEncoded);
827
+ const accountInfo2 = await provider.getAccountInfo({
828
+ address: publicKey,
829
+ tokenBalancesFilter: null,
830
+ stateKeyFilter: null,
831
+ certificateByNonce: null
832
+ });
833
+ const intentEnvelope = await new TransactionBuilder({
834
+ networkId,
835
+ signer,
836
+ nonce: accountInfo2.nextNonce
837
+ }).addExternalClaim({
838
+ claim: {
839
+ verifierCommittee: [],
840
+ verifierQuorum: 0,
841
+ claimData: intentBytes
842
+ },
843
+ signatures: []
844
+ }).sign();
845
+ const intentResult = await provider.submitTransaction(intentEnvelope);
846
+ if (intentResult.type !== "Success") {
847
+ throw new FastError(
848
+ "TX_FAILED",
849
+ `Intent claim submission incomplete: ${intentResult.type}`,
850
+ {
851
+ note: "The intent claim transaction was not fully confirmed. Try again."
852
+ }
853
+ );
854
+ }
855
+ const intentCrossSign = await evmSign(intentResult.value, crossSignUrl);
856
+ const intentFastTxId = extractClaimId(intentCrossSign.transaction);
857
+ const externalAddress = resolveExternalAddress(
858
+ intents,
859
+ externalAddressOverride
860
+ );
861
+ if (!externalAddress) {
862
+ throw new FastError(
863
+ "INVALID_PARAMS",
864
+ "executeIntent requires externalAddress when intents do not include a transfer recipient or execute target",
865
+ {
866
+ note: "Pass externalAddress for flows like buildDepositBackIntent() or buildRevokeIntent()."
867
+ }
868
+ );
869
+ }
870
+ await relayExecute({
871
+ relayerUrl,
872
+ encodedTransferClaim: Array.from(
873
+ new Uint8Array(transferCrossSign.transaction.map(Number))
874
+ ),
875
+ transferProof: transferCrossSign.signature,
876
+ transferFastTxId,
877
+ fastsetAddress: fastAddress,
878
+ externalAddress,
879
+ encodedIntentClaim: Array.from(
880
+ new Uint8Array(intentCrossSign.transaction.map(Number))
881
+ ),
882
+ intentProof: intentCrossSign.signature,
883
+ intentFastTxId,
884
+ intentClaimId: intentFastTxId,
885
+ externalTokenAddress: tokenEvmAddress
886
+ });
887
+ return {
888
+ txHash: transferFastTxId,
889
+ orderId: transferFastTxId,
890
+ estimatedTime: "1-5 minutes"
891
+ };
892
+ }
893
+ async function executeWithdraw(params) {
894
+ const { receiverEvmAddress, tokenEvmAddress, ...rest } = params;
895
+ const intent = buildTransferIntent(tokenEvmAddress, receiverEvmAddress);
896
+ return executeIntent({ ...rest, tokenEvmAddress, intents: [intent] });
897
+ }
898
+ export {
899
+ CHAIN_MAP,
900
+ ERC20_ABI,
901
+ FastError,
902
+ InsufficientBalanceError,
903
+ IntentAction,
904
+ buildDepositBackIntent,
905
+ buildDepositTransaction,
906
+ buildExecuteIntent,
907
+ buildIntentClaimBytes,
908
+ buildRevokeIntent,
909
+ buildTransferIntent,
910
+ createEvmExecutor,
911
+ createEvmWallet,
912
+ encodeDepositCalldata,
913
+ encodeIntentClaim,
914
+ encodeTransferClaim,
915
+ evmSign,
916
+ executeDeposit,
917
+ executeIntent,
918
+ executeWithdraw,
919
+ extractClaimId,
920
+ fastAddressToBytes,
921
+ fastAddressToBytes32,
922
+ getEvmErc20Balance,
923
+ getEvmNativeBalance,
924
+ hashTransferClaim,
925
+ relayExecute,
926
+ smartDeposit
927
+ };