@matterlabs/zksync-js 0.0.1 → 0.0.3

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 (103) hide show
  1. package/README.md +12 -12
  2. package/dist/adapters/ethers/client.cjs +642 -1
  3. package/dist/adapters/ethers/client.cjs.map +1 -1
  4. package/dist/adapters/ethers/client.js +6 -5
  5. package/dist/adapters/ethers/estimator.d.ts +4 -0
  6. package/dist/adapters/ethers/index.cjs +1279 -925
  7. package/dist/adapters/ethers/index.cjs.map +1 -1
  8. package/dist/adapters/ethers/index.d.ts +1 -0
  9. package/dist/adapters/ethers/index.js +9 -8
  10. package/dist/adapters/ethers/resources/contracts/contracts.d.ts +9 -0
  11. package/dist/adapters/ethers/resources/contracts/index.d.ts +2 -0
  12. package/dist/adapters/ethers/resources/contracts/types.d.ts +60 -0
  13. package/dist/adapters/ethers/resources/deposits/context.d.ts +21 -7
  14. package/dist/adapters/ethers/resources/deposits/index.d.ts +3 -1
  15. package/dist/adapters/ethers/resources/deposits/routes/types.d.ts +2 -6
  16. package/dist/adapters/ethers/resources/deposits/services/fee.d.ts +6 -0
  17. package/dist/adapters/ethers/resources/deposits/services/gas.d.ts +41 -0
  18. package/dist/adapters/ethers/resources/tokens/index.d.ts +1 -0
  19. package/dist/adapters/ethers/resources/tokens/tokens.d.ts +10 -0
  20. package/dist/adapters/ethers/resources/utils.d.ts +3 -17
  21. package/dist/adapters/ethers/resources/withdrawals/context.d.ts +15 -7
  22. package/dist/adapters/ethers/resources/withdrawals/index.d.ts +3 -1
  23. package/dist/adapters/ethers/resources/withdrawals/routes/types.d.ts +2 -2
  24. package/dist/adapters/ethers/resources/withdrawals/services/fees.d.ts +14 -0
  25. package/dist/adapters/ethers/resources/withdrawals/services/gas.d.ts +12 -0
  26. package/dist/adapters/ethers/sdk.cjs +1388 -1501
  27. package/dist/adapters/ethers/sdk.cjs.map +1 -1
  28. package/dist/adapters/ethers/sdk.d.ts +5 -22
  29. package/dist/adapters/ethers/sdk.js +7 -6
  30. package/dist/adapters/viem/client.cjs.map +1 -1
  31. package/dist/adapters/viem/client.d.ts +1 -1
  32. package/dist/adapters/viem/client.js +4 -5
  33. package/dist/adapters/viem/estimator.d.ts +4 -0
  34. package/dist/adapters/viem/index.cjs +1233 -744
  35. package/dist/adapters/viem/index.cjs.map +1 -1
  36. package/dist/adapters/viem/index.d.ts +3 -0
  37. package/dist/adapters/viem/index.js +8 -8
  38. package/dist/adapters/viem/resources/contracts/contracts.d.ts +9 -0
  39. package/dist/adapters/viem/resources/contracts/index.d.ts +2 -0
  40. package/dist/adapters/viem/resources/contracts/types.d.ts +61 -0
  41. package/dist/adapters/viem/resources/deposits/context.d.ts +21 -7
  42. package/dist/adapters/viem/resources/deposits/index.d.ts +3 -1
  43. package/dist/adapters/viem/resources/deposits/routes/types.d.ts +2 -6
  44. package/dist/adapters/viem/resources/deposits/services/fee.d.ts +6 -0
  45. package/dist/adapters/viem/resources/deposits/services/gas.d.ts +37 -0
  46. package/dist/adapters/viem/resources/tokens/index.d.ts +1 -0
  47. package/dist/adapters/viem/resources/tokens/tokens.d.ts +3 -0
  48. package/dist/adapters/viem/resources/utils.d.ts +3 -19
  49. package/dist/adapters/viem/resources/withdrawals/context.d.ts +14 -9
  50. package/dist/adapters/viem/resources/withdrawals/index.d.ts +3 -1
  51. package/dist/adapters/viem/resources/withdrawals/routes/types.d.ts +12 -2
  52. package/dist/adapters/viem/resources/withdrawals/services/fee.d.ts +17 -0
  53. package/dist/adapters/viem/resources/withdrawals/services/gas.d.ts +12 -0
  54. package/dist/adapters/viem/sdk.cjs +1225 -699
  55. package/dist/adapters/viem/sdk.cjs.map +1 -1
  56. package/dist/adapters/viem/sdk.d.ts +5 -25
  57. package/dist/adapters/viem/sdk.js +6 -6
  58. package/dist/{chunk-3LALBFFE.js → chunk-3MRGU4HV.js} +9 -5
  59. package/dist/{chunk-CGO27P7F.js → chunk-5YWP4CZP.js} +849 -835
  60. package/dist/{chunk-4HLJJKIY.js → chunk-6K6VJQAL.js} +2 -2
  61. package/dist/{chunk-6GCT6TLS.js → chunk-F2ENUV3A.js} +13 -1
  62. package/dist/{chunk-7M4V3FMT.js → chunk-JXUFGIJG.js} +986 -678
  63. package/dist/chunk-LL3WKCFJ.js +231 -0
  64. package/dist/{chunk-Y75OMFK6.js → chunk-M5J2MM2U.js} +351 -1
  65. package/dist/{chunk-263G6636.js → chunk-NCAIVYBR.js} +1 -14
  66. package/dist/{chunk-DI2CJDPZ.js → chunk-NEC2ZKHI.js} +5 -13
  67. package/dist/chunk-NTEIA5KA.js +13 -0
  68. package/dist/chunk-XRE7H466.js +157 -0
  69. package/dist/{chunk-BD2LUO5T.js → chunk-YUK547UF.js} +3 -3
  70. package/dist/core/abi.d.ts +9 -0
  71. package/dist/core/adapters/interfaces.d.ts +25 -0
  72. package/dist/core/codec/ntv.d.ts +48 -0
  73. package/dist/core/constants.cjs +12 -0
  74. package/dist/core/constants.cjs.map +1 -1
  75. package/dist/core/constants.d.ts +6 -0
  76. package/dist/core/constants.js +1 -1
  77. package/dist/core/index.cjs +4508 -1
  78. package/dist/core/index.cjs.map +1 -1
  79. package/dist/core/index.d.ts +2 -0
  80. package/dist/core/index.js +5 -4
  81. package/dist/core/resources/deposits/fee.d.ts +15 -0
  82. package/dist/core/resources/deposits/gas.d.ts +38 -0
  83. package/dist/core/resources/withdrawals/gas.d.ts +14 -0
  84. package/dist/core/types/errors.d.ts +1 -1
  85. package/dist/core/types/fees.d.ts +40 -0
  86. package/dist/core/types/flows/base.d.ts +0 -10
  87. package/dist/core/types/flows/deposits.d.ts +20 -6
  88. package/dist/core/types/flows/route.d.ts +2 -3
  89. package/dist/core/types/flows/token.d.ts +192 -0
  90. package/dist/core/types/flows/withdrawals.d.ts +12 -6
  91. package/dist/core/utils/addr.d.ts +2 -0
  92. package/dist/index.cjs +4520 -1
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.js +5 -4
  96. package/package.json +5 -1
  97. package/dist/adapters/ethers/resources/token-info.d.ts +0 -31
  98. package/dist/adapters/ethers/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  99. package/dist/adapters/viem/resources/token-info.d.ts +0 -34
  100. package/dist/adapters/viem/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  101. package/dist/chunk-B77GWPO5.js +0 -339
  102. package/dist/core/internal/abi-registry.d.ts +0 -9
  103. package/dist/core/utils/gas.d.ts +0 -13
@@ -1,20 +1,50 @@
1
- import { assertNoLegacyGas, assertPriorityFeeBounds, REVERT_TO_READINESS } from './chunk-263G6636.js';
2
- import { findL1MessageSentLog, messengerLogIndex, isAddressEq, isHash66, pickDepositRoute, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-DI2CJDPZ.js';
3
- import { IL1Nullifier_default, IERC20_default, L1NativeTokenVault_default, L2NativeTokenVault_default, Mailbox_default, IBridgehub_default, IL2AssetRouter_default, IBaseToken_default } from './chunk-Y75OMFK6.js';
4
- import { isZKsyncError, createError, shapeCause, OP_WITHDRAWALS, OP_DEPOSITS, isReceiptNotFound } from './chunk-B77GWPO5.js';
5
- import { ETH_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L1_FEE_ESTIMATION_COEF_NUMERATOR, L1_FEE_ESTIMATION_COEF_DENOMINATOR, L1_MESSENGER_ADDRESS, L2_ASSET_ROUTER_ADDRESS, FORMAL_ETH_ADDRESS, L2_BASE_TOKEN_ADDRESS, TOPIC_CANONICAL_ASSIGNED, TOPIC_CANONICAL_SUCCESS } from './chunk-6GCT6TLS.js';
6
- import { encodeAbiParameters, keccak256, concat, decodeErrorResult, decodeAbiParameters, decodeEventLog } from 'viem';
1
+ import { createNTVCodec, buildFeeBreakdown, quoteL2Gas, quoteL1Gas, quoteL2Gas2 } from './chunk-LL3WKCFJ.js';
2
+ import { findL1MessageSentLog, messengerLogIndex, normalizeL1Token, isAddressEq, hexEq, isHash66, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-NEC2ZKHI.js';
3
+ import { REVERT_TO_READINESS } from './chunk-NCAIVYBR.js';
4
+ import { IL1Nullifier_default, IERC20_default, L1NativeTokenVault_default, L2NativeTokenVault_default, Mailbox_default, isZKsyncError, createError, shapeCause, OP_WITHDRAWALS, OP_DEPOSITS, isReceiptNotFound, IBridgehub_default, IL2AssetRouter_default, IBaseToken_default } from './chunk-M5J2MM2U.js';
5
+ import { ETH_ADDRESS, L1_MESSENGER_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_BASE_TOKEN_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, TOPIC_CANONICAL_ASSIGNED, TOPIC_CANONICAL_SUCCESS, SAFE_L1_BRIDGE_GAS } from './chunk-F2ENUV3A.js';
6
+ import { keccak256, encodeAbiParameters, concat, decodeErrorResult, decodeAbiParameters, decodeEventLog, encodeFunctionData, zeroAddress } from 'viem';
7
7
 
8
- function encodeNativeTokenVaultAssetId(chainId, address) {
9
- const encoded = encodeAbiParameters(
10
- [
11
- { type: "uint256", name: "originChainId" },
12
- { type: "address", name: "ntv" },
13
- { type: "address", name: "token" }
14
- ],
15
- [chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, address]
16
- );
17
- return keccak256(encoded);
8
+ // src/adapters/viem/resources/deposits/context.ts
9
+ async function commonCtx(p, client, tokens, contracts) {
10
+ const { bridgehub, l1AssetRouter } = await contracts.addresses();
11
+ const chainId = await client.l2.getChainId();
12
+ const sender = client.account.address;
13
+ const gasPerPubdata = p.gasPerPubdata ?? 800n;
14
+ const operatorTip = p.operatorTip ?? 0n;
15
+ const refundRecipient = p.refundRecipient ?? sender;
16
+ const resolvedToken = await tokens.resolve(p.token, { chain: "l1" });
17
+ const baseTokenAssetId = resolvedToken.baseTokenAssetId;
18
+ const baseTokenL1 = await tokens.l1TokenFromAssetId(baseTokenAssetId);
19
+ const baseIsEth = resolvedToken.isChainEthBased;
20
+ const route = (() => {
21
+ if (resolvedToken.kind === "eth") {
22
+ return baseIsEth ? "eth-base" : "eth-nonbase";
23
+ }
24
+ if (resolvedToken.kind === "base") {
25
+ return baseIsEth ? "eth-base" : "erc20-base";
26
+ }
27
+ return "erc20-nonbase";
28
+ })();
29
+ return {
30
+ client,
31
+ tokens,
32
+ contracts,
33
+ resolvedToken,
34
+ baseTokenAssetId,
35
+ baseTokenL1,
36
+ baseIsEth,
37
+ l1AssetRouter,
38
+ route,
39
+ bridgehub,
40
+ chainIdL2: BigInt(chainId),
41
+ sender,
42
+ gasOverrides: p.l1TxOverrides,
43
+ l2GasLimit: p.l2GasLimit,
44
+ gasPerPubdata,
45
+ operatorTip,
46
+ refundRecipient
47
+ };
18
48
  }
19
49
  function encodeNativeTokenVaultTransferData(amount, receiver, token) {
20
50
  return encodeAbiParameters(
@@ -36,123 +66,6 @@ function encodeSecondBridgeDataV1(assetId, transferData) {
36
66
  );
37
67
  return concat(["0x01", data]);
38
68
  }
39
- var encodeNTVAssetId = encodeNativeTokenVaultAssetId;
40
- var encodeNTVTransferData = encodeNativeTokenVaultTransferData;
41
- function scaleGasLimit(gasLimit) {
42
- return gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR) / BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR);
43
- }
44
- async function checkBaseCost(baseCost, value) {
45
- const resolved = await value;
46
- if (baseCost > resolved) {
47
- throw new Error(
48
- `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${String(baseCost)}, provided value: ${String(resolved)}!`
49
- );
50
- }
51
- }
52
- async function getFeeOverrides(client, overrides) {
53
- assertNoLegacyGas(overrides);
54
- let maxFeePerGasFromProvider;
55
- let maxPriorityFromProvider;
56
- let gasPriceFromProvider;
57
- try {
58
- const fees = await client.l1.estimateFeesPerGas();
59
- const { maxFeePerGas: maxFeePerGas2, maxPriorityFeePerGas: maxPriorityFeePerGas2 } = fees;
60
- if (maxFeePerGas2 != null && maxPriorityFeePerGas2 != null) {
61
- maxFeePerGasFromProvider = maxFeePerGas2;
62
- maxPriorityFromProvider = maxPriorityFeePerGas2;
63
- gasPriceFromProvider = fees.gasPrice ?? maxFeePerGas2;
64
- } else if (fees.gasPrice != null) {
65
- gasPriceFromProvider = fees.gasPrice;
66
- }
67
- } catch {
68
- }
69
- if (gasPriceFromProvider == null) {
70
- try {
71
- gasPriceFromProvider = await client.l1.getGasPrice();
72
- } catch {
73
- }
74
- }
75
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeePerGasFromProvider ?? gasPriceFromProvider;
76
- if (maxFeePerGas == null) {
77
- throw new Error("L1 provider returned no gas price data");
78
- }
79
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
80
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
81
- const gasPriceForBaseCost = overrides?.maxFeePerGas ?? maxFeePerGasFromProvider ?? gasPriceFromProvider ?? maxFeePerGas;
82
- return {
83
- gasLimit: overrides?.gasLimit,
84
- maxFeePerGas,
85
- maxPriorityFeePerGas,
86
- gasPriceForBaseCost
87
- };
88
- }
89
- async function getL2FeeOverrides(client, overrides) {
90
- assertNoLegacyGas(overrides);
91
- let maxFeePerGasFromProvider;
92
- let maxPriorityFromProvider;
93
- let gasPriceFromProvider;
94
- try {
95
- const fees = await client.l2.estimateFeesPerGas();
96
- if (fees?.maxFeePerGas != null && fees.maxPriorityFeePerGas != null) {
97
- maxFeePerGasFromProvider = fees.maxFeePerGas;
98
- maxPriorityFromProvider = fees.maxPriorityFeePerGas;
99
- gasPriceFromProvider = fees.gasPrice ?? fees.maxFeePerGas;
100
- } else if (fees?.gasPrice != null) {
101
- gasPriceFromProvider = fees.gasPrice;
102
- }
103
- } catch {
104
- }
105
- if (gasPriceFromProvider == null) {
106
- try {
107
- gasPriceFromProvider = await client.l2.getGasPrice();
108
- } catch {
109
- }
110
- }
111
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeePerGasFromProvider ?? gasPriceFromProvider;
112
- if (maxFeePerGas == null) {
113
- throw new Error("provider returned no gas price data");
114
- }
115
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
116
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
117
- return {
118
- gasLimit: overrides?.gasLimit,
119
- maxFeePerGas,
120
- maxPriorityFeePerGas
121
- };
122
- }
123
- function buildViemFeeOverrides(fees) {
124
- return {
125
- maxFeePerGas: fees.maxFeePerGas,
126
- maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
127
- gas: fees.gasLimit
128
- };
129
- }
130
- async function getGasPriceWei(client) {
131
- try {
132
- const gp = await client.l1.getGasPrice();
133
- if (gp != null) return gp;
134
- } catch {
135
- }
136
- try {
137
- const fees = await client.l1.estimateFeesPerGas();
138
- if (fees?.maxFeePerGas != null) return fees.maxFeePerGas;
139
- } catch {
140
- }
141
- throw new Error("provider returned no gas price data");
142
- }
143
- function buildDirectRequestStruct(args) {
144
- return {
145
- chainId: args.chainId,
146
- l2Contract: args.l2Contract,
147
- mintValue: args.mintValue,
148
- l2Value: args.l2Value,
149
- l2Calldata: "0x",
150
- l2GasLimit: args.l2GasLimit,
151
- l2GasPerPubdataByteLimit: args.gasPerPubdata,
152
- factoryDeps: [],
153
- refundRecipient: args.refundRecipient
154
- };
155
- }
156
69
  function encodeSecondBridgeArgs(token, amount, l2Receiver) {
157
70
  return encodeAbiParameters(
158
71
  [
@@ -169,30 +82,17 @@ function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
169
82
  function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
170
83
  return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
171
84
  }
172
-
173
- // src/adapters/viem/resources/deposits/context.ts
174
- async function commonCtx(p, client) {
175
- const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
176
- const chainId = await client.l2.getChainId();
177
- const sender = client.account.address;
178
- const fee = await getFeeOverrides(client, p.l1TxOverrides);
179
- const l2GasLimit = p.l2GasLimit ?? 300000n;
180
- const gasPerPubdata = p.gasPerPubdata ?? 800n;
181
- const operatorTip = p.operatorTip ?? 0n;
182
- const refundRecipient = p.refundRecipient ?? sender;
183
- const route = await pickDepositRoute(client, BigInt(chainId), p.token);
85
+ function buildDirectRequestStruct(args) {
184
86
  return {
185
- client,
186
- l1AssetRouter,
187
- route,
188
- bridgehub,
189
- chainIdL2: BigInt(chainId),
190
- sender,
191
- fee,
192
- l2GasLimit,
193
- gasPerPubdata,
194
- operatorTip,
195
- refundRecipient
87
+ chainId: args.chainId,
88
+ l2Contract: args.l2Contract,
89
+ mintValue: args.mintValue,
90
+ l2Value: args.l2Value,
91
+ l2Calldata: "0x",
92
+ l2GasLimit: args.l2GasLimit,
93
+ l2GasPerPubdataByteLimit: args.gasPerPubdata,
94
+ factoryDeps: [],
95
+ refundRecipient: args.refundRecipient
196
96
  };
197
97
  }
198
98
  var ERROR_ABIS = [];
@@ -323,7 +223,7 @@ function createErrorHandlers(resource) {
323
223
  function wrap2(operation, fn, opts) {
324
224
  return run("INTERNAL", operation, fn, opts);
325
225
  }
326
- function wrapAs9(kind, operation, fn, opts) {
226
+ function wrapAs10(kind, operation, fn, opts) {
327
227
  return run(kind, operation, fn, opts);
328
228
  }
329
229
  async function toResult2(operation, fn, opts) {
@@ -344,44 +244,225 @@ function createErrorHandlers(resource) {
344
244
  return { ok: false, error: shaped };
345
245
  }
346
246
  }
347
- return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
247
+ return { wrap: wrap2, wrapAs: wrapAs10, toResult: toResult2 };
348
248
  }
349
249
 
350
- // src/adapters/viem/resources/deposits/routes/eth.ts
250
+ // src/adapters/viem/estimator.ts
251
+ function toCoreTx(tx) {
252
+ return {
253
+ to: tx.to,
254
+ from: tx.from,
255
+ data: tx.data,
256
+ value: tx.value,
257
+ gasLimit: tx.gas,
258
+ maxFeePerGas: tx.maxFeePerGas,
259
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
260
+ };
261
+ }
262
+ function viemToGasEstimator(client) {
263
+ return {
264
+ async estimateGas(tx, stateOverrides) {
265
+ if (stateOverrides) {
266
+ try {
267
+ const result = await client.request({
268
+ method: "eth_estimateGas",
269
+ params: [
270
+ {
271
+ from: tx.from,
272
+ to: tx.to,
273
+ data: tx.data,
274
+ value: tx.value,
275
+ gas: tx.gasLimit,
276
+ maxFeePerGas: tx.maxFeePerGas,
277
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
278
+ },
279
+ "latest",
280
+ stateOverrides
281
+ ]
282
+ });
283
+ return BigInt(result);
284
+ } catch (error) {
285
+ console.warn(
286
+ "Failed to estimate gas with state overrides, falling back to standard estimation:",
287
+ error
288
+ );
289
+ }
290
+ }
291
+ return await client.estimateGas({
292
+ account: tx.from,
293
+ to: tx.to,
294
+ data: tx.data,
295
+ value: tx.value,
296
+ gas: tx.gasLimit,
297
+ maxFeePerGas: tx.maxFeePerGas,
298
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
299
+ });
300
+ },
301
+ async estimateFeesPerGas() {
302
+ try {
303
+ const fees = await client.estimateFeesPerGas();
304
+ return {
305
+ maxFeePerGas: fees.maxFeePerGas,
306
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas
307
+ };
308
+ } catch {
309
+ }
310
+ try {
311
+ const gp = await client.getGasPrice();
312
+ return { gasPrice: gp };
313
+ } catch {
314
+ return {};
315
+ }
316
+ },
317
+ async getGasPrice() {
318
+ return await client.getGasPrice();
319
+ },
320
+ async call(tx) {
321
+ const res = await client.call({
322
+ to: tx.to,
323
+ data: tx.data,
324
+ value: tx.value,
325
+ account: tx.from
326
+ });
327
+ return res.data ?? "0x";
328
+ }
329
+ };
330
+ }
331
+
332
+ // src/adapters/viem/resources/deposits/services/gas.ts
333
+ async function quoteL1Gas2(input) {
334
+ const { ctx, tx, overrides, fallbackGasLimit } = input;
335
+ const estimator = viemToGasEstimator(ctx.client.l1);
336
+ return quoteL1Gas({
337
+ estimator,
338
+ tx: toCoreTx(tx),
339
+ overrides,
340
+ fallbackGasLimit
341
+ });
342
+ }
343
+ async function quoteL2Gas3(input) {
344
+ const { ctx, route, l2TxForModeling, overrideGasLimit } = input;
345
+ const estimator = viemToGasEstimator(ctx.client.l2);
346
+ return quoteL2Gas({
347
+ estimator,
348
+ route,
349
+ tx: l2TxForModeling ? toCoreTx(l2TxForModeling) : void 0,
350
+ gasPerPubdata: ctx.gasPerPubdata,
351
+ l2GasLimit: ctx.l2GasLimit,
352
+ // TODO: investigate if this should be passed here; weird viem quirk
353
+ overrideGasLimit,
354
+ stateOverrides: input.stateOverrides
355
+ });
356
+ }
357
+ async function determineErc20L2Gas(input) {
358
+ const { ctx, l1Token } = input;
359
+ const DEFAULT_SAFE_L2_GAS_LIMIT = 3000000n;
360
+ if (ctx.l2GasLimit != null) {
361
+ return quoteL2Gas3({
362
+ ctx,
363
+ route: "erc20-nonbase",
364
+ overrideGasLimit: ctx.l2GasLimit
365
+ });
366
+ }
367
+ try {
368
+ const l2TokenAddress = ctx.tokens ? await ctx.tokens.toL2Address(l1Token) : await (await ctx.contracts.l2NativeTokenVault()).read.l2TokenAddress([l1Token]);
369
+ if (l2TokenAddress === zeroAddress) {
370
+ return quoteL2Gas3({
371
+ ctx,
372
+ route: "erc20-nonbase",
373
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
374
+ });
375
+ }
376
+ const modelTx = {
377
+ to: input.modelTx?.to ?? ctx.sender,
378
+ from: input.modelTx?.from ?? ctx.sender,
379
+ data: input.modelTx?.data ?? "0x",
380
+ value: input.modelTx?.value ?? 0n
381
+ };
382
+ const gas = await quoteL2Gas3({
383
+ ctx,
384
+ route: "erc20-nonbase",
385
+ l2TxForModeling: modelTx
386
+ });
387
+ if (!gas) {
388
+ return quoteL2Gas3({
389
+ ctx,
390
+ route: "erc20-nonbase",
391
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
392
+ });
393
+ }
394
+ return gas;
395
+ } catch (err) {
396
+ console.warn("Failed to determine ERC20 L2 gas; defaulting to safe gas limit.", err);
397
+ return quoteL2Gas3({
398
+ ctx,
399
+ route: "erc20-nonbase",
400
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
401
+ });
402
+ }
403
+ }
404
+
405
+ // src/adapters/viem/resources/deposits/services/fee.ts
351
406
  var { wrapAs } = createErrorHandlers("deposits");
407
+ async function quoteL2BaseCost(input) {
408
+ const { ctx, l2GasLimit } = input;
409
+ const estimator = viemToGasEstimator(ctx.client.l1);
410
+ const fees = await estimator.estimateFeesPerGas();
411
+ const gasPrice = fees.maxFeePerGas ?? fees.gasPrice ?? await estimator.getGasPrice();
412
+ return wrapAs(
413
+ "RPC",
414
+ "deposits.fees.l2BaseCost",
415
+ async () => {
416
+ return await ctx.client.l1.readContract({
417
+ address: ctx.bridgehub,
418
+ abi: IBridgehub_default,
419
+ functionName: "l2TransactionBaseCost",
420
+ args: [ctx.chainIdL2, gasPrice, l2GasLimit, ctx.gasPerPubdata]
421
+ });
422
+ },
423
+ { ctx: { chainIdL2: ctx.chainIdL2 } }
424
+ );
425
+ }
426
+
427
+ // src/adapters/viem/resources/deposits/routes/eth.ts
428
+ var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
352
429
  function routeEthDirect() {
353
430
  return {
354
431
  async build(p, ctx) {
355
- const { gasPriceForBaseCost } = ctx.fee;
356
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
357
- const rawBaseCost = await wrapAs(
358
- "CONTRACT",
359
- OP_DEPOSITS.eth.baseCost,
360
- () => ctx.client.l1.readContract({
361
- address: ctx.bridgehub,
362
- abi: IBridgehub_default,
363
- functionName: "l2TransactionBaseCost",
364
- args: [ctx.chainIdL2, gasPriceForBaseCost, ctx.l2GasLimit, ctx.gasPerPubdata]
365
- }),
366
- {
367
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
368
- message: "Could not fetch L2 base cost from Bridgehub."
432
+ const l2TxModel = {
433
+ to: p.to ?? ctx.sender,
434
+ from: ctx.sender,
435
+ data: "0x",
436
+ value: p.amount
437
+ };
438
+ const l2GasParams = await quoteL2Gas3({
439
+ ctx,
440
+ route: "eth-base",
441
+ l2TxForModeling: l2TxModel,
442
+ overrideGasLimit: ctx.l2GasLimit,
443
+ stateOverrides: {
444
+ [ctx.sender]: {
445
+ balance: "0xffffffffffffffffffff"
446
+ }
369
447
  }
370
- );
371
- const baseCost = rawBaseCost;
448
+ });
449
+ if (!l2GasParams) {
450
+ throw new Error("Failed to estimate L2 gas for deposit.");
451
+ }
452
+ const baseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2GasParams.gasLimit });
372
453
  const l2Contract = p.to ?? ctx.sender;
373
454
  const l2Value = p.amount;
374
455
  const mintValue = baseCost + ctx.operatorTip + l2Value;
375
456
  const req = buildDirectRequestStruct({
376
457
  chainId: ctx.chainIdL2,
377
458
  mintValue,
378
- l2GasLimit: ctx.l2GasLimit,
459
+ l2GasLimit: l2GasParams.gasLimit,
379
460
  gasPerPubdata: ctx.gasPerPubdata,
380
461
  refundRecipient: ctx.refundRecipient,
381
462
  l2Contract,
382
463
  l2Value
383
464
  });
384
- const sim = await wrapAs(
465
+ const sim = await wrapAs2(
385
466
  "RPC",
386
467
  OP_DEPOSITS.eth.estGas,
387
468
  () => ctx.client.l1.simulateContract({
@@ -397,120 +478,116 @@ function routeEthDirect() {
397
478
  message: "Failed to simulate Bridgehub.requestL2TransactionDirect."
398
479
  }
399
480
  );
400
- const resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
481
+ const data = encodeFunctionData({
482
+ abi: sim.request.abi,
483
+ functionName: sim.request.functionName,
484
+ args: sim.request.args
485
+ });
486
+ const l1TxCandidate = {
487
+ to: ctx.bridgehub,
488
+ data,
489
+ value: mintValue,
490
+ from: ctx.sender,
491
+ ...ctx.gasOverrides
492
+ };
493
+ const l1Gas = await quoteL1Gas2({
494
+ ctx,
495
+ tx: l1TxCandidate,
496
+ overrides: ctx.gasOverrides
497
+ });
401
498
  const steps = [
402
499
  {
403
500
  key: "bridgehub:direct",
404
501
  kind: "bridgehub:direct",
405
502
  description: "Bridge ETH via Bridgehub.requestL2TransactionDirect",
406
- tx: { ...sim.request, ...txFeeOverrides }
503
+ tx: { ...sim.request, ...l1Gas }
407
504
  }
408
505
  ];
506
+ const fees = buildFeeBreakdown({
507
+ feeToken: ETH_ADDRESS,
508
+ l1Gas,
509
+ l2Gas: l2GasParams,
510
+ l2BaseCost: baseCost,
511
+ operatorTip: ctx.operatorTip,
512
+ mintValue
513
+ });
409
514
  return {
410
515
  steps,
411
516
  approvals: [],
412
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
517
+ fees
413
518
  };
414
519
  }
415
520
  };
416
521
  }
417
-
418
- // src/adapters/viem/resources/deposits/routes/erc20-nonbase.ts
419
- var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
420
- var BASE_COST_BUFFER_BPS = 100n;
421
- var BPS = 10000n;
422
- var withBuffer = (x) => x * (BPS + BASE_COST_BUFFER_BPS) / BPS;
522
+ var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
423
523
  function routeErc20NonBase() {
424
524
  return {
525
+ // TODO: do we even need these validations?
425
526
  async preflight(p, ctx) {
426
- await wrapAs2(
527
+ await wrapAs3(
427
528
  "VALIDATION",
428
529
  OP_DEPOSITS.nonbase.assertNotEthAsset,
429
530
  () => {
430
- if (isETH(p.token)) {
531
+ if (ctx.resolvedToken?.kind === "eth" || isETH(p.token)) {
431
532
  throw new Error("erc20-nonbase route requires an ERC-20 token (not ETH).");
432
533
  }
433
534
  },
434
535
  { ctx: { token: p.token } }
435
536
  );
436
- const baseToken = await wrapAs2(
437
- "CONTRACT",
438
- OP_DEPOSITS.nonbase.baseToken,
439
- () => ctx.client.l1.readContract({
440
- address: ctx.bridgehub,
441
- abi: IBridgehub_default,
442
- functionName: "baseToken",
443
- args: [ctx.chainIdL2]
444
- }),
445
- { ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 } }
446
- );
447
- await wrapAs2(
537
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
538
+ await wrapAs3(
448
539
  "VALIDATION",
449
540
  OP_DEPOSITS.nonbase.assertNonBaseToken,
450
541
  () => {
451
- if (normalizeAddrEq(baseToken, p.token)) {
542
+ if (ctx.resolvedToken?.kind === "base" || normalizeAddrEq(baseToken, p.token)) {
452
543
  throw new Error("erc20-nonbase route requires a non-base ERC-20 deposit token.");
453
544
  }
454
545
  },
455
546
  { ctx: { depositToken: p.token, baseToken } }
456
547
  );
457
- return;
458
548
  },
459
549
  async build(p, ctx) {
460
- const { gasPriceForBaseCost } = ctx.fee;
461
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
462
- const baseToken = await wrapAs2(
463
- "CONTRACT",
464
- OP_DEPOSITS.nonbase.baseToken,
465
- () => ctx.client.l1.readContract({
466
- address: ctx.bridgehub,
467
- abi: IBridgehub_default,
468
- functionName: "baseToken",
469
- args: [ctx.chainIdL2]
470
- }),
471
- { ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 } }
472
- );
473
- const MIN_L2_GAS_FOR_ERC20 = 2500000n;
474
- const l2GasLimitUsed = ctx.l2GasLimit && ctx.l2GasLimit > 0n ? ctx.l2GasLimit < MIN_L2_GAS_FOR_ERC20 ? MIN_L2_GAS_FOR_ERC20 : ctx.l2GasLimit : MIN_L2_GAS_FOR_ERC20;
475
- const rawBaseCost = await wrapAs2(
476
- "CONTRACT",
477
- OP_DEPOSITS.nonbase.baseCost,
478
- () => ctx.client.l1.readContract({
479
- address: ctx.bridgehub,
480
- abi: IBridgehub_default,
481
- functionName: "l2TransactionBaseCost",
482
- args: [ctx.chainIdL2, gasPriceForBaseCost, l2GasLimitUsed, ctx.gasPerPubdata]
483
- }),
484
- { ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 } }
485
- );
486
- const baseCost = rawBaseCost;
487
- const mintValue = withBuffer(baseCost + ctx.operatorTip);
550
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
551
+ const baseIsEth = ctx.baseIsEth ?? isETH(baseToken);
552
+ const assetRouter = ctx.l1AssetRouter;
553
+ const l2Gas = await determineErc20L2Gas({
554
+ ctx,
555
+ l1Token: p.token,
556
+ modelTx: {
557
+ to: p.to ?? ctx.sender,
558
+ from: ctx.sender,
559
+ data: "0x",
560
+ value: 0n
561
+ }
562
+ });
563
+ if (!l2Gas) throw new Error("Failed to establish L2 gas parameters.");
564
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
565
+ const mintValue = l2BaseCost + ctx.operatorTip;
488
566
  const approvals = [];
489
567
  const steps = [];
490
- const depositAllowance = await wrapAs2(
568
+ const depositAllowance = await wrapAs3(
491
569
  "CONTRACT",
492
- OP_DEPOSITS.nonbase.allowance,
570
+ OP_DEPOSITS.nonbase.allowanceToken,
493
571
  () => ctx.client.l1.readContract({
494
572
  address: p.token,
495
573
  abi: IERC20_default,
496
574
  functionName: "allowance",
497
- args: [ctx.sender, ctx.l1AssetRouter]
575
+ args: [ctx.sender, assetRouter]
498
576
  }),
499
577
  {
500
- ctx: { where: "erc20.allowance", token: p.token, spender: ctx.l1AssetRouter },
501
- message: "Failed to read ERC-20 allowance for deposit token."
578
+ ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
579
+ message: "Failed to read deposit-token allowance."
502
580
  }
503
581
  );
504
- const needsDepositApprove = depositAllowance < p.amount;
505
- if (needsDepositApprove) {
506
- const approveDepReq = await wrapAs2(
582
+ if (depositAllowance < p.amount) {
583
+ const approveSim = await wrapAs3(
507
584
  "CONTRACT",
508
585
  OP_DEPOSITS.nonbase.estGas,
509
586
  () => ctx.client.l1.simulateContract({
510
587
  address: p.token,
511
588
  abi: IERC20_default,
512
589
  functionName: "approve",
513
- args: [ctx.l1AssetRouter, p.amount],
590
+ args: [assetRouter, p.amount],
514
591
  account: ctx.client.account
515
592
  }),
516
593
  {
@@ -518,60 +595,55 @@ function routeErc20NonBase() {
518
595
  message: "Failed to simulate deposit token approve."
519
596
  }
520
597
  );
521
- approvals.push({ token: p.token, spender: ctx.l1AssetRouter, amount: p.amount });
598
+ approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
522
599
  steps.push({
523
- key: `approve:${p.token}:${ctx.l1AssetRouter}`,
600
+ key: `approve:${p.token}:${assetRouter}`,
524
601
  kind: "approve",
525
602
  description: `Approve deposit token for amount`,
526
- tx: { ...approveDepReq.request, ...txFeeOverrides }
603
+ tx: { ...approveSim.request }
527
604
  });
528
605
  }
529
- const baseIsEth = isETH(baseToken);
530
- let msgValue = 0n;
531
606
  if (!baseIsEth) {
532
- const baseAllowance = await wrapAs2(
607
+ const baseAllowance = await wrapAs3(
533
608
  "CONTRACT",
534
- OP_DEPOSITS.nonbase.allowanceFees,
609
+ OP_DEPOSITS.nonbase.allowanceBase,
535
610
  () => ctx.client.l1.readContract({
536
611
  address: baseToken,
537
612
  abi: IERC20_default,
538
613
  functionName: "allowance",
539
- args: [ctx.sender, ctx.l1AssetRouter]
614
+ args: [ctx.sender, assetRouter]
540
615
  }),
541
616
  {
542
- ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
617
+ ctx: { where: "erc20.allowance", token: baseToken, spender: assetRouter },
543
618
  message: "Failed to read base-token allowance."
544
619
  }
545
620
  );
546
621
  if (baseAllowance < mintValue) {
547
- const approveBaseReq = await wrapAs2(
622
+ const approveBaseSim = await wrapAs3(
548
623
  "CONTRACT",
549
624
  OP_DEPOSITS.nonbase.estGas,
550
625
  () => ctx.client.l1.simulateContract({
551
626
  address: baseToken,
552
627
  abi: IERC20_default,
553
628
  functionName: "approve",
554
- args: [ctx.l1AssetRouter, mintValue],
629
+ args: [assetRouter, mintValue],
555
630
  account: ctx.client.account
556
631
  }),
557
632
  {
558
633
  ctx: { where: "l1.simulateContract", to: baseToken },
559
- message: "Failed to simulate base-token approve."
634
+ message: "Failed to simulate base token approve."
560
635
  }
561
636
  );
562
- approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
637
+ approvals.push({ token: baseToken, spender: assetRouter, amount: mintValue });
563
638
  steps.push({
564
- key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
639
+ key: `approve:${baseToken}:${assetRouter}`,
565
640
  kind: "approve",
566
641
  description: `Approve base token for mintValue`,
567
- tx: { ...approveBaseReq.request, ...txFeeOverrides }
642
+ tx: { ...approveBaseSim.request }
568
643
  });
569
644
  }
570
- msgValue = 0n;
571
- } else {
572
- msgValue = mintValue;
573
645
  }
574
- const secondBridgeCalldata = await wrapAs2(
646
+ const secondBridgeCalldata = await wrapAs3(
575
647
  "INTERNAL",
576
648
  OP_DEPOSITS.nonbase.encodeCalldata,
577
649
  () => Promise.resolve(encodeSecondBridgeErc20Args(p.token, p.amount, p.to ?? ctx.sender)),
@@ -580,44 +652,60 @@ function routeErc20NonBase() {
580
652
  where: "encodeSecondBridgeErc20Args",
581
653
  token: p.token,
582
654
  amount: p.amount.toString()
583
- }
655
+ },
656
+ message: "Failed to encode bridging calldata."
584
657
  }
585
658
  );
586
- const outer = {
659
+ const requestStruct = {
587
660
  chainId: ctx.chainIdL2,
588
661
  mintValue,
589
662
  l2Value: 0n,
590
- l2GasLimit: l2GasLimitUsed,
663
+ l2GasLimit: l2Gas.gasLimit,
591
664
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
592
665
  refundRecipient: ctx.refundRecipient,
593
- secondBridgeAddress: ctx.l1AssetRouter,
666
+ secondBridgeAddress: assetRouter,
594
667
  secondBridgeValue: 0n,
595
668
  secondBridgeCalldata
596
669
  };
670
+ const msgValue = baseIsEth ? mintValue : 0n;
671
+ const calldata = encodeFunctionData({
672
+ abi: IBridgehub_default,
673
+ functionName: "requestL2TransactionTwoBridges",
674
+ args: [requestStruct]
675
+ });
676
+ const l1TxCandidate = {
677
+ to: ctx.bridgehub,
678
+ data: calldata,
679
+ value: msgValue,
680
+ from: ctx.sender,
681
+ ...ctx.gasOverrides
682
+ };
683
+ const l1Gas = await quoteL1Gas2({
684
+ ctx,
685
+ tx: l1TxCandidate,
686
+ overrides: ctx.gasOverrides,
687
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
688
+ });
597
689
  const approvalsNeeded = approvals.length > 0;
598
690
  let bridgeTx;
599
- let resolvedL1GasLimit;
600
- const gasOverride = txFeeOverrides.gas;
601
691
  if (approvalsNeeded) {
602
692
  bridgeTx = {
603
693
  address: ctx.bridgehub,
604
694
  abi: IBridgehub_default,
605
695
  functionName: "requestL2TransactionTwoBridges",
606
- args: [outer],
696
+ args: [requestStruct],
607
697
  value: msgValue,
608
- account: ctx.client.account,
609
- ...txFeeOverrides
698
+ account: ctx.client.account
610
699
  };
611
- resolvedL1GasLimit = gasOverride ?? ctx.l2GasLimit;
612
700
  } else {
613
- const sim = await wrapAs2(
701
+ const sim = await wrapAs3(
614
702
  "CONTRACT",
615
703
  OP_DEPOSITS.nonbase.estGas,
616
704
  () => ctx.client.l1.simulateContract({
617
705
  address: ctx.bridgehub,
618
706
  abi: IBridgehub_default,
619
707
  functionName: "requestL2TransactionTwoBridges",
620
- args: [outer],
708
+ args: [requestStruct],
621
709
  value: msgValue,
622
710
  account: ctx.client.account
623
711
  }),
@@ -626,67 +714,64 @@ function routeErc20NonBase() {
626
714
  message: "Failed to simulate two-bridges request."
627
715
  }
628
716
  );
629
- bridgeTx = { ...sim.request, ...txFeeOverrides };
630
- resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
717
+ bridgeTx = { ...sim.request };
718
+ }
719
+ if (l1Gas) {
720
+ bridgeTx = {
721
+ ...bridgeTx,
722
+ gas: l1Gas.gasLimit,
723
+ maxFeePerGas: l1Gas.maxFeePerGas,
724
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
725
+ };
631
726
  }
632
727
  steps.push({
633
- key: "bridgehub:two-bridges:nonbase",
728
+ key: "bridgehub:two-bridges:erc20-nonbase",
634
729
  kind: "bridgehub:two-bridges",
635
730
  description: baseIsEth ? "Bridge ERC-20 (fees in ETH) via Bridgehub.requestL2TransactionTwoBridges" : "Bridge ERC-20 (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
636
731
  tx: bridgeTx
637
732
  });
733
+ const fees = buildFeeBreakdown({
734
+ feeToken: baseToken,
735
+ l1Gas,
736
+ l2Gas,
737
+ l2BaseCost,
738
+ operatorTip: ctx.operatorTip,
739
+ mintValue
740
+ });
638
741
  return {
639
742
  steps,
640
743
  approvals,
641
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
744
+ fees
642
745
  };
643
746
  }
644
747
  };
645
748
  }
646
-
647
- // src/adapters/viem/resources/deposits/routes/eth-nonbase.ts
648
- var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
649
- var BASE_COST_BUFFER_BPS2 = 100n;
650
- var BPS2 = 10000n;
651
- var withBuffer2 = (x) => x * (BPS2 + BASE_COST_BUFFER_BPS2) / BPS2;
749
+ var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
652
750
  function routeEthNonBase() {
653
751
  return {
752
+ // TODO: do we even need these validations?
654
753
  async preflight(p, ctx) {
655
- await wrapAs3(
754
+ await wrapAs4(
656
755
  "VALIDATION",
657
756
  OP_DEPOSITS.ethNonBase.assertEthAsset,
658
757
  () => {
659
- if (!isETH(p.token)) {
758
+ if (ctx.resolvedToken?.kind !== "eth" && !isETH(p.token)) {
660
759
  throw new Error("eth-nonbase route requires ETH as the deposit asset.");
661
760
  }
662
761
  },
663
762
  { ctx: { token: p.token } }
664
763
  );
665
- const baseToken = await wrapAs3(
666
- "CONTRACT",
667
- OP_DEPOSITS.ethNonBase.baseToken,
668
- () => ctx.client.l1.readContract({
669
- address: ctx.bridgehub,
670
- abi: IBridgehub_default,
671
- functionName: "baseToken",
672
- args: [ctx.chainIdL2]
673
- }),
674
- {
675
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
676
- message: "Failed to read base token."
677
- }
678
- );
679
- await wrapAs3(
764
+ await wrapAs4(
680
765
  "VALIDATION",
681
766
  OP_DEPOSITS.ethNonBase.assertNonEthBase,
682
767
  () => {
683
- if (isETH(baseToken)) {
768
+ if (ctx.baseIsEth) {
684
769
  throw new Error("eth-nonbase route requires target chain base token \u2260 ETH.");
685
770
  }
686
771
  },
687
- { ctx: { baseToken, chainIdL2: ctx.chainIdL2 } }
772
+ { ctx: { baseIsEth: ctx.baseIsEth, chainIdL2: ctx.chainIdL2 } }
688
773
  );
689
- const ethBal = await wrapAs3(
774
+ const ethBal = await wrapAs4(
690
775
  "RPC",
691
776
  OP_DEPOSITS.ethNonBase.ethBalance,
692
777
  () => ctx.client.l1.getBalance({ address: ctx.sender }),
@@ -695,7 +780,7 @@ function routeEthNonBase() {
695
780
  message: "Failed to read L1 ETH balance."
696
781
  }
697
782
  );
698
- await wrapAs3(
783
+ await wrapAs4(
699
784
  "VALIDATION",
700
785
  OP_DEPOSITS.ethNonBase.assertEthBalance,
701
786
  () => {
@@ -705,45 +790,27 @@ function routeEthNonBase() {
705
790
  },
706
791
  { ctx: { required: p.amount.toString(), balance: ethBal.toString() } }
707
792
  );
708
- return;
709
793
  },
710
794
  async build(p, ctx) {
711
- const { gasPriceForBaseCost } = ctx.fee;
712
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
713
- const baseToken = await wrapAs3(
714
- "CONTRACT",
715
- OP_DEPOSITS.ethNonBase.baseToken,
716
- () => ctx.client.l1.readContract({
717
- address: ctx.bridgehub,
718
- abi: IBridgehub_default,
719
- functionName: "baseToken",
720
- args: [ctx.chainIdL2]
721
- }),
722
- {
723
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
724
- message: "Failed to read base token."
725
- }
726
- );
727
- const rawBaseCost = await wrapAs3(
728
- "CONTRACT",
729
- OP_DEPOSITS.ethNonBase.baseCost,
730
- () => ctx.client.l1.readContract({
731
- address: ctx.bridgehub,
732
- abi: IBridgehub_default,
733
- functionName: "l2TransactionBaseCost",
734
- args: [ctx.chainIdL2, gasPriceForBaseCost, ctx.l2GasLimit, ctx.gasPerPubdata]
735
- }),
736
- {
737
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
738
- message: "Could not fetch L2 base cost."
739
- }
740
- );
741
- const baseCost = BigInt(rawBaseCost);
742
- const mintValueRaw = baseCost + ctx.operatorTip;
743
- const mintValue = withBuffer2(mintValueRaw);
795
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
796
+ const l2TxModel = {
797
+ to: p.to ?? ctx.sender,
798
+ from: ctx.sender,
799
+ data: "0x",
800
+ value: 0n
801
+ };
802
+ const l2Gas = await quoteL2Gas3({
803
+ ctx,
804
+ route: "eth-nonbase",
805
+ l2TxForModeling: l2TxModel,
806
+ overrideGasLimit: ctx.l2GasLimit
807
+ });
808
+ if (!l2Gas) throw new Error("Failed to estimate L2 gas parameters.");
809
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
810
+ const mintValue = l2BaseCost + ctx.operatorTip;
744
811
  const approvals = [];
745
812
  const steps = [];
746
- const allowance = await wrapAs3(
813
+ const allowance = await wrapAs4(
747
814
  "CONTRACT",
748
815
  OP_DEPOSITS.ethNonBase.allowanceBase,
749
816
  () => ctx.client.l1.readContract({
@@ -759,7 +826,7 @@ function routeEthNonBase() {
759
826
  );
760
827
  const needsApprove = allowance < mintValue;
761
828
  if (needsApprove) {
762
- const approveSim = await wrapAs3(
829
+ const approveSim = await wrapAs4(
763
830
  "CONTRACT",
764
831
  OP_DEPOSITS.ethNonBase.estGas,
765
832
  () => ctx.client.l1.simulateContract({
@@ -778,11 +845,11 @@ function routeEthNonBase() {
778
845
  steps.push({
779
846
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
780
847
  kind: "approve",
781
- description: `Approve base token for mintValue`,
848
+ description: `Approve base token for fees (mintValue)`,
782
849
  tx: { ...approveSim.request }
783
850
  });
784
851
  }
785
- const secondBridgeCalldata = await wrapAs3(
852
+ const secondBridgeCalldata = await wrapAs4(
786
853
  "INTERNAL",
787
854
  OP_DEPOSITS.ethNonBase.encodeCalldata,
788
855
  () => Promise.resolve(encodeSecondBridgeEthArgs(p.amount, p.to ?? ctx.sender)),
@@ -795,11 +862,11 @@ function routeEthNonBase() {
795
862
  message: "Failed to encode ETH bridging calldata."
796
863
  }
797
864
  );
798
- const outer = {
865
+ const requestStruct = {
799
866
  chainId: ctx.chainIdL2,
800
867
  mintValue,
801
- l2Value: 0n,
802
- l2GasLimit: ctx.l2GasLimit,
868
+ l2Value: p.amount,
869
+ l2GasLimit: l2Gas.gasLimit,
803
870
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
804
871
  refundRecipient: ctx.refundRecipient,
805
872
  secondBridgeAddress: ctx.l1AssetRouter,
@@ -807,29 +874,32 @@ function routeEthNonBase() {
807
874
  secondBridgeCalldata
808
875
  };
809
876
  let bridgeTx;
810
- let resolvedL1GasLimit;
877
+ let calldata;
811
878
  if (needsApprove) {
812
879
  bridgeTx = {
813
880
  address: ctx.bridgehub,
814
881
  abi: IBridgehub_default,
815
882
  functionName: "requestL2TransactionTwoBridges",
816
- args: [outer],
883
+ args: [requestStruct],
817
884
  value: p.amount,
818
885
  // base ≠ ETH ⇒ msg.value == secondBridgeValue
819
886
  account: ctx.client.account
820
887
  };
821
- resolvedL1GasLimit = ctx.l2GasLimit;
888
+ calldata = encodeFunctionData({
889
+ abi: IBridgehub_default,
890
+ functionName: "requestL2TransactionTwoBridges",
891
+ args: [requestStruct]
892
+ });
822
893
  } else {
823
- const twoBridgesSim = await wrapAs3(
894
+ const sim = await wrapAs4(
824
895
  "CONTRACT",
825
896
  OP_DEPOSITS.ethNonBase.estGas,
826
897
  () => ctx.client.l1.simulateContract({
827
898
  address: ctx.bridgehub,
828
899
  abi: IBridgehub_default,
829
900
  functionName: "requestL2TransactionTwoBridges",
830
- args: [outer],
901
+ args: [requestStruct],
831
902
  value: p.amount,
832
- // base ≠ ETH ⇒ msg.value == secondBridgeValue
833
903
  account: ctx.client.account
834
904
  }),
835
905
  {
@@ -837,8 +907,33 @@ function routeEthNonBase() {
837
907
  message: "Failed to simulate Bridgehub two-bridges request."
838
908
  }
839
909
  );
840
- bridgeTx = { ...twoBridgesSim.request, ...txFeeOverrides };
841
- resolvedL1GasLimit = twoBridgesSim.request.gas ?? ctx.l2GasLimit;
910
+ calldata = encodeFunctionData({
911
+ abi: sim.request.abi,
912
+ functionName: sim.request.functionName,
913
+ args: sim.request.args
914
+ });
915
+ bridgeTx = { ...sim.request };
916
+ }
917
+ const l1TxCandidate = {
918
+ to: ctx.bridgehub,
919
+ data: calldata,
920
+ value: p.amount,
921
+ from: ctx.sender,
922
+ ...ctx.gasOverrides
923
+ };
924
+ const l1Gas = await quoteL1Gas2({
925
+ ctx,
926
+ tx: l1TxCandidate,
927
+ overrides: ctx.gasOverrides,
928
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
929
+ });
930
+ if (l1Gas) {
931
+ bridgeTx = {
932
+ ...bridgeTx,
933
+ gas: l1Gas.gasLimit,
934
+ maxFeePerGas: l1Gas.maxFeePerGas,
935
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
936
+ };
842
937
  }
843
938
  steps.push({
844
939
  key: "bridgehub:two-bridges:eth-nonbase",
@@ -846,48 +941,38 @@ function routeEthNonBase() {
846
941
  description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
847
942
  tx: bridgeTx
848
943
  });
944
+ const fees = buildFeeBreakdown({
945
+ feeToken: baseToken,
946
+ l1Gas,
947
+ l2Gas,
948
+ l2BaseCost,
949
+ operatorTip: ctx.operatorTip,
950
+ mintValue
951
+ });
849
952
  return {
850
953
  steps,
851
954
  approvals,
852
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
955
+ fees
853
956
  };
854
957
  }
855
958
  };
856
959
  }
857
-
858
- // src/adapters/viem/resources/deposits/routes/erc20-base.ts
859
- var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
860
- var BASE_COST_BUFFER_BPS3 = 100n;
861
- var BPS3 = 10000n;
862
- var withBuffer3 = (x) => x * (BPS3 + BASE_COST_BUFFER_BPS3) / BPS3;
960
+ var { wrapAs: wrapAs5 } = createErrorHandlers("deposits");
863
961
  function routeErc20Base() {
864
962
  return {
865
963
  async preflight(p, ctx) {
866
- await wrapAs4(
964
+ await wrapAs5(
867
965
  "VALIDATION",
868
966
  OP_DEPOSITS.base.assertErc20Asset,
869
967
  () => {
870
- if (isETH(p.token)) {
968
+ if (ctx.resolvedToken?.kind === "eth" || isETH(p.token)) {
871
969
  throw new Error("erc20-base route requires an ERC-20 token (not ETH).");
872
970
  }
873
971
  },
874
972
  { ctx: { token: p.token } }
875
973
  );
876
- const baseToken = await wrapAs4(
877
- "CONTRACT",
878
- OP_DEPOSITS.base.baseToken,
879
- () => ctx.client.l1.readContract({
880
- address: ctx.bridgehub,
881
- abi: IBridgehub_default,
882
- functionName: "baseToken",
883
- args: [ctx.chainIdL2]
884
- }),
885
- {
886
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
887
- message: "Failed to read base token."
888
- }
889
- );
890
- await wrapAs4(
974
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
975
+ await wrapAs5(
891
976
  "VALIDATION",
892
977
  OP_DEPOSITS.base.assertMatchesBase,
893
978
  () => {
@@ -897,45 +982,27 @@ function routeErc20Base() {
897
982
  },
898
983
  { ctx: { baseToken, provided: p.token, chainIdL2: ctx.chainIdL2 } }
899
984
  );
900
- return;
901
985
  },
902
986
  async build(p, ctx) {
903
- const { gasPriceForBaseCost } = ctx.fee;
904
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
905
- const gasOverride = txFeeOverrides.gas;
906
- const baseToken = await wrapAs4(
907
- "CONTRACT",
908
- OP_DEPOSITS.base.baseToken,
909
- () => ctx.client.l1.readContract({
910
- address: ctx.bridgehub,
911
- abi: IBridgehub_default,
912
- functionName: "baseToken",
913
- args: [ctx.chainIdL2]
914
- }),
915
- {
916
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
917
- message: "Failed to read base token."
918
- }
919
- );
920
- const rawBaseCost = await wrapAs4(
921
- "CONTRACT",
922
- OP_DEPOSITS.base.baseCost,
923
- () => ctx.client.l1.readContract({
924
- address: ctx.bridgehub,
925
- abi: IBridgehub_default,
926
- functionName: "l2TransactionBaseCost",
927
- args: [ctx.chainIdL2, gasPriceForBaseCost, ctx.l2GasLimit, ctx.gasPerPubdata]
928
- }),
929
- {
930
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
931
- message: "Could not fetch L2 base cost from Bridgehub."
932
- }
933
- );
934
- const baseCost = rawBaseCost;
935
- const l2Value = p.amount;
936
- const rawMintValue = baseCost + ctx.operatorTip + l2Value;
937
- const mintValue = withBuffer3(rawMintValue);
938
- const allowance = await wrapAs4(
987
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
988
+ const l2TxModel = {
989
+ to: p.to ?? ctx.sender,
990
+ from: ctx.sender,
991
+ data: "0x",
992
+ value: 0n
993
+ };
994
+ const l2Gas = await quoteL2Gas3({
995
+ ctx,
996
+ route: "erc20-base",
997
+ l2TxForModeling: l2TxModel,
998
+ overrideGasLimit: ctx.l2GasLimit
999
+ });
1000
+ if (!l2Gas) throw new Error("Failed to estimate L2 gas parameters.");
1001
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
1002
+ const mintValue = l2BaseCost + ctx.operatorTip + p.amount;
1003
+ const approvals = [];
1004
+ const steps = [];
1005
+ const allowance = await wrapAs5(
939
1006
  "CONTRACT",
940
1007
  OP_DEPOSITS.base.allowance,
941
1008
  () => ctx.client.l1.readContract({
@@ -949,11 +1016,9 @@ function routeErc20Base() {
949
1016
  message: "Failed to read base-token allowance."
950
1017
  }
951
1018
  );
952
- const approvals = [];
953
- const steps = [];
954
1019
  const needsApprove = allowance < mintValue;
955
1020
  if (needsApprove) {
956
- const approveSim = await wrapAs4(
1021
+ const approveSim = await wrapAs5(
957
1022
  "CONTRACT",
958
1023
  OP_DEPOSITS.base.estGas,
959
1024
  () => ctx.client.l1.simulateContract({
@@ -973,20 +1038,20 @@ function routeErc20Base() {
973
1038
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
974
1039
  kind: "approve",
975
1040
  description: "Approve base token for mintValue",
976
- tx: { ...approveSim.request, ...txFeeOverrides }
1041
+ tx: { ...approveSim.request }
977
1042
  });
978
1043
  }
979
1044
  const req = buildDirectRequestStruct({
980
1045
  chainId: ctx.chainIdL2,
981
1046
  mintValue,
982
- l2GasLimit: ctx.l2GasLimit,
1047
+ l2GasLimit: l2Gas.gasLimit,
983
1048
  gasPerPubdata: ctx.gasPerPubdata,
984
1049
  refundRecipient: ctx.refundRecipient,
985
1050
  l2Contract: p.to ?? ctx.sender,
986
- l2Value
1051
+ l2Value: p.amount
987
1052
  });
988
1053
  let bridgeTx;
989
- let resolvedL1GasLimit;
1054
+ let calldata;
990
1055
  if (needsApprove) {
991
1056
  bridgeTx = {
992
1057
  address: ctx.bridgehub,
@@ -994,13 +1059,16 @@ function routeErc20Base() {
994
1059
  functionName: "requestL2TransactionDirect",
995
1060
  args: [req],
996
1061
  value: 0n,
997
- // base is ERC-20 ⇒ msg.value MUST be 0
998
- account: ctx.client.account,
999
- ...txFeeOverrides
1062
+ // base token is ERC-20 ⇒ msg.value MUST be 0
1063
+ account: ctx.client.account
1000
1064
  };
1001
- resolvedL1GasLimit = gasOverride ?? ctx.l2GasLimit;
1065
+ calldata = encodeFunctionData({
1066
+ abi: IBridgehub_default,
1067
+ functionName: "requestL2TransactionDirect",
1068
+ args: [req]
1069
+ });
1002
1070
  } else {
1003
- const sim = await wrapAs4(
1071
+ const sim = await wrapAs5(
1004
1072
  "RPC",
1005
1073
  OP_DEPOSITS.base.estGas,
1006
1074
  () => ctx.client.l1.simulateContract({
@@ -1016,8 +1084,33 @@ function routeErc20Base() {
1016
1084
  message: "Failed to simulate Bridgehub.requestL2TransactionDirect."
1017
1085
  }
1018
1086
  );
1019
- bridgeTx = { ...sim.request, ...txFeeOverrides };
1020
- resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
1087
+ calldata = encodeFunctionData({
1088
+ abi: sim.request.abi,
1089
+ functionName: sim.request.functionName,
1090
+ args: sim.request.args
1091
+ });
1092
+ bridgeTx = { ...sim.request };
1093
+ }
1094
+ const l1TxCandidate = {
1095
+ to: ctx.bridgehub,
1096
+ data: calldata,
1097
+ value: 0n,
1098
+ from: ctx.sender,
1099
+ ...ctx.gasOverrides
1100
+ };
1101
+ const l1Gas = await quoteL1Gas2({
1102
+ ctx,
1103
+ tx: l1TxCandidate,
1104
+ overrides: ctx.gasOverrides,
1105
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
1106
+ });
1107
+ if (l1Gas) {
1108
+ bridgeTx = {
1109
+ ...bridgeTx,
1110
+ gas: l1Gas.gasLimit,
1111
+ maxFeePerGas: l1Gas.maxFeePerGas,
1112
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
1113
+ };
1021
1114
  }
1022
1115
  steps.push({
1023
1116
  key: "bridgehub:direct:erc20-base",
@@ -1025,10 +1118,18 @@ function routeErc20Base() {
1025
1118
  description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
1026
1119
  tx: bridgeTx
1027
1120
  });
1121
+ const fees = buildFeeBreakdown({
1122
+ feeToken: baseToken,
1123
+ l1Gas,
1124
+ l2Gas,
1125
+ l2BaseCost,
1126
+ operatorTip: ctx.operatorTip,
1127
+ mintValue
1128
+ });
1028
1129
  return {
1029
1130
  steps,
1030
1131
  approvals,
1031
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
1132
+ fees
1032
1133
  };
1033
1134
  }
1034
1135
  };
@@ -1108,6 +1209,260 @@ async function waitForL2ExecutionFromL1Tx(l1, l2, l1TxHash) {
1108
1209
  }
1109
1210
  return { l2Receipt, l2TxHash };
1110
1211
  }
1212
+ var { wrapAs: wrapAs6 } = createErrorHandlers("tokens");
1213
+ var ntvCodec = createNTVCodec({
1214
+ encode: (types, values) => encodeAbiParameters(
1215
+ types.map((t, i) => ({ type: t, name: `arg${i}` })),
1216
+ values
1217
+ ),
1218
+ keccak256: (data) => keccak256(data)
1219
+ });
1220
+ function createTokensResource(client) {
1221
+ let l2NtvL1ChainIdPromise = null;
1222
+ let baseTokenAssetIdPromise = null;
1223
+ let wethL1Promise = null;
1224
+ let wethL2Promise = null;
1225
+ async function getL1ChainId() {
1226
+ if (!l2NtvL1ChainIdPromise) {
1227
+ l2NtvL1ChainIdPromise = wrapAs6("INTERNAL", "getL1ChainId", async () => {
1228
+ const { l2NativeTokenVault } = await client.contracts();
1229
+ return await l2NativeTokenVault.read.L1_CHAIN_ID();
1230
+ });
1231
+ }
1232
+ return l2NtvL1ChainIdPromise;
1233
+ }
1234
+ async function getBaseTokenAssetId() {
1235
+ if (!baseTokenAssetIdPromise) {
1236
+ baseTokenAssetIdPromise = wrapAs6("INTERNAL", "baseTokenAssetId", async () => {
1237
+ const { l2NativeTokenVault } = await client.contracts();
1238
+ const assetId = await l2NativeTokenVault.read.BASE_TOKEN_ASSET_ID();
1239
+ return assetId;
1240
+ });
1241
+ }
1242
+ return baseTokenAssetIdPromise;
1243
+ }
1244
+ async function getWethL1() {
1245
+ if (!wethL1Promise) {
1246
+ wethL1Promise = wrapAs6("INTERNAL", "wethL1", async () => {
1247
+ const { l1NativeTokenVault } = await client.contracts();
1248
+ const weth = await l1NativeTokenVault.read.WETH_TOKEN();
1249
+ return weth;
1250
+ });
1251
+ }
1252
+ return wethL1Promise;
1253
+ }
1254
+ async function getWethL2() {
1255
+ if (!wethL2Promise) {
1256
+ wethL2Promise = wrapAs6("INTERNAL", "wethL2", async () => {
1257
+ const { l2NativeTokenVault } = await client.contracts();
1258
+ const weth = await l2NativeTokenVault.read.WETH_TOKEN();
1259
+ return weth;
1260
+ });
1261
+ }
1262
+ return wethL2Promise;
1263
+ }
1264
+ async function toL2Address(l1Token) {
1265
+ return wrapAs6("CONTRACT", "tokens.toL2Address", async () => {
1266
+ const normalized = normalizeL1Token(l1Token);
1267
+ const chainId = BigInt(await client.l2.getChainId());
1268
+ const baseToken = await client.baseToken(chainId);
1269
+ if (isAddressEq(normalized, baseToken)) {
1270
+ return L2_BASE_TOKEN_ADDRESS;
1271
+ }
1272
+ const { l2NativeTokenVault } = await client.contracts();
1273
+ const l2Token = await l2NativeTokenVault.read.l2TokenAddress([normalized]);
1274
+ return l2Token;
1275
+ });
1276
+ }
1277
+ async function toL1Address(l2Token) {
1278
+ return wrapAs6("CONTRACT", "tokens.toL1Address", async () => {
1279
+ if (isAddressEq(l2Token, ETH_ADDRESS)) return ETH_ADDRESS;
1280
+ if (isAddressEq(l2Token, L2_BASE_TOKEN_ADDRESS)) {
1281
+ const chainId = BigInt(await client.l2.getChainId());
1282
+ return await client.baseToken(chainId);
1283
+ }
1284
+ const { l2AssetRouter } = await client.contracts();
1285
+ const l1Token = await l2AssetRouter.read.l1TokenAddress([l2Token]);
1286
+ return l1Token;
1287
+ });
1288
+ }
1289
+ async function assetIdOfL1(l1Token) {
1290
+ return wrapAs6("CONTRACT", "tokens.assetIdOfL1", async () => {
1291
+ const normalized = normalizeL1Token(l1Token);
1292
+ const { l1NativeTokenVault } = await client.contracts();
1293
+ return await l1NativeTokenVault.read.assetId([normalized]);
1294
+ });
1295
+ }
1296
+ async function assetIdOfL2(l2Token) {
1297
+ return wrapAs6("CONTRACT", "tokens.assetIdOfL2", async () => {
1298
+ const { l2NativeTokenVault } = await client.contracts();
1299
+ return await l2NativeTokenVault.read.assetId([l2Token]);
1300
+ });
1301
+ }
1302
+ async function l2TokenFromAssetId(assetId) {
1303
+ return wrapAs6("CONTRACT", "tokens.l2TokenFromAssetId", async () => {
1304
+ const { l2NativeTokenVault } = await client.contracts();
1305
+ return await l2NativeTokenVault.read.tokenAddress([assetId]);
1306
+ });
1307
+ }
1308
+ async function l1TokenFromAssetId(assetId) {
1309
+ return wrapAs6("CONTRACT", "tokens.l1TokenFromAssetId", async () => {
1310
+ const { l1NativeTokenVault } = await client.contracts();
1311
+ return await l1NativeTokenVault.read.tokenAddress([assetId]);
1312
+ });
1313
+ }
1314
+ async function originChainId(assetId) {
1315
+ return wrapAs6("CONTRACT", "tokens.originChainId", async () => {
1316
+ const { l2NativeTokenVault } = await client.contracts();
1317
+ return await l2NativeTokenVault.read.originChainId([assetId]);
1318
+ });
1319
+ }
1320
+ async function baseTokenAssetId() {
1321
+ return getBaseTokenAssetId();
1322
+ }
1323
+ async function isChainEthBased() {
1324
+ return wrapAs6("CONTRACT", "tokens.isChainEthBased", async () => {
1325
+ const baseAssetId = await getBaseTokenAssetId();
1326
+ const l1ChainId = await getL1ChainId();
1327
+ const ethAssetId = ntvCodec.encodeAssetId(
1328
+ l1ChainId,
1329
+ L2_NATIVE_TOKEN_VAULT_ADDRESS,
1330
+ ETH_ADDRESS
1331
+ );
1332
+ return hexEq(baseAssetId, ethAssetId);
1333
+ });
1334
+ }
1335
+ async function wethL1() {
1336
+ return getWethL1();
1337
+ }
1338
+ async function wethL2() {
1339
+ return getWethL2();
1340
+ }
1341
+ async function computeL2BridgedAddress(args) {
1342
+ return wrapAs6("CONTRACT", "tokens.computeL2BridgedAddress", async () => {
1343
+ const normalized = normalizeL1Token(args.l1Token);
1344
+ const { l2NativeTokenVault } = await client.contracts();
1345
+ const predicted = await l2NativeTokenVault.read.calculateCreate2TokenAddress([
1346
+ args.originChainId,
1347
+ normalized
1348
+ ]);
1349
+ return predicted;
1350
+ });
1351
+ }
1352
+ async function resolve(ref, opts) {
1353
+ return wrapAs6("CONTRACT", "tokens.resolve", async () => {
1354
+ let chain;
1355
+ let address;
1356
+ if (typeof ref === "string") {
1357
+ chain = opts?.chain ?? "l1";
1358
+ address = ref;
1359
+ } else {
1360
+ chain = ref.chain;
1361
+ address = ref.address;
1362
+ }
1363
+ let l1;
1364
+ let l2;
1365
+ if (chain === "l1") {
1366
+ l1 = normalizeL1Token(address);
1367
+ l2 = await toL2Address(address);
1368
+ } else {
1369
+ l2 = address;
1370
+ l1 = await toL1Address(address);
1371
+ }
1372
+ const assetId = await assetIdOfL1(l1);
1373
+ const originChainIdVal = await originChainId(assetId);
1374
+ const [baseAssetId, wethL1Addr, wethL2Addr, ethBased] = await Promise.all([
1375
+ baseTokenAssetId(),
1376
+ wethL1(),
1377
+ wethL2(),
1378
+ isChainEthBased()
1379
+ ]);
1380
+ let kind;
1381
+ if (isAddressEq(l1, ETH_ADDRESS)) {
1382
+ kind = "eth";
1383
+ } else if (hexEq(assetId, baseAssetId)) {
1384
+ kind = "base";
1385
+ } else {
1386
+ kind = "erc20";
1387
+ }
1388
+ return {
1389
+ kind,
1390
+ l1,
1391
+ l2,
1392
+ assetId,
1393
+ originChainId: originChainIdVal,
1394
+ isChainEthBased: ethBased,
1395
+ baseTokenAssetId: baseAssetId,
1396
+ wethL1: wethL1Addr,
1397
+ wethL2: wethL2Addr
1398
+ };
1399
+ });
1400
+ }
1401
+ return {
1402
+ resolve,
1403
+ toL2Address,
1404
+ toL1Address,
1405
+ assetIdOfL1,
1406
+ assetIdOfL2,
1407
+ l2TokenFromAssetId,
1408
+ l1TokenFromAssetId,
1409
+ originChainId,
1410
+ baseTokenAssetId,
1411
+ isChainEthBased,
1412
+ wethL1,
1413
+ wethL2,
1414
+ computeL2BridgedAddress
1415
+ };
1416
+ }
1417
+
1418
+ // src/adapters/viem/resources/contracts/contracts.ts
1419
+ function createContractsResource(client) {
1420
+ async function addresses() {
1421
+ return client.ensureAddresses();
1422
+ }
1423
+ async function instances() {
1424
+ return client.contracts();
1425
+ }
1426
+ async function bridgehub() {
1427
+ const { bridgehub: bridgehub2 } = await instances();
1428
+ return bridgehub2;
1429
+ }
1430
+ async function l1AssetRouter() {
1431
+ const { l1AssetRouter: l1AssetRouter2 } = await instances();
1432
+ return l1AssetRouter2;
1433
+ }
1434
+ async function l1NativeTokenVault() {
1435
+ const { l1NativeTokenVault: l1NativeTokenVault2 } = await instances();
1436
+ return l1NativeTokenVault2;
1437
+ }
1438
+ async function l1Nullifier() {
1439
+ const { l1Nullifier: l1Nullifier2 } = await instances();
1440
+ return l1Nullifier2;
1441
+ }
1442
+ async function l2AssetRouter() {
1443
+ const { l2AssetRouter: l2AssetRouter2 } = await instances();
1444
+ return l2AssetRouter2;
1445
+ }
1446
+ async function l2NativeTokenVault() {
1447
+ const { l2NativeTokenVault: l2NativeTokenVault2 } = await instances();
1448
+ return l2NativeTokenVault2;
1449
+ }
1450
+ async function l2BaseTokenSystem() {
1451
+ const { l2BaseTokenSystem: l2BaseTokenSystem2 } = await instances();
1452
+ return l2BaseTokenSystem2;
1453
+ }
1454
+ return {
1455
+ addresses,
1456
+ instances,
1457
+ bridgehub,
1458
+ l1AssetRouter,
1459
+ l1NativeTokenVault,
1460
+ l1Nullifier,
1461
+ l2AssetRouter,
1462
+ l2NativeTokenVault,
1463
+ l2BaseTokenSystem
1464
+ };
1465
+ }
1111
1466
 
1112
1467
  // src/adapters/viem/resources/deposits/index.ts
1113
1468
  var { wrap, toResult } = createErrorHandlers("deposits");
@@ -1117,37 +1472,26 @@ var ROUTES = {
1117
1472
  "erc20-nonbase": routeErc20NonBase(),
1118
1473
  "erc20-base": routeErc20Base()
1119
1474
  };
1120
- function createDepositsResource(client) {
1475
+ function createDepositsResource(client, tokens, contracts) {
1476
+ const tokensResource = tokens ?? createTokensResource(client);
1477
+ const contractsResource = contracts ?? createContractsResource(client);
1121
1478
  async function buildPlan(p) {
1122
- const ctx = await commonCtx(p, client);
1479
+ const ctx = await commonCtx(p, client, tokensResource, contractsResource);
1123
1480
  const route = ctx.route;
1124
1481
  await ROUTES[route].preflight?.(p, ctx);
1125
- const { steps, approvals, quoteExtras } = await ROUTES[route].build(p, ctx);
1126
- const { baseCost, mintValue } = quoteExtras;
1127
- const fallbackGasLimit = quoteExtras.l1GasLimit;
1128
- const resolveGasLimit = () => {
1129
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
1130
- for (let i = steps.length - 1; i >= 0; i--) {
1131
- const candidate = steps[i].tx.gas;
1132
- if (candidate != null) return candidate;
1133
- }
1134
- if (fallbackGasLimit != null) return fallbackGasLimit;
1135
- return ctx.l2GasLimit;
1136
- };
1137
- const gasLimit = resolveGasLimit();
1482
+ const { steps, approvals, fees } = await ROUTES[route].build(p, ctx);
1138
1483
  return {
1139
1484
  route: ctx.route,
1140
1485
  summary: {
1141
1486
  route: ctx.route,
1142
1487
  approvalsNeeded: approvals,
1143
- baseCost,
1144
- mintValue,
1145
- gasPerPubdata: ctx.gasPerPubdata,
1146
- fees: {
1147
- gasLimit,
1148
- maxFeePerGas: ctx.fee.maxFeePerGas,
1149
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
1150
- }
1488
+ amounts: {
1489
+ transfer: { token: p.token, amount: p.amount }
1490
+ },
1491
+ fees,
1492
+ // Legacy fields (maintained for backward compatibility)
1493
+ baseCost: fees.l2?.baseCost,
1494
+ mintValue: fees.mintValue
1151
1495
  },
1152
1496
  steps
1153
1497
  };
@@ -1222,7 +1566,7 @@ function createDepositsResource(client) {
1222
1566
  step.tx.gas = overrides.gasLimit;
1223
1567
  }
1224
1568
  }
1225
- if (step.tx.gas == null) {
1569
+ if (!p.l1TxOverrides?.gasLimit) {
1226
1570
  try {
1227
1571
  const feePart = step.tx.maxFeePerGas != null && step.tx.maxPriorityFeePerGas != null ? {
1228
1572
  maxFeePerGas: step.tx.maxFeePerGas,
@@ -1436,32 +1780,8 @@ function createDepositsResource(client) {
1436
1780
  return { quote, tryQuote, prepare, tryPrepare, create, tryCreate, status, wait, tryWait };
1437
1781
  }
1438
1782
 
1439
- // src/adapters/viem/resources/token-info.ts
1440
- async function ntvBaseAssetId(l2, ntv) {
1441
- return l2.readContract({
1442
- address: ntv,
1443
- abi: L2NativeTokenVault_default,
1444
- functionName: "BASE_TOKEN_ASSET_ID"
1445
- });
1446
- }
1447
- async function ntvL1ChainId(l2, ntv) {
1448
- return l2.readContract({
1449
- address: ntv,
1450
- abi: L2NativeTokenVault_default,
1451
- functionName: "L1_CHAIN_ID"
1452
- });
1453
- }
1454
- async function isEthBasedChain(l2, ntv) {
1455
- const [baseAssetId, l1ChainId] = await Promise.all([
1456
- ntvBaseAssetId(l2, ntv),
1457
- ntvL1ChainId(l2, ntv)
1458
- ]);
1459
- const ethAssetId = encodeNativeTokenVaultAssetId(l1ChainId, ETH_ADDRESS);
1460
- return baseAssetId.toLowerCase() === ethAssetId.toLowerCase();
1461
- }
1462
-
1463
1783
  // src/adapters/viem/resources/withdrawals/context.ts
1464
- async function commonCtx2(p, client) {
1784
+ async function commonCtx2(p, client, tokens, contracts) {
1465
1785
  const sender = client.account.address;
1466
1786
  const {
1467
1787
  bridgehub,
@@ -1470,18 +1790,20 @@ async function commonCtx2(p, client) {
1470
1790
  l2AssetRouter,
1471
1791
  l2NativeTokenVault,
1472
1792
  l2BaseTokenSystem
1473
- } = await client.ensureAddresses();
1793
+ } = await contracts.addresses();
1474
1794
  const chainIdL2 = BigInt(await client.l2.getChainId());
1475
- const baseIsEth = await isEthBasedChain(client.l2, l2NativeTokenVault);
1476
- const fee = await getL2FeeOverrides(client, p.l2TxOverrides);
1477
- const route = pickWithdrawRoute({
1478
- token: p.token,
1479
- baseIsEth
1480
- });
1481
- const l2GasLimit = p.l2GasLimit ?? 300000n;
1482
- const gasBufferPct = 15;
1795
+ const resolvedToken = await tokens.resolve(p.token, { chain: "l2" });
1796
+ const baseTokenAssetId = resolvedToken.baseTokenAssetId;
1797
+ const baseTokenL1 = await tokens.l1TokenFromAssetId(baseTokenAssetId);
1798
+ const baseIsEth = resolvedToken.isChainEthBased;
1799
+ const route = pickWithdrawRoute({ token: p.token, baseIsEth });
1483
1800
  return {
1484
1801
  client,
1802
+ tokens,
1803
+ contracts,
1804
+ resolvedToken,
1805
+ baseTokenAssetId,
1806
+ baseTokenL1,
1485
1807
  bridgehub,
1486
1808
  chainIdL2,
1487
1809
  sender,
@@ -1492,56 +1814,101 @@ async function commonCtx2(p, client) {
1492
1814
  l2NativeTokenVault,
1493
1815
  l2BaseTokenSystem,
1494
1816
  baseIsEth,
1495
- l2GasLimit,
1496
- gasBufferPct,
1497
- fee
1817
+ gasOverrides: p.l2TxOverrides
1818
+ };
1819
+ }
1820
+
1821
+ // src/adapters/viem/resources/withdrawals/services/gas.ts
1822
+ async function quoteL2Gas4(input) {
1823
+ const { ctx, tx } = input;
1824
+ const estimator = viemToGasEstimator(ctx.client.l2);
1825
+ return quoteL2Gas2({
1826
+ estimator,
1827
+ tx: toCoreTx(tx),
1828
+ overrides: ctx.gasOverrides
1829
+ });
1830
+ }
1831
+
1832
+ // src/adapters/viem/resources/withdrawals/services/fee.ts
1833
+ function buildFeeBreakdown2(p) {
1834
+ const l2Total = p.l2Gas?.maxCost ?? 0n;
1835
+ const l2 = {
1836
+ total: l2Total,
1837
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
1838
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
1839
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas
1840
+ };
1841
+ return {
1842
+ token: p.feeToken,
1843
+ maxTotal: l2Total,
1844
+ l2
1498
1845
  };
1499
1846
  }
1500
1847
 
1501
1848
  // src/adapters/viem/resources/withdrawals/routes/eth.ts
1502
- var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
1849
+ var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1503
1850
  function routeEthBase() {
1504
1851
  return {
1505
1852
  async build(p, ctx) {
1506
- const toL1 = p.to ?? ctx.sender;
1507
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
1508
- const sim = await wrapAs5(
1509
- "CONTRACT",
1510
- OP_WITHDRAWALS.eth.estGas,
1511
- () => ctx.client.l2.simulateContract({
1512
- address: L2_BASE_TOKEN_ADDRESS,
1513
- abi: IBaseToken_default,
1514
- functionName: "withdraw",
1515
- args: [toL1],
1516
- value: p.amount,
1517
- account: ctx.client.account,
1518
- ...txFeeOverrides
1519
- }),
1853
+ const steps = [];
1854
+ const data = await wrapAs7(
1855
+ "INTERNAL",
1856
+ OP_WITHDRAWALS.eth.encodeWithdraw,
1857
+ () => Promise.resolve(
1858
+ encodeFunctionData({
1859
+ abi: IBaseToken_default,
1860
+ functionName: "withdraw",
1861
+ args: [p.to ?? ctx.sender]
1862
+ })
1863
+ ),
1520
1864
  {
1521
- ctx: { where: "l2.simulateContract", to: L2_BASE_TOKEN_ADDRESS },
1522
- message: "Failed to simulate L2 ETH withdraw."
1865
+ ctx: { where: "L2BaseToken.withdraw", to: p.to ?? ctx.sender },
1866
+ message: "Failed to encode ETH withdraw calldata."
1523
1867
  }
1524
1868
  );
1525
- const steps = [
1526
- {
1527
- key: "l2-base-token:withdraw",
1528
- kind: "l2-base-token:withdraw",
1529
- description: "Withdraw ETH via L2 Base Token System",
1530
- tx: { ...sim.request, ...txFeeOverrides }
1531
- }
1532
- ];
1533
- return { steps, approvals: [], quoteExtras: {} };
1869
+ const L2tx = {
1870
+ to: L2_BASE_TOKEN_ADDRESS,
1871
+ data,
1872
+ value: p.amount,
1873
+ from: ctx.sender
1874
+ };
1875
+ const l2Gas = await quoteL2Gas4({ ctx, tx: L2tx });
1876
+ if (l2Gas) {
1877
+ L2tx.gas = l2Gas.gasLimit;
1878
+ L2tx.maxFeePerGas = l2Gas.maxFeePerGas;
1879
+ L2tx.maxPriorityFeePerGas = l2Gas.maxPriorityFeePerGas;
1880
+ }
1881
+ const tx = {
1882
+ address: L2_BASE_TOKEN_ADDRESS,
1883
+ abi: IBaseToken_default,
1884
+ functionName: "withdraw",
1885
+ args: [p.to ?? ctx.sender],
1886
+ value: p.amount,
1887
+ account: ctx.client.account,
1888
+ ...l2Gas
1889
+ };
1890
+ const fees = buildFeeBreakdown2({
1891
+ feeToken: L2_BASE_TOKEN_ADDRESS,
1892
+ l2Gas
1893
+ });
1894
+ steps.push({
1895
+ key: "l2-base-token:withdraw",
1896
+ kind: "l2-base-token:withdraw",
1897
+ description: "Withdraw ETH via L2 Base Token System",
1898
+ tx
1899
+ });
1900
+ return { steps, approvals: [], fees };
1534
1901
  }
1535
1902
  };
1536
1903
  }
1537
- var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
1904
+ var { wrapAs: wrapAs8 } = createErrorHandlers("withdrawals");
1538
1905
  function routeErc20NonBase2() {
1539
1906
  return {
1540
1907
  // TODO: add preflight validations here
1541
1908
  async build(p, ctx) {
1542
- const toL1 = p.to ?? ctx.sender;
1543
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
1544
- const current = await wrapAs6(
1909
+ const steps = [];
1910
+ const approvals = [];
1911
+ const current = await wrapAs8(
1545
1912
  "CONTRACT",
1546
1913
  OP_WITHDRAWALS.erc20.allowance,
1547
1914
  () => ctx.client.l2.readContract({
@@ -1561,12 +1928,26 @@ function routeErc20NonBase2() {
1561
1928
  message: "Failed to read L2 ERC-20 allowance."
1562
1929
  }
1563
1930
  );
1564
- const needsApprove = current < p.amount;
1565
- const steps = [];
1566
- const approvals = [];
1567
- if (needsApprove) {
1931
+ if (current < p.amount) {
1568
1932
  approvals.push({ token: p.token, spender: ctx.l2NativeTokenVault, amount: p.amount });
1569
- const approveSim = await wrapAs6(
1933
+ const data = encodeFunctionData({
1934
+ abi: IERC20_default,
1935
+ functionName: "approve",
1936
+ args: [ctx.l2NativeTokenVault, p.amount]
1937
+ });
1938
+ const approveTxCandidate = {
1939
+ to: p.token,
1940
+ data,
1941
+ value: 0n,
1942
+ from: ctx.sender
1943
+ };
1944
+ const approveGas = await quoteL2Gas4({ ctx, tx: approveTxCandidate });
1945
+ if (approveGas) {
1946
+ approveTxCandidate.gas = approveGas.gasLimit;
1947
+ approveTxCandidate.maxFeePerGas = approveGas.maxFeePerGas;
1948
+ approveTxCandidate.maxPriorityFeePerGas = approveGas.maxPriorityFeePerGas;
1949
+ }
1950
+ const approveSim = await wrapAs8(
1570
1951
  "CONTRACT",
1571
1952
  OP_WITHDRAWALS.erc20.estGas,
1572
1953
  () => ctx.client.l2.simulateContract({
@@ -1575,21 +1956,26 @@ function routeErc20NonBase2() {
1575
1956
  functionName: "approve",
1576
1957
  args: [ctx.l2NativeTokenVault, p.amount],
1577
1958
  account: ctx.client.account,
1578
- ...txFeeOverrides
1959
+ ...approveGas
1579
1960
  }),
1580
1961
  {
1581
1962
  ctx: { where: "l2.simulateContract", to: p.token },
1582
1963
  message: "Failed to simulate L2 ERC-20 approve."
1583
1964
  }
1584
1965
  );
1966
+ const { ...approveRequest } = approveSim.request;
1967
+ const approveTx = {
1968
+ ...approveRequest
1969
+ };
1585
1970
  steps.push({
1586
1971
  key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
1587
1972
  kind: "approve:l2",
1588
1973
  description: `Approve ${p.amount} to NativeTokenVault`,
1589
- tx: { ...approveSim.request, ...txFeeOverrides }
1974
+ tx: approveTx
1590
1975
  });
1591
1976
  }
1592
- const ensure = await wrapAs6(
1977
+ const resolved = ctx.resolvedToken ?? (ctx.tokens ? await ctx.tokens.resolve(p.token, { chain: "l2" }) : void 0);
1978
+ const assetId = resolved?.assetId ?? (await wrapAs8(
1593
1979
  "CONTRACT",
1594
1980
  OP_WITHDRAWALS.erc20.ensureRegistered,
1595
1981
  () => ctx.client.l2.simulateContract({
@@ -1603,28 +1989,44 @@ function routeErc20NonBase2() {
1603
1989
  ctx: { where: "L2NativeTokenVault.ensureTokenIsRegistered", token: p.token },
1604
1990
  message: "Failed to ensure token is registered in L2NativeTokenVault."
1605
1991
  }
1606
- );
1607
- const assetId = ensure.result;
1992
+ )).result;
1608
1993
  const assetData = encodeAbiParameters(
1609
1994
  [
1610
1995
  { type: "uint256", name: "amount" },
1611
1996
  { type: "address", name: "l1Receiver" },
1612
1997
  { type: "address", name: "l2Token" }
1613
1998
  ],
1614
- [p.amount, toL1, p.token]
1999
+ [p.amount, p.to ?? ctx.sender, p.token]
1615
2000
  );
2001
+ const withdrawCalldata = encodeFunctionData({
2002
+ abi: IL2AssetRouter_default,
2003
+ functionName: "withdraw",
2004
+ args: [assetId, assetData]
2005
+ });
2006
+ const withdrawTxCandidate = {
2007
+ to: ctx.l2AssetRouter,
2008
+ data: withdrawCalldata,
2009
+ value: 0n,
2010
+ from: ctx.sender
2011
+ };
2012
+ const withdrawGas = await quoteL2Gas4({ ctx, tx: withdrawTxCandidate });
2013
+ if (withdrawGas) {
2014
+ withdrawTxCandidate.gas = withdrawGas.gasLimit;
2015
+ withdrawTxCandidate.maxFeePerGas = withdrawGas.maxFeePerGas;
2016
+ withdrawTxCandidate.maxPriorityFeePerGas = withdrawGas.maxPriorityFeePerGas;
2017
+ }
1616
2018
  let withdrawTx;
1617
- if (needsApprove) {
2019
+ if (current < p.amount) {
1618
2020
  withdrawTx = {
1619
2021
  address: ctx.l2AssetRouter,
1620
2022
  abi: IL2AssetRouter_default,
1621
2023
  functionName: "withdraw",
1622
2024
  args: [assetId, assetData],
1623
2025
  account: ctx.client.account,
1624
- ...txFeeOverrides
2026
+ ...withdrawGas
1625
2027
  };
1626
2028
  } else {
1627
- const sim = await wrapAs6(
2029
+ const sim = await wrapAs8(
1628
2030
  "CONTRACT",
1629
2031
  OP_WITHDRAWALS.erc20.estGas,
1630
2032
  () => ctx.client.l2.simulateContract({
@@ -1633,14 +2035,18 @@ function routeErc20NonBase2() {
1633
2035
  functionName: "withdraw",
1634
2036
  args: [assetId, assetData],
1635
2037
  account: ctx.client.account,
1636
- ...txFeeOverrides
2038
+ ...withdrawGas
1637
2039
  }),
1638
2040
  {
1639
2041
  ctx: { where: "l2.simulateContract", to: ctx.l2AssetRouter },
1640
2042
  message: "Failed to simulate L2 ERC-20 withdraw."
1641
2043
  }
1642
2044
  );
1643
- withdrawTx = { ...sim.request, ...txFeeOverrides };
2045
+ const { ...withdrawRequest } = sim.request;
2046
+ withdrawTx = {
2047
+ ...withdrawRequest,
2048
+ ...withdrawGas
2049
+ };
1644
2050
  }
1645
2051
  steps.push({
1646
2052
  key: "l2-asset-router:withdraw",
@@ -1648,63 +2054,15 @@ function routeErc20NonBase2() {
1648
2054
  description: "Burn on L2 & send L2\u2192L1 message",
1649
2055
  tx: withdrawTx
1650
2056
  });
1651
- return { steps, approvals, quoteExtras: {} };
1652
- }
1653
- };
1654
- }
1655
-
1656
- // src/adapters/viem/resources/withdrawals/routes/eth-nonbase.ts
1657
- var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1658
- function routeEthNonBase2() {
1659
- return {
1660
- async preflight(p, ctx) {
1661
- await wrapAs7(
1662
- "VALIDATION",
1663
- OP_WITHDRAWALS.ethNonBase.assertNonEthBase,
1664
- () => {
1665
- if (p.token.toLowerCase() !== L2_BASE_TOKEN_ADDRESS.toLowerCase()) {
1666
- throw new Error("eth-nonbase route requires the L2 base-token alias (0x\u2026800A).");
1667
- }
1668
- if (ctx.baseIsEth) {
1669
- throw new Error("eth-nonbase route requires chain base \u2260 ETH.");
1670
- }
1671
- },
1672
- { ctx: { token: p.token, baseIsEth: ctx.baseIsEth } }
1673
- );
1674
- },
1675
- async build(p, ctx) {
1676
- const toL1 = p.to ?? ctx.sender;
1677
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
1678
- const sim = await wrapAs7(
1679
- "CONTRACT",
1680
- OP_WITHDRAWALS.ethNonBase.estGas,
1681
- () => ctx.client.l2.simulateContract({
1682
- address: L2_BASE_TOKEN_ADDRESS,
1683
- abi: IBaseToken_default,
1684
- functionName: "withdraw",
1685
- args: [toL1],
1686
- value: p.amount,
1687
- account: ctx.client.account,
1688
- ...txFeeOverrides
1689
- }),
1690
- {
1691
- ctx: { where: "l2.simulateContract", to: L2_BASE_TOKEN_ADDRESS },
1692
- message: "Failed to simulate L2 base-token withdraw."
1693
- }
1694
- );
1695
- const steps = [
1696
- {
1697
- key: "l2-base-token:withdraw",
1698
- kind: "l2-base-token:withdraw",
1699
- description: "Withdraw base token via L2 Base Token System (base \u2260 ETH)",
1700
- tx: { ...sim.request, ...txFeeOverrides }
1701
- }
1702
- ];
1703
- return { steps, approvals: [], quoteExtras: {} };
2057
+ const fees = buildFeeBreakdown2({
2058
+ feeToken: ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2),
2059
+ l2Gas: withdrawGas
2060
+ });
2061
+ return { steps, approvals, fees };
1704
2062
  }
1705
2063
  };
1706
2064
  }
1707
- var { wrapAs: wrapAs8 } = createErrorHandlers("withdrawals");
2065
+ var { wrapAs: wrapAs9 } = createErrorHandlers("withdrawals");
1708
2066
  var IL1NullifierMini = [
1709
2067
  {
1710
2068
  type: "function",
@@ -1721,7 +2079,7 @@ var IL1NullifierMini = [
1721
2079
  function createFinalizationServices(client) {
1722
2080
  return {
1723
2081
  async fetchFinalizeDepositParams(l2TxHash) {
1724
- const parsed = await wrapAs8(
2082
+ const parsed = await wrapAs9(
1725
2083
  "RPC",
1726
2084
  OP_WITHDRAWALS.finalize.fetchParams.receipt,
1727
2085
  () => client.zks.getReceiptWithL2ToL1(l2TxHash),
@@ -1738,7 +2096,7 @@ function createFinalizationServices(client) {
1738
2096
  context: { l2TxHash }
1739
2097
  });
1740
2098
  }
1741
- const ev = await wrapAs8(
2099
+ const ev = await wrapAs9(
1742
2100
  "INTERNAL",
1743
2101
  OP_WITHDRAWALS.finalize.fetchParams.findMessage,
1744
2102
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
@@ -1748,7 +2106,7 @@ function createFinalizationServices(client) {
1748
2106
  message: "Failed to locate L1MessageSent event in L2 receipt."
1749
2107
  }
1750
2108
  );
1751
- const message = await wrapAs8(
2109
+ const message = await wrapAs9(
1752
2110
  "INTERNAL",
1753
2111
  OP_WITHDRAWALS.finalize.fetchParams.decodeMessage,
1754
2112
  () => {
@@ -1760,7 +2118,7 @@ function createFinalizationServices(client) {
1760
2118
  message: "Failed to decode withdrawal message."
1761
2119
  }
1762
2120
  );
1763
- const raw = await wrapAs8(
2121
+ const raw = await wrapAs9(
1764
2122
  "RPC",
1765
2123
  OP_WITHDRAWALS.finalize.fetchParams.rawReceipt,
1766
2124
  () => client.zks.getReceiptWithL2ToL1(l2TxHash),
@@ -1777,7 +2135,7 @@ function createFinalizationServices(client) {
1777
2135
  context: { l2TxHash }
1778
2136
  });
1779
2137
  }
1780
- const idx = await wrapAs8(
2138
+ const idx = await wrapAs9(
1781
2139
  "INTERNAL",
1782
2140
  OP_WITHDRAWALS.finalize.fetchParams.messengerIndex,
1783
2141
  () => Promise.resolve(messengerLogIndex(raw, { index: 0, messenger: L1_MESSENGER_ADDRESS })),
@@ -1786,7 +2144,7 @@ function createFinalizationServices(client) {
1786
2144
  message: "Failed to derive messenger log index."
1787
2145
  }
1788
2146
  );
1789
- const proof = await wrapAs8(
2147
+ const proof = await wrapAs9(
1790
2148
  "RPC",
1791
2149
  OP_WITHDRAWALS.finalize.fetchParams.proof,
1792
2150
  () => client.zks.getL2ToL1LogProof(l2TxHash, idx),
@@ -1795,7 +2153,7 @@ function createFinalizationServices(client) {
1795
2153
  message: "Failed to fetch L2\u2192L1 log proof."
1796
2154
  }
1797
2155
  );
1798
- const chainId = await wrapAs8(
2156
+ const chainId = await wrapAs9(
1799
2157
  "RPC",
1800
2158
  OP_WITHDRAWALS.finalize.fetchParams.network,
1801
2159
  () => client.l2.getChainId(),
@@ -1811,7 +2169,7 @@ function createFinalizationServices(client) {
1811
2169
  message,
1812
2170
  merkleProof: proof.proof
1813
2171
  };
1814
- const { l1Nullifier } = await wrapAs8(
2172
+ const { l1Nullifier } = await wrapAs9(
1815
2173
  "INTERNAL",
1816
2174
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1817
2175
  () => client.ensureAddresses(),
@@ -1823,7 +2181,7 @@ function createFinalizationServices(client) {
1823
2181
  return { params, nullifier: l1Nullifier };
1824
2182
  },
1825
2183
  async simulateFinalizeReadiness(params) {
1826
- const { l1Nullifier } = await wrapAs8(
2184
+ const { l1Nullifier } = await wrapAs9(
1827
2185
  "INTERNAL",
1828
2186
  OP_WITHDRAWALS.finalize.readiness.ensureAddresses,
1829
2187
  () => client.ensureAddresses(),
@@ -1834,7 +2192,7 @@ function createFinalizationServices(client) {
1834
2192
  );
1835
2193
  const done = await (async () => {
1836
2194
  try {
1837
- const result = await wrapAs8(
2195
+ const result = await wrapAs9(
1838
2196
  "RPC",
1839
2197
  OP_WITHDRAWALS.finalize.readiness.isFinalized,
1840
2198
  () => client.l1.readContract({
@@ -1868,7 +2226,7 @@ function createFinalizationServices(client) {
1868
2226
  }
1869
2227
  },
1870
2228
  async isWithdrawalFinalized(key) {
1871
- const { l1Nullifier } = await wrapAs8(
2229
+ const { l1Nullifier } = await wrapAs9(
1872
2230
  "INTERNAL",
1873
2231
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1874
2232
  () => client.ensureAddresses(),
@@ -1877,7 +2235,7 @@ function createFinalizationServices(client) {
1877
2235
  message: "Failed to ensure L1 Nullifier address."
1878
2236
  }
1879
2237
  );
1880
- return await wrapAs8(
2238
+ return await wrapAs9(
1881
2239
  "RPC",
1882
2240
  OP_WITHDRAWALS.finalize.isFinalized,
1883
2241
  () => client.l1.readContract({
@@ -1893,7 +2251,7 @@ function createFinalizationServices(client) {
1893
2251
  );
1894
2252
  },
1895
2253
  async estimateFinalization(params) {
1896
- const { l1Nullifier } = await wrapAs8(
2254
+ const { l1Nullifier } = await wrapAs9(
1897
2255
  "INTERNAL",
1898
2256
  OP_WITHDRAWALS.finalize.estimate,
1899
2257
  () => client.ensureAddresses(),
@@ -1902,7 +2260,7 @@ function createFinalizationServices(client) {
1902
2260
  message: "Failed to ensure L1 Nullifier address."
1903
2261
  }
1904
2262
  );
1905
- const gasLimit = await wrapAs8(
2263
+ const gasLimit = await wrapAs9(
1906
2264
  "RPC",
1907
2265
  OP_WITHDRAWALS.finalize.estimate,
1908
2266
  () => client.l1.estimateContractGas({
@@ -1926,7 +2284,7 @@ function createFinalizationServices(client) {
1926
2284
  let maxFeePerGas;
1927
2285
  let maxPriorityFeePerGas;
1928
2286
  try {
1929
- const fee = await wrapAs8(
2287
+ const fee = await wrapAs9(
1930
2288
  "RPC",
1931
2289
  OP_WITHDRAWALS.finalize.estimate,
1932
2290
  () => client.l1.estimateFeesPerGas(),
@@ -1945,7 +2303,7 @@ function createFinalizationServices(client) {
1945
2303
  })();
1946
2304
  maxPriorityFeePerGas = fee.maxPriorityFeePerGas ?? 0n;
1947
2305
  } catch {
1948
- const gasPrice = await wrapAs8(
2306
+ const gasPrice = await wrapAs9(
1949
2307
  "RPC",
1950
2308
  OP_WITHDRAWALS.finalize.estimate,
1951
2309
  () => client.l1.getGasPrice(),
@@ -1964,7 +2322,7 @@ function createFinalizationServices(client) {
1964
2322
  };
1965
2323
  },
1966
2324
  async finalizeDeposit(params) {
1967
- const { l1Nullifier } = await wrapAs8(
2325
+ const { l1Nullifier } = await wrapAs9(
1968
2326
  "INTERNAL",
1969
2327
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1970
2328
  () => client.ensureAddresses(),
@@ -2023,40 +2381,32 @@ function createFinalizationServices(client) {
2023
2381
 
2024
2382
  // src/adapters/viem/resources/withdrawals/index.ts
2025
2383
  var ROUTES2 = {
2026
- "eth-base": routeEthBase(),
2384
+ base: routeEthBase(),
2027
2385
  // BaseTokenSystem.withdraw, chain base = ETH
2028
- "eth-nonbase": routeEthNonBase2(),
2029
- // BaseTokenSystem.withdraw, chain base ≠ ETH
2030
2386
  "erc20-nonbase": routeErc20NonBase2()
2031
2387
  // AssetRouter.withdraw for non-base ERC-20s
2032
2388
  };
2033
- function createWithdrawalsResource(client) {
2389
+ function createWithdrawalsResource(client, tokens, contracts) {
2034
2390
  const svc = createFinalizationServices(client);
2035
2391
  const { wrap: wrap2, toResult: toResult2 } = createErrorHandlers("withdrawals");
2392
+ const tokensResource = tokens ?? createTokensResource(client);
2393
+ const contractsResource = contracts ?? createContractsResource(client);
2036
2394
  async function buildPlan(p) {
2037
- const ctx = await commonCtx2(p, client);
2395
+ const ctx = await commonCtx2(p, client, tokensResource, contractsResource);
2038
2396
  await ROUTES2[ctx.route].preflight?.(p, ctx);
2039
- const { steps, approvals } = await ROUTES2[ctx.route].build(p, ctx);
2040
- const resolveGasLimit = () => {
2041
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
2042
- for (let i = steps.length - 1; i >= 0; i--) {
2043
- const candidate = steps[i].tx.gas;
2044
- if (candidate != null) return candidate;
2045
- }
2046
- return void 0;
2047
- };
2048
- const gasLimit = resolveGasLimit();
2049
- const summary = {
2397
+ const { steps, approvals, fees } = await ROUTES2[ctx.route].build(p, ctx);
2398
+ return {
2050
2399
  route: ctx.route,
2051
- approvalsNeeded: approvals,
2052
- suggestedL2GasLimit: ctx.l2GasLimit,
2053
- fees: {
2054
- gasLimit,
2055
- maxFeePerGas: ctx.fee.maxFeePerGas,
2056
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
2057
- }
2400
+ summary: {
2401
+ route: ctx.route,
2402
+ approvalsNeeded: approvals,
2403
+ amounts: {
2404
+ transfer: { token: p.token, amount: p.amount }
2405
+ },
2406
+ fees
2407
+ },
2408
+ steps
2058
2409
  };
2059
- return { route: ctx.route, summary, steps };
2060
2410
  }
2061
2411
  const finalizeCache = /* @__PURE__ */ new Map();
2062
2412
  const quote = (p) => wrap2(OP_WITHDRAWALS.quote, async () => (await buildPlan(p)).summary, {
@@ -2090,7 +2440,7 @@ function createWithdrawalsResource(client) {
2090
2440
  }
2091
2441
  if (overrides.gasLimit != null) step.tx.gas = overrides.gasLimit;
2092
2442
  }
2093
- if (step.tx.gas == null) {
2443
+ if (!p.l2TxOverrides?.gasLimit) {
2094
2444
  try {
2095
2445
  const feePart = step.tx.maxFeePerGas != null && step.tx.maxPriorityFeePerGas != null ? {
2096
2446
  maxFeePerGas: step.tx.maxFeePerGas,
@@ -2389,56 +2739,14 @@ function createWithdrawalsResource(client) {
2389
2739
 
2390
2740
  // src/adapters/viem/sdk.ts
2391
2741
  function createViemSdk(client) {
2742
+ const tokens = createTokensResource(client);
2743
+ const contracts = createContractsResource(client);
2392
2744
  return {
2393
- deposits: createDepositsResource(client),
2394
- withdrawals: createWithdrawalsResource(client),
2395
- helpers: {
2396
- addresses: () => client.ensureAddresses(),
2397
- contracts: () => client.contracts(),
2398
- async l1AssetRouter() {
2399
- const { l1AssetRouter } = await client.contracts();
2400
- return l1AssetRouter;
2401
- },
2402
- async l1NativeTokenVault() {
2403
- const { l1NativeTokenVault } = await client.contracts();
2404
- return l1NativeTokenVault;
2405
- },
2406
- async l1Nullifier() {
2407
- const { l1Nullifier } = await client.contracts();
2408
- return l1Nullifier;
2409
- },
2410
- async baseToken(chainId) {
2411
- const id = chainId ?? BigInt(await client.l2.getChainId());
2412
- return client.baseToken(id);
2413
- },
2414
- async l2TokenAddress(l1Token) {
2415
- if (isAddressEq(l1Token, FORMAL_ETH_ADDRESS)) {
2416
- return ETH_ADDRESS;
2417
- }
2418
- const base = await client.baseToken(BigInt(await client.l2.getChainId()));
2419
- if (isAddressEq(l1Token, base)) {
2420
- return L2_BASE_TOKEN_ADDRESS;
2421
- }
2422
- const { l2NativeTokenVault } = await client.contracts();
2423
- const addr = await l2NativeTokenVault.read.l2TokenAddress([l1Token]);
2424
- return addr;
2425
- },
2426
- async l1TokenAddress(l2Token) {
2427
- if (isAddressEq(l2Token, FORMAL_ETH_ADDRESS)) {
2428
- return FORMAL_ETH_ADDRESS;
2429
- }
2430
- const { l2AssetRouter } = await client.contracts();
2431
- const addr = await l2AssetRouter.read.l1TokenAddress([l2Token]);
2432
- return addr;
2433
- },
2434
- async assetId(l1Token) {
2435
- const norm = isAddressEq(l1Token, FORMAL_ETH_ADDRESS) ? ETH_ADDRESS : l1Token;
2436
- const { l1NativeTokenVault } = await client.contracts();
2437
- const id = await l1NativeTokenVault.read.assetId([norm]);
2438
- return id;
2439
- }
2440
- }
2745
+ deposits: createDepositsResource(client, tokens, contracts),
2746
+ withdrawals: createWithdrawalsResource(client, tokens, contracts),
2747
+ tokens,
2748
+ contracts
2441
2749
  };
2442
2750
  }
2443
2751
 
2444
- export { buildDirectRequestStruct, buildViemFeeOverrides, checkBaseCost, classifyReadinessFromRevert, createDepositsResource, createErrorHandlers, createFinalizationServices, createViemSdk, createWithdrawalsResource, decodeRevert, encodeNTVAssetId, encodeNTVTransferData, encodeNativeTokenVaultAssetId, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs, getFeeOverrides, getGasPriceWei, getL2FeeOverrides, registerErrorAbi, scaleGasLimit, toZKsyncError };
2752
+ export { buildDirectRequestStruct, classifyReadinessFromRevert, createContractsResource, createDepositsResource, createErrorHandlers, createFinalizationServices, createTokensResource, createViemSdk, createWithdrawalsResource, decodeRevert, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs, registerErrorAbi, toZKsyncError };