@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,8 +1,8 @@
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';
1
+ import { createErrorHandlers, toZKsyncError, classifyReadinessFromRevert } from './chunk-XRE7H466.js';
2
+ import { buildFeeBreakdown, quoteL2Gas, quoteL2BaseCost, quoteL1Gas, quoteL2Gas2 } from './chunk-QJS6ETEE.js';
3
+ import { findL1MessageSentLog, messengerLogIndex, isAddressEq, isHash66, pickDepositRoute, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-HLUANWGN.js';
4
+ import { OP_WITHDRAWALS, IL1Nullifier_default, createError, OP_DEPOSITS, IERC20_default, isZKsyncError, isReceiptNotFound, IBridgehub_default, L2NativeTokenVault_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
6
  import { Interface, AbiCoder, ethers, Contract, NonceManager } from 'ethers';
7
7
 
8
8
  var I_BRIDGEHUB = new Interface([
@@ -68,8 +68,29 @@ async function waitForL2ExecutionFromL1Tx(l1, l2, l1TxHash) {
68
68
  }
69
69
  return { l2Receipt, l2TxHash };
70
70
  }
71
- function supportsGetGasPrice(provider) {
72
- return typeof provider === "object" && provider !== null && typeof provider.getGasPrice === "function";
71
+
72
+ // src/adapters/ethers/resources/deposits/context.ts
73
+ async function commonCtx(p, client) {
74
+ const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
75
+ const { chainId } = await client.l2.getNetwork();
76
+ const sender = await client.signer.getAddress();
77
+ const gasPerPubdata = p.gasPerPubdata ?? 800n;
78
+ const operatorTip = p.operatorTip ?? 0n;
79
+ const refundRecipient = p.refundRecipient ?? sender;
80
+ const route = await pickDepositRoute(client, BigInt(chainId), p.token);
81
+ return {
82
+ client,
83
+ l1AssetRouter,
84
+ route,
85
+ bridgehub,
86
+ chainIdL2: BigInt(chainId),
87
+ sender,
88
+ gasOverrides: p.l1TxOverrides,
89
+ l2GasLimit: p.l2GasLimit,
90
+ gasPerPubdata,
91
+ operatorTip,
92
+ refundRecipient
93
+ };
73
94
  }
74
95
  function encodeNativeTokenVaultAssetId(chainId, address) {
75
96
  const abi = new AbiCoder();
@@ -95,78 +116,18 @@ function encodeNTVAssetId(chainId, address) {
95
116
  );
96
117
  return ethers.keccak256(hex);
97
118
  }
98
- function encodeNTVTransferData(amount, receiver, token) {
99
- return new AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
100
- }
101
- function scaleGasLimit(gasLimit) {
102
- return gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR) / BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR);
103
- }
104
- async function checkBaseCost(baseCost, value) {
105
- const resolvedValue = await value;
106
- if (baseCost > resolvedValue) {
107
- throw new Error(
108
- `The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${String(baseCost)}, provided value: ${String(resolvedValue)}!`
109
- );
110
- }
111
- }
112
- async function getFeeOverrides(client, overrides) {
113
- assertNoLegacyGas(overrides);
114
- const fd = await client.l1.getFeeData();
115
- const maxFeeFromProvider = fd.maxFeePerGas ?? void 0;
116
- const maxPriorityFromProvider = fd.maxPriorityFeePerGas ?? void 0;
117
- const gasPriceFallback = fd.gasPrice ?? void 0;
118
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback;
119
- if (maxFeePerGas == null) throw new Error("provider returned no gas price data");
120
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
121
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
122
- const gasPriceForBaseCost = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback ?? maxFeePerGas;
123
- return {
124
- gasLimit: overrides?.gasLimit,
125
- maxFeePerGas,
126
- maxPriorityFeePerGas,
127
- gasPriceForBaseCost
128
- };
119
+ var encodeNTVTransferData = encodeNativeTokenVaultTransferData;
120
+ function encodeSecondBridgeArgs(token, amount, l2Receiver) {
121
+ return AbiCoder.defaultAbiCoder().encode(
122
+ ["address", "uint256", "address"],
123
+ [token, amount, l2Receiver]
124
+ );
129
125
  }
130
- async function getL2FeeOverrides(client, overrides) {
131
- assertNoLegacyGas(overrides);
132
- let maxFeeFromProvider;
133
- let maxPriorityFromProvider;
134
- let gasPriceFallback;
135
- try {
136
- const fd = await client.l2.getFeeData();
137
- if (fd?.maxFeePerGas != null) maxFeeFromProvider = fd.maxFeePerGas;
138
- if (fd?.maxPriorityFeePerGas != null) {
139
- maxPriorityFromProvider = fd.maxPriorityFeePerGas;
140
- }
141
- if (fd?.gasPrice != null) gasPriceFallback = fd.gasPrice;
142
- } catch {
143
- }
144
- if (gasPriceFallback == null) {
145
- try {
146
- if (supportsGetGasPrice(client.l2)) {
147
- const gp = await client.l2.getGasPrice();
148
- gasPriceFallback = typeof gp === "bigint" ? gp : BigInt(gp.toString());
149
- }
150
- } catch {
151
- }
152
- }
153
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback;
154
- if (maxFeePerGas == null) {
155
- throw new Error("L2 provider returned no gas price data");
156
- }
157
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
158
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
159
- return {
160
- gasLimit: overrides?.gasLimit,
161
- maxFeePerGas,
162
- maxPriorityFeePerGas
163
- };
126
+ function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
127
+ return encodeSecondBridgeArgs(token, amount, l2Receiver);
164
128
  }
165
- async function getGasPriceWei(client) {
166
- const fd = await client.l1.getFeeData();
167
- if (fd.gasPrice != null) return fd.gasPrice;
168
- if (fd.maxFeePerGas != null) return fd.maxFeePerGas;
169
- throw new Error("provider returned no gas price data");
129
+ function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
130
+ return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
170
131
  }
171
132
  function buildDirectRequestStruct(args) {
172
133
  return {
@@ -181,296 +142,256 @@ function buildDirectRequestStruct(args) {
181
142
  refundRecipient: args.refundRecipient
182
143
  };
183
144
  }
184
- function encodeSecondBridgeArgs(token, amount, l2Receiver) {
185
- return AbiCoder.defaultAbiCoder().encode(
186
- ["address", "uint256", "address"],
187
- [token, amount, l2Receiver]
188
- );
189
- }
190
- function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
191
- return encodeSecondBridgeArgs(token, amount, l2Receiver);
192
- }
193
- function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
194
- return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
195
- }
196
145
 
197
- // src/adapters/ethers/resources/deposits/context.ts
198
- async function commonCtx(p, client) {
199
- const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
200
- const { chainId } = await client.l2.getNetwork();
201
- const sender = await client.signer.getAddress();
202
- const fee = await getFeeOverrides(client, p.l1TxOverrides);
203
- const l2GasLimit = p.l2GasLimit ?? 300000n;
204
- const gasPerPubdata = p.gasPerPubdata ?? 800n;
205
- const operatorTip = p.operatorTip ?? 0n;
206
- const refundRecipient = p.refundRecipient ?? sender;
207
- const route = await pickDepositRoute(client, BigInt(chainId), p.token);
146
+ // src/adapters/ethers/estimator.ts
147
+ function toCoreTx(tx) {
208
148
  return {
209
- client,
210
- l1AssetRouter,
211
- route,
212
- bridgehub,
213
- chainIdL2: BigInt(chainId),
214
- sender,
215
- fee,
216
- l2GasLimit,
217
- gasPerPubdata,
218
- operatorTip,
219
- refundRecipient
149
+ to: tx.to,
150
+ from: tx.from,
151
+ data: tx.data,
152
+ value: tx.value ? BigInt(tx.value) : void 0,
153
+ gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : void 0,
154
+ maxFeePerGas: tx.maxFeePerGas ? BigInt(tx.maxFeePerGas) : void 0,
155
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : void 0
220
156
  };
221
157
  }
222
- var ERROR_IFACES = [];
223
- var IFACE_ERROR_STRING = new Interface(["error Error(string)"]);
224
- var IFACE_PANIC = new Interface(["error Panic(uint256)"]);
225
- (function bootstrapDefaultIfaces() {
226
- try {
227
- ERROR_IFACES.push({
228
- name: "IL1Nullifier",
229
- iface: new Interface(IL1Nullifier_default)
230
- });
231
- } catch {
232
- }
233
- try {
234
- ERROR_IFACES.push({ name: "IERC20", iface: new Interface(IERC20_default) });
235
- } catch {
236
- }
237
- try {
238
- ERROR_IFACES.push({
239
- name: "IL1NativeTokenVault",
240
- iface: new Interface(L1NativeTokenVault_default)
241
- });
242
- } catch {
243
- }
244
- try {
245
- ERROR_IFACES.push({
246
- name: "IL2NativeTokenVault",
247
- iface: new Interface(L2NativeTokenVault_default)
248
- });
249
- } catch {
250
- }
251
- try {
252
- ERROR_IFACES.push({ name: "Mailbox", iface: new Interface(Mailbox_default) });
253
- } catch {
254
- }
255
- })();
256
- function registerErrorAbi(name, abi) {
257
- const existing = ERROR_IFACES.findIndex((x) => x.name === name);
258
- const entry = { name, iface: new Interface(abi) };
259
- if (existing >= 0) ERROR_IFACES[existing] = entry;
260
- else ERROR_IFACES.push(entry);
261
- }
262
- function extractRevertData(e) {
263
- const maybe = (
264
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
265
- e?.data?.data ?? e?.error?.data ?? e?.data ?? e?.error?.error?.data ?? e?.info?.error?.data
266
- );
267
- if (typeof maybe === "string" && maybe.startsWith("0x") && maybe.length >= 10) {
268
- return maybe;
269
- }
270
- return void 0;
271
- }
272
- function decodeRevert(e) {
273
- const data = extractRevertData(e);
274
- if (!data) return;
275
- const selector = `0x${data.slice(2, 10)}`;
276
- try {
277
- const parsed = IFACE_ERROR_STRING.parseError(data);
278
- if (parsed?.name === "Error") {
279
- const args = parsed.args ? Array.from(parsed.args) : void 0;
280
- return { selector, name: "Error", args };
281
- }
282
- } catch {
283
- }
284
- try {
285
- const parsed = IFACE_PANIC.parseError(data);
286
- if (parsed?.name === "Panic") {
287
- const args = parsed.args ? Array.from(parsed.args) : void 0;
288
- return { selector, name: "Panic", args };
289
- }
290
- } catch {
291
- }
292
- for (const { name, iface } of ERROR_IFACES) {
293
- try {
294
- const parsed = iface.parseError(data);
295
- if (parsed) {
296
- const args = parsed.args ? Array.from(parsed.args) : void 0;
297
- return {
298
- selector,
299
- name: parsed.name,
300
- args,
301
- contract: name
302
- };
158
+ function ethersToGasEstimator(provider) {
159
+ return {
160
+ async estimateGas(tx, stateOverrides) {
161
+ const ethTx = {
162
+ to: tx.to,
163
+ from: tx.from,
164
+ data: tx.data,
165
+ value: tx.value,
166
+ gasLimit: tx.gasLimit,
167
+ maxFeePerGas: tx.maxFeePerGas,
168
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
169
+ };
170
+ if (stateOverrides && "send" in provider) {
171
+ try {
172
+ const jsonRpcProvider = provider;
173
+ const result = await jsonRpcProvider.send("eth_estimateGas", [
174
+ ethTx,
175
+ "latest",
176
+ stateOverrides
177
+ ]);
178
+ return BigInt(result);
179
+ } catch (error) {
180
+ console.warn(
181
+ "Failed to estimate gas with state overrides, falling back to standard estimation:",
182
+ error
183
+ );
184
+ }
185
+ } else if (stateOverrides) {
186
+ console.warn('Provider does not support "send", skipping state overrides estimation.');
303
187
  }
304
- } catch {
188
+ return await provider.estimateGas(ethTx);
189
+ },
190
+ async estimateFeesPerGas() {
191
+ const fd = await provider.getFeeData();
192
+ return {
193
+ maxFeePerGas: fd.maxFeePerGas != null ? BigInt(fd.maxFeePerGas) : void 0,
194
+ maxPriorityFeePerGas: fd.maxPriorityFeePerGas != null ? BigInt(fd.maxPriorityFeePerGas) : void 0,
195
+ gasPrice: fd.gasPrice != null ? BigInt(fd.gasPrice) : void 0
196
+ };
197
+ },
198
+ async getGasPrice() {
199
+ const fd = await provider.getFeeData();
200
+ if (fd.gasPrice != null) return BigInt(fd.gasPrice);
201
+ throw new Error("Could not fetch gas price");
202
+ },
203
+ async call(tx) {
204
+ const ethTx = {
205
+ to: tx.to,
206
+ data: tx.data,
207
+ value: tx.value,
208
+ from: tx.from
209
+ };
210
+ return await provider.call(ethTx);
305
211
  }
306
- }
307
- return { selector };
212
+ };
308
213
  }
309
- function classifyReadinessFromRevert(e) {
310
- const r = decodeRevert(e);
311
- const name = r?.name;
312
- if (name && REVERT_TO_READINESS[name]) return REVERT_TO_READINESS[name];
313
- const msg = (() => {
314
- if (typeof e !== "object" || e === null) return "";
315
- const obj = e;
316
- const maybeMsg = obj["shortMessage"] ?? obj["message"];
317
- return typeof maybeMsg === "string" ? maybeMsg : "";
318
- })();
319
- const lower = String(msg).toLowerCase();
320
- if (lower.includes("paused")) return { kind: "NOT_READY", reason: "paused" };
321
- if (name || r?.selector) {
322
- return { kind: "UNFINALIZABLE", reason: "unsupported", detail: name ?? r?.selector };
323
- }
324
- return { kind: "NOT_READY", reason: "unknown", detail: lower || void 0 };
214
+
215
+ // src/adapters/ethers/resources/deposits/services/fee.ts
216
+ var { wrapAs } = createErrorHandlers("deposits");
217
+ var encode = (abi, fn, args) => {
218
+ return new Interface(abi).encodeFunctionData(fn, args);
219
+ };
220
+ async function quoteL2BaseCost2(input) {
221
+ const { ctx, l2GasLimit } = input;
222
+ const estimator = ethersToGasEstimator(ctx.client.l1);
223
+ return wrapAs(
224
+ "RPC",
225
+ "deposits.fees.l2BaseCost",
226
+ () => quoteL2BaseCost({
227
+ estimator,
228
+ encode,
229
+ bridgehub: ctx.bridgehub,
230
+ chainIdL2: ctx.chainIdL2,
231
+ l2GasLimit,
232
+ gasPerPubdata: ctx.gasPerPubdata
233
+ }),
234
+ { ctx: { chainIdL2: ctx.chainIdL2 } }
235
+ );
325
236
  }
326
237
 
327
- // src/adapters/ethers/errors/error-ops.ts
328
- function toZKsyncError(type, base, err) {
329
- if (isZKsyncError(err)) return err;
330
- const revert = decodeRevert(err);
331
- return createError(type, { ...base, ...revert ? { revert } : {}, cause: shapeCause(err) });
238
+ // src/adapters/ethers/resources/deposits/services/gas.ts
239
+ async function quoteL1Gas2(input) {
240
+ const { ctx, tx, overrides, fallbackGasLimit } = input;
241
+ const estimator = ethersToGasEstimator(ctx.client.l1);
242
+ return quoteL1Gas({
243
+ estimator,
244
+ tx: toCoreTx(tx),
245
+ overrides,
246
+ fallbackGasLimit
247
+ });
332
248
  }
333
- function resolveMessage(op, msg) {
334
- if (!msg) return `Error during ${op}.`;
335
- return typeof msg === "function" ? msg() : msg;
249
+ async function quoteL2Gas3(input) {
250
+ const { ctx, route, l2TxForModeling, overrideGasLimit } = input;
251
+ const estimator = ethersToGasEstimator(ctx.client.l2);
252
+ return quoteL2Gas({
253
+ estimator,
254
+ route,
255
+ tx: l2TxForModeling ? toCoreTx(l2TxForModeling) : void 0,
256
+ gasPerPubdata: ctx.gasPerPubdata,
257
+ l2GasLimit: ctx.l2GasLimit,
258
+ overrideGasLimit,
259
+ stateOverrides: input.stateOverrides
260
+ });
336
261
  }
337
- function createErrorHandlers(resource) {
338
- async function run(kind, operation, fn, opts) {
339
- try {
340
- return await fn();
341
- } catch (e) {
342
- if (isZKsyncError(e)) throw e;
343
- const message = resolveMessage(operation, opts?.message);
344
- throw toZKsyncError(kind, { resource, operation, context: opts?.ctx ?? {}, message }, e);
345
- }
346
- }
347
- function wrap2(operation, fn, opts) {
348
- return run("INTERNAL", operation, fn, opts);
349
- }
350
- function wrapAs9(kind, operation, fn, opts) {
351
- return run(kind, operation, fn, opts);
262
+ async function determineErc20L2Gas(input) {
263
+ const { ctx, l1Token } = input;
264
+ const DEFAULT_SAFE_L2_GAS_LIMIT = 3000000n;
265
+ if (ctx.l2GasLimit != null) {
266
+ return quoteL2Gas3({
267
+ ctx,
268
+ route: "erc20-nonbase",
269
+ overrideGasLimit: ctx.l2GasLimit
270
+ });
352
271
  }
353
- async function toResult2(operation, fn, opts) {
354
- try {
355
- const value = await wrap2(operation, fn, opts);
356
- return { ok: true, value };
357
- } catch (e) {
358
- const shaped = isZKsyncError(e) ? e : toZKsyncError(
359
- "INTERNAL",
360
- {
361
- resource,
362
- operation,
363
- context: opts?.ctx ?? {},
364
- message: resolveMessage(operation, opts?.message)
365
- },
366
- e
367
- );
368
- return { ok: false, error: shaped };
272
+ try {
273
+ const { l2NativeTokenVault } = await ctx.client.contracts();
274
+ const l2TokenAddress = await l2NativeTokenVault.l2TokenAddress(l1Token);
275
+ if (l2TokenAddress === "0x0000000000000000000000000000000000000000") {
276
+ return quoteL2Gas3({
277
+ ctx,
278
+ route: "erc20-nonbase",
279
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
280
+ });
281
+ }
282
+ const modelTx = {
283
+ to: input.modelTx?.to ?? ctx.sender,
284
+ from: input.modelTx?.from ?? ctx.sender,
285
+ data: input.modelTx?.data ?? "0x",
286
+ value: input.modelTx?.value ?? 0n
287
+ };
288
+ const gas = await quoteL2Gas3({
289
+ ctx,
290
+ route: "erc20-nonbase",
291
+ l2TxForModeling: modelTx
292
+ });
293
+ if (!gas) {
294
+ return quoteL2Gas3({
295
+ ctx,
296
+ route: "erc20-nonbase",
297
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
298
+ });
369
299
  }
300
+ return gas;
301
+ } catch (err) {
302
+ console.warn("Failed to determine ERC20 L2 gas; defaulting to safe gas limit.", err);
303
+ return quoteL2Gas3({
304
+ ctx,
305
+ route: "erc20-nonbase",
306
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
307
+ });
370
308
  }
371
- return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
372
309
  }
373
310
 
374
311
  // src/adapters/ethers/resources/deposits/routes/eth.ts
375
- var { wrapAs } = createErrorHandlers("deposits");
376
312
  function routeEthDirect() {
377
313
  return {
378
314
  async build(p, ctx) {
379
315
  const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
380
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
381
- const rawBaseCost = await wrapAs(
382
- "CONTRACT",
383
- OP_DEPOSITS.eth.baseCost,
384
- () => bh.l2TransactionBaseCost(
385
- ctx.chainIdL2,
386
- gasPriceForBaseCost,
387
- ctx.l2GasLimit,
388
- ctx.gasPerPubdata
389
- ),
390
- {
391
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
392
- message: "Could not fetch L2 base cost from Bridgehub."
316
+ const l2TxModel = {
317
+ to: p.to ?? ctx.sender,
318
+ from: ctx.sender,
319
+ data: "0x",
320
+ value: 0n
321
+ };
322
+ const l2GasParams = await quoteL2Gas3({
323
+ ctx,
324
+ route: "eth-base",
325
+ l2TxForModeling: l2TxModel,
326
+ overrideGasLimit: ctx.l2GasLimit,
327
+ stateOverrides: {
328
+ [ctx.sender]: {
329
+ balance: "0xffffffffffffffffffff"
330
+ }
393
331
  }
394
- );
395
- const baseCost = BigInt(rawBaseCost);
396
- const l2Contract = p.to ?? ctx.sender;
397
- const l2Value = p.amount;
398
- const mintValue = baseCost + ctx.operatorTip + l2Value;
332
+ });
333
+ if (!l2GasParams) {
334
+ throw new Error("Failed to estimate L2 gas for deposit.");
335
+ }
336
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
337
+ const mintValue = baseCost + ctx.operatorTip + p.amount;
399
338
  const req = buildDirectRequestStruct({
400
339
  chainId: ctx.chainIdL2,
401
340
  mintValue,
402
- l2GasLimit: ctx.l2GasLimit,
341
+ l2GasLimit: l2GasParams.gasLimit,
403
342
  gasPerPubdata: ctx.gasPerPubdata,
404
343
  refundRecipient: ctx.refundRecipient,
405
- l2Contract,
406
- l2Value
344
+ l2Contract: p.to ?? ctx.sender,
345
+ l2Value: p.amount
407
346
  });
408
347
  const data = bh.interface.encodeFunctionData("requestL2TransactionDirect", [req]);
409
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
410
- const tx = {
348
+ const l1TxCandidate = {
411
349
  to: ctx.bridgehub,
412
350
  data,
413
351
  value: mintValue,
414
352
  from: ctx.sender,
415
- ...txFeeOverrides
353
+ ...ctx.gasOverrides
416
354
  };
417
- if (overrideGasLimit != null) {
418
- tx.gasLimit = overrideGasLimit;
419
- resolvedL1GasLimit = overrideGasLimit;
420
- } else {
421
- try {
422
- const est = await wrapAs(
423
- "RPC",
424
- OP_DEPOSITS.eth.estGas,
425
- () => ctx.client.l1.estimateGas(tx),
426
- {
427
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
428
- message: "Failed to estimate gas for Bridgehub request."
429
- }
430
- );
431
- const buffered = BigInt(est) * 115n / 100n;
432
- tx.gasLimit = buffered;
433
- resolvedL1GasLimit = buffered;
434
- } catch {
435
- }
355
+ const l1GasParams = await quoteL1Gas2({
356
+ ctx,
357
+ tx: l1TxCandidate,
358
+ overrides: ctx.gasOverrides
359
+ });
360
+ if (l1GasParams) {
361
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
362
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
363
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
436
364
  }
437
365
  const steps = [
438
366
  {
439
367
  key: "bridgehub:direct",
440
368
  kind: "bridgehub:direct",
441
369
  description: "Bridge ETH via Bridgehub.requestL2TransactionDirect",
442
- tx
370
+ tx: l1TxCandidate
443
371
  }
444
372
  ];
373
+ const fees = buildFeeBreakdown({
374
+ feeToken: ETH_ADDRESS,
375
+ l1Gas: l1GasParams,
376
+ l2Gas: l2GasParams,
377
+ l2BaseCost: baseCost,
378
+ operatorTip: ctx.operatorTip,
379
+ mintValue
380
+ });
445
381
  return {
446
382
  steps,
447
383
  approvals: [],
448
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
384
+ fees
449
385
  };
450
386
  }
451
387
  };
452
388
  }
453
389
  var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
454
- var MIN_L2_GAS_FOR_ERC20 = 2500000n;
455
390
  function routeErc20NonBase() {
456
391
  return {
457
- async preflight() {
458
- },
459
- async build(p, ctx) {
460
- const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
461
- const assetRouter = ctx.l1AssetRouter;
462
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
463
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
464
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
465
- const baseToken = await wrapAs2(
466
- "CONTRACT",
467
- OP_DEPOSITS.nonbase.baseToken ?? "deposits.erc20-nonbase:baseToken",
468
- () => bh.baseToken(ctx.chainIdL2),
469
- {
470
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
471
- message: "Failed to read base token."
472
- }
473
- );
392
+ // TODO: do we even need these validations?
393
+ async preflight(p, ctx) {
394
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
474
395
  await wrapAs2(
475
396
  "VALIDATION",
476
397
  OP_DEPOSITS.nonbase.assertNonBaseToken,
@@ -481,52 +402,51 @@ function routeErc20NonBase() {
481
402
  },
482
403
  { ctx: { depositToken: p.token, baseToken } }
483
404
  );
484
- 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;
485
- const rawBaseCost = await wrapAs2(
486
- "RPC",
487
- OP_DEPOSITS.nonbase.baseCost,
488
- () => bh.l2TransactionBaseCost(
489
- ctx.chainIdL2,
490
- gasPriceForBaseCost,
491
- l2GasLimitUsed,
492
- ctx.gasPerPubdata
493
- ),
494
- {
495
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
496
- message: "Could not fetch L2 base cost from Bridgehub."
405
+ },
406
+ async build(p, ctx) {
407
+ const l1Signer = ctx.client.getL1Signer();
408
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
409
+ const baseIsEth = isETH(baseToken);
410
+ const l2GasParams = await determineErc20L2Gas({
411
+ ctx,
412
+ l1Token: p.token,
413
+ modelTx: {
414
+ to: p.to ?? ctx.sender,
415
+ from: ctx.sender,
416
+ data: "0x",
417
+ value: 0n
497
418
  }
498
- );
499
- const baseCost = BigInt(rawBaseCost);
419
+ });
420
+ if (!l2GasParams) throw new Error("Failed to establish L2 gas parameters.");
421
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
500
422
  const mintValue = baseCost + ctx.operatorTip;
501
423
  const approvals = [];
502
424
  const steps = [];
503
- const l1Signer = ctx.client.getL1Signer();
504
- {
505
- const erc20Deposit = new Contract(p.token, IERC20_default, l1Signer);
506
- const allowanceToken = await wrapAs2(
507
- "RPC",
508
- OP_DEPOSITS.nonbase.allowanceToken,
509
- () => erc20Deposit.allowance(ctx.sender, assetRouter),
510
- {
511
- ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
512
- message: "Failed to read deposit-token allowance."
513
- }
514
- );
515
- if (allowanceToken < p.amount) {
516
- approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
517
- const data = erc20Deposit.interface.encodeFunctionData("approve", [
518
- assetRouter,
519
- p.amount
520
- ]);
521
- steps.push({
522
- key: `approve:${p.token}:${assetRouter}`,
523
- kind: "approve",
524
- description: `Approve ${p.amount} for router (deposit token)`,
525
- tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
526
- });
425
+ const assetRouter = ctx.l1AssetRouter;
426
+ const erc20Deposit = new Contract(p.token, IERC20_default, l1Signer);
427
+ const allowanceToken = await wrapAs2(
428
+ "RPC",
429
+ OP_DEPOSITS.nonbase.allowanceToken,
430
+ () => erc20Deposit.allowance(ctx.sender, assetRouter),
431
+ {
432
+ ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
433
+ message: "Failed to read deposit-token allowance."
527
434
  }
435
+ );
436
+ if (allowanceToken < p.amount) {
437
+ approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
438
+ steps.push({
439
+ key: `approve:${p.token}:${assetRouter}`,
440
+ kind: "approve",
441
+ description: `Approve ${p.amount} for router (deposit token)`,
442
+ tx: {
443
+ to: p.token,
444
+ data: erc20Deposit.interface.encodeFunctionData("approve", [assetRouter, p.amount]),
445
+ from: ctx.sender,
446
+ ...ctx.gasOverrides
447
+ }
448
+ });
528
449
  }
529
- const baseIsEth = isETH(baseToken);
530
450
  if (!baseIsEth) {
531
451
  const erc20Base = new Contract(baseToken, IERC20_default, l1Signer);
532
452
  const allowanceBase = await wrapAs2(
@@ -540,12 +460,16 @@ function routeErc20NonBase() {
540
460
  );
541
461
  if (allowanceBase < mintValue) {
542
462
  approvals.push({ token: baseToken, spender: assetRouter, amount: mintValue });
543
- const data = erc20Base.interface.encodeFunctionData("approve", [assetRouter, mintValue]);
544
463
  steps.push({
545
464
  key: `approve:${baseToken}:${assetRouter}`,
546
465
  kind: "approve",
547
466
  description: `Approve base token for mintValue`,
548
- tx: { to: baseToken, data, from: ctx.sender, ...txOverrides }
467
+ tx: {
468
+ to: baseToken,
469
+ data: erc20Base.interface.encodeFunctionData("approve", [assetRouter, mintValue]),
470
+ from: ctx.sender,
471
+ ...ctx.gasOverrides
472
+ }
549
473
  });
550
474
  }
551
475
  }
@@ -558,64 +482,63 @@ function routeErc20NonBase() {
558
482
  message: "Failed to encode bridging calldata."
559
483
  }
560
484
  );
561
- const outer = {
485
+ const requestStruct = {
562
486
  chainId: ctx.chainIdL2,
563
487
  mintValue,
564
- // fees (in ETH if base=ETH, else pulled as base ERC-20)
565
488
  l2Value: 0n,
566
- l2GasLimit: l2GasLimitUsed,
489
+ l2GasLimit: l2GasParams.gasLimit,
567
490
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
568
491
  refundRecipient: ctx.refundRecipient,
569
492
  secondBridgeAddress: assetRouter,
570
493
  secondBridgeValue: 0n,
571
494
  secondBridgeCalldata
572
495
  };
573
- const dataTwo = bh.interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
574
- const bridgeTx = {
496
+ const bh = (await ctx.client.contracts()).bridgehub;
497
+ const data = bh.interface.encodeFunctionData("requestL2TransactionTwoBridges", [
498
+ requestStruct
499
+ ]);
500
+ const txValue = baseIsEth ? mintValue : 0n;
501
+ const l1TxCandidate = {
575
502
  to: ctx.bridgehub,
576
- data: dataTwo,
577
- value: baseIsEth ? mintValue : 0n,
503
+ data,
504
+ value: txValue,
578
505
  from: ctx.sender,
579
- ...txOverrides
506
+ ...ctx.gasOverrides
580
507
  };
581
- if (overrideGasLimit != null) {
582
- bridgeTx.gasLimit = overrideGasLimit;
583
- resolvedL1GasLimit = overrideGasLimit;
584
- } else {
585
- try {
586
- const est = await wrapAs2(
587
- "RPC",
588
- OP_DEPOSITS.nonbase.estGas,
589
- () => ctx.client.l1.estimateGas(bridgeTx),
590
- {
591
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub, baseIsEth },
592
- message: "Failed to estimate gas for Bridgehub request."
593
- }
594
- );
595
- const buffered = BigInt(est) * 125n / 100n;
596
- bridgeTx.gasLimit = buffered;
597
- resolvedL1GasLimit = buffered;
598
- } catch {
599
- }
508
+ const l1GasParams = await quoteL1Gas2({
509
+ ctx,
510
+ tx: l1TxCandidate,
511
+ overrides: ctx.gasOverrides,
512
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
513
+ });
514
+ if (l1GasParams) {
515
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
516
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
517
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
600
518
  }
601
519
  steps.push({
602
- key: "bridgehub:two-bridges:nonbase",
520
+ key: "bridgehub:two-bridges",
603
521
  kind: "bridgehub:two-bridges",
604
- description: baseIsEth ? "Bridge ERC-20 (fees in ETH) via Bridgehub.requestL2TransactionTwoBridges" : "Bridge ERC-20 (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
605
- tx: bridgeTx
522
+ description: baseIsEth ? "Bridge ERC-20 (Fees paid in ETH)" : "Bridge ERC-20 (Fees paid in Base Token)",
523
+ tx: l1TxCandidate
524
+ });
525
+ const fees = buildFeeBreakdown({
526
+ feeToken: baseToken,
527
+ l1Gas: l1GasParams,
528
+ l2Gas: l2GasParams,
529
+ l2BaseCost: baseCost,
530
+ operatorTip: ctx.operatorTip,
531
+ mintValue
606
532
  });
607
533
  return {
608
534
  steps,
609
535
  approvals,
610
- quoteExtras: { baseCost, mintValue, baseToken, baseIsEth, l1GasLimit: resolvedL1GasLimit }
536
+ fees
611
537
  };
612
538
  }
613
539
  };
614
540
  }
615
541
  var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
616
- var BASE_COST_BUFFER_BPS = 100n;
617
- var BPS = 10000n;
618
- var withBuffer = (x) => x * (BPS + BASE_COST_BUFFER_BPS) / BPS;
619
542
  function routeEthNonBase() {
620
543
  return {
621
544
  async preflight(p, ctx) {
@@ -629,16 +552,7 @@ function routeEthNonBase() {
629
552
  },
630
553
  { ctx: { token: p.token } }
631
554
  );
632
- const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
633
- const baseToken = await wrapAs3(
634
- "CONTRACT",
635
- OP_DEPOSITS.ethNonBase.baseToken,
636
- () => bh.baseToken(ctx.chainIdL2),
637
- {
638
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
639
- message: "Failed to read base token."
640
- }
641
- );
555
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
642
556
  await wrapAs3(
643
557
  "VALIDATION",
644
558
  OP_DEPOSITS.ethNonBase.assertNonEthBase,
@@ -671,61 +585,48 @@ function routeEthNonBase() {
671
585
  return;
672
586
  },
673
587
  async build(p, ctx) {
674
- const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
675
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
676
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
677
- const baseToken = await wrapAs3(
678
- "CONTRACT",
679
- OP_DEPOSITS.ethNonBase.baseToken,
680
- () => bh.baseToken(ctx.chainIdL2),
681
- {
682
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
683
- message: "Failed to read base token."
684
- }
685
- );
686
- const rawBaseCost = await wrapAs3(
588
+ const l1Signer = ctx.client.getL1Signer();
589
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
590
+ const l2TxModel = {
591
+ to: p.to ?? ctx.sender,
592
+ from: ctx.sender,
593
+ data: "0x",
594
+ value: 0n
595
+ };
596
+ const l2GasParams = await quoteL2Gas3({
597
+ ctx,
598
+ route: "eth-nonbase",
599
+ l2TxForModeling: l2TxModel,
600
+ overrideGasLimit: ctx.l2GasLimit
601
+ });
602
+ if (!l2GasParams) throw new Error("Failed to estimate L2 gas parameters.");
603
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
604
+ const mintValue = baseCost + ctx.operatorTip;
605
+ const approvals = [];
606
+ const steps = [];
607
+ const erc20Base = new Contract(baseToken, IERC20_default, l1Signer);
608
+ const allowance = await wrapAs3(
687
609
  "RPC",
688
- OP_DEPOSITS.ethNonBase.baseCost,
689
- () => bh.l2TransactionBaseCost(
690
- ctx.chainIdL2,
691
- gasPriceForBaseCost,
692
- ctx.l2GasLimit,
693
- ctx.gasPerPubdata
694
- ),
610
+ OP_DEPOSITS.ethNonBase.allowanceBase,
611
+ () => erc20Base.allowance(ctx.sender, ctx.l1AssetRouter),
695
612
  {
696
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
697
- message: "Could not fetch L2 base cost."
613
+ ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
614
+ message: "Failed to read base-token allowance."
698
615
  }
699
616
  );
700
- const baseCost = BigInt(rawBaseCost);
701
- const mintValueRaw = baseCost + ctx.operatorTip;
702
- const mintValue = withBuffer(mintValueRaw);
703
- const approvals = [];
704
- const steps = [];
705
- {
706
- const erc20 = new Contract(baseToken, IERC20_default, ctx.client.getL1Signer());
707
- const allowance = await wrapAs3(
708
- "RPC",
709
- OP_DEPOSITS.ethNonBase.allowanceBase,
710
- () => erc20.allowance(ctx.sender, ctx.l1AssetRouter),
711
- {
712
- ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
713
- message: "Failed to read base-token allowance."
617
+ if (allowance < mintValue) {
618
+ approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
619
+ steps.push({
620
+ key: `approve:${baseToken}`,
621
+ kind: "approve",
622
+ description: `Approve base token for fees (mintValue)`,
623
+ tx: {
624
+ to: baseToken,
625
+ data: erc20Base.interface.encodeFunctionData("approve", [ctx.l1AssetRouter, mintValue]),
626
+ from: ctx.sender,
627
+ ...ctx.gasOverrides
714
628
  }
715
- );
716
- if (allowance < mintValue) {
717
- approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
718
- const data = erc20.interface.encodeFunctionData("approve", [
719
- ctx.l1AssetRouter,
720
- mintValue
721
- ]);
722
- steps.push({
723
- key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
724
- kind: "approve",
725
- description: `Approve base token for mintValue`,
726
- tx: { to: baseToken, data, from: ctx.sender, ...txOverrides }
727
- });
728
- }
629
+ });
729
630
  }
730
631
  const secondBridgeCalldata = await wrapAs3(
731
632
  "INTERNAL",
@@ -739,69 +640,64 @@ function routeEthNonBase() {
739
640
  }
740
641
  }
741
642
  );
742
- const outer = {
643
+ const requestStruct = {
743
644
  chainId: ctx.chainIdL2,
744
645
  mintValue,
745
- l2Value: 0n,
746
- l2GasLimit: ctx.l2GasLimit,
646
+ l2Value: p.amount,
647
+ l2GasLimit: l2GasParams.gasLimit,
747
648
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
748
649
  refundRecipient: ctx.refundRecipient,
749
650
  secondBridgeAddress: ctx.l1AssetRouter,
750
651
  secondBridgeValue: p.amount,
751
652
  secondBridgeCalldata
752
653
  };
753
- const dataTwo = new Contract(
654
+ const data = new Contract(
754
655
  ctx.bridgehub,
755
656
  IBridgehub_default,
756
657
  ctx.client.l1
757
- ).interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
758
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
759
- const bridgeTx = {
658
+ ).interface.encodeFunctionData("requestL2TransactionTwoBridges", [requestStruct]);
659
+ const l1TxCandidate = {
760
660
  to: ctx.bridgehub,
761
- data: dataTwo,
661
+ data,
762
662
  value: p.amount,
763
663
  // base ≠ ETH ⇒ msg.value == secondBridgeValue
764
664
  from: ctx.sender,
765
- ...txOverrides
665
+ ...ctx.gasOverrides
766
666
  };
767
- if (overrideGasLimit != null) {
768
- bridgeTx.gasLimit = overrideGasLimit;
769
- resolvedL1GasLimit = overrideGasLimit;
770
- } else {
771
- try {
772
- const est = await wrapAs3(
773
- "RPC",
774
- OP_DEPOSITS.ethNonBase.estGas,
775
- () => ctx.client.l1.estimateGas(bridgeTx),
776
- {
777
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
778
- message: "Failed to estimate gas for Bridgehub request."
779
- }
780
- );
781
- const buffered = BigInt(est) * 115n / 100n;
782
- bridgeTx.gasLimit = buffered;
783
- resolvedL1GasLimit = buffered;
784
- } catch {
785
- }
667
+ const l1GasParams = await quoteL1Gas2({
668
+ ctx,
669
+ tx: l1TxCandidate,
670
+ overrides: ctx.gasOverrides,
671
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
672
+ });
673
+ if (l1GasParams) {
674
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
675
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
676
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
786
677
  }
787
678
  steps.push({
788
679
  key: "bridgehub:two-bridges:eth-nonbase",
789
680
  kind: "bridgehub:two-bridges",
790
681
  description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
791
- tx: bridgeTx
682
+ tx: l1TxCandidate
683
+ });
684
+ const fees = buildFeeBreakdown({
685
+ feeToken: baseToken,
686
+ l1Gas: l1GasParams,
687
+ l2Gas: l2GasParams,
688
+ l2BaseCost: baseCost,
689
+ operatorTip: ctx.operatorTip,
690
+ mintValue
792
691
  });
793
692
  return {
794
693
  steps,
795
694
  approvals,
796
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
695
+ fees
797
696
  };
798
697
  }
799
698
  };
800
699
  }
801
700
  var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
802
- var BASE_COST_BUFFER_BPS2 = 100n;
803
- var BPS2 = 10000n;
804
- var withBuffer2 = (x) => x * (BPS2 + BASE_COST_BUFFER_BPS2) / BPS2;
805
701
  function routeErc20Base() {
806
702
  return {
807
703
  async preflight(p, ctx) {
@@ -815,16 +711,7 @@ function routeErc20Base() {
815
711
  },
816
712
  { ctx: { token: p.token } }
817
713
  );
818
- const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
819
- const baseToken = await wrapAs4(
820
- "CONTRACT",
821
- OP_DEPOSITS.base.baseToken,
822
- () => bh.baseToken(ctx.chainIdL2),
823
- {
824
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
825
- message: "Failed to read base token."
826
- }
827
- );
714
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
828
715
  await wrapAs4(
829
716
  "VALIDATION",
830
717
  OP_DEPOSITS.base.assertMatchesBase,
@@ -838,41 +725,27 @@ function routeErc20Base() {
838
725
  return;
839
726
  },
840
727
  async build(p, ctx) {
841
- const bh = new Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
842
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
843
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
844
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
845
- const baseToken = await wrapAs4(
846
- "CONTRACT",
847
- OP_DEPOSITS.base.baseToken,
848
- () => bh.baseToken(ctx.chainIdL2),
849
- {
850
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
851
- message: "Failed to read base token."
852
- }
853
- );
854
- const rawBaseCost = await wrapAs4(
855
- "RPC",
856
- OP_DEPOSITS.base.baseCost,
857
- () => bh.l2TransactionBaseCost(
858
- ctx.chainIdL2,
859
- gasPriceForBaseCost,
860
- ctx.l2GasLimit,
861
- ctx.gasPerPubdata
862
- ),
863
- {
864
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
865
- message: "Could not fetch L2 base cost from Bridgehub."
866
- }
867
- );
868
- const baseCost = BigInt(rawBaseCost);
869
- const l2Value = p.amount;
870
- const rawMintValue = baseCost + ctx.operatorTip + l2Value;
871
- const mintValue = withBuffer2(rawMintValue);
728
+ const l1Signer = ctx.client.getL1Signer();
729
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
730
+ const l2TxModel = {
731
+ to: p.to ?? ctx.sender,
732
+ from: ctx.sender,
733
+ data: "0x",
734
+ value: 0n
735
+ };
736
+ const l2GasParams = await quoteL2Gas3({
737
+ ctx,
738
+ route: "erc20-base",
739
+ l2TxForModeling: l2TxModel,
740
+ overrideGasLimit: ctx.l2GasLimit
741
+ });
742
+ if (!l2GasParams) throw new Error("Failed to estimate L2 gas parameters.");
743
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
744
+ const mintValue = baseCost + ctx.operatorTip + p.amount;
872
745
  const approvals = [];
873
746
  const steps = [];
874
747
  {
875
- const erc20 = new Contract(baseToken, IERC20_default, ctx.client.getL1Signer());
748
+ const erc20 = new Contract(baseToken, IERC20_default, l1Signer);
876
749
  const allowance = await wrapAs4(
877
750
  "RPC",
878
751
  OP_DEPOSITS.base.allowance,
@@ -884,70 +757,70 @@ function routeErc20Base() {
884
757
  );
885
758
  if (allowance < mintValue) {
886
759
  approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
887
- const data2 = erc20.interface.encodeFunctionData("approve", [
888
- ctx.l1AssetRouter,
889
- mintValue
890
- ]);
891
760
  steps.push({
892
761
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
893
762
  kind: "approve",
894
763
  description: "Approve base token for mintValue",
895
- tx: { to: baseToken, data: data2, from: ctx.sender, ...txOverrides }
764
+ tx: {
765
+ to: baseToken,
766
+ data: erc20.interface.encodeFunctionData("approve", [ctx.l1AssetRouter, mintValue]),
767
+ from: ctx.sender,
768
+ ...ctx.gasOverrides
769
+ }
896
770
  });
897
771
  }
898
772
  }
899
- const req = buildDirectRequestStruct({
773
+ const requestStruct = buildDirectRequestStruct({
900
774
  chainId: ctx.chainIdL2,
901
775
  mintValue,
902
- l2GasLimit: ctx.l2GasLimit,
776
+ l2GasLimit: l2GasParams.gasLimit,
903
777
  gasPerPubdata: ctx.gasPerPubdata,
904
778
  refundRecipient: ctx.refundRecipient,
905
779
  l2Contract: p.to ?? ctx.sender,
906
- l2Value
780
+ l2Value: p.amount
907
781
  });
908
782
  const data = new Contract(
909
783
  ctx.bridgehub,
910
784
  IBridgehub_default,
911
785
  ctx.client.l1
912
- ).interface.encodeFunctionData("requestL2TransactionDirect", [req]);
913
- const tx = {
786
+ ).interface.encodeFunctionData("requestL2TransactionDirect", [requestStruct]);
787
+ const l1TxCandidate = {
914
788
  to: ctx.bridgehub,
915
789
  data,
916
790
  value: 0n,
917
791
  // base token is ERC-20 ⇒ msg.value MUST be 0
918
792
  from: ctx.sender,
919
- ...txOverrides
793
+ ...ctx.gasOverrides
920
794
  };
921
- if (overrideGasLimit != null) {
922
- tx.gasLimit = overrideGasLimit;
923
- resolvedL1GasLimit = overrideGasLimit;
924
- } else {
925
- try {
926
- const est = await wrapAs4(
927
- "RPC",
928
- OP_DEPOSITS.base.estGas,
929
- () => ctx.client.l1.estimateGas(tx),
930
- {
931
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
932
- message: "Failed to estimate gas for Bridgehub request."
933
- }
934
- );
935
- const buffered = BigInt(est) * 115n / 100n;
936
- tx.gasLimit = buffered;
937
- resolvedL1GasLimit = buffered;
938
- } catch {
939
- }
795
+ const l1GasParams = await quoteL1Gas2({
796
+ ctx,
797
+ tx: l1TxCandidate,
798
+ overrides: ctx.gasOverrides,
799
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
800
+ });
801
+ if (l1GasParams) {
802
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
803
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
804
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
940
805
  }
941
806
  steps.push({
942
807
  key: "bridgehub:direct:erc20-base",
943
808
  kind: "bridgehub:direct",
944
809
  description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
945
- tx
810
+ tx: l1TxCandidate
811
+ });
812
+ const fees = buildFeeBreakdown({
813
+ feeToken: baseToken,
814
+ l1Gas: l1GasParams,
815
+ l2Gas: l2GasParams,
816
+ l2BaseCost: baseCost,
817
+ operatorTip: ctx.operatorTip,
818
+ mintValue
946
819
  });
947
820
  return {
948
821
  steps,
949
822
  approvals,
950
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
823
+ fees
951
824
  };
952
825
  }
953
826
  };
@@ -966,37 +839,19 @@ function createDepositsResource(client) {
966
839
  const ctx = await commonCtx(p, client);
967
840
  const route = ctx.route;
968
841
  await ROUTES[route].preflight?.(p, ctx);
969
- const { steps, approvals, quoteExtras } = await ROUTES[route].build(p, ctx);
970
- const { baseCost, mintValue } = quoteExtras;
971
- const fallbackGasLimit = quoteExtras.l1GasLimit;
972
- const resolveGasLimit = () => {
973
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
974
- for (let i = steps.length - 1; i >= 0; i--) {
975
- const candidate = steps[i].tx.gasLimit;
976
- if (candidate == null) continue;
977
- if (typeof candidate === "bigint") return candidate;
978
- try {
979
- return BigInt(candidate.toString());
980
- } catch {
981
- }
982
- }
983
- if (fallbackGasLimit != null) return fallbackGasLimit;
984
- return ctx.l2GasLimit;
985
- };
986
- const gasLimit = resolveGasLimit();
842
+ const { steps, approvals, fees } = await ROUTES[route].build(p, ctx);
987
843
  return {
988
844
  route: ctx.route,
989
845
  summary: {
990
846
  route: ctx.route,
991
847
  approvalsNeeded: approvals,
992
- baseCost,
993
- mintValue,
994
- gasPerPubdata: ctx.gasPerPubdata,
995
- fees: {
996
- gasLimit,
997
- maxFeePerGas: ctx.fee.maxFeePerGas,
998
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
999
- }
848
+ amounts: {
849
+ transfer: { token: p.token, amount: p.amount }
850
+ },
851
+ fees,
852
+ // Legacy fields (maintained for backward compatibility)
853
+ baseCost: fees.l2?.baseCost,
854
+ mintValue: fees.mintValue
1000
855
  },
1001
856
  steps
1002
857
  };
@@ -1065,7 +920,7 @@ function createDepositsResource(client) {
1065
920
  step.tx.maxPriorityFeePerGas = overrides.maxPriorityFeePerGas;
1066
921
  }
1067
922
  }
1068
- if (!step.tx.gasLimit) {
923
+ if (!p.l1TxOverrides?.gasLimit) {
1069
924
  try {
1070
925
  const est = await client.l1.estimateGas(step.tx);
1071
926
  step.tx.gasLimit = BigInt(est) * 115n / 100n;
@@ -1277,10 +1132,7 @@ async function commonCtx2(p, client) {
1277
1132
  const { chainId } = await client.l2.getNetwork();
1278
1133
  const chainIdL2 = BigInt(chainId);
1279
1134
  const baseIsEth = await isEthBasedChain(client.l2, l2NativeTokenVault);
1280
- const fee = await getL2FeeOverrides(client, p.l2TxOverrides);
1281
1135
  const route = pickWithdrawRoute({ token: p.token, baseIsEth });
1282
- const l2GasLimit = p.l2GasLimit ?? 300000n;
1283
- const gasBufferPct = 15;
1284
1136
  return {
1285
1137
  client,
1286
1138
  bridgehub,
@@ -1293,29 +1145,50 @@ async function commonCtx2(p, client) {
1293
1145
  l2NativeTokenVault,
1294
1146
  l2BaseTokenSystem,
1295
1147
  baseIsEth,
1296
- l2GasLimit,
1297
- gasBufferPct,
1298
- fee
1148
+ gasOverrides: p.l2TxOverrides
1149
+ };
1150
+ }
1151
+
1152
+ // src/adapters/ethers/resources/withdrawals/services/gas.ts
1153
+ async function quoteL2Gas4(input) {
1154
+ const { ctx, tx } = input;
1155
+ const estimator = ethersToGasEstimator(ctx.client.l2);
1156
+ return quoteL2Gas2({
1157
+ estimator,
1158
+ tx: toCoreTx(tx),
1159
+ overrides: ctx.gasOverrides
1160
+ });
1161
+ }
1162
+
1163
+ // src/adapters/ethers/resources/withdrawals/services/fees.ts
1164
+ function buildFeeBreakdown2(p) {
1165
+ const l2Total = p.l2Gas?.maxCost ?? 0n;
1166
+ const l2 = {
1167
+ total: l2Total,
1168
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
1169
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
1170
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas
1171
+ };
1172
+ return {
1173
+ token: p.feeToken,
1174
+ maxTotal: l2Total,
1175
+ l2
1299
1176
  };
1300
1177
  }
1178
+
1179
+ // src/adapters/ethers/resources/withdrawals/routes/eth.ts
1301
1180
  var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
1302
1181
  function routeEthBase() {
1303
1182
  return {
1304
1183
  async build(p, ctx) {
1305
1184
  const steps = [];
1306
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
1307
- const base = new Contract(
1308
- L2_BASE_TOKEN_ADDRESS,
1309
- new Interface(IBaseToken_default),
1310
- ctx.client.l2
1311
- );
1312
- const toL1 = p.to ?? ctx.sender;
1185
+ const base = (await ctx.client.contracts()).l2BaseTokenSystem;
1313
1186
  const data = await wrapAs5(
1314
1187
  "INTERNAL",
1315
1188
  OP_WITHDRAWALS.eth.encodeWithdraw,
1316
- () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [toL1])),
1189
+ () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [p.to ?? ctx.sender])),
1317
1190
  {
1318
- ctx: { where: "L2BaseToken.withdraw", to: toL1 },
1191
+ ctx: { where: "L2BaseToken.withdraw", to: p.to ?? ctx.sender },
1319
1192
  message: "Failed to encode ETH withdraw calldata."
1320
1193
  }
1321
1194
  );
@@ -1323,34 +1196,25 @@ function routeEthBase() {
1323
1196
  to: L2_BASE_TOKEN_ADDRESS,
1324
1197
  data,
1325
1198
  from: ctx.sender,
1326
- value: p.amount,
1327
- maxFeePerGas,
1328
- maxPriorityFeePerGas
1199
+ value: p.amount
1329
1200
  };
1330
- if (overrideGasLimit != null) {
1331
- tx.gasLimit = overrideGasLimit;
1332
- } else {
1333
- try {
1334
- const est = await wrapAs5(
1335
- "RPC",
1336
- OP_WITHDRAWALS.eth.estGas,
1337
- () => ctx.client.l2.estimateGas(tx),
1338
- {
1339
- ctx: { where: "l2.estimateGas", to: L2_BASE_TOKEN_ADDRESS },
1340
- message: "Failed to estimate gas for L2 ETH withdraw."
1341
- }
1342
- );
1343
- tx.gasLimit = BigInt(est) * 115n / 100n;
1344
- } catch {
1345
- }
1201
+ const gas = await quoteL2Gas4({ ctx, tx });
1202
+ if (gas) {
1203
+ tx.gasLimit = gas.gasLimit;
1204
+ tx.maxFeePerGas = gas.maxFeePerGas;
1205
+ tx.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
1346
1206
  }
1207
+ const fees = buildFeeBreakdown2({
1208
+ feeToken: L2_BASE_TOKEN_ADDRESS,
1209
+ l2Gas: gas
1210
+ });
1347
1211
  steps.push({
1348
1212
  key: "l2-base-token:withdraw",
1349
1213
  kind: "l2-base-token:withdraw",
1350
1214
  description: "Withdraw ETH via L2 Base Token System",
1351
1215
  tx
1352
1216
  });
1353
- return { steps, approvals: [], quoteExtras: {} };
1217
+ return { steps, approvals: [], fees };
1354
1218
  }
1355
1219
  };
1356
1220
  }
@@ -1363,8 +1227,6 @@ function routeErc20NonBase2() {
1363
1227
  async build(p, ctx) {
1364
1228
  const steps = [];
1365
1229
  const approvals = [];
1366
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
1367
- const txOverrides = overrideGasLimit != null ? { maxFeePerGas, maxPriorityFeePerGas, gasLimit: overrideGasLimit } : { maxFeePerGas, maxPriorityFeePerGas };
1368
1230
  const erc20 = new Contract(p.token, IERC20_default, ctx.client.getL2Signer());
1369
1231
  const current = await wrapAs6(
1370
1232
  "CONTRACT",
@@ -1386,14 +1248,25 @@ function routeErc20NonBase2() {
1386
1248
  ctx.l2NativeTokenVault,
1387
1249
  p.amount
1388
1250
  ]);
1251
+ const approveTx = {
1252
+ to: p.token,
1253
+ data,
1254
+ from: ctx.sender
1255
+ };
1256
+ const approveGas = await quoteL2Gas4({ ctx, tx: approveTx });
1257
+ if (approveGas) {
1258
+ approveTx.gasLimit = approveGas.gasLimit;
1259
+ approveTx.maxFeePerGas = approveGas.maxFeePerGas;
1260
+ approveTx.maxPriorityFeePerGas = approveGas.maxPriorityFeePerGas;
1261
+ }
1389
1262
  steps.push({
1390
1263
  key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
1391
1264
  kind: "approve:l2",
1392
1265
  description: `Approve ${p.amount} to NativeTokenVault`,
1393
- tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
1266
+ tx: approveTx
1394
1267
  });
1395
1268
  }
1396
- const ntv = new Contract(ctx.l2NativeTokenVault, L2NativeTokenVault_default, ctx.client.l2);
1269
+ const ntv = (await ctx.client.contracts()).l2NativeTokenVault;
1397
1270
  const assetId = await wrapAs6(
1398
1271
  "CONTRACT",
1399
1272
  OP_WITHDRAWALS.erc20.ensureRegistered,
@@ -1407,17 +1280,14 @@ function routeErc20NonBase2() {
1407
1280
  "INTERNAL",
1408
1281
  OP_WITHDRAWALS.erc20.encodeAssetData,
1409
1282
  () => Promise.resolve(
1410
- AbiCoder.defaultAbiCoder().encode(
1411
- ["uint256", "address", "address"],
1412
- [p.amount, p.to ?? ctx.sender, p.token]
1413
- )
1283
+ encodeNativeTokenVaultTransferData(p.amount, p.to ?? ctx.sender, p.token)
1414
1284
  ),
1415
1285
  {
1416
1286
  ctx: { where: "AbiCoder.encode", token: p.token, to: p.to ?? ctx.sender },
1417
1287
  message: "Failed to encode burn/withdraw asset data."
1418
1288
  }
1419
1289
  );
1420
- const l2ar = new Contract(ctx.l2AssetRouter, IL2AssetRouter_default, ctx.client.l2);
1290
+ const l2ar = (await ctx.client.contracts()).l2AssetRouter;
1421
1291
  const dataWithdraw = await wrapAs6(
1422
1292
  "INTERNAL",
1423
1293
  OP_WITHDRAWALS.erc20.encodeWithdraw,
@@ -1430,85 +1300,29 @@ function routeErc20NonBase2() {
1430
1300
  const withdrawTx = {
1431
1301
  to: ctx.l2AssetRouter,
1432
1302
  data: dataWithdraw,
1433
- from: ctx.sender,
1434
- ...txOverrides
1303
+ from: ctx.sender
1435
1304
  };
1305
+ const withdrawGas = await quoteL2Gas4({ ctx, tx: withdrawTx });
1306
+ if (withdrawGas) {
1307
+ withdrawTx.gasLimit = withdrawGas.gasLimit;
1308
+ withdrawTx.maxFeePerGas = withdrawGas.maxFeePerGas;
1309
+ withdrawTx.maxPriorityFeePerGas = withdrawGas.maxPriorityFeePerGas;
1310
+ }
1436
1311
  steps.push({
1437
1312
  key: "l2-asset-router:withdraw",
1438
1313
  kind: "l2-asset-router:withdraw",
1439
1314
  description: "Burn on L2 & send L2\u2192L1 message",
1440
1315
  tx: withdrawTx
1441
1316
  });
1442
- return { steps, approvals, quoteExtras: {} };
1443
- }
1444
- };
1445
- }
1446
- var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1447
- function routeEthNonBase2() {
1448
- return {
1449
- async preflight(p, ctx) {
1450
- await wrapAs7(
1451
- "VALIDATION",
1452
- OP_WITHDRAWALS.ethNonBase.assertNonEthBase,
1453
- () => {
1454
- if (p.token.toLowerCase() !== L2_BASE_TOKEN_ADDRESS.toLowerCase()) {
1455
- throw new Error("eth-nonbase route requires the L2 base-token alias (0x\u2026800A).");
1456
- }
1457
- if (ctx.baseIsEth) {
1458
- throw new Error("eth-nonbase route requires chain base \u2260 ETH.");
1459
- }
1460
- },
1461
- { ctx: { token: p.token, baseIsEth: ctx.baseIsEth } }
1462
- );
1463
- },
1464
- async build(p, ctx) {
1465
- const steps = [];
1466
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
1467
- const toL1 = p.to ?? ctx.sender;
1468
- const iface = new Interface(IBaseToken_default);
1469
- const data = await wrapAs7(
1470
- "INTERNAL",
1471
- OP_WITHDRAWALS.eth.encodeWithdraw,
1472
- // reuse label for base-token system call
1473
- () => Promise.resolve(iface.encodeFunctionData("withdraw", [toL1])),
1474
- { ctx: { where: "L2BaseToken.withdraw", to: toL1 } }
1475
- );
1476
- const tx = {
1477
- to: L2_BASE_TOKEN_ADDRESS,
1478
- data,
1479
- from: ctx.sender,
1480
- value: p.amount,
1481
- maxFeePerGas,
1482
- maxPriorityFeePerGas
1483
- };
1484
- if (overrideGasLimit != null) {
1485
- tx.gasLimit = overrideGasLimit;
1486
- } else {
1487
- try {
1488
- const est = await wrapAs7(
1489
- "RPC",
1490
- OP_WITHDRAWALS.eth.estGas,
1491
- () => ctx.client.l2.estimateGas(tx),
1492
- {
1493
- ctx: { where: "l2.estimateGas", to: L2_BASE_TOKEN_ADDRESS },
1494
- message: "Failed to estimate gas for L2 base-token withdraw."
1495
- }
1496
- );
1497
- tx.gasLimit = BigInt(est) * 115n / 100n;
1498
- } catch {
1499
- }
1500
- }
1501
- steps.push({
1502
- key: "l2-base-token:withdraw",
1503
- kind: "l2-base-token:withdraw",
1504
- description: "Withdraw base token via L2 Base Token System (base \u2260 ETH)",
1505
- tx
1317
+ const fees = buildFeeBreakdown2({
1318
+ feeToken: await ctx.client.baseToken(ctx.chainIdL2),
1319
+ l2Gas: withdrawGas
1506
1320
  });
1507
- return { steps, approvals: [], quoteExtras: {} };
1321
+ return { steps, approvals, fees };
1508
1322
  }
1509
1323
  };
1510
1324
  }
1511
- var { wrapAs: wrapAs8 } = createErrorHandlers("withdrawals");
1325
+ var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1512
1326
  var IL1NullifierMini = [
1513
1327
  "function isWithdrawalFinalized(uint256,uint256,uint256) view returns (bool)"
1514
1328
  ];
@@ -1516,7 +1330,7 @@ function createFinalizationServices(client) {
1516
1330
  const { l1, l2, signer } = client;
1517
1331
  return {
1518
1332
  async fetchFinalizeDepositParams(l2TxHash) {
1519
- const parsed = await wrapAs8(
1333
+ const parsed = await wrapAs7(
1520
1334
  "RPC",
1521
1335
  OP_WITHDRAWALS.finalize.fetchParams.receipt,
1522
1336
  () => client.zks.getReceiptWithL2ToL1(l2TxHash),
@@ -1533,7 +1347,7 @@ function createFinalizationServices(client) {
1533
1347
  context: { l2TxHash }
1534
1348
  });
1535
1349
  }
1536
- const ev = await wrapAs8(
1350
+ const ev = await wrapAs7(
1537
1351
  "INTERNAL",
1538
1352
  OP_WITHDRAWALS.finalize.fetchParams.findMessage,
1539
1353
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
@@ -1543,7 +1357,7 @@ function createFinalizationServices(client) {
1543
1357
  message: "Failed to locate L1MessageSent event in L2 receipt."
1544
1358
  }
1545
1359
  );
1546
- const message = await wrapAs8(
1360
+ const message = await wrapAs7(
1547
1361
  "INTERNAL",
1548
1362
  OP_WITHDRAWALS.finalize.fetchParams.decodeMessage,
1549
1363
  () => Promise.resolve(AbiCoder.defaultAbiCoder().decode(["bytes"], ev.data)[0]),
@@ -1552,7 +1366,7 @@ function createFinalizationServices(client) {
1552
1366
  message: "Failed to decode withdrawal message."
1553
1367
  }
1554
1368
  );
1555
- const raw = await wrapAs8(
1369
+ const raw = await wrapAs7(
1556
1370
  "RPC",
1557
1371
  OP_WITHDRAWALS.finalize.fetchParams.rawReceipt,
1558
1372
  () => client.zks.getReceiptWithL2ToL1(l2TxHash),
@@ -1569,7 +1383,7 @@ function createFinalizationServices(client) {
1569
1383
  context: { l2TxHash }
1570
1384
  });
1571
1385
  }
1572
- const idx = await wrapAs8(
1386
+ const idx = await wrapAs7(
1573
1387
  "INTERNAL",
1574
1388
  OP_WITHDRAWALS.finalize.fetchParams.messengerIndex,
1575
1389
  () => Promise.resolve(messengerLogIndex(raw, { index: 0, messenger: L1_MESSENGER_ADDRESS })),
@@ -1578,7 +1392,7 @@ function createFinalizationServices(client) {
1578
1392
  message: "Failed to derive messenger log index."
1579
1393
  }
1580
1394
  );
1581
- const proof = await wrapAs8(
1395
+ const proof = await wrapAs7(
1582
1396
  "RPC",
1583
1397
  OP_WITHDRAWALS.finalize.fetchParams.proof,
1584
1398
  () => client.zks.getL2ToL1LogProof(l2TxHash, idx),
@@ -1587,7 +1401,7 @@ function createFinalizationServices(client) {
1587
1401
  message: "Failed to fetch L2\u2192L1 log proof."
1588
1402
  }
1589
1403
  );
1590
- const { chainId } = await wrapAs8(
1404
+ const { chainId } = await wrapAs7(
1591
1405
  "RPC",
1592
1406
  OP_WITHDRAWALS.finalize.fetchParams.network,
1593
1407
  () => l2.getNetwork(),
@@ -1606,7 +1420,7 @@ function createFinalizationServices(client) {
1606
1420
  message,
1607
1421
  merkleProof: proof.proof
1608
1422
  };
1609
- const { l1Nullifier } = await wrapAs8(
1423
+ const { l1Nullifier } = await wrapAs7(
1610
1424
  "INTERNAL",
1611
1425
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1612
1426
  () => client.ensureAddresses(),
@@ -1618,7 +1432,7 @@ function createFinalizationServices(client) {
1618
1432
  return { params, nullifier: l1Nullifier };
1619
1433
  },
1620
1434
  async simulateFinalizeReadiness(params) {
1621
- const { l1Nullifier } = await wrapAs8(
1435
+ const { l1Nullifier } = await wrapAs7(
1622
1436
  "INTERNAL",
1623
1437
  OP_WITHDRAWALS.finalize.readiness.ensureAddresses,
1624
1438
  () => client.ensureAddresses(),
@@ -1630,7 +1444,7 @@ function createFinalizationServices(client) {
1630
1444
  const done = await (async () => {
1631
1445
  try {
1632
1446
  const cMini = new Contract(l1Nullifier, IL1NullifierMini, l1);
1633
- const isFinalized = await wrapAs8(
1447
+ const isFinalized = await wrapAs7(
1634
1448
  "RPC",
1635
1449
  OP_WITHDRAWALS.finalize.readiness.isFinalized,
1636
1450
  () => cMini.isWithdrawalFinalized(
@@ -1658,7 +1472,7 @@ function createFinalizationServices(client) {
1658
1472
  }
1659
1473
  },
1660
1474
  async isWithdrawalFinalized(key) {
1661
- const { l1Nullifier } = await wrapAs8(
1475
+ const { l1Nullifier } = await wrapAs7(
1662
1476
  "INTERNAL",
1663
1477
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1664
1478
  () => client.ensureAddresses(),
@@ -1668,7 +1482,7 @@ function createFinalizationServices(client) {
1668
1482
  }
1669
1483
  );
1670
1484
  const c = new Contract(l1Nullifier, IL1NullifierMini, l1);
1671
- return await wrapAs8(
1485
+ return await wrapAs7(
1672
1486
  "RPC",
1673
1487
  OP_WITHDRAWALS.finalize.isFinalized,
1674
1488
  () => c.isWithdrawalFinalized(key.chainIdL2, key.l2BatchNumber, key.l2MessageIndex),
@@ -1679,7 +1493,7 @@ function createFinalizationServices(client) {
1679
1493
  );
1680
1494
  },
1681
1495
  async estimateFinalization(params) {
1682
- const { l1Nullifier } = await wrapAs8(
1496
+ const { l1Nullifier } = await wrapAs7(
1683
1497
  "INTERNAL",
1684
1498
  OP_WITHDRAWALS.finalize.estimate,
1685
1499
  () => client.ensureAddresses(),
@@ -1690,7 +1504,7 @@ function createFinalizationServices(client) {
1690
1504
  );
1691
1505
  const signer2 = client.getL1Signer();
1692
1506
  const c = new Contract(l1Nullifier, IL1Nullifier_default, signer2);
1693
- const gasLimit = await wrapAs8(
1507
+ const gasLimit = await wrapAs7(
1694
1508
  "RPC",
1695
1509
  OP_WITHDRAWALS.finalize.estimate,
1696
1510
  () => c.finalizeDeposit.estimateGas(params),
@@ -1705,7 +1519,7 @@ function createFinalizationServices(client) {
1705
1519
  message: "Failed to estimate gas for finalizeDeposit."
1706
1520
  }
1707
1521
  );
1708
- const feeData = await wrapAs8("RPC", OP_WITHDRAWALS.finalize.estimate, () => l1.getFeeData(), {
1522
+ const feeData = await wrapAs7("RPC", OP_WITHDRAWALS.finalize.estimate, () => l1.getFeeData(), {
1709
1523
  ctx: { where: "l1.getFeeData" },
1710
1524
  message: "Failed to estimate fee data for finalizeDeposit."
1711
1525
  });
@@ -1726,7 +1540,7 @@ function createFinalizationServices(client) {
1726
1540
  };
1727
1541
  },
1728
1542
  async finalizeDeposit(params) {
1729
- const { l1Nullifier } = await wrapAs8(
1543
+ const { l1Nullifier } = await wrapAs7(
1730
1544
  "INTERNAL",
1731
1545
  OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
1732
1546
  () => client.ensureAddresses(),
@@ -1782,10 +1596,8 @@ function createFinalizationServices(client) {
1782
1596
 
1783
1597
  // src/adapters/ethers/resources/withdrawals/index.ts
1784
1598
  var ROUTES2 = {
1785
- "eth-base": routeEthBase(),
1599
+ base: routeEthBase(),
1786
1600
  // BaseTokenSystem.withdraw, chain base = ETH
1787
- "eth-nonbase": routeEthNonBase2(),
1788
- // BaseTokenSystem.withdraw, chain base ≠ ETH
1789
1601
  "erc20-nonbase": routeErc20NonBase2()
1790
1602
  // AssetRouter.withdraw for non-base ERC-20s
1791
1603
  };
@@ -1795,32 +1607,19 @@ function createWithdrawalsResource(client) {
1795
1607
  async function buildPlan(p) {
1796
1608
  const ctx = await commonCtx2(p, client);
1797
1609
  await ROUTES2[ctx.route].preflight?.(p, ctx);
1798
- const { steps, approvals } = await ROUTES2[ctx.route].build(p, ctx);
1799
- const resolveGasLimit = () => {
1800
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
1801
- for (let i = steps.length - 1; i >= 0; i--) {
1802
- const candidate = steps[i].tx.gasLimit;
1803
- if (candidate == null) continue;
1804
- if (typeof candidate === "bigint") return candidate;
1805
- try {
1806
- return BigInt(candidate.toString());
1807
- } catch {
1808
- }
1809
- }
1810
- return void 0;
1811
- };
1812
- const gasLimit = resolveGasLimit();
1813
- const summary = {
1610
+ const { steps, approvals, fees } = await ROUTES2[ctx.route].build(p, ctx);
1611
+ return {
1814
1612
  route: ctx.route,
1815
- approvalsNeeded: approvals,
1816
- suggestedL2GasLimit: ctx.l2GasLimit,
1817
- fees: {
1818
- gasLimit,
1819
- maxFeePerGas: ctx.fee.maxFeePerGas,
1820
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
1821
- }
1613
+ summary: {
1614
+ route: ctx.route,
1615
+ approvalsNeeded: approvals,
1616
+ amounts: {
1617
+ transfer: { token: p.token, amount: p.amount }
1618
+ },
1619
+ fees
1620
+ },
1621
+ steps
1822
1622
  };
1823
- return { route: ctx.route, summary, steps };
1824
1623
  }
1825
1624
  const finalizeCache = /* @__PURE__ */ new Map();
1826
1625
  const quote = (p) => wrap2(
@@ -2184,4 +1983,4 @@ function createEthersSdk(client) {
2184
1983
  };
2185
1984
  }
2186
1985
 
2187
- export { buildDirectRequestStruct, checkBaseCost, classifyReadinessFromRevert, createDepositsResource, createErrorHandlers, createEthersSdk, createFinalizationServices, createWithdrawalsResource, decodeRevert, encodeNTVAssetId, encodeNTVTransferData, encodeNativeTokenVaultAssetId, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs, getFeeOverrides, getGasPriceWei, getL2FeeOverrides, registerErrorAbi, scaleGasLimit, toZKsyncError };
1986
+ export { buildDirectRequestStruct, createDepositsResource, createEthersSdk, createFinalizationServices, createWithdrawalsResource, encodeNTVAssetId, encodeNTVTransferData, encodeNativeTokenVaultAssetId, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs };