@matterlabs/zksync-js 0.0.1 → 0.0.2

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 (80) 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 +934 -801
  7. package/dist/adapters/ethers/index.cjs.map +1 -1
  8. package/dist/adapters/ethers/index.js +9 -8
  9. package/dist/adapters/ethers/resources/deposits/context.d.ts +5 -5
  10. package/dist/adapters/ethers/resources/deposits/routes/types.d.ts +2 -6
  11. package/dist/adapters/ethers/resources/deposits/services/fee.d.ts +6 -0
  12. package/dist/adapters/ethers/resources/deposits/services/gas.d.ts +40 -0
  13. package/dist/adapters/ethers/resources/utils.d.ts +4 -15
  14. package/dist/adapters/ethers/resources/withdrawals/context.d.ts +4 -4
  15. package/dist/adapters/ethers/resources/withdrawals/routes/types.d.ts +2 -2
  16. package/dist/adapters/ethers/resources/withdrawals/services/fees.d.ts +14 -0
  17. package/dist/adapters/ethers/resources/withdrawals/services/gas.d.ts +12 -0
  18. package/dist/adapters/ethers/sdk.cjs +947 -1292
  19. package/dist/adapters/ethers/sdk.cjs.map +1 -1
  20. package/dist/adapters/ethers/sdk.js +7 -6
  21. package/dist/adapters/viem/client.cjs.map +1 -1
  22. package/dist/adapters/viem/client.d.ts +1 -1
  23. package/dist/adapters/viem/client.js +4 -5
  24. package/dist/adapters/viem/estimator.d.ts +4 -0
  25. package/dist/adapters/viem/index.cjs +944 -662
  26. package/dist/adapters/viem/index.cjs.map +1 -1
  27. package/dist/adapters/viem/index.js +8 -8
  28. package/dist/adapters/viem/resources/deposits/context.d.ts +5 -5
  29. package/dist/adapters/viem/resources/deposits/routes/types.d.ts +2 -6
  30. package/dist/adapters/viem/resources/deposits/services/fee.d.ts +6 -0
  31. package/dist/adapters/viem/resources/deposits/services/gas.d.ts +36 -0
  32. package/dist/adapters/viem/resources/utils.d.ts +3 -16
  33. package/dist/adapters/viem/resources/withdrawals/context.d.ts +3 -6
  34. package/dist/adapters/viem/resources/withdrawals/routes/types.d.ts +12 -2
  35. package/dist/adapters/viem/resources/withdrawals/services/fee.d.ts +17 -0
  36. package/dist/adapters/viem/resources/withdrawals/services/gas.d.ts +12 -0
  37. package/dist/adapters/viem/sdk.cjs +877 -563
  38. package/dist/adapters/viem/sdk.cjs.map +1 -1
  39. package/dist/adapters/viem/sdk.d.ts +1 -1
  40. package/dist/adapters/viem/sdk.js +6 -6
  41. package/dist/{chunk-3LALBFFE.js → chunk-3MRGU4HV.js} +9 -5
  42. package/dist/{chunk-4HLJJKIY.js → chunk-6K6VJQAL.js} +2 -2
  43. package/dist/{chunk-CGO27P7F.js → chunk-BCCKWWOX.js} +540 -741
  44. package/dist/{chunk-6GCT6TLS.js → chunk-F2ENUV3A.js} +13 -1
  45. package/dist/{chunk-DI2CJDPZ.js → chunk-HLUANWGN.js} +2 -2
  46. package/dist/{chunk-Y75OMFK6.js → chunk-M5J2MM2U.js} +351 -1
  47. package/dist/{chunk-263G6636.js → chunk-NCAIVYBR.js} +1 -14
  48. package/dist/{chunk-7M4V3FMT.js → chunk-OC6ZVLSP.js} +669 -559
  49. package/dist/chunk-QJS6ETEE.js +217 -0
  50. package/dist/chunk-XRE7H466.js +157 -0
  51. package/dist/{chunk-BD2LUO5T.js → chunk-YUK547UF.js} +3 -3
  52. package/dist/core/abi.d.ts +9 -0
  53. package/dist/core/adapters/interfaces.d.ts +25 -0
  54. package/dist/core/constants.cjs +12 -0
  55. package/dist/core/constants.cjs.map +1 -1
  56. package/dist/core/constants.d.ts +6 -0
  57. package/dist/core/constants.js +1 -1
  58. package/dist/core/index.cjs +4504 -1
  59. package/dist/core/index.cjs.map +1 -1
  60. package/dist/core/index.d.ts +1 -0
  61. package/dist/core/index.js +4 -4
  62. package/dist/core/resources/deposits/fee.d.ts +15 -0
  63. package/dist/core/resources/deposits/gas.d.ts +38 -0
  64. package/dist/core/resources/withdrawals/gas.d.ts +14 -0
  65. package/dist/core/types/errors.d.ts +1 -1
  66. package/dist/core/types/fees.d.ts +40 -0
  67. package/dist/core/types/flows/base.d.ts +0 -10
  68. package/dist/core/types/flows/deposits.d.ts +20 -6
  69. package/dist/core/types/flows/route.d.ts +2 -3
  70. package/dist/core/types/flows/withdrawals.d.ts +12 -6
  71. package/dist/index.cjs +4516 -1
  72. package/dist/index.cjs.map +1 -1
  73. package/dist/index.d.ts +1 -0
  74. package/dist/index.js +4 -4
  75. package/package.json +5 -1
  76. package/dist/adapters/ethers/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  77. package/dist/adapters/viem/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  78. package/dist/chunk-B77GWPO5.js +0 -339
  79. package/dist/core/internal/abi-registry.d.ts +0 -9
  80. package/dist/core/utils/gas.d.ts +0 -13
@@ -1,10 +1,33 @@
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 { buildFeeBreakdown, quoteL2Gas, quoteL1Gas, quoteL2Gas2 } from './chunk-QJS6ETEE.js';
2
+ import { findL1MessageSentLog, messengerLogIndex, isAddressEq, isHash66, pickDepositRoute, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-HLUANWGN.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, L2_NATIVE_TOKEN_VAULT_ADDRESS, L1_MESSENGER_ADDRESS, L2_ASSET_ROUTER_ADDRESS, FORMAL_ETH_ADDRESS, L2_BASE_TOKEN_ADDRESS, TOPIC_CANONICAL_ASSIGNED, TOPIC_CANONICAL_SUCCESS, SAFE_L1_BRIDGE_GAS } from './chunk-F2ENUV3A.js';
6
+ import { encodeAbiParameters, keccak256, concat, decodeErrorResult, decodeAbiParameters, decodeEventLog, encodeFunctionData, zeroAddress } from 'viem';
7
7
 
8
+ // src/adapters/viem/resources/deposits/context.ts
9
+ async function commonCtx(p, client) {
10
+ const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
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 route = await pickDepositRoute(client, BigInt(chainId), p.token);
17
+ return {
18
+ client,
19
+ l1AssetRouter,
20
+ route,
21
+ bridgehub,
22
+ chainIdL2: BigInt(chainId),
23
+ sender,
24
+ gasOverrides: p.l1TxOverrides,
25
+ l2GasLimit: p.l2GasLimit,
26
+ gasPerPubdata,
27
+ operatorTip,
28
+ refundRecipient
29
+ };
30
+ }
8
31
  function encodeNativeTokenVaultAssetId(chainId, address) {
9
32
  const encoded = encodeAbiParameters(
10
33
  [
@@ -38,121 +61,6 @@ function encodeSecondBridgeDataV1(assetId, transferData) {
38
61
  }
39
62
  var encodeNTVAssetId = encodeNativeTokenVaultAssetId;
40
63
  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
64
  function encodeSecondBridgeArgs(token, amount, l2Receiver) {
157
65
  return encodeAbiParameters(
158
66
  [
@@ -169,30 +77,17 @@ function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
169
77
  function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
170
78
  return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
171
79
  }
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);
80
+ function buildDirectRequestStruct(args) {
184
81
  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
82
+ chainId: args.chainId,
83
+ l2Contract: args.l2Contract,
84
+ mintValue: args.mintValue,
85
+ l2Value: args.l2Value,
86
+ l2Calldata: "0x",
87
+ l2GasLimit: args.l2GasLimit,
88
+ l2GasPerPubdataByteLimit: args.gasPerPubdata,
89
+ factoryDeps: [],
90
+ refundRecipient: args.refundRecipient
196
91
  };
197
92
  }
198
93
  var ERROR_ABIS = [];
@@ -347,41 +242,228 @@ function createErrorHandlers(resource) {
347
242
  return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
348
243
  }
349
244
 
350
- // src/adapters/viem/resources/deposits/routes/eth.ts
245
+ // src/adapters/viem/estimator.ts
246
+ function toCoreTx(tx) {
247
+ return {
248
+ to: tx.to,
249
+ from: tx.from,
250
+ data: tx.data,
251
+ value: tx.value,
252
+ gasLimit: tx.gas,
253
+ maxFeePerGas: tx.maxFeePerGas,
254
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
255
+ };
256
+ }
257
+ function viemToGasEstimator(client) {
258
+ return {
259
+ async estimateGas(tx, stateOverrides) {
260
+ if (stateOverrides) {
261
+ try {
262
+ const result = await client.request({
263
+ method: "eth_estimateGas",
264
+ params: [
265
+ {
266
+ from: tx.from,
267
+ to: tx.to,
268
+ data: tx.data,
269
+ value: tx.value,
270
+ gas: tx.gasLimit,
271
+ maxFeePerGas: tx.maxFeePerGas,
272
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
273
+ },
274
+ "latest",
275
+ stateOverrides
276
+ ]
277
+ });
278
+ return BigInt(result);
279
+ } catch (error) {
280
+ console.warn(
281
+ "Failed to estimate gas with state overrides, falling back to standard estimation:",
282
+ error
283
+ );
284
+ }
285
+ }
286
+ return await client.estimateGas({
287
+ account: tx.from,
288
+ to: tx.to,
289
+ data: tx.data,
290
+ value: tx.value,
291
+ gas: tx.gasLimit,
292
+ maxFeePerGas: tx.maxFeePerGas,
293
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
294
+ });
295
+ },
296
+ async estimateFeesPerGas() {
297
+ try {
298
+ const fees = await client.estimateFeesPerGas();
299
+ return {
300
+ maxFeePerGas: fees.maxFeePerGas,
301
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas
302
+ };
303
+ } catch {
304
+ }
305
+ try {
306
+ const gp = await client.getGasPrice();
307
+ return { gasPrice: gp };
308
+ } catch {
309
+ return {};
310
+ }
311
+ },
312
+ async getGasPrice() {
313
+ return await client.getGasPrice();
314
+ },
315
+ async call(tx) {
316
+ const res = await client.call({
317
+ to: tx.to,
318
+ data: tx.data,
319
+ value: tx.value,
320
+ account: tx.from
321
+ });
322
+ return res.data ?? "0x";
323
+ }
324
+ };
325
+ }
326
+
327
+ // src/adapters/viem/resources/deposits/services/gas.ts
328
+ async function quoteL1Gas2(input) {
329
+ const { ctx, tx, overrides, fallbackGasLimit } = input;
330
+ const estimator = viemToGasEstimator(ctx.client.l1);
331
+ return quoteL1Gas({
332
+ estimator,
333
+ tx: toCoreTx(tx),
334
+ overrides,
335
+ fallbackGasLimit
336
+ });
337
+ }
338
+ async function quoteL2Gas3(input) {
339
+ const { ctx, route, l2TxForModeling, overrideGasLimit } = input;
340
+ const estimator = viemToGasEstimator(ctx.client.l2);
341
+ return quoteL2Gas({
342
+ estimator,
343
+ route,
344
+ tx: l2TxForModeling ? toCoreTx(l2TxForModeling) : void 0,
345
+ gasPerPubdata: ctx.gasPerPubdata,
346
+ l2GasLimit: ctx.l2GasLimit,
347
+ // TODO: investigate if this should be passed here; weird viem quirk
348
+ overrideGasLimit,
349
+ stateOverrides: input.stateOverrides
350
+ });
351
+ }
352
+ async function determineErc20L2Gas(input) {
353
+ const { ctx, l1Token } = input;
354
+ const DEFAULT_SAFE_L2_GAS_LIMIT = 3000000n;
355
+ if (ctx.l2GasLimit != null) {
356
+ return quoteL2Gas3({
357
+ ctx,
358
+ route: "erc20-nonbase",
359
+ overrideGasLimit: ctx.l2GasLimit
360
+ });
361
+ }
362
+ try {
363
+ const l2NativeTokenVault = (await ctx.client.contracts()).l2NativeTokenVault;
364
+ const l2TokenAddress = await ctx.client.l2.readContract({
365
+ address: l2NativeTokenVault.address,
366
+ abi: l2NativeTokenVault.abi,
367
+ functionName: "l2TokenAddress",
368
+ args: [l1Token]
369
+ });
370
+ if (l2TokenAddress === zeroAddress) {
371
+ return quoteL2Gas3({
372
+ ctx,
373
+ route: "erc20-nonbase",
374
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
375
+ });
376
+ }
377
+ const modelTx = {
378
+ to: input.modelTx?.to ?? ctx.sender,
379
+ from: input.modelTx?.from ?? ctx.sender,
380
+ data: input.modelTx?.data ?? "0x",
381
+ value: input.modelTx?.value ?? 0n
382
+ };
383
+ const gas = await quoteL2Gas3({
384
+ ctx,
385
+ route: "erc20-nonbase",
386
+ l2TxForModeling: modelTx
387
+ });
388
+ if (!gas) {
389
+ return quoteL2Gas3({
390
+ ctx,
391
+ route: "erc20-nonbase",
392
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
393
+ });
394
+ }
395
+ return gas;
396
+ } catch (err) {
397
+ console.warn("Failed to determine ERC20 L2 gas; defaulting to safe gas limit.", err);
398
+ return quoteL2Gas3({
399
+ ctx,
400
+ route: "erc20-nonbase",
401
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
402
+ });
403
+ }
404
+ }
405
+
406
+ // src/adapters/viem/resources/deposits/services/fee.ts
351
407
  var { wrapAs } = createErrorHandlers("deposits");
408
+ async function quoteL2BaseCost(input) {
409
+ const { ctx, l2GasLimit } = input;
410
+ const estimator = viemToGasEstimator(ctx.client.l1);
411
+ const fees = await estimator.estimateFeesPerGas();
412
+ const gasPrice = fees.maxFeePerGas ?? fees.gasPrice ?? await estimator.getGasPrice();
413
+ return wrapAs(
414
+ "RPC",
415
+ "deposits.fees.l2BaseCost",
416
+ async () => {
417
+ return await ctx.client.l1.readContract({
418
+ address: ctx.bridgehub,
419
+ abi: IBridgehub_default,
420
+ functionName: "l2TransactionBaseCost",
421
+ args: [ctx.chainIdL2, gasPrice, l2GasLimit, ctx.gasPerPubdata]
422
+ });
423
+ },
424
+ { ctx: { chainIdL2: ctx.chainIdL2 } }
425
+ );
426
+ }
427
+
428
+ // src/adapters/viem/resources/deposits/routes/eth.ts
429
+ var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
352
430
  function routeEthDirect() {
353
431
  return {
354
432
  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."
433
+ const l2TxModel = {
434
+ to: p.to ?? ctx.sender,
435
+ from: ctx.sender,
436
+ data: "0x",
437
+ value: p.amount
438
+ };
439
+ const l2GasParams = await quoteL2Gas3({
440
+ ctx,
441
+ route: "eth-base",
442
+ l2TxForModeling: l2TxModel,
443
+ overrideGasLimit: ctx.l2GasLimit,
444
+ stateOverrides: {
445
+ [ctx.sender]: {
446
+ balance: "0xffffffffffffffffffff"
447
+ }
369
448
  }
370
- );
371
- const baseCost = rawBaseCost;
449
+ });
450
+ if (!l2GasParams) {
451
+ throw new Error("Failed to estimate L2 gas for deposit.");
452
+ }
453
+ const baseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2GasParams.gasLimit });
372
454
  const l2Contract = p.to ?? ctx.sender;
373
455
  const l2Value = p.amount;
374
456
  const mintValue = baseCost + ctx.operatorTip + l2Value;
375
457
  const req = buildDirectRequestStruct({
376
458
  chainId: ctx.chainIdL2,
377
459
  mintValue,
378
- l2GasLimit: ctx.l2GasLimit,
460
+ l2GasLimit: l2GasParams.gasLimit,
379
461
  gasPerPubdata: ctx.gasPerPubdata,
380
462
  refundRecipient: ctx.refundRecipient,
381
463
  l2Contract,
382
464
  l2Value
383
465
  });
384
- const sim = await wrapAs(
466
+ const sim = await wrapAs2(
385
467
  "RPC",
386
468
  OP_DEPOSITS.eth.estGas,
387
469
  () => ctx.client.l1.simulateContract({
@@ -397,33 +479,53 @@ function routeEthDirect() {
397
479
  message: "Failed to simulate Bridgehub.requestL2TransactionDirect."
398
480
  }
399
481
  );
400
- const resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
482
+ const data = encodeFunctionData({
483
+ abi: sim.request.abi,
484
+ functionName: sim.request.functionName,
485
+ args: sim.request.args
486
+ });
487
+ const l1TxCandidate = {
488
+ to: ctx.bridgehub,
489
+ data,
490
+ value: mintValue,
491
+ from: ctx.sender,
492
+ ...ctx.gasOverrides
493
+ };
494
+ const l1Gas = await quoteL1Gas2({
495
+ ctx,
496
+ tx: l1TxCandidate,
497
+ overrides: ctx.gasOverrides
498
+ });
401
499
  const steps = [
402
500
  {
403
501
  key: "bridgehub:direct",
404
502
  kind: "bridgehub:direct",
405
503
  description: "Bridge ETH via Bridgehub.requestL2TransactionDirect",
406
- tx: { ...sim.request, ...txFeeOverrides }
504
+ tx: { ...sim.request, ...l1Gas }
407
505
  }
408
506
  ];
507
+ const fees = buildFeeBreakdown({
508
+ feeToken: ETH_ADDRESS,
509
+ l1Gas,
510
+ l2Gas: l2GasParams,
511
+ l2BaseCost: baseCost,
512
+ operatorTip: ctx.operatorTip,
513
+ mintValue
514
+ });
409
515
  return {
410
516
  steps,
411
517
  approvals: [],
412
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
518
+ fees
413
519
  };
414
520
  }
415
521
  };
416
522
  }
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;
523
+ var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
423
524
  function routeErc20NonBase() {
424
525
  return {
526
+ // TODO: do we even need these validations?
425
527
  async preflight(p, ctx) {
426
- await wrapAs2(
528
+ await wrapAs3(
427
529
  "VALIDATION",
428
530
  OP_DEPOSITS.nonbase.assertNotEthAsset,
429
531
  () => {
@@ -433,18 +535,8 @@ function routeErc20NonBase() {
433
535
  },
434
536
  { ctx: { token: p.token } }
435
537
  );
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(
538
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
539
+ await wrapAs3(
448
540
  "VALIDATION",
449
541
  OP_DEPOSITS.nonbase.assertNonBaseToken,
450
542
  () => {
@@ -454,63 +546,49 @@ function routeErc20NonBase() {
454
546
  },
455
547
  { ctx: { depositToken: p.token, baseToken } }
456
548
  );
457
- return;
458
549
  },
459
550
  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);
551
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
552
+ const baseIsEth = isETH(baseToken);
553
+ const assetRouter = ctx.l1AssetRouter;
554
+ const l2Gas = await determineErc20L2Gas({
555
+ ctx,
556
+ l1Token: p.token,
557
+ modelTx: {
558
+ to: p.to ?? ctx.sender,
559
+ from: ctx.sender,
560
+ data: "0x",
561
+ value: 0n
562
+ }
563
+ });
564
+ if (!l2Gas) throw new Error("Failed to establish L2 gas parameters.");
565
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
566
+ const mintValue = l2BaseCost + ctx.operatorTip;
488
567
  const approvals = [];
489
568
  const steps = [];
490
- const depositAllowance = await wrapAs2(
569
+ const depositAllowance = await wrapAs3(
491
570
  "CONTRACT",
492
- OP_DEPOSITS.nonbase.allowance,
571
+ OP_DEPOSITS.nonbase.allowanceToken,
493
572
  () => ctx.client.l1.readContract({
494
573
  address: p.token,
495
574
  abi: IERC20_default,
496
575
  functionName: "allowance",
497
- args: [ctx.sender, ctx.l1AssetRouter]
576
+ args: [ctx.sender, assetRouter]
498
577
  }),
499
578
  {
500
- ctx: { where: "erc20.allowance", token: p.token, spender: ctx.l1AssetRouter },
501
- message: "Failed to read ERC-20 allowance for deposit token."
579
+ ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
580
+ message: "Failed to read deposit-token allowance."
502
581
  }
503
582
  );
504
- const needsDepositApprove = depositAllowance < p.amount;
505
- if (needsDepositApprove) {
506
- const approveDepReq = await wrapAs2(
583
+ if (depositAllowance < p.amount) {
584
+ const approveSim = await wrapAs3(
507
585
  "CONTRACT",
508
586
  OP_DEPOSITS.nonbase.estGas,
509
587
  () => ctx.client.l1.simulateContract({
510
588
  address: p.token,
511
589
  abi: IERC20_default,
512
590
  functionName: "approve",
513
- args: [ctx.l1AssetRouter, p.amount],
591
+ args: [assetRouter, p.amount],
514
592
  account: ctx.client.account
515
593
  }),
516
594
  {
@@ -518,60 +596,55 @@ function routeErc20NonBase() {
518
596
  message: "Failed to simulate deposit token approve."
519
597
  }
520
598
  );
521
- approvals.push({ token: p.token, spender: ctx.l1AssetRouter, amount: p.amount });
599
+ approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
522
600
  steps.push({
523
- key: `approve:${p.token}:${ctx.l1AssetRouter}`,
601
+ key: `approve:${p.token}:${assetRouter}`,
524
602
  kind: "approve",
525
603
  description: `Approve deposit token for amount`,
526
- tx: { ...approveDepReq.request, ...txFeeOverrides }
604
+ tx: { ...approveSim.request }
527
605
  });
528
606
  }
529
- const baseIsEth = isETH(baseToken);
530
- let msgValue = 0n;
531
607
  if (!baseIsEth) {
532
- const baseAllowance = await wrapAs2(
608
+ const baseAllowance = await wrapAs3(
533
609
  "CONTRACT",
534
- OP_DEPOSITS.nonbase.allowanceFees,
610
+ OP_DEPOSITS.nonbase.allowanceBase,
535
611
  () => ctx.client.l1.readContract({
536
612
  address: baseToken,
537
613
  abi: IERC20_default,
538
614
  functionName: "allowance",
539
- args: [ctx.sender, ctx.l1AssetRouter]
615
+ args: [ctx.sender, assetRouter]
540
616
  }),
541
617
  {
542
- ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
618
+ ctx: { where: "erc20.allowance", token: baseToken, spender: assetRouter },
543
619
  message: "Failed to read base-token allowance."
544
620
  }
545
621
  );
546
622
  if (baseAllowance < mintValue) {
547
- const approveBaseReq = await wrapAs2(
623
+ const approveBaseSim = await wrapAs3(
548
624
  "CONTRACT",
549
625
  OP_DEPOSITS.nonbase.estGas,
550
626
  () => ctx.client.l1.simulateContract({
551
627
  address: baseToken,
552
628
  abi: IERC20_default,
553
629
  functionName: "approve",
554
- args: [ctx.l1AssetRouter, mintValue],
630
+ args: [assetRouter, mintValue],
555
631
  account: ctx.client.account
556
632
  }),
557
633
  {
558
634
  ctx: { where: "l1.simulateContract", to: baseToken },
559
- message: "Failed to simulate base-token approve."
635
+ message: "Failed to simulate base token approve."
560
636
  }
561
637
  );
562
- approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
638
+ approvals.push({ token: baseToken, spender: assetRouter, amount: mintValue });
563
639
  steps.push({
564
- key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
640
+ key: `approve:${baseToken}:${assetRouter}`,
565
641
  kind: "approve",
566
642
  description: `Approve base token for mintValue`,
567
- tx: { ...approveBaseReq.request, ...txFeeOverrides }
643
+ tx: { ...approveBaseSim.request }
568
644
  });
569
645
  }
570
- msgValue = 0n;
571
- } else {
572
- msgValue = mintValue;
573
646
  }
574
- const secondBridgeCalldata = await wrapAs2(
647
+ const secondBridgeCalldata = await wrapAs3(
575
648
  "INTERNAL",
576
649
  OP_DEPOSITS.nonbase.encodeCalldata,
577
650
  () => Promise.resolve(encodeSecondBridgeErc20Args(p.token, p.amount, p.to ?? ctx.sender)),
@@ -580,44 +653,60 @@ function routeErc20NonBase() {
580
653
  where: "encodeSecondBridgeErc20Args",
581
654
  token: p.token,
582
655
  amount: p.amount.toString()
583
- }
656
+ },
657
+ message: "Failed to encode bridging calldata."
584
658
  }
585
659
  );
586
- const outer = {
660
+ const requestStruct = {
587
661
  chainId: ctx.chainIdL2,
588
662
  mintValue,
589
663
  l2Value: 0n,
590
- l2GasLimit: l2GasLimitUsed,
664
+ l2GasLimit: l2Gas.gasLimit,
591
665
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
592
666
  refundRecipient: ctx.refundRecipient,
593
- secondBridgeAddress: ctx.l1AssetRouter,
667
+ secondBridgeAddress: assetRouter,
594
668
  secondBridgeValue: 0n,
595
669
  secondBridgeCalldata
596
670
  };
671
+ const msgValue = baseIsEth ? mintValue : 0n;
672
+ const calldata = encodeFunctionData({
673
+ abi: IBridgehub_default,
674
+ functionName: "requestL2TransactionTwoBridges",
675
+ args: [requestStruct]
676
+ });
677
+ const l1TxCandidate = {
678
+ to: ctx.bridgehub,
679
+ data: calldata,
680
+ value: msgValue,
681
+ from: ctx.sender,
682
+ ...ctx.gasOverrides
683
+ };
684
+ const l1Gas = await quoteL1Gas2({
685
+ ctx,
686
+ tx: l1TxCandidate,
687
+ overrides: ctx.gasOverrides,
688
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
689
+ });
597
690
  const approvalsNeeded = approvals.length > 0;
598
691
  let bridgeTx;
599
- let resolvedL1GasLimit;
600
- const gasOverride = txFeeOverrides.gas;
601
692
  if (approvalsNeeded) {
602
693
  bridgeTx = {
603
694
  address: ctx.bridgehub,
604
695
  abi: IBridgehub_default,
605
696
  functionName: "requestL2TransactionTwoBridges",
606
- args: [outer],
697
+ args: [requestStruct],
607
698
  value: msgValue,
608
- account: ctx.client.account,
609
- ...txFeeOverrides
699
+ account: ctx.client.account
610
700
  };
611
- resolvedL1GasLimit = gasOverride ?? ctx.l2GasLimit;
612
701
  } else {
613
- const sim = await wrapAs2(
702
+ const sim = await wrapAs3(
614
703
  "CONTRACT",
615
704
  OP_DEPOSITS.nonbase.estGas,
616
705
  () => ctx.client.l1.simulateContract({
617
706
  address: ctx.bridgehub,
618
707
  abi: IBridgehub_default,
619
708
  functionName: "requestL2TransactionTwoBridges",
620
- args: [outer],
709
+ args: [requestStruct],
621
710
  value: msgValue,
622
711
  account: ctx.client.account
623
712
  }),
@@ -626,33 +715,44 @@ function routeErc20NonBase() {
626
715
  message: "Failed to simulate two-bridges request."
627
716
  }
628
717
  );
629
- bridgeTx = { ...sim.request, ...txFeeOverrides };
630
- resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
718
+ bridgeTx = { ...sim.request };
719
+ }
720
+ if (l1Gas) {
721
+ bridgeTx = {
722
+ ...bridgeTx,
723
+ gas: l1Gas.gasLimit,
724
+ maxFeePerGas: l1Gas.maxFeePerGas,
725
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
726
+ };
631
727
  }
632
728
  steps.push({
633
- key: "bridgehub:two-bridges:nonbase",
729
+ key: "bridgehub:two-bridges:erc20-nonbase",
634
730
  kind: "bridgehub:two-bridges",
635
731
  description: baseIsEth ? "Bridge ERC-20 (fees in ETH) via Bridgehub.requestL2TransactionTwoBridges" : "Bridge ERC-20 (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
636
732
  tx: bridgeTx
637
733
  });
734
+ const fees = buildFeeBreakdown({
735
+ feeToken: baseToken,
736
+ l1Gas,
737
+ l2Gas,
738
+ l2BaseCost,
739
+ operatorTip: ctx.operatorTip,
740
+ mintValue
741
+ });
638
742
  return {
639
743
  steps,
640
744
  approvals,
641
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
745
+ fees
642
746
  };
643
747
  }
644
748
  };
645
749
  }
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;
750
+ var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
652
751
  function routeEthNonBase() {
653
752
  return {
753
+ // TODO: do we even need these validations?
654
754
  async preflight(p, ctx) {
655
- await wrapAs3(
755
+ await wrapAs4(
656
756
  "VALIDATION",
657
757
  OP_DEPOSITS.ethNonBase.assertEthAsset,
658
758
  () => {
@@ -662,21 +762,8 @@ function routeEthNonBase() {
662
762
  },
663
763
  { ctx: { token: p.token } }
664
764
  );
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(
765
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
766
+ await wrapAs4(
680
767
  "VALIDATION",
681
768
  OP_DEPOSITS.ethNonBase.assertNonEthBase,
682
769
  () => {
@@ -686,7 +773,7 @@ function routeEthNonBase() {
686
773
  },
687
774
  { ctx: { baseToken, chainIdL2: ctx.chainIdL2 } }
688
775
  );
689
- const ethBal = await wrapAs3(
776
+ const ethBal = await wrapAs4(
690
777
  "RPC",
691
778
  OP_DEPOSITS.ethNonBase.ethBalance,
692
779
  () => ctx.client.l1.getBalance({ address: ctx.sender }),
@@ -695,7 +782,7 @@ function routeEthNonBase() {
695
782
  message: "Failed to read L1 ETH balance."
696
783
  }
697
784
  );
698
- await wrapAs3(
785
+ await wrapAs4(
699
786
  "VALIDATION",
700
787
  OP_DEPOSITS.ethNonBase.assertEthBalance,
701
788
  () => {
@@ -705,45 +792,27 @@ function routeEthNonBase() {
705
792
  },
706
793
  { ctx: { required: p.amount.toString(), balance: ethBal.toString() } }
707
794
  );
708
- return;
709
795
  },
710
796
  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);
797
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
798
+ const l2TxModel = {
799
+ to: p.to ?? ctx.sender,
800
+ from: ctx.sender,
801
+ data: "0x",
802
+ value: 0n
803
+ };
804
+ const l2Gas = await quoteL2Gas3({
805
+ ctx,
806
+ route: "eth-nonbase",
807
+ l2TxForModeling: l2TxModel,
808
+ overrideGasLimit: ctx.l2GasLimit
809
+ });
810
+ if (!l2Gas) throw new Error("Failed to estimate L2 gas parameters.");
811
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
812
+ const mintValue = l2BaseCost + ctx.operatorTip;
744
813
  const approvals = [];
745
814
  const steps = [];
746
- const allowance = await wrapAs3(
815
+ const allowance = await wrapAs4(
747
816
  "CONTRACT",
748
817
  OP_DEPOSITS.ethNonBase.allowanceBase,
749
818
  () => ctx.client.l1.readContract({
@@ -759,7 +828,7 @@ function routeEthNonBase() {
759
828
  );
760
829
  const needsApprove = allowance < mintValue;
761
830
  if (needsApprove) {
762
- const approveSim = await wrapAs3(
831
+ const approveSim = await wrapAs4(
763
832
  "CONTRACT",
764
833
  OP_DEPOSITS.ethNonBase.estGas,
765
834
  () => ctx.client.l1.simulateContract({
@@ -778,11 +847,11 @@ function routeEthNonBase() {
778
847
  steps.push({
779
848
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
780
849
  kind: "approve",
781
- description: `Approve base token for mintValue`,
850
+ description: `Approve base token for fees (mintValue)`,
782
851
  tx: { ...approveSim.request }
783
852
  });
784
853
  }
785
- const secondBridgeCalldata = await wrapAs3(
854
+ const secondBridgeCalldata = await wrapAs4(
786
855
  "INTERNAL",
787
856
  OP_DEPOSITS.ethNonBase.encodeCalldata,
788
857
  () => Promise.resolve(encodeSecondBridgeEthArgs(p.amount, p.to ?? ctx.sender)),
@@ -795,11 +864,11 @@ function routeEthNonBase() {
795
864
  message: "Failed to encode ETH bridging calldata."
796
865
  }
797
866
  );
798
- const outer = {
867
+ const requestStruct = {
799
868
  chainId: ctx.chainIdL2,
800
869
  mintValue,
801
- l2Value: 0n,
802
- l2GasLimit: ctx.l2GasLimit,
870
+ l2Value: p.amount,
871
+ l2GasLimit: l2Gas.gasLimit,
803
872
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
804
873
  refundRecipient: ctx.refundRecipient,
805
874
  secondBridgeAddress: ctx.l1AssetRouter,
@@ -807,29 +876,32 @@ function routeEthNonBase() {
807
876
  secondBridgeCalldata
808
877
  };
809
878
  let bridgeTx;
810
- let resolvedL1GasLimit;
879
+ let calldata;
811
880
  if (needsApprove) {
812
881
  bridgeTx = {
813
882
  address: ctx.bridgehub,
814
883
  abi: IBridgehub_default,
815
884
  functionName: "requestL2TransactionTwoBridges",
816
- args: [outer],
885
+ args: [requestStruct],
817
886
  value: p.amount,
818
887
  // base ≠ ETH ⇒ msg.value == secondBridgeValue
819
888
  account: ctx.client.account
820
889
  };
821
- resolvedL1GasLimit = ctx.l2GasLimit;
890
+ calldata = encodeFunctionData({
891
+ abi: IBridgehub_default,
892
+ functionName: "requestL2TransactionTwoBridges",
893
+ args: [requestStruct]
894
+ });
822
895
  } else {
823
- const twoBridgesSim = await wrapAs3(
896
+ const sim = await wrapAs4(
824
897
  "CONTRACT",
825
898
  OP_DEPOSITS.ethNonBase.estGas,
826
899
  () => ctx.client.l1.simulateContract({
827
900
  address: ctx.bridgehub,
828
901
  abi: IBridgehub_default,
829
902
  functionName: "requestL2TransactionTwoBridges",
830
- args: [outer],
903
+ args: [requestStruct],
831
904
  value: p.amount,
832
- // base ≠ ETH ⇒ msg.value == secondBridgeValue
833
905
  account: ctx.client.account
834
906
  }),
835
907
  {
@@ -837,8 +909,33 @@ function routeEthNonBase() {
837
909
  message: "Failed to simulate Bridgehub two-bridges request."
838
910
  }
839
911
  );
840
- bridgeTx = { ...twoBridgesSim.request, ...txFeeOverrides };
841
- resolvedL1GasLimit = twoBridgesSim.request.gas ?? ctx.l2GasLimit;
912
+ calldata = encodeFunctionData({
913
+ abi: sim.request.abi,
914
+ functionName: sim.request.functionName,
915
+ args: sim.request.args
916
+ });
917
+ bridgeTx = { ...sim.request };
918
+ }
919
+ const l1TxCandidate = {
920
+ to: ctx.bridgehub,
921
+ data: calldata,
922
+ value: p.amount,
923
+ from: ctx.sender,
924
+ ...ctx.gasOverrides
925
+ };
926
+ const l1Gas = await quoteL1Gas2({
927
+ ctx,
928
+ tx: l1TxCandidate,
929
+ overrides: ctx.gasOverrides,
930
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
931
+ });
932
+ if (l1Gas) {
933
+ bridgeTx = {
934
+ ...bridgeTx,
935
+ gas: l1Gas.gasLimit,
936
+ maxFeePerGas: l1Gas.maxFeePerGas,
937
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
938
+ };
842
939
  }
843
940
  steps.push({
844
941
  key: "bridgehub:two-bridges:eth-nonbase",
@@ -846,24 +943,27 @@ function routeEthNonBase() {
846
943
  description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
847
944
  tx: bridgeTx
848
945
  });
946
+ const fees = buildFeeBreakdown({
947
+ feeToken: baseToken,
948
+ l1Gas,
949
+ l2Gas,
950
+ l2BaseCost,
951
+ operatorTip: ctx.operatorTip,
952
+ mintValue
953
+ });
849
954
  return {
850
955
  steps,
851
956
  approvals,
852
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
957
+ fees
853
958
  };
854
959
  }
855
960
  };
856
961
  }
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;
962
+ var { wrapAs: wrapAs5 } = createErrorHandlers("deposits");
863
963
  function routeErc20Base() {
864
964
  return {
865
965
  async preflight(p, ctx) {
866
- await wrapAs4(
966
+ await wrapAs5(
867
967
  "VALIDATION",
868
968
  OP_DEPOSITS.base.assertErc20Asset,
869
969
  () => {
@@ -873,21 +973,8 @@ function routeErc20Base() {
873
973
  },
874
974
  { ctx: { token: p.token } }
875
975
  );
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(
976
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
977
+ await wrapAs5(
891
978
  "VALIDATION",
892
979
  OP_DEPOSITS.base.assertMatchesBase,
893
980
  () => {
@@ -897,45 +984,27 @@ function routeErc20Base() {
897
984
  },
898
985
  { ctx: { baseToken, provided: p.token, chainIdL2: ctx.chainIdL2 } }
899
986
  );
900
- return;
901
987
  },
902
988
  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(
989
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
990
+ const l2TxModel = {
991
+ to: p.to ?? ctx.sender,
992
+ from: ctx.sender,
993
+ data: "0x",
994
+ value: 0n
995
+ };
996
+ const l2Gas = await quoteL2Gas3({
997
+ ctx,
998
+ route: "erc20-base",
999
+ l2TxForModeling: l2TxModel,
1000
+ overrideGasLimit: ctx.l2GasLimit
1001
+ });
1002
+ if (!l2Gas) throw new Error("Failed to estimate L2 gas parameters.");
1003
+ const l2BaseCost = await quoteL2BaseCost({ ctx, l2GasLimit: l2Gas.gasLimit });
1004
+ const mintValue = l2BaseCost + ctx.operatorTip + p.amount;
1005
+ const approvals = [];
1006
+ const steps = [];
1007
+ const allowance = await wrapAs5(
939
1008
  "CONTRACT",
940
1009
  OP_DEPOSITS.base.allowance,
941
1010
  () => ctx.client.l1.readContract({
@@ -949,11 +1018,9 @@ function routeErc20Base() {
949
1018
  message: "Failed to read base-token allowance."
950
1019
  }
951
1020
  );
952
- const approvals = [];
953
- const steps = [];
954
1021
  const needsApprove = allowance < mintValue;
955
1022
  if (needsApprove) {
956
- const approveSim = await wrapAs4(
1023
+ const approveSim = await wrapAs5(
957
1024
  "CONTRACT",
958
1025
  OP_DEPOSITS.base.estGas,
959
1026
  () => ctx.client.l1.simulateContract({
@@ -973,20 +1040,20 @@ function routeErc20Base() {
973
1040
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
974
1041
  kind: "approve",
975
1042
  description: "Approve base token for mintValue",
976
- tx: { ...approveSim.request, ...txFeeOverrides }
1043
+ tx: { ...approveSim.request }
977
1044
  });
978
1045
  }
979
1046
  const req = buildDirectRequestStruct({
980
1047
  chainId: ctx.chainIdL2,
981
1048
  mintValue,
982
- l2GasLimit: ctx.l2GasLimit,
1049
+ l2GasLimit: l2Gas.gasLimit,
983
1050
  gasPerPubdata: ctx.gasPerPubdata,
984
1051
  refundRecipient: ctx.refundRecipient,
985
1052
  l2Contract: p.to ?? ctx.sender,
986
- l2Value
1053
+ l2Value: p.amount
987
1054
  });
988
1055
  let bridgeTx;
989
- let resolvedL1GasLimit;
1056
+ let calldata;
990
1057
  if (needsApprove) {
991
1058
  bridgeTx = {
992
1059
  address: ctx.bridgehub,
@@ -994,13 +1061,16 @@ function routeErc20Base() {
994
1061
  functionName: "requestL2TransactionDirect",
995
1062
  args: [req],
996
1063
  value: 0n,
997
- // base is ERC-20 ⇒ msg.value MUST be 0
998
- account: ctx.client.account,
999
- ...txFeeOverrides
1064
+ // base token is ERC-20 ⇒ msg.value MUST be 0
1065
+ account: ctx.client.account
1000
1066
  };
1001
- resolvedL1GasLimit = gasOverride ?? ctx.l2GasLimit;
1067
+ calldata = encodeFunctionData({
1068
+ abi: IBridgehub_default,
1069
+ functionName: "requestL2TransactionDirect",
1070
+ args: [req]
1071
+ });
1002
1072
  } else {
1003
- const sim = await wrapAs4(
1073
+ const sim = await wrapAs5(
1004
1074
  "RPC",
1005
1075
  OP_DEPOSITS.base.estGas,
1006
1076
  () => ctx.client.l1.simulateContract({
@@ -1016,8 +1086,33 @@ function routeErc20Base() {
1016
1086
  message: "Failed to simulate Bridgehub.requestL2TransactionDirect."
1017
1087
  }
1018
1088
  );
1019
- bridgeTx = { ...sim.request, ...txFeeOverrides };
1020
- resolvedL1GasLimit = sim.request.gas ?? ctx.l2GasLimit;
1089
+ calldata = encodeFunctionData({
1090
+ abi: sim.request.abi,
1091
+ functionName: sim.request.functionName,
1092
+ args: sim.request.args
1093
+ });
1094
+ bridgeTx = { ...sim.request };
1095
+ }
1096
+ const l1TxCandidate = {
1097
+ to: ctx.bridgehub,
1098
+ data: calldata,
1099
+ value: 0n,
1100
+ from: ctx.sender,
1101
+ ...ctx.gasOverrides
1102
+ };
1103
+ const l1Gas = await quoteL1Gas2({
1104
+ ctx,
1105
+ tx: l1TxCandidate,
1106
+ overrides: ctx.gasOverrides,
1107
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
1108
+ });
1109
+ if (l1Gas) {
1110
+ bridgeTx = {
1111
+ ...bridgeTx,
1112
+ gas: l1Gas.gasLimit,
1113
+ maxFeePerGas: l1Gas.maxFeePerGas,
1114
+ maxPriorityFeePerGas: l1Gas.maxPriorityFeePerGas
1115
+ };
1021
1116
  }
1022
1117
  steps.push({
1023
1118
  key: "bridgehub:direct:erc20-base",
@@ -1025,10 +1120,18 @@ function routeErc20Base() {
1025
1120
  description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
1026
1121
  tx: bridgeTx
1027
1122
  });
1123
+ const fees = buildFeeBreakdown({
1124
+ feeToken: baseToken,
1125
+ l1Gas,
1126
+ l2Gas,
1127
+ l2BaseCost,
1128
+ operatorTip: ctx.operatorTip,
1129
+ mintValue
1130
+ });
1028
1131
  return {
1029
1132
  steps,
1030
1133
  approvals,
1031
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
1134
+ fees
1032
1135
  };
1033
1136
  }
1034
1137
  };
@@ -1122,32 +1225,19 @@ function createDepositsResource(client) {
1122
1225
  const ctx = await commonCtx(p, client);
1123
1226
  const route = ctx.route;
1124
1227
  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();
1228
+ const { steps, approvals, fees } = await ROUTES[route].build(p, ctx);
1138
1229
  return {
1139
1230
  route: ctx.route,
1140
1231
  summary: {
1141
1232
  route: ctx.route,
1142
1233
  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
- }
1234
+ amounts: {
1235
+ transfer: { token: p.token, amount: p.amount }
1236
+ },
1237
+ fees,
1238
+ // Legacy fields (maintained for backward compatibility)
1239
+ baseCost: fees.l2?.baseCost,
1240
+ mintValue: fees.mintValue
1151
1241
  },
1152
1242
  steps
1153
1243
  };
@@ -1222,7 +1312,7 @@ function createDepositsResource(client) {
1222
1312
  step.tx.gas = overrides.gasLimit;
1223
1313
  }
1224
1314
  }
1225
- if (step.tx.gas == null) {
1315
+ if (!p.l1TxOverrides?.gasLimit) {
1226
1316
  try {
1227
1317
  const feePart = step.tx.maxFeePerGas != null && step.tx.maxPriorityFeePerGas != null ? {
1228
1318
  maxFeePerGas: step.tx.maxFeePerGas,
@@ -1473,13 +1563,7 @@ async function commonCtx2(p, client) {
1473
1563
  } = await client.ensureAddresses();
1474
1564
  const chainIdL2 = BigInt(await client.l2.getChainId());
1475
1565
  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;
1566
+ const route = pickWithdrawRoute({ token: p.token, baseIsEth });
1483
1567
  return {
1484
1568
  client,
1485
1569
  bridgehub,
@@ -1492,56 +1576,101 @@ async function commonCtx2(p, client) {
1492
1576
  l2NativeTokenVault,
1493
1577
  l2BaseTokenSystem,
1494
1578
  baseIsEth,
1495
- l2GasLimit,
1496
- gasBufferPct,
1497
- fee
1579
+ gasOverrides: p.l2TxOverrides
1580
+ };
1581
+ }
1582
+
1583
+ // src/adapters/viem/resources/withdrawals/services/gas.ts
1584
+ async function quoteL2Gas4(input) {
1585
+ const { ctx, tx } = input;
1586
+ const estimator = viemToGasEstimator(ctx.client.l2);
1587
+ return quoteL2Gas2({
1588
+ estimator,
1589
+ tx: toCoreTx(tx),
1590
+ overrides: ctx.gasOverrides
1591
+ });
1592
+ }
1593
+
1594
+ // src/adapters/viem/resources/withdrawals/services/fee.ts
1595
+ function buildFeeBreakdown2(p) {
1596
+ const l2Total = p.l2Gas?.maxCost ?? 0n;
1597
+ const l2 = {
1598
+ total: l2Total,
1599
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
1600
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
1601
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas
1602
+ };
1603
+ return {
1604
+ token: p.feeToken,
1605
+ maxTotal: l2Total,
1606
+ l2
1498
1607
  };
1499
1608
  }
1500
1609
 
1501
1610
  // src/adapters/viem/resources/withdrawals/routes/eth.ts
1502
- var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
1611
+ var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
1503
1612
  function routeEthBase() {
1504
1613
  return {
1505
1614
  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
- }),
1615
+ const steps = [];
1616
+ const data = await wrapAs6(
1617
+ "INTERNAL",
1618
+ OP_WITHDRAWALS.eth.encodeWithdraw,
1619
+ () => Promise.resolve(
1620
+ encodeFunctionData({
1621
+ abi: IBaseToken_default,
1622
+ functionName: "withdraw",
1623
+ args: [p.to ?? ctx.sender]
1624
+ })
1625
+ ),
1520
1626
  {
1521
- ctx: { where: "l2.simulateContract", to: L2_BASE_TOKEN_ADDRESS },
1522
- message: "Failed to simulate L2 ETH withdraw."
1627
+ ctx: { where: "L2BaseToken.withdraw", to: p.to ?? ctx.sender },
1628
+ message: "Failed to encode ETH withdraw calldata."
1523
1629
  }
1524
1630
  );
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: {} };
1631
+ const L2tx = {
1632
+ to: L2_BASE_TOKEN_ADDRESS,
1633
+ data,
1634
+ value: p.amount,
1635
+ from: ctx.sender
1636
+ };
1637
+ const l2Gas = await quoteL2Gas4({ ctx, tx: L2tx });
1638
+ if (l2Gas) {
1639
+ L2tx.gas = l2Gas.gasLimit;
1640
+ L2tx.maxFeePerGas = l2Gas.maxFeePerGas;
1641
+ L2tx.maxPriorityFeePerGas = l2Gas.maxPriorityFeePerGas;
1642
+ }
1643
+ const tx = {
1644
+ address: L2_BASE_TOKEN_ADDRESS,
1645
+ abi: IBaseToken_default,
1646
+ functionName: "withdraw",
1647
+ args: [p.to ?? ctx.sender],
1648
+ value: p.amount,
1649
+ account: ctx.client.account,
1650
+ ...l2Gas
1651
+ };
1652
+ const fees = buildFeeBreakdown2({
1653
+ feeToken: L2_BASE_TOKEN_ADDRESS,
1654
+ l2Gas
1655
+ });
1656
+ steps.push({
1657
+ key: "l2-base-token:withdraw",
1658
+ kind: "l2-base-token:withdraw",
1659
+ description: "Withdraw ETH via L2 Base Token System",
1660
+ tx
1661
+ });
1662
+ return { steps, approvals: [], fees };
1534
1663
  }
1535
1664
  };
1536
1665
  }
1537
- var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
1666
+ var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1538
1667
  function routeErc20NonBase2() {
1539
1668
  return {
1540
1669
  // TODO: add preflight validations here
1541
1670
  async build(p, ctx) {
1542
- const toL1 = p.to ?? ctx.sender;
1543
- const txFeeOverrides = buildViemFeeOverrides(ctx.fee);
1544
- const current = await wrapAs6(
1671
+ const steps = [];
1672
+ const approvals = [];
1673
+ const current = await wrapAs7(
1545
1674
  "CONTRACT",
1546
1675
  OP_WITHDRAWALS.erc20.allowance,
1547
1676
  () => ctx.client.l2.readContract({
@@ -1561,12 +1690,26 @@ function routeErc20NonBase2() {
1561
1690
  message: "Failed to read L2 ERC-20 allowance."
1562
1691
  }
1563
1692
  );
1564
- const needsApprove = current < p.amount;
1565
- const steps = [];
1566
- const approvals = [];
1567
- if (needsApprove) {
1693
+ if (current < p.amount) {
1568
1694
  approvals.push({ token: p.token, spender: ctx.l2NativeTokenVault, amount: p.amount });
1569
- const approveSim = await wrapAs6(
1695
+ const data = encodeFunctionData({
1696
+ abi: IERC20_default,
1697
+ functionName: "approve",
1698
+ args: [ctx.l2NativeTokenVault, p.amount]
1699
+ });
1700
+ const approveTxCandidate = {
1701
+ to: p.token,
1702
+ data,
1703
+ value: 0n,
1704
+ from: ctx.sender
1705
+ };
1706
+ const approveGas = await quoteL2Gas4({ ctx, tx: approveTxCandidate });
1707
+ if (approveGas) {
1708
+ approveTxCandidate.gas = approveGas.gasLimit;
1709
+ approveTxCandidate.maxFeePerGas = approveGas.maxFeePerGas;
1710
+ approveTxCandidate.maxPriorityFeePerGas = approveGas.maxPriorityFeePerGas;
1711
+ }
1712
+ const approveSim = await wrapAs7(
1570
1713
  "CONTRACT",
1571
1714
  OP_WITHDRAWALS.erc20.estGas,
1572
1715
  () => ctx.client.l2.simulateContract({
@@ -1575,21 +1718,25 @@ function routeErc20NonBase2() {
1575
1718
  functionName: "approve",
1576
1719
  args: [ctx.l2NativeTokenVault, p.amount],
1577
1720
  account: ctx.client.account,
1578
- ...txFeeOverrides
1721
+ ...approveGas
1579
1722
  }),
1580
1723
  {
1581
1724
  ctx: { where: "l2.simulateContract", to: p.token },
1582
1725
  message: "Failed to simulate L2 ERC-20 approve."
1583
1726
  }
1584
1727
  );
1728
+ const { ...approveRequest } = approveSim.request;
1729
+ const approveTx = {
1730
+ ...approveRequest
1731
+ };
1585
1732
  steps.push({
1586
1733
  key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
1587
1734
  kind: "approve:l2",
1588
1735
  description: `Approve ${p.amount} to NativeTokenVault`,
1589
- tx: { ...approveSim.request, ...txFeeOverrides }
1736
+ tx: approveTx
1590
1737
  });
1591
1738
  }
1592
- const ensure = await wrapAs6(
1739
+ const ensure = await wrapAs7(
1593
1740
  "CONTRACT",
1594
1741
  OP_WITHDRAWALS.erc20.ensureRegistered,
1595
1742
  () => ctx.client.l2.simulateContract({
@@ -1611,20 +1758,37 @@ function routeErc20NonBase2() {
1611
1758
  { type: "address", name: "l1Receiver" },
1612
1759
  { type: "address", name: "l2Token" }
1613
1760
  ],
1614
- [p.amount, toL1, p.token]
1761
+ [p.amount, p.to ?? ctx.sender, p.token]
1615
1762
  );
1763
+ const withdrawCalldata = encodeFunctionData({
1764
+ abi: IL2AssetRouter_default,
1765
+ functionName: "withdraw",
1766
+ args: [assetId, assetData]
1767
+ });
1768
+ const withdrawTxCandidate = {
1769
+ to: ctx.l2AssetRouter,
1770
+ data: withdrawCalldata,
1771
+ value: 0n,
1772
+ from: ctx.sender
1773
+ };
1774
+ const withdrawGas = await quoteL2Gas4({ ctx, tx: withdrawTxCandidate });
1775
+ if (withdrawGas) {
1776
+ withdrawTxCandidate.gas = withdrawGas.gasLimit;
1777
+ withdrawTxCandidate.maxFeePerGas = withdrawGas.maxFeePerGas;
1778
+ withdrawTxCandidate.maxPriorityFeePerGas = withdrawGas.maxPriorityFeePerGas;
1779
+ }
1616
1780
  let withdrawTx;
1617
- if (needsApprove) {
1781
+ if (current < p.amount) {
1618
1782
  withdrawTx = {
1619
1783
  address: ctx.l2AssetRouter,
1620
1784
  abi: IL2AssetRouter_default,
1621
1785
  functionName: "withdraw",
1622
1786
  args: [assetId, assetData],
1623
1787
  account: ctx.client.account,
1624
- ...txFeeOverrides
1788
+ ...withdrawGas
1625
1789
  };
1626
1790
  } else {
1627
- const sim = await wrapAs6(
1791
+ const sim = await wrapAs7(
1628
1792
  "CONTRACT",
1629
1793
  OP_WITHDRAWALS.erc20.estGas,
1630
1794
  () => ctx.client.l2.simulateContract({
@@ -1633,14 +1797,18 @@ function routeErc20NonBase2() {
1633
1797
  functionName: "withdraw",
1634
1798
  args: [assetId, assetData],
1635
1799
  account: ctx.client.account,
1636
- ...txFeeOverrides
1800
+ ...withdrawGas
1637
1801
  }),
1638
1802
  {
1639
1803
  ctx: { where: "l2.simulateContract", to: ctx.l2AssetRouter },
1640
1804
  message: "Failed to simulate L2 ERC-20 withdraw."
1641
1805
  }
1642
1806
  );
1643
- withdrawTx = { ...sim.request, ...txFeeOverrides };
1807
+ const { ...withdrawRequest } = sim.request;
1808
+ withdrawTx = {
1809
+ ...withdrawRequest,
1810
+ ...withdrawGas
1811
+ };
1644
1812
  }
1645
1813
  steps.push({
1646
1814
  key: "l2-asset-router:withdraw",
@@ -1648,59 +1816,11 @@ function routeErc20NonBase2() {
1648
1816
  description: "Burn on L2 & send L2\u2192L1 message",
1649
1817
  tx: withdrawTx
1650
1818
  });
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: {} };
1819
+ const fees = buildFeeBreakdown2({
1820
+ feeToken: await ctx.client.baseToken(ctx.chainIdL2),
1821
+ l2Gas: withdrawGas
1822
+ });
1823
+ return { steps, approvals, fees };
1704
1824
  }
1705
1825
  };
1706
1826
  }
@@ -2023,10 +2143,8 @@ function createFinalizationServices(client) {
2023
2143
 
2024
2144
  // src/adapters/viem/resources/withdrawals/index.ts
2025
2145
  var ROUTES2 = {
2026
- "eth-base": routeEthBase(),
2146
+ base: routeEthBase(),
2027
2147
  // BaseTokenSystem.withdraw, chain base = ETH
2028
- "eth-nonbase": routeEthNonBase2(),
2029
- // BaseTokenSystem.withdraw, chain base ≠ ETH
2030
2148
  "erc20-nonbase": routeErc20NonBase2()
2031
2149
  // AssetRouter.withdraw for non-base ERC-20s
2032
2150
  };
@@ -2036,27 +2154,19 @@ function createWithdrawalsResource(client) {
2036
2154
  async function buildPlan(p) {
2037
2155
  const ctx = await commonCtx2(p, client);
2038
2156
  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 = {
2157
+ const { steps, approvals, fees } = await ROUTES2[ctx.route].build(p, ctx);
2158
+ return {
2050
2159
  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
- }
2160
+ summary: {
2161
+ route: ctx.route,
2162
+ approvalsNeeded: approvals,
2163
+ amounts: {
2164
+ transfer: { token: p.token, amount: p.amount }
2165
+ },
2166
+ fees
2167
+ },
2168
+ steps
2058
2169
  };
2059
- return { route: ctx.route, summary, steps };
2060
2170
  }
2061
2171
  const finalizeCache = /* @__PURE__ */ new Map();
2062
2172
  const quote = (p) => wrap2(OP_WITHDRAWALS.quote, async () => (await buildPlan(p)).summary, {
@@ -2090,7 +2200,7 @@ function createWithdrawalsResource(client) {
2090
2200
  }
2091
2201
  if (overrides.gasLimit != null) step.tx.gas = overrides.gasLimit;
2092
2202
  }
2093
- if (step.tx.gas == null) {
2203
+ if (!p.l2TxOverrides?.gasLimit) {
2094
2204
  try {
2095
2205
  const feePart = step.tx.maxFeePerGas != null && step.tx.maxPriorityFeePerGas != null ? {
2096
2206
  maxFeePerGas: step.tx.maxFeePerGas,
@@ -2441,4 +2551,4 @@ function createViemSdk(client) {
2441
2551
  };
2442
2552
  }
2443
2553
 
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 };
2554
+ export { buildDirectRequestStruct, classifyReadinessFromRevert, createDepositsResource, createErrorHandlers, createFinalizationServices, createViemSdk, createWithdrawalsResource, decodeRevert, encodeNTVAssetId, encodeNTVTransferData, encodeNativeTokenVaultAssetId, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs, registerErrorAbi, toZKsyncError };