@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
@@ -223,35 +223,17 @@ var OP_DEPOSITS = {
223
223
  assertErc20Asset: "deposits.erc20-base:assertErc20Asset",
224
224
  assertMatchesBase: "deposits.erc20-base:assertMatchesBase",
225
225
  baseToken: "deposits.erc20-base:baseToken",
226
- allowance: "deposits.erc20-base:allowance",
227
- baseCost: "deposits.erc20-base:l2TransactionBaseCost",
228
- estGas: "deposits.erc20-base:estimateGas"
229
- },
226
+ allowance: "deposits.erc20-base:allowance"},
230
227
  nonbase: {
231
- baseToken: "deposits.erc20-nonbase:baseToken",
232
- assertNotEthAsset: "deposits.erc20-nonbase:assertNotEthAsset",
233
- allowance: "deposits.erc20-nonbase:allowance",
234
- allowanceFees: "deposits.erc20-nonbase:allowanceFeesBaseToken",
235
- baseCost: "deposits.erc20-nonbase:l2TransactionBaseCost",
236
228
  encodeCalldata: "deposits.erc20-nonbase:encodeSecondBridgeErc20Args",
237
- estGas: "deposits.erc20-nonbase:estimateGas",
238
- assertBaseIsEth: "deposits.erc20-nonbase:assertBaseIsEth",
239
- assertBaseIsErc20: "deposits.erc20-nonbase:assertBaseIsErc20",
240
229
  assertNonBaseToken: "deposits.erc20-nonbase:assertNonBaseToken",
241
230
  allowanceToken: "deposits.erc20-nonbase:allowanceToken",
242
231
  allowanceBase: "deposits.erc20-nonbase:allowanceBase"
243
232
  },
244
- eth: {
245
- baseCost: "deposits.eth:l2TransactionBaseCost",
246
- estGas: "deposits.eth:estimateGas"
247
- },
248
233
  ethNonBase: {
249
- baseToken: "deposits.eth-nonbase:baseToken",
250
- baseCost: "deposits.eth-nonbase:l2TransactionBaseCost",
251
234
  allowanceBase: "deposits.eth-nonbase:allowanceBaseToken",
252
235
  ethBalance: "deposits.eth-nonbase:getEthBalance",
253
236
  encodeCalldata: "deposits.eth-nonbase:encodeSecondBridgeEthArgs",
254
- estGas: "deposits.eth-nonbase:estimateGas",
255
237
  assertEthAsset: "deposits.eth-nonbase:assertEthAsset",
256
238
  assertNonEthBase: "deposits.eth-nonbase:assertNonEthBase",
257
239
  assertEthBalance: "deposits.eth-nonbase:assertEthBalance"
@@ -603,8 +585,12 @@ var TOPIC_L1_MESSAGE_SENT_NEW = k256hex("L1MessageSent(uint256,bytes32,bytes)");
603
585
  var TOPIC_L1_MESSAGE_SENT_LEG = k256hex("L1MessageSent(address,bytes32,bytes)");
604
586
  var TOPIC_CANONICAL_ASSIGNED = "0x779f441679936c5441b671969f37400b8c3ed0071cb47444431bf985754560df";
605
587
  var TOPIC_CANONICAL_SUCCESS = "0xe4def01b981193a97a9e81230d7b9f31812ceaf23f864a828a82c687911cb2df";
606
- var L1_FEE_ESTIMATION_COEF_NUMERATOR = 12;
607
- var L1_FEE_ESTIMATION_COEF_DENOMINATOR = 10;
588
+ var BUFFER = 20n;
589
+ var TX_OVERHEAD_GAS = 10000n;
590
+ var TX_MEMORY_OVERHEAD_GAS = 10n;
591
+ var DEFAULT_PUBDATA_BYTES = 155n;
592
+ var DEFAULT_ABI_BYTES = 400n;
593
+ var SAFE_L1_BRIDGE_GAS = 600000n;
608
594
 
609
595
  // src/core/internal/abis/IBridgehub.ts
610
596
  var IBridgehubABI = [
@@ -5094,7 +5080,183 @@ var MailboxABI = [
5094
5080
  ];
5095
5081
  var Mailbox_default = MailboxABI;
5096
5082
 
5083
+ // src/core/errors/withdrawal-revert-map.ts
5084
+ var REVERT_TO_READINESS = {
5085
+ // Already done
5086
+ WithdrawalAlreadyFinalized: { kind: "FINALIZED" },
5087
+ // Temporary — try later
5088
+ BatchNotExecuted: { kind: "NOT_READY", reason: "batch-not-executed" },
5089
+ LocalRootIsZero: { kind: "NOT_READY", reason: "root-missing" },
5090
+ // Permanent — won’t become ready for this tx
5091
+ WrongL2Sender: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5092
+ InvalidSelector: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5093
+ L2WithdrawalMessageWrongLength: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5094
+ WrongMsgLength: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5095
+ TokenNotLegacy: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5096
+ TokenIsLegacy: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5097
+ InvalidProof: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5098
+ InvalidChainId: { kind: "UNFINALIZABLE", reason: "invalid-chain" },
5099
+ NotSettlementLayer: { kind: "UNFINALIZABLE", reason: "settlement-layer" },
5100
+ // Likely environment mismatch — treat as permanent for this tx
5101
+ OnlyEraSupported: { kind: "UNFINALIZABLE", reason: "unsupported" },
5102
+ LocalRootMustBeZero: { kind: "UNFINALIZABLE", reason: "unsupported" }
5103
+ };
5104
+
5105
+ // src/adapters/ethers/errors/revert.ts
5106
+ var ERROR_IFACES = [];
5107
+ var IFACE_ERROR_STRING = new ethers.Interface(["error Error(string)"]);
5108
+ var IFACE_PANIC = new ethers.Interface(["error Panic(uint256)"]);
5109
+ (function bootstrapDefaultIfaces() {
5110
+ try {
5111
+ ERROR_IFACES.push({
5112
+ name: "IL1Nullifier",
5113
+ iface: new ethers.Interface(IL1Nullifier_default)
5114
+ });
5115
+ } catch {
5116
+ }
5117
+ try {
5118
+ ERROR_IFACES.push({ name: "IERC20", iface: new ethers.Interface(IERC20_default) });
5119
+ } catch {
5120
+ }
5121
+ try {
5122
+ ERROR_IFACES.push({
5123
+ name: "IL1NativeTokenVault",
5124
+ iface: new ethers.Interface(L1NativeTokenVault_default)
5125
+ });
5126
+ } catch {
5127
+ }
5128
+ try {
5129
+ ERROR_IFACES.push({
5130
+ name: "IL2NativeTokenVault",
5131
+ iface: new ethers.Interface(L2NativeTokenVault_default)
5132
+ });
5133
+ } catch {
5134
+ }
5135
+ try {
5136
+ ERROR_IFACES.push({ name: "Mailbox", iface: new ethers.Interface(Mailbox_default) });
5137
+ } catch {
5138
+ }
5139
+ })();
5140
+ function registerErrorAbi(name, abi) {
5141
+ const existing = ERROR_IFACES.findIndex((x) => x.name === name);
5142
+ const entry = { name, iface: new ethers.Interface(abi) };
5143
+ if (existing >= 0) ERROR_IFACES[existing] = entry;
5144
+ else ERROR_IFACES.push(entry);
5145
+ }
5146
+ function extractRevertData(e) {
5147
+ const maybe = (
5148
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5149
+ e?.data?.data ?? e?.error?.data ?? e?.data ?? e?.error?.error?.data ?? e?.info?.error?.data
5150
+ );
5151
+ if (typeof maybe === "string" && maybe.startsWith("0x") && maybe.length >= 10) {
5152
+ return maybe;
5153
+ }
5154
+ return void 0;
5155
+ }
5156
+ function decodeRevert(e) {
5157
+ const data = extractRevertData(e);
5158
+ if (!data) return;
5159
+ const selector = `0x${data.slice(2, 10)}`;
5160
+ try {
5161
+ const parsed = IFACE_ERROR_STRING.parseError(data);
5162
+ if (parsed?.name === "Error") {
5163
+ const args = parsed.args ? Array.from(parsed.args) : void 0;
5164
+ return { selector, name: "Error", args };
5165
+ }
5166
+ } catch {
5167
+ }
5168
+ try {
5169
+ const parsed = IFACE_PANIC.parseError(data);
5170
+ if (parsed?.name === "Panic") {
5171
+ const args = parsed.args ? Array.from(parsed.args) : void 0;
5172
+ return { selector, name: "Panic", args };
5173
+ }
5174
+ } catch {
5175
+ }
5176
+ for (const { name, iface } of ERROR_IFACES) {
5177
+ try {
5178
+ const parsed = iface.parseError(data);
5179
+ if (parsed) {
5180
+ const args = parsed.args ? Array.from(parsed.args) : void 0;
5181
+ return {
5182
+ selector,
5183
+ name: parsed.name,
5184
+ args,
5185
+ contract: name
5186
+ };
5187
+ }
5188
+ } catch {
5189
+ }
5190
+ }
5191
+ return { selector };
5192
+ }
5193
+ function classifyReadinessFromRevert(e) {
5194
+ const r = decodeRevert(e);
5195
+ const name = r?.name;
5196
+ if (name && REVERT_TO_READINESS[name]) return REVERT_TO_READINESS[name];
5197
+ const msg = (() => {
5198
+ if (typeof e !== "object" || e === null) return "";
5199
+ const obj = e;
5200
+ const maybeMsg = obj["shortMessage"] ?? obj["message"];
5201
+ return typeof maybeMsg === "string" ? maybeMsg : "";
5202
+ })();
5203
+ const lower = String(msg).toLowerCase();
5204
+ if (lower.includes("paused")) return { kind: "NOT_READY", reason: "paused" };
5205
+ if (name || r?.selector) {
5206
+ return { kind: "UNFINALIZABLE", reason: "unsupported", detail: name ?? r?.selector };
5207
+ }
5208
+ return { kind: "NOT_READY", reason: "unknown", detail: lower || void 0 };
5209
+ }
5210
+
5211
+ // src/adapters/ethers/errors/error-ops.ts
5212
+ function toZKsyncError(type, base, err) {
5213
+ if (isZKsyncError(err)) return err;
5214
+ const revert = decodeRevert(err);
5215
+ return createError(type, { ...base, ...revert ? { revert } : {}, cause: shapeCause(err) });
5216
+ }
5217
+ function resolveMessage(op, msg) {
5218
+ if (!msg) return `Error during ${op}.`;
5219
+ return typeof msg === "function" ? msg() : msg;
5220
+ }
5221
+ function createErrorHandlers(resource) {
5222
+ async function run(kind, operation, fn, opts) {
5223
+ try {
5224
+ return await fn();
5225
+ } catch (e) {
5226
+ if (isZKsyncError(e)) throw e;
5227
+ const message = resolveMessage(operation, opts?.message);
5228
+ throw toZKsyncError(kind, { resource, operation, context: opts?.ctx ?? {}, message }, e);
5229
+ }
5230
+ }
5231
+ function wrap2(operation, fn, opts) {
5232
+ return run("INTERNAL", operation, fn, opts);
5233
+ }
5234
+ function wrapAs9(kind, operation, fn, opts) {
5235
+ return run(kind, operation, fn, opts);
5236
+ }
5237
+ async function toResult2(operation, fn, opts) {
5238
+ try {
5239
+ const value = await wrap2(operation, fn, opts);
5240
+ return { ok: true, value };
5241
+ } catch (e) {
5242
+ const shaped = isZKsyncError(e) ? e : toZKsyncError(
5243
+ "INTERNAL",
5244
+ {
5245
+ resource,
5246
+ operation,
5247
+ context: opts?.ctx ?? {},
5248
+ message: resolveMessage(operation, opts?.message)
5249
+ },
5250
+ e
5251
+ );
5252
+ return { ok: false, error: shaped };
5253
+ }
5254
+ }
5255
+ return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
5256
+ }
5257
+
5097
5258
  // src/adapters/ethers/client.ts
5259
+ var { wrapAs } = createErrorHandlers("client");
5098
5260
  function createEthersClient(args) {
5099
5261
  const { l1, l2, signer } = args;
5100
5262
  let boundSigner = signer;
@@ -5192,7 +5354,10 @@ function createEthersClient(args) {
5192
5354
  async function baseToken(chainId) {
5193
5355
  const { bridgehub } = await ensureAddresses();
5194
5356
  const bh = new ethers.Contract(bridgehub, IBridgehub_default, l1);
5195
- return await bh.baseToken(chainId);
5357
+ return await wrapAs("CONTRACT", OP_DEPOSITS.base.baseToken, () => bh.baseToken(chainId), {
5358
+ ctx: { where: "bridgehub.baseToken", chainIdL2: chainId },
5359
+ message: "Failed to read base token."
5360
+ });
5196
5361
  }
5197
5362
  const client = {
5198
5363
  kind: "ethers",
@@ -5297,22 +5462,38 @@ async function waitForL2ExecutionFromL1Tx(l1, l2, l1TxHash) {
5297
5462
  return { l2Receipt, l2TxHash };
5298
5463
  }
5299
5464
 
5300
- // src/core/utils/gas.ts
5301
- function assertNoLegacyGas(overrides) {
5302
- if (!overrides) return;
5303
- if ("gasPrice" in overrides && overrides.gasPrice !== void 0) {
5304
- throw new Error("Legacy gasPrice is not supported; use EIP-1559 fields instead.");
5305
- }
5306
- }
5307
- function assertPriorityFeeBounds(fees) {
5308
- if (fees.maxPriorityFeePerGas > fees.maxFeePerGas) {
5309
- throw new Error("maxPriorityFeePerGas cannot exceed maxFeePerGas.");
5465
+ // src/core/resources/deposits/route.ts
5466
+ async function pickDepositRoute(client, chainIdL2, token) {
5467
+ if (isETH(token)) {
5468
+ const base2 = await client.baseToken(chainIdL2);
5469
+ return isETH(base2) ? "eth-base" : "eth-nonbase";
5310
5470
  }
5471
+ const base = await client.baseToken(chainIdL2);
5472
+ return normalizeAddrEq(token, base) ? "erc20-base" : "erc20-nonbase";
5311
5473
  }
5312
5474
 
5313
- // src/adapters/ethers/resources/utils.ts
5314
- function supportsGetGasPrice(provider) {
5315
- return typeof provider === "object" && provider !== null && typeof provider.getGasPrice === "function";
5475
+ // src/adapters/ethers/resources/deposits/context.ts
5476
+ async function commonCtx(p, client) {
5477
+ const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
5478
+ const { chainId } = await client.l2.getNetwork();
5479
+ const sender = await client.signer.getAddress();
5480
+ const gasPerPubdata = p.gasPerPubdata ?? 800n;
5481
+ const operatorTip = p.operatorTip ?? 0n;
5482
+ const refundRecipient = p.refundRecipient ?? sender;
5483
+ const route = await pickDepositRoute(client, BigInt(chainId), p.token);
5484
+ return {
5485
+ client,
5486
+ l1AssetRouter,
5487
+ route,
5488
+ bridgehub,
5489
+ chainIdL2: BigInt(chainId),
5490
+ sender,
5491
+ gasOverrides: p.l1TxOverrides,
5492
+ l2GasLimit: p.l2GasLimit,
5493
+ gasPerPubdata,
5494
+ operatorTip,
5495
+ refundRecipient
5496
+ };
5316
5497
  }
5317
5498
  function encodeNativeTokenVaultAssetId(chainId, address) {
5318
5499
  const abi = new ethers.AbiCoder();
@@ -5338,78 +5519,18 @@ function encodeNTVAssetId(chainId, address) {
5338
5519
  );
5339
5520
  return ethers.ethers.keccak256(hex);
5340
5521
  }
5341
- function encodeNTVTransferData(amount, receiver, token) {
5342
- return new ethers.AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
5343
- }
5344
- function scaleGasLimit(gasLimit) {
5345
- return gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR) / BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR);
5346
- }
5347
- async function checkBaseCost(baseCost, value) {
5348
- const resolvedValue = await value;
5349
- if (baseCost > resolvedValue) {
5350
- throw new Error(
5351
- `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)}!`
5352
- );
5353
- }
5354
- }
5355
- async function getFeeOverrides(client, overrides) {
5356
- assertNoLegacyGas(overrides);
5357
- const fd = await client.l1.getFeeData();
5358
- const maxFeeFromProvider = fd.maxFeePerGas ?? void 0;
5359
- const maxPriorityFromProvider = fd.maxPriorityFeePerGas ?? void 0;
5360
- const gasPriceFallback = fd.gasPrice ?? void 0;
5361
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback;
5362
- if (maxFeePerGas == null) throw new Error("provider returned no gas price data");
5363
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
5364
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
5365
- const gasPriceForBaseCost = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback ?? maxFeePerGas;
5366
- return {
5367
- gasLimit: overrides?.gasLimit,
5368
- maxFeePerGas,
5369
- maxPriorityFeePerGas,
5370
- gasPriceForBaseCost
5371
- };
5522
+ var encodeNTVTransferData = encodeNativeTokenVaultTransferData;
5523
+ function encodeSecondBridgeArgs(token, amount, l2Receiver) {
5524
+ return ethers.AbiCoder.defaultAbiCoder().encode(
5525
+ ["address", "uint256", "address"],
5526
+ [token, amount, l2Receiver]
5527
+ );
5372
5528
  }
5373
- async function getL2FeeOverrides(client, overrides) {
5374
- assertNoLegacyGas(overrides);
5375
- let maxFeeFromProvider;
5376
- let maxPriorityFromProvider;
5377
- let gasPriceFallback;
5378
- try {
5379
- const fd = await client.l2.getFeeData();
5380
- if (fd?.maxFeePerGas != null) maxFeeFromProvider = fd.maxFeePerGas;
5381
- if (fd?.maxPriorityFeePerGas != null) {
5382
- maxPriorityFromProvider = fd.maxPriorityFeePerGas;
5383
- }
5384
- if (fd?.gasPrice != null) gasPriceFallback = fd.gasPrice;
5385
- } catch {
5386
- }
5387
- if (gasPriceFallback == null) {
5388
- try {
5389
- if (supportsGetGasPrice(client.l2)) {
5390
- const gp = await client.l2.getGasPrice();
5391
- gasPriceFallback = typeof gp === "bigint" ? gp : BigInt(gp.toString());
5392
- }
5393
- } catch {
5394
- }
5395
- }
5396
- const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback;
5397
- if (maxFeePerGas == null) {
5398
- throw new Error("L2 provider returned no gas price data");
5399
- }
5400
- const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
5401
- assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
5402
- return {
5403
- gasLimit: overrides?.gasLimit,
5404
- maxFeePerGas,
5405
- maxPriorityFeePerGas
5406
- };
5529
+ function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
5530
+ return encodeSecondBridgeArgs(token, amount, l2Receiver);
5407
5531
  }
5408
- async function getGasPriceWei(client) {
5409
- const fd = await client.l1.getFeeData();
5410
- if (fd.gasPrice != null) return fd.gasPrice;
5411
- if (fd.maxFeePerGas != null) return fd.maxFeePerGas;
5412
- throw new Error("provider returned no gas price data");
5532
+ function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
5533
+ return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
5413
5534
  }
5414
5535
  function buildDirectRequestStruct(args) {
5415
5536
  return {
@@ -5424,331 +5545,409 @@ function buildDirectRequestStruct(args) {
5424
5545
  refundRecipient: args.refundRecipient
5425
5546
  };
5426
5547
  }
5427
- function encodeSecondBridgeArgs(token, amount, l2Receiver) {
5428
- return ethers.AbiCoder.defaultAbiCoder().encode(
5429
- ["address", "uint256", "address"],
5430
- [token, amount, l2Receiver]
5431
- );
5432
- }
5433
- function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
5434
- return encodeSecondBridgeArgs(token, amount, l2Receiver);
5435
- }
5436
- function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
5437
- return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
5438
- }
5439
5548
 
5440
- // src/core/resources/deposits/route.ts
5441
- async function pickDepositRoute(client, chainIdL2, token) {
5442
- if (isETH(token)) {
5443
- const base2 = await client.baseToken(chainIdL2);
5444
- return isETH(base2) ? "eth-base" : "eth-nonbase";
5445
- }
5446
- const base = await client.baseToken(chainIdL2);
5447
- return normalizeAddrEq(token, base) ? "erc20-base" : "erc20-nonbase";
5448
- }
5449
-
5450
- // src/adapters/ethers/resources/deposits/context.ts
5451
- async function commonCtx(p, client) {
5452
- const { bridgehub, l1AssetRouter } = await client.ensureAddresses();
5453
- const { chainId } = await client.l2.getNetwork();
5454
- const sender = await client.signer.getAddress();
5455
- const fee = await getFeeOverrides(client, p.l1TxOverrides);
5456
- const l2GasLimit = p.l2GasLimit ?? 300000n;
5457
- const gasPerPubdata = p.gasPerPubdata ?? 800n;
5458
- const operatorTip = p.operatorTip ?? 0n;
5459
- const refundRecipient = p.refundRecipient ?? sender;
5460
- const route = await pickDepositRoute(client, BigInt(chainId), p.token);
5549
+ // src/core/resources/deposits/gas.ts
5550
+ function makeGasQuote(p) {
5551
+ const maxPriorityFeePerGas = p.maxPriorityFeePerGas ?? 0n;
5461
5552
  return {
5462
- client,
5463
- l1AssetRouter,
5464
- route,
5465
- bridgehub,
5466
- chainIdL2: BigInt(chainId),
5467
- sender,
5468
- fee,
5469
- l2GasLimit,
5470
- gasPerPubdata,
5471
- operatorTip,
5472
- refundRecipient
5553
+ gasLimit: p.gasLimit,
5554
+ maxFeePerGas: p.maxFeePerGas,
5555
+ maxPriorityFeePerGas,
5556
+ gasPerPubdata: p.gasPerPubdata,
5557
+ maxCost: p.gasLimit * p.maxFeePerGas
5473
5558
  };
5474
5559
  }
5475
-
5476
- // src/core/errors/withdrawal-revert-map.ts
5477
- var REVERT_TO_READINESS = {
5478
- // Already done
5479
- WithdrawalAlreadyFinalized: { kind: "FINALIZED" },
5480
- // Temporary — try later
5481
- BatchNotExecuted: { kind: "NOT_READY", reason: "batch-not-executed" },
5482
- LocalRootIsZero: { kind: "NOT_READY", reason: "root-missing" },
5483
- // Permanent — won’t become ready for this tx
5484
- WrongL2Sender: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5485
- InvalidSelector: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5486
- L2WithdrawalMessageWrongLength: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5487
- WrongMsgLength: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5488
- TokenNotLegacy: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5489
- TokenIsLegacy: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5490
- InvalidProof: { kind: "UNFINALIZABLE", reason: "message-invalid" },
5491
- InvalidChainId: { kind: "UNFINALIZABLE", reason: "invalid-chain" },
5492
- NotSettlementLayer: { kind: "UNFINALIZABLE", reason: "settlement-layer" },
5493
- // Likely environment mismatch — treat as permanent for this tx
5494
- OnlyEraSupported: { kind: "UNFINALIZABLE", reason: "unsupported" },
5495
- LocalRootMustBeZero: { kind: "UNFINALIZABLE", reason: "unsupported" }
5496
- };
5497
-
5498
- // src/adapters/ethers/errors/revert.ts
5499
- var ERROR_IFACES = [];
5500
- var IFACE_ERROR_STRING = new ethers.Interface(["error Error(string)"]);
5501
- var IFACE_PANIC = new ethers.Interface(["error Panic(uint256)"]);
5502
- (function bootstrapDefaultIfaces() {
5560
+ async function fetchFees(estimator) {
5503
5561
  try {
5504
- ERROR_IFACES.push({
5505
- name: "IL1Nullifier",
5506
- iface: new ethers.Interface(IL1Nullifier_default)
5507
- });
5562
+ const fees = await estimator.estimateFeesPerGas();
5563
+ if (fees.maxFeePerGas != null) {
5564
+ return {
5565
+ maxFeePerGas: fees.maxFeePerGas,
5566
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas ?? 0n
5567
+ };
5568
+ }
5569
+ if (fees.gasPrice != null) {
5570
+ return {
5571
+ maxFeePerGas: fees.gasPrice,
5572
+ maxPriorityFeePerGas: 0n
5573
+ };
5574
+ }
5508
5575
  } catch {
5509
5576
  }
5510
5577
  try {
5511
- ERROR_IFACES.push({ name: "IERC20", iface: new ethers.Interface(IERC20_default) });
5578
+ const gp = await estimator.getGasPrice();
5579
+ return { maxFeePerGas: gp, maxPriorityFeePerGas: 0n };
5512
5580
  } catch {
5581
+ return { maxFeePerGas: 0n, maxPriorityFeePerGas: 0n };
5582
+ }
5583
+ }
5584
+ async function quoteL1Gas(input) {
5585
+ const { estimator, tx, overrides, fallbackGasLimit } = input;
5586
+ let market;
5587
+ const getMarket = async () => {
5588
+ if (market) return market;
5589
+ market = await fetchFees(estimator);
5590
+ return market;
5591
+ };
5592
+ const maxFeePerGas = overrides?.maxFeePerGas ?? (tx.maxFeePerGas != null ? BigInt(tx.maxFeePerGas) : (await getMarket()).maxFeePerGas);
5593
+ const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? (tx.maxPriorityFeePerGas != null ? BigInt(tx.maxPriorityFeePerGas) : (await getMarket()).maxPriorityFeePerGas);
5594
+ const explicitGasLimit = overrides?.gasLimit ?? (tx.gasLimit != null ? BigInt(tx.gasLimit) : void 0);
5595
+ if (explicitGasLimit != null) {
5596
+ return makeGasQuote({ gasLimit: explicitGasLimit, maxFeePerGas, maxPriorityFeePerGas });
5513
5597
  }
5514
5598
  try {
5515
- ERROR_IFACES.push({
5516
- name: "IL1NativeTokenVault",
5517
- iface: new ethers.Interface(L1NativeTokenVault_default)
5599
+ const est = await estimator.estimateGas(tx);
5600
+ const buffered = BigInt(est) * (100n + BUFFER) / 100n;
5601
+ return makeGasQuote({ gasLimit: buffered, maxFeePerGas, maxPriorityFeePerGas });
5602
+ } catch (err) {
5603
+ if (fallbackGasLimit != null) {
5604
+ return makeGasQuote({ gasLimit: fallbackGasLimit, maxFeePerGas, maxPriorityFeePerGas });
5605
+ }
5606
+ console.warn("L1 gas estimation failed", err);
5607
+ return void 0;
5608
+ }
5609
+ }
5610
+ async function quoteL2Gas(input) {
5611
+ const { estimator, route, tx, gasPerPubdata, l2GasLimit, overrideGasLimit, stateOverrides } = input;
5612
+ const market = await fetchFees(estimator);
5613
+ const maxFeePerGas = market.maxFeePerGas || market.maxPriorityFeePerGas || 0n;
5614
+ const txGasLimit = tx?.gasLimit != null ? BigInt(tx.gasLimit) : void 0;
5615
+ const explicit = overrideGasLimit ?? txGasLimit;
5616
+ if (explicit != null) {
5617
+ return makeGasQuote({
5618
+ gasLimit: explicit,
5619
+ maxFeePerGas,
5620
+ gasPerPubdata
5518
5621
  });
5519
- } catch {
5520
5622
  }
5521
- try {
5522
- ERROR_IFACES.push({
5523
- name: "IL2NativeTokenVault",
5524
- iface: new ethers.Interface(L2NativeTokenVault_default)
5623
+ if (!tx) {
5624
+ return makeGasQuote({
5625
+ gasLimit: l2GasLimit ?? 0n,
5626
+ maxFeePerGas,
5627
+ gasPerPubdata
5525
5628
  });
5526
- } catch {
5527
5629
  }
5528
5630
  try {
5529
- ERROR_IFACES.push({ name: "Mailbox", iface: new ethers.Interface(Mailbox_default) });
5530
- } catch {
5631
+ const execEstimate = await estimator.estimateGas(tx, stateOverrides);
5632
+ const memoryBytes = route === "erc20-nonbase" ? 500n : DEFAULT_ABI_BYTES;
5633
+ const pubdataBytes = route === "erc20-nonbase" ? 200n : DEFAULT_PUBDATA_BYTES;
5634
+ const pp = gasPerPubdata ?? 800n;
5635
+ const memoryOverhead = memoryBytes * TX_MEMORY_OVERHEAD_GAS;
5636
+ const pubdataOverhead = pubdataBytes * pp;
5637
+ let total = BigInt(execEstimate) + TX_OVERHEAD_GAS + memoryOverhead + pubdataOverhead;
5638
+ total = total * (100n + BUFFER) / 100n;
5639
+ return makeGasQuote({
5640
+ gasLimit: total,
5641
+ maxFeePerGas,
5642
+ gasPerPubdata: pp
5643
+ });
5644
+ } catch (err) {
5645
+ console.warn("L2 gas estimation failed", err);
5646
+ return makeGasQuote({
5647
+ gasLimit: l2GasLimit ?? 0n,
5648
+ maxFeePerGas,
5649
+ gasPerPubdata
5650
+ });
5531
5651
  }
5532
- })();
5533
- function registerErrorAbi(name, abi) {
5534
- const existing = ERROR_IFACES.findIndex((x) => x.name === name);
5535
- const entry = { name, iface: new ethers.Interface(abi) };
5536
- if (existing >= 0) ERROR_IFACES[existing] = entry;
5537
- else ERROR_IFACES.push(entry);
5538
5652
  }
5539
- function extractRevertData(e) {
5540
- const maybe = (
5541
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
5542
- e?.data?.data ?? e?.error?.data ?? e?.data ?? e?.error?.error?.data ?? e?.info?.error?.data
5543
- );
5544
- if (typeof maybe === "string" && maybe.startsWith("0x") && maybe.length >= 10) {
5545
- return maybe;
5653
+ async function quoteL2BaseCost(input) {
5654
+ const { estimator, encode: encode2, bridgehub, chainIdL2, l2GasLimit, gasPerPubdata } = input;
5655
+ const market = await fetchFees(estimator);
5656
+ const l1GasPrice = market.maxFeePerGas || market.maxPriorityFeePerGas || 0n;
5657
+ if (l1GasPrice === 0n) {
5658
+ throw new Error("Could not fetch L1 gas price for Bridgehub base cost calculation.");
5546
5659
  }
5547
- return void 0;
5660
+ const data = encode2(IBridgehub_default, "l2TransactionBaseCost", [
5661
+ chainIdL2,
5662
+ l1GasPrice,
5663
+ l2GasLimit,
5664
+ gasPerPubdata
5665
+ ]);
5666
+ const raw = await estimator.call({
5667
+ to: bridgehub,
5668
+ data
5669
+ });
5670
+ return BigInt(raw);
5548
5671
  }
5549
- function decodeRevert(e) {
5550
- const data = extractRevertData(e);
5551
- if (!data) return;
5552
- const selector = `0x${data.slice(2, 10)}`;
5553
- try {
5554
- const parsed = IFACE_ERROR_STRING.parseError(data);
5555
- if (parsed?.name === "Error") {
5556
- const args = parsed.args ? Array.from(parsed.args) : void 0;
5557
- return { selector, name: "Error", args };
5558
- }
5559
- } catch {
5560
- }
5561
- try {
5562
- const parsed = IFACE_PANIC.parseError(data);
5563
- if (parsed?.name === "Panic") {
5564
- const args = parsed.args ? Array.from(parsed.args) : void 0;
5565
- return { selector, name: "Panic", args };
5566
- }
5567
- } catch {
5568
- }
5569
- for (const { name, iface } of ERROR_IFACES) {
5570
- try {
5571
- const parsed = iface.parseError(data);
5572
- if (parsed) {
5573
- const args = parsed.args ? Array.from(parsed.args) : void 0;
5574
- return {
5575
- selector,
5576
- name: parsed.name,
5577
- args,
5578
- contract: name
5579
- };
5672
+
5673
+ // src/adapters/ethers/estimator.ts
5674
+ function toCoreTx(tx) {
5675
+ return {
5676
+ to: tx.to,
5677
+ from: tx.from,
5678
+ data: tx.data,
5679
+ value: tx.value ? BigInt(tx.value) : void 0,
5680
+ gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : void 0,
5681
+ maxFeePerGas: tx.maxFeePerGas ? BigInt(tx.maxFeePerGas) : void 0,
5682
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : void 0
5683
+ };
5684
+ }
5685
+ function ethersToGasEstimator(provider) {
5686
+ return {
5687
+ async estimateGas(tx, stateOverrides) {
5688
+ const ethTx = {
5689
+ to: tx.to,
5690
+ from: tx.from,
5691
+ data: tx.data,
5692
+ value: tx.value,
5693
+ gasLimit: tx.gasLimit,
5694
+ maxFeePerGas: tx.maxFeePerGas,
5695
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
5696
+ };
5697
+ if (stateOverrides && "send" in provider) {
5698
+ try {
5699
+ const jsonRpcProvider = provider;
5700
+ const result = await jsonRpcProvider.send("eth_estimateGas", [
5701
+ ethTx,
5702
+ "latest",
5703
+ stateOverrides
5704
+ ]);
5705
+ return BigInt(result);
5706
+ } catch (error) {
5707
+ console.warn(
5708
+ "Failed to estimate gas with state overrides, falling back to standard estimation:",
5709
+ error
5710
+ );
5711
+ }
5712
+ } else if (stateOverrides) {
5713
+ console.warn('Provider does not support "send", skipping state overrides estimation.');
5580
5714
  }
5581
- } catch {
5715
+ return await provider.estimateGas(ethTx);
5716
+ },
5717
+ async estimateFeesPerGas() {
5718
+ const fd = await provider.getFeeData();
5719
+ return {
5720
+ maxFeePerGas: fd.maxFeePerGas != null ? BigInt(fd.maxFeePerGas) : void 0,
5721
+ maxPriorityFeePerGas: fd.maxPriorityFeePerGas != null ? BigInt(fd.maxPriorityFeePerGas) : void 0,
5722
+ gasPrice: fd.gasPrice != null ? BigInt(fd.gasPrice) : void 0
5723
+ };
5724
+ },
5725
+ async getGasPrice() {
5726
+ const fd = await provider.getFeeData();
5727
+ if (fd.gasPrice != null) return BigInt(fd.gasPrice);
5728
+ throw new Error("Could not fetch gas price");
5729
+ },
5730
+ async call(tx) {
5731
+ const ethTx = {
5732
+ to: tx.to,
5733
+ data: tx.data,
5734
+ value: tx.value,
5735
+ from: tx.from
5736
+ };
5737
+ return await provider.call(ethTx);
5582
5738
  }
5583
- }
5584
- return { selector };
5739
+ };
5585
5740
  }
5586
- function classifyReadinessFromRevert(e) {
5587
- const r = decodeRevert(e);
5588
- const name = r?.name;
5589
- if (name && REVERT_TO_READINESS[name]) return REVERT_TO_READINESS[name];
5590
- const msg = (() => {
5591
- if (typeof e !== "object" || e === null) return "";
5592
- const obj = e;
5593
- const maybeMsg = obj["shortMessage"] ?? obj["message"];
5594
- return typeof maybeMsg === "string" ? maybeMsg : "";
5595
- })();
5596
- const lower = String(msg).toLowerCase();
5597
- if (lower.includes("paused")) return { kind: "NOT_READY", reason: "paused" };
5598
- if (name || r?.selector) {
5599
- return { kind: "UNFINALIZABLE", reason: "unsupported", detail: name ?? r?.selector };
5600
- }
5601
- return { kind: "NOT_READY", reason: "unknown", detail: lower || void 0 };
5741
+
5742
+ // src/adapters/ethers/resources/deposits/services/fee.ts
5743
+ var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
5744
+ var encode = (abi, fn, args) => {
5745
+ return new ethers.Interface(abi).encodeFunctionData(fn, args);
5746
+ };
5747
+ async function quoteL2BaseCost2(input) {
5748
+ const { ctx, l2GasLimit } = input;
5749
+ const estimator = ethersToGasEstimator(ctx.client.l1);
5750
+ return wrapAs2(
5751
+ "RPC",
5752
+ "deposits.fees.l2BaseCost",
5753
+ () => quoteL2BaseCost({
5754
+ estimator,
5755
+ encode,
5756
+ bridgehub: ctx.bridgehub,
5757
+ chainIdL2: ctx.chainIdL2,
5758
+ l2GasLimit,
5759
+ gasPerPubdata: ctx.gasPerPubdata
5760
+ }),
5761
+ { ctx: { chainIdL2: ctx.chainIdL2 } }
5762
+ );
5602
5763
  }
5603
5764
 
5604
- // src/adapters/ethers/errors/error-ops.ts
5605
- function toZKsyncError(type, base, err) {
5606
- if (isZKsyncError(err)) return err;
5607
- const revert = decodeRevert(err);
5608
- return createError(type, { ...base, ...revert ? { revert } : {}, cause: shapeCause(err) });
5765
+ // src/adapters/ethers/resources/deposits/services/gas.ts
5766
+ async function quoteL1Gas2(input) {
5767
+ const { ctx, tx, overrides, fallbackGasLimit } = input;
5768
+ const estimator = ethersToGasEstimator(ctx.client.l1);
5769
+ return quoteL1Gas({
5770
+ estimator,
5771
+ tx: toCoreTx(tx),
5772
+ overrides,
5773
+ fallbackGasLimit
5774
+ });
5609
5775
  }
5610
- function resolveMessage(op, msg) {
5611
- if (!msg) return `Error during ${op}.`;
5612
- return typeof msg === "function" ? msg() : msg;
5776
+ async function quoteL2Gas2(input) {
5777
+ const { ctx, route, l2TxForModeling, overrideGasLimit } = input;
5778
+ const estimator = ethersToGasEstimator(ctx.client.l2);
5779
+ return quoteL2Gas({
5780
+ estimator,
5781
+ route,
5782
+ tx: l2TxForModeling ? toCoreTx(l2TxForModeling) : void 0,
5783
+ gasPerPubdata: ctx.gasPerPubdata,
5784
+ l2GasLimit: ctx.l2GasLimit,
5785
+ overrideGasLimit,
5786
+ stateOverrides: input.stateOverrides
5787
+ });
5613
5788
  }
5614
- function createErrorHandlers(resource) {
5615
- async function run(kind, operation, fn, opts) {
5616
- try {
5617
- return await fn();
5618
- } catch (e) {
5619
- if (isZKsyncError(e)) throw e;
5620
- const message = resolveMessage(operation, opts?.message);
5621
- throw toZKsyncError(kind, { resource, operation, context: opts?.ctx ?? {}, message }, e);
5622
- }
5623
- }
5624
- function wrap2(operation, fn, opts) {
5625
- return run("INTERNAL", operation, fn, opts);
5626
- }
5627
- function wrapAs9(kind, operation, fn, opts) {
5628
- return run(kind, operation, fn, opts);
5789
+ async function determineErc20L2Gas(input) {
5790
+ const { ctx, l1Token } = input;
5791
+ const DEFAULT_SAFE_L2_GAS_LIMIT = 3000000n;
5792
+ if (ctx.l2GasLimit != null) {
5793
+ return quoteL2Gas2({
5794
+ ctx,
5795
+ route: "erc20-nonbase",
5796
+ overrideGasLimit: ctx.l2GasLimit
5797
+ });
5629
5798
  }
5630
- async function toResult2(operation, fn, opts) {
5631
- try {
5632
- const value = await wrap2(operation, fn, opts);
5633
- return { ok: true, value };
5634
- } catch (e) {
5635
- const shaped = isZKsyncError(e) ? e : toZKsyncError(
5636
- "INTERNAL",
5637
- {
5638
- resource,
5639
- operation,
5640
- context: opts?.ctx ?? {},
5641
- message: resolveMessage(operation, opts?.message)
5642
- },
5643
- e
5644
- );
5645
- return { ok: false, error: shaped };
5799
+ try {
5800
+ const { l2NativeTokenVault } = await ctx.client.contracts();
5801
+ const l2TokenAddress = await l2NativeTokenVault.l2TokenAddress(l1Token);
5802
+ if (l2TokenAddress === "0x0000000000000000000000000000000000000000") {
5803
+ return quoteL2Gas2({
5804
+ ctx,
5805
+ route: "erc20-nonbase",
5806
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
5807
+ });
5808
+ }
5809
+ const modelTx = {
5810
+ to: input.modelTx?.to ?? ctx.sender,
5811
+ from: input.modelTx?.from ?? ctx.sender,
5812
+ data: input.modelTx?.data ?? "0x",
5813
+ value: input.modelTx?.value ?? 0n
5814
+ };
5815
+ const gas = await quoteL2Gas2({
5816
+ ctx,
5817
+ route: "erc20-nonbase",
5818
+ l2TxForModeling: modelTx
5819
+ });
5820
+ if (!gas) {
5821
+ return quoteL2Gas2({
5822
+ ctx,
5823
+ route: "erc20-nonbase",
5824
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
5825
+ });
5646
5826
  }
5827
+ return gas;
5828
+ } catch (err) {
5829
+ console.warn("Failed to determine ERC20 L2 gas; defaulting to safe gas limit.", err);
5830
+ return quoteL2Gas2({
5831
+ ctx,
5832
+ route: "erc20-nonbase",
5833
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
5834
+ });
5647
5835
  }
5648
- return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
5836
+ }
5837
+
5838
+ // src/core/resources/deposits/fee.ts
5839
+ function buildFeeBreakdown(p) {
5840
+ const l1MaxTotal = p.l1Gas?.maxCost ?? 0n;
5841
+ const l2Total = p.l2BaseCost + p.operatorTip;
5842
+ const l1 = {
5843
+ gasLimit: p.l1Gas?.gasLimit ?? 0n,
5844
+ maxFeePerGas: p.l1Gas?.maxFeePerGas ?? 0n,
5845
+ maxPriorityFeePerGas: p.l1Gas?.maxPriorityFeePerGas,
5846
+ maxTotal: l1MaxTotal
5847
+ };
5848
+ const l2 = {
5849
+ total: l2Total,
5850
+ baseCost: p.l2BaseCost,
5851
+ operatorTip: p.operatorTip,
5852
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
5853
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
5854
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas,
5855
+ gasPerPubdata: p.l2Gas?.gasPerPubdata ?? 0n
5856
+ };
5857
+ return {
5858
+ token: p.feeToken,
5859
+ maxTotal: l1MaxTotal + l2Total,
5860
+ mintValue: p.mintValue,
5861
+ l1,
5862
+ l2
5863
+ };
5649
5864
  }
5650
5865
 
5651
5866
  // src/adapters/ethers/resources/deposits/routes/eth.ts
5652
- var { wrapAs } = createErrorHandlers("deposits");
5653
5867
  function routeEthDirect() {
5654
5868
  return {
5655
5869
  async build(p, ctx) {
5656
5870
  const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
5657
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
5658
- const rawBaseCost = await wrapAs(
5659
- "CONTRACT",
5660
- OP_DEPOSITS.eth.baseCost,
5661
- () => bh.l2TransactionBaseCost(
5662
- ctx.chainIdL2,
5663
- gasPriceForBaseCost,
5664
- ctx.l2GasLimit,
5665
- ctx.gasPerPubdata
5666
- ),
5667
- {
5668
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
5669
- message: "Could not fetch L2 base cost from Bridgehub."
5871
+ const l2TxModel = {
5872
+ to: p.to ?? ctx.sender,
5873
+ from: ctx.sender,
5874
+ data: "0x",
5875
+ value: 0n
5876
+ };
5877
+ const l2GasParams = await quoteL2Gas2({
5878
+ ctx,
5879
+ route: "eth-base",
5880
+ l2TxForModeling: l2TxModel,
5881
+ overrideGasLimit: ctx.l2GasLimit,
5882
+ stateOverrides: {
5883
+ [ctx.sender]: {
5884
+ balance: "0xffffffffffffffffffff"
5885
+ }
5670
5886
  }
5671
- );
5672
- const baseCost = BigInt(rawBaseCost);
5673
- const l2Contract = p.to ?? ctx.sender;
5674
- const l2Value = p.amount;
5675
- const mintValue = baseCost + ctx.operatorTip + l2Value;
5887
+ });
5888
+ if (!l2GasParams) {
5889
+ throw new Error("Failed to estimate L2 gas for deposit.");
5890
+ }
5891
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
5892
+ const mintValue = baseCost + ctx.operatorTip + p.amount;
5676
5893
  const req = buildDirectRequestStruct({
5677
5894
  chainId: ctx.chainIdL2,
5678
5895
  mintValue,
5679
- l2GasLimit: ctx.l2GasLimit,
5896
+ l2GasLimit: l2GasParams.gasLimit,
5680
5897
  gasPerPubdata: ctx.gasPerPubdata,
5681
5898
  refundRecipient: ctx.refundRecipient,
5682
- l2Contract,
5683
- l2Value
5899
+ l2Contract: p.to ?? ctx.sender,
5900
+ l2Value: p.amount
5684
5901
  });
5685
5902
  const data = bh.interface.encodeFunctionData("requestL2TransactionDirect", [req]);
5686
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
5687
- const tx = {
5903
+ const l1TxCandidate = {
5688
5904
  to: ctx.bridgehub,
5689
5905
  data,
5690
5906
  value: mintValue,
5691
5907
  from: ctx.sender,
5692
- ...txFeeOverrides
5908
+ ...ctx.gasOverrides
5693
5909
  };
5694
- if (overrideGasLimit != null) {
5695
- tx.gasLimit = overrideGasLimit;
5696
- resolvedL1GasLimit = overrideGasLimit;
5697
- } else {
5698
- try {
5699
- const est = await wrapAs(
5700
- "RPC",
5701
- OP_DEPOSITS.eth.estGas,
5702
- () => ctx.client.l1.estimateGas(tx),
5703
- {
5704
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
5705
- message: "Failed to estimate gas for Bridgehub request."
5706
- }
5707
- );
5708
- const buffered = BigInt(est) * 115n / 100n;
5709
- tx.gasLimit = buffered;
5710
- resolvedL1GasLimit = buffered;
5711
- } catch {
5712
- }
5910
+ const l1GasParams = await quoteL1Gas2({
5911
+ ctx,
5912
+ tx: l1TxCandidate,
5913
+ overrides: ctx.gasOverrides
5914
+ });
5915
+ if (l1GasParams) {
5916
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
5917
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
5918
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
5713
5919
  }
5714
5920
  const steps = [
5715
5921
  {
5716
5922
  key: "bridgehub:direct",
5717
5923
  kind: "bridgehub:direct",
5718
5924
  description: "Bridge ETH via Bridgehub.requestL2TransactionDirect",
5719
- tx
5925
+ tx: l1TxCandidate
5720
5926
  }
5721
5927
  ];
5928
+ const fees = buildFeeBreakdown({
5929
+ feeToken: ETH_ADDRESS,
5930
+ l1Gas: l1GasParams,
5931
+ l2Gas: l2GasParams,
5932
+ l2BaseCost: baseCost,
5933
+ operatorTip: ctx.operatorTip,
5934
+ mintValue
5935
+ });
5722
5936
  return {
5723
5937
  steps,
5724
5938
  approvals: [],
5725
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
5939
+ fees
5726
5940
  };
5727
5941
  }
5728
5942
  };
5729
- }
5730
- var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
5731
- var MIN_L2_GAS_FOR_ERC20 = 2500000n;
5732
- function routeErc20NonBase() {
5733
- return {
5734
- async preflight() {
5735
- },
5736
- async build(p, ctx) {
5737
- const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
5738
- const assetRouter = ctx.l1AssetRouter;
5739
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
5740
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
5741
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
5742
- const baseToken = await wrapAs2(
5743
- "CONTRACT",
5744
- OP_DEPOSITS.nonbase.baseToken,
5745
- () => bh.baseToken(ctx.chainIdL2),
5746
- {
5747
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
5748
- message: "Failed to read base token."
5749
- }
5750
- );
5751
- await wrapAs2(
5943
+ }
5944
+ var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
5945
+ function routeErc20NonBase() {
5946
+ return {
5947
+ // TODO: do we even need these validations?
5948
+ async preflight(p, ctx) {
5949
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
5950
+ await wrapAs3(
5752
5951
  "VALIDATION",
5753
5952
  OP_DEPOSITS.nonbase.assertNonBaseToken,
5754
5953
  () => {
@@ -5758,55 +5957,54 @@ function routeErc20NonBase() {
5758
5957
  },
5759
5958
  { ctx: { depositToken: p.token, baseToken } }
5760
5959
  );
5761
- 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;
5762
- const rawBaseCost = await wrapAs2(
5763
- "RPC",
5764
- OP_DEPOSITS.nonbase.baseCost,
5765
- () => bh.l2TransactionBaseCost(
5766
- ctx.chainIdL2,
5767
- gasPriceForBaseCost,
5768
- l2GasLimitUsed,
5769
- ctx.gasPerPubdata
5770
- ),
5771
- {
5772
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
5773
- message: "Could not fetch L2 base cost from Bridgehub."
5960
+ },
5961
+ async build(p, ctx) {
5962
+ const l1Signer = ctx.client.getL1Signer();
5963
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
5964
+ const baseIsEth = isETH(baseToken);
5965
+ const l2GasParams = await determineErc20L2Gas({
5966
+ ctx,
5967
+ l1Token: p.token,
5968
+ modelTx: {
5969
+ to: p.to ?? ctx.sender,
5970
+ from: ctx.sender,
5971
+ data: "0x",
5972
+ value: 0n
5774
5973
  }
5775
- );
5776
- const baseCost = BigInt(rawBaseCost);
5974
+ });
5975
+ if (!l2GasParams) throw new Error("Failed to establish L2 gas parameters.");
5976
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
5777
5977
  const mintValue = baseCost + ctx.operatorTip;
5778
5978
  const approvals = [];
5779
5979
  const steps = [];
5780
- const l1Signer = ctx.client.getL1Signer();
5781
- {
5782
- const erc20Deposit = new ethers.Contract(p.token, IERC20_default, l1Signer);
5783
- const allowanceToken = await wrapAs2(
5784
- "RPC",
5785
- OP_DEPOSITS.nonbase.allowanceToken,
5786
- () => erc20Deposit.allowance(ctx.sender, assetRouter),
5787
- {
5788
- ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
5789
- message: "Failed to read deposit-token allowance."
5790
- }
5791
- );
5792
- if (allowanceToken < p.amount) {
5793
- approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
5794
- const data = erc20Deposit.interface.encodeFunctionData("approve", [
5795
- assetRouter,
5796
- p.amount
5797
- ]);
5798
- steps.push({
5799
- key: `approve:${p.token}:${assetRouter}`,
5800
- kind: "approve",
5801
- description: `Approve ${p.amount} for router (deposit token)`,
5802
- tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
5803
- });
5980
+ const assetRouter = ctx.l1AssetRouter;
5981
+ const erc20Deposit = new ethers.Contract(p.token, IERC20_default, l1Signer);
5982
+ const allowanceToken = await wrapAs3(
5983
+ "RPC",
5984
+ OP_DEPOSITS.nonbase.allowanceToken,
5985
+ () => erc20Deposit.allowance(ctx.sender, assetRouter),
5986
+ {
5987
+ ctx: { where: "erc20.allowance", token: p.token, spender: assetRouter },
5988
+ message: "Failed to read deposit-token allowance."
5804
5989
  }
5990
+ );
5991
+ if (allowanceToken < p.amount) {
5992
+ approvals.push({ token: p.token, spender: assetRouter, amount: p.amount });
5993
+ steps.push({
5994
+ key: `approve:${p.token}:${assetRouter}`,
5995
+ kind: "approve",
5996
+ description: `Approve ${p.amount} for router (deposit token)`,
5997
+ tx: {
5998
+ to: p.token,
5999
+ data: erc20Deposit.interface.encodeFunctionData("approve", [assetRouter, p.amount]),
6000
+ from: ctx.sender,
6001
+ ...ctx.gasOverrides
6002
+ }
6003
+ });
5805
6004
  }
5806
- const baseIsEth = isETH(baseToken);
5807
6005
  if (!baseIsEth) {
5808
6006
  const erc20Base = new ethers.Contract(baseToken, IERC20_default, l1Signer);
5809
- const allowanceBase = await wrapAs2(
6007
+ const allowanceBase = await wrapAs3(
5810
6008
  "RPC",
5811
6009
  OP_DEPOSITS.nonbase.allowanceBase,
5812
6010
  () => erc20Base.allowance(ctx.sender, assetRouter),
@@ -5817,16 +6015,20 @@ function routeErc20NonBase() {
5817
6015
  );
5818
6016
  if (allowanceBase < mintValue) {
5819
6017
  approvals.push({ token: baseToken, spender: assetRouter, amount: mintValue });
5820
- const data = erc20Base.interface.encodeFunctionData("approve", [assetRouter, mintValue]);
5821
6018
  steps.push({
5822
6019
  key: `approve:${baseToken}:${assetRouter}`,
5823
6020
  kind: "approve",
5824
6021
  description: `Approve base token for mintValue`,
5825
- tx: { to: baseToken, data, from: ctx.sender, ...txOverrides }
6022
+ tx: {
6023
+ to: baseToken,
6024
+ data: erc20Base.interface.encodeFunctionData("approve", [assetRouter, mintValue]),
6025
+ from: ctx.sender,
6026
+ ...ctx.gasOverrides
6027
+ }
5826
6028
  });
5827
6029
  }
5828
6030
  }
5829
- const secondBridgeCalldata = await wrapAs2(
6031
+ const secondBridgeCalldata = await wrapAs3(
5830
6032
  "INTERNAL",
5831
6033
  OP_DEPOSITS.nonbase.encodeCalldata,
5832
6034
  () => Promise.resolve(encodeSecondBridgeErc20Args(p.token, p.amount, p.to ?? ctx.sender)),
@@ -5835,68 +6037,67 @@ function routeErc20NonBase() {
5835
6037
  message: "Failed to encode bridging calldata."
5836
6038
  }
5837
6039
  );
5838
- const outer = {
6040
+ const requestStruct = {
5839
6041
  chainId: ctx.chainIdL2,
5840
6042
  mintValue,
5841
- // fees (in ETH if base=ETH, else pulled as base ERC-20)
5842
6043
  l2Value: 0n,
5843
- l2GasLimit: l2GasLimitUsed,
6044
+ l2GasLimit: l2GasParams.gasLimit,
5844
6045
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
5845
6046
  refundRecipient: ctx.refundRecipient,
5846
6047
  secondBridgeAddress: assetRouter,
5847
6048
  secondBridgeValue: 0n,
5848
6049
  secondBridgeCalldata
5849
6050
  };
5850
- const dataTwo = bh.interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
5851
- const bridgeTx = {
6051
+ const bh = (await ctx.client.contracts()).bridgehub;
6052
+ const data = bh.interface.encodeFunctionData("requestL2TransactionTwoBridges", [
6053
+ requestStruct
6054
+ ]);
6055
+ const txValue = baseIsEth ? mintValue : 0n;
6056
+ const l1TxCandidate = {
5852
6057
  to: ctx.bridgehub,
5853
- data: dataTwo,
5854
- value: baseIsEth ? mintValue : 0n,
6058
+ data,
6059
+ value: txValue,
5855
6060
  from: ctx.sender,
5856
- ...txOverrides
6061
+ ...ctx.gasOverrides
5857
6062
  };
5858
- if (overrideGasLimit != null) {
5859
- bridgeTx.gasLimit = overrideGasLimit;
5860
- resolvedL1GasLimit = overrideGasLimit;
5861
- } else {
5862
- try {
5863
- const est = await wrapAs2(
5864
- "RPC",
5865
- OP_DEPOSITS.nonbase.estGas,
5866
- () => ctx.client.l1.estimateGas(bridgeTx),
5867
- {
5868
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub, baseIsEth },
5869
- message: "Failed to estimate gas for Bridgehub request."
5870
- }
5871
- );
5872
- const buffered = BigInt(est) * 125n / 100n;
5873
- bridgeTx.gasLimit = buffered;
5874
- resolvedL1GasLimit = buffered;
5875
- } catch {
5876
- }
6063
+ const l1GasParams = await quoteL1Gas2({
6064
+ ctx,
6065
+ tx: l1TxCandidate,
6066
+ overrides: ctx.gasOverrides,
6067
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
6068
+ });
6069
+ if (l1GasParams) {
6070
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
6071
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
6072
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
5877
6073
  }
5878
6074
  steps.push({
5879
- key: "bridgehub:two-bridges:nonbase",
6075
+ key: "bridgehub:two-bridges",
5880
6076
  kind: "bridgehub:two-bridges",
5881
- description: baseIsEth ? "Bridge ERC-20 (fees in ETH) via Bridgehub.requestL2TransactionTwoBridges" : "Bridge ERC-20 (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
5882
- tx: bridgeTx
6077
+ description: baseIsEth ? "Bridge ERC-20 (Fees paid in ETH)" : "Bridge ERC-20 (Fees paid in Base Token)",
6078
+ tx: l1TxCandidate
6079
+ });
6080
+ const fees = buildFeeBreakdown({
6081
+ feeToken: baseToken,
6082
+ l1Gas: l1GasParams,
6083
+ l2Gas: l2GasParams,
6084
+ l2BaseCost: baseCost,
6085
+ operatorTip: ctx.operatorTip,
6086
+ mintValue
5883
6087
  });
5884
6088
  return {
5885
6089
  steps,
5886
6090
  approvals,
5887
- quoteExtras: { baseCost, mintValue, baseToken, baseIsEth, l1GasLimit: resolvedL1GasLimit }
6091
+ fees
5888
6092
  };
5889
6093
  }
5890
6094
  };
5891
6095
  }
5892
- var { wrapAs: wrapAs3 } = createErrorHandlers("deposits");
5893
- var BASE_COST_BUFFER_BPS = 100n;
5894
- var BPS = 10000n;
5895
- var withBuffer = (x) => x * (BPS + BASE_COST_BUFFER_BPS) / BPS;
6096
+ var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
5896
6097
  function routeEthNonBase() {
5897
6098
  return {
5898
6099
  async preflight(p, ctx) {
5899
- await wrapAs3(
6100
+ await wrapAs4(
5900
6101
  "VALIDATION",
5901
6102
  OP_DEPOSITS.ethNonBase.assertEthAsset,
5902
6103
  () => {
@@ -5906,17 +6107,8 @@ function routeEthNonBase() {
5906
6107
  },
5907
6108
  { ctx: { token: p.token } }
5908
6109
  );
5909
- const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
5910
- const baseToken = await wrapAs3(
5911
- "CONTRACT",
5912
- OP_DEPOSITS.ethNonBase.baseToken,
5913
- () => bh.baseToken(ctx.chainIdL2),
5914
- {
5915
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
5916
- message: "Failed to read base token."
5917
- }
5918
- );
5919
- await wrapAs3(
6110
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
6111
+ await wrapAs4(
5920
6112
  "VALIDATION",
5921
6113
  OP_DEPOSITS.ethNonBase.assertNonEthBase,
5922
6114
  () => {
@@ -5926,7 +6118,7 @@ function routeEthNonBase() {
5926
6118
  },
5927
6119
  { ctx: { baseToken, chainIdL2: ctx.chainIdL2 } }
5928
6120
  );
5929
- const ethBal = await wrapAs3(
6121
+ const ethBal = await wrapAs4(
5930
6122
  "RPC",
5931
6123
  OP_DEPOSITS.ethNonBase.ethBalance,
5932
6124
  () => ctx.client.l1.getBalance(ctx.sender),
@@ -5935,7 +6127,7 @@ function routeEthNonBase() {
5935
6127
  message: "Failed to read L1 ETH balance."
5936
6128
  }
5937
6129
  );
5938
- await wrapAs3(
6130
+ await wrapAs4(
5939
6131
  "VALIDATION",
5940
6132
  OP_DEPOSITS.ethNonBase.assertEthBalance,
5941
6133
  () => {
@@ -5948,63 +6140,50 @@ function routeEthNonBase() {
5948
6140
  return;
5949
6141
  },
5950
6142
  async build(p, ctx) {
5951
- const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
5952
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
5953
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
5954
- const baseToken = await wrapAs3(
5955
- "CONTRACT",
5956
- OP_DEPOSITS.ethNonBase.baseToken,
5957
- () => bh.baseToken(ctx.chainIdL2),
5958
- {
5959
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
5960
- message: "Failed to read base token."
5961
- }
5962
- );
5963
- const rawBaseCost = await wrapAs3(
6143
+ const l1Signer = ctx.client.getL1Signer();
6144
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
6145
+ const l2TxModel = {
6146
+ to: p.to ?? ctx.sender,
6147
+ from: ctx.sender,
6148
+ data: "0x",
6149
+ value: 0n
6150
+ };
6151
+ const l2GasParams = await quoteL2Gas2({
6152
+ ctx,
6153
+ route: "eth-nonbase",
6154
+ l2TxForModeling: l2TxModel,
6155
+ overrideGasLimit: ctx.l2GasLimit
6156
+ });
6157
+ if (!l2GasParams) throw new Error("Failed to estimate L2 gas parameters.");
6158
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
6159
+ const mintValue = baseCost + ctx.operatorTip;
6160
+ const approvals = [];
6161
+ const steps = [];
6162
+ const erc20Base = new ethers.Contract(baseToken, IERC20_default, l1Signer);
6163
+ const allowance = await wrapAs4(
5964
6164
  "RPC",
5965
- OP_DEPOSITS.ethNonBase.baseCost,
5966
- () => bh.l2TransactionBaseCost(
5967
- ctx.chainIdL2,
5968
- gasPriceForBaseCost,
5969
- ctx.l2GasLimit,
5970
- ctx.gasPerPubdata
5971
- ),
6165
+ OP_DEPOSITS.ethNonBase.allowanceBase,
6166
+ () => erc20Base.allowance(ctx.sender, ctx.l1AssetRouter),
5972
6167
  {
5973
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
5974
- message: "Could not fetch L2 base cost."
6168
+ ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
6169
+ message: "Failed to read base-token allowance."
5975
6170
  }
5976
6171
  );
5977
- const baseCost = BigInt(rawBaseCost);
5978
- const mintValueRaw = baseCost + ctx.operatorTip;
5979
- const mintValue = withBuffer(mintValueRaw);
5980
- const approvals = [];
5981
- const steps = [];
5982
- {
5983
- const erc20 = new ethers.Contract(baseToken, IERC20_default, ctx.client.getL1Signer());
5984
- const allowance = await wrapAs3(
5985
- "RPC",
5986
- OP_DEPOSITS.ethNonBase.allowanceBase,
5987
- () => erc20.allowance(ctx.sender, ctx.l1AssetRouter),
5988
- {
5989
- ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
5990
- message: "Failed to read base-token allowance."
6172
+ if (allowance < mintValue) {
6173
+ approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
6174
+ steps.push({
6175
+ key: `approve:${baseToken}`,
6176
+ kind: "approve",
6177
+ description: `Approve base token for fees (mintValue)`,
6178
+ tx: {
6179
+ to: baseToken,
6180
+ data: erc20Base.interface.encodeFunctionData("approve", [ctx.l1AssetRouter, mintValue]),
6181
+ from: ctx.sender,
6182
+ ...ctx.gasOverrides
5991
6183
  }
5992
- );
5993
- if (allowance < mintValue) {
5994
- approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
5995
- const data = erc20.interface.encodeFunctionData("approve", [
5996
- ctx.l1AssetRouter,
5997
- mintValue
5998
- ]);
5999
- steps.push({
6000
- key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
6001
- kind: "approve",
6002
- description: `Approve base token for mintValue`,
6003
- tx: { to: baseToken, data, from: ctx.sender, ...txOverrides }
6004
- });
6005
- }
6184
+ });
6006
6185
  }
6007
- const secondBridgeCalldata = await wrapAs3(
6186
+ const secondBridgeCalldata = await wrapAs4(
6008
6187
  "INTERNAL",
6009
6188
  OP_DEPOSITS.ethNonBase.encodeCalldata,
6010
6189
  () => Promise.resolve(encodeSecondBridgeEthArgs(p.amount, p.to ?? ctx.sender)),
@@ -6016,73 +6195,68 @@ function routeEthNonBase() {
6016
6195
  }
6017
6196
  }
6018
6197
  );
6019
- const outer = {
6198
+ const requestStruct = {
6020
6199
  chainId: ctx.chainIdL2,
6021
6200
  mintValue,
6022
- l2Value: 0n,
6023
- l2GasLimit: ctx.l2GasLimit,
6201
+ l2Value: p.amount,
6202
+ l2GasLimit: l2GasParams.gasLimit,
6024
6203
  l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
6025
6204
  refundRecipient: ctx.refundRecipient,
6026
6205
  secondBridgeAddress: ctx.l1AssetRouter,
6027
6206
  secondBridgeValue: p.amount,
6028
6207
  secondBridgeCalldata
6029
6208
  };
6030
- const dataTwo = new ethers.Contract(
6209
+ const data = new ethers.Contract(
6031
6210
  ctx.bridgehub,
6032
6211
  IBridgehub_default,
6033
6212
  ctx.client.l1
6034
- ).interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
6035
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
6036
- const bridgeTx = {
6213
+ ).interface.encodeFunctionData("requestL2TransactionTwoBridges", [requestStruct]);
6214
+ const l1TxCandidate = {
6037
6215
  to: ctx.bridgehub,
6038
- data: dataTwo,
6216
+ data,
6039
6217
  value: p.amount,
6040
6218
  // base ≠ ETH ⇒ msg.value == secondBridgeValue
6041
6219
  from: ctx.sender,
6042
- ...txOverrides
6220
+ ...ctx.gasOverrides
6043
6221
  };
6044
- if (overrideGasLimit != null) {
6045
- bridgeTx.gasLimit = overrideGasLimit;
6046
- resolvedL1GasLimit = overrideGasLimit;
6047
- } else {
6048
- try {
6049
- const est = await wrapAs3(
6050
- "RPC",
6051
- OP_DEPOSITS.ethNonBase.estGas,
6052
- () => ctx.client.l1.estimateGas(bridgeTx),
6053
- {
6054
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
6055
- message: "Failed to estimate gas for Bridgehub request."
6056
- }
6057
- );
6058
- const buffered = BigInt(est) * 115n / 100n;
6059
- bridgeTx.gasLimit = buffered;
6060
- resolvedL1GasLimit = buffered;
6061
- } catch {
6062
- }
6222
+ const l1GasParams = await quoteL1Gas2({
6223
+ ctx,
6224
+ tx: l1TxCandidate,
6225
+ overrides: ctx.gasOverrides,
6226
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
6227
+ });
6228
+ if (l1GasParams) {
6229
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
6230
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
6231
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
6063
6232
  }
6064
6233
  steps.push({
6065
6234
  key: "bridgehub:two-bridges:eth-nonbase",
6066
6235
  kind: "bridgehub:two-bridges",
6067
6236
  description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
6068
- tx: bridgeTx
6237
+ tx: l1TxCandidate
6238
+ });
6239
+ const fees = buildFeeBreakdown({
6240
+ feeToken: baseToken,
6241
+ l1Gas: l1GasParams,
6242
+ l2Gas: l2GasParams,
6243
+ l2BaseCost: baseCost,
6244
+ operatorTip: ctx.operatorTip,
6245
+ mintValue
6069
6246
  });
6070
6247
  return {
6071
6248
  steps,
6072
6249
  approvals,
6073
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
6250
+ fees
6074
6251
  };
6075
6252
  }
6076
6253
  };
6077
6254
  }
6078
- var { wrapAs: wrapAs4 } = createErrorHandlers("deposits");
6079
- var BASE_COST_BUFFER_BPS2 = 100n;
6080
- var BPS2 = 10000n;
6081
- var withBuffer2 = (x) => x * (BPS2 + BASE_COST_BUFFER_BPS2) / BPS2;
6255
+ var { wrapAs: wrapAs5 } = createErrorHandlers("deposits");
6082
6256
  function routeErc20Base() {
6083
6257
  return {
6084
6258
  async preflight(p, ctx) {
6085
- await wrapAs4(
6259
+ await wrapAs5(
6086
6260
  "VALIDATION",
6087
6261
  OP_DEPOSITS.base.assertErc20Asset,
6088
6262
  () => {
@@ -6092,17 +6266,8 @@ function routeErc20Base() {
6092
6266
  },
6093
6267
  { ctx: { token: p.token } }
6094
6268
  );
6095
- const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
6096
- const baseToken = await wrapAs4(
6097
- "CONTRACT",
6098
- OP_DEPOSITS.base.baseToken,
6099
- () => bh.baseToken(ctx.chainIdL2),
6100
- {
6101
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
6102
- message: "Failed to read base token."
6103
- }
6104
- );
6105
- await wrapAs4(
6269
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
6270
+ await wrapAs5(
6106
6271
  "VALIDATION",
6107
6272
  OP_DEPOSITS.base.assertMatchesBase,
6108
6273
  () => {
@@ -6115,42 +6280,28 @@ function routeErc20Base() {
6115
6280
  return;
6116
6281
  },
6117
6282
  async build(p, ctx) {
6118
- const bh = new ethers.Contract(ctx.bridgehub, IBridgehub_default, ctx.client.l1);
6119
- const { gasPriceForBaseCost, gasLimit: overrideGasLimit, ...txFeeOverrides } = ctx.fee;
6120
- const txOverrides = overrideGasLimit != null ? { ...txFeeOverrides, gasLimit: overrideGasLimit } : txFeeOverrides;
6121
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
6122
- const baseToken = await wrapAs4(
6123
- "CONTRACT",
6124
- OP_DEPOSITS.base.baseToken,
6125
- () => bh.baseToken(ctx.chainIdL2),
6126
- {
6127
- ctx: { where: "bridgehub.baseToken", chainIdL2: ctx.chainIdL2 },
6128
- message: "Failed to read base token."
6129
- }
6130
- );
6131
- const rawBaseCost = await wrapAs4(
6132
- "RPC",
6133
- OP_DEPOSITS.base.baseCost,
6134
- () => bh.l2TransactionBaseCost(
6135
- ctx.chainIdL2,
6136
- gasPriceForBaseCost,
6137
- ctx.l2GasLimit,
6138
- ctx.gasPerPubdata
6139
- ),
6140
- {
6141
- ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
6142
- message: "Could not fetch L2 base cost from Bridgehub."
6143
- }
6144
- );
6145
- const baseCost = BigInt(rawBaseCost);
6146
- const l2Value = p.amount;
6147
- const rawMintValue = baseCost + ctx.operatorTip + l2Value;
6148
- const mintValue = withBuffer2(rawMintValue);
6283
+ const l1Signer = ctx.client.getL1Signer();
6284
+ const baseToken = await ctx.client.baseToken(ctx.chainIdL2);
6285
+ const l2TxModel = {
6286
+ to: p.to ?? ctx.sender,
6287
+ from: ctx.sender,
6288
+ data: "0x",
6289
+ value: 0n
6290
+ };
6291
+ const l2GasParams = await quoteL2Gas2({
6292
+ ctx,
6293
+ route: "erc20-base",
6294
+ l2TxForModeling: l2TxModel,
6295
+ overrideGasLimit: ctx.l2GasLimit
6296
+ });
6297
+ if (!l2GasParams) throw new Error("Failed to estimate L2 gas parameters.");
6298
+ const baseCost = await quoteL2BaseCost2({ ctx, l2GasLimit: l2GasParams.gasLimit });
6299
+ const mintValue = baseCost + ctx.operatorTip + p.amount;
6149
6300
  const approvals = [];
6150
6301
  const steps = [];
6151
6302
  {
6152
- const erc20 = new ethers.Contract(baseToken, IERC20_default, ctx.client.getL1Signer());
6153
- const allowance = await wrapAs4(
6303
+ const erc20 = new ethers.Contract(baseToken, IERC20_default, l1Signer);
6304
+ const allowance = await wrapAs5(
6154
6305
  "RPC",
6155
6306
  OP_DEPOSITS.base.allowance,
6156
6307
  () => erc20.allowance(ctx.sender, ctx.l1AssetRouter),
@@ -6161,70 +6312,70 @@ function routeErc20Base() {
6161
6312
  );
6162
6313
  if (allowance < mintValue) {
6163
6314
  approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
6164
- const data2 = erc20.interface.encodeFunctionData("approve", [
6165
- ctx.l1AssetRouter,
6166
- mintValue
6167
- ]);
6168
6315
  steps.push({
6169
6316
  key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
6170
6317
  kind: "approve",
6171
6318
  description: "Approve base token for mintValue",
6172
- tx: { to: baseToken, data: data2, from: ctx.sender, ...txOverrides }
6319
+ tx: {
6320
+ to: baseToken,
6321
+ data: erc20.interface.encodeFunctionData("approve", [ctx.l1AssetRouter, mintValue]),
6322
+ from: ctx.sender,
6323
+ ...ctx.gasOverrides
6324
+ }
6173
6325
  });
6174
6326
  }
6175
6327
  }
6176
- const req = buildDirectRequestStruct({
6328
+ const requestStruct = buildDirectRequestStruct({
6177
6329
  chainId: ctx.chainIdL2,
6178
6330
  mintValue,
6179
- l2GasLimit: ctx.l2GasLimit,
6331
+ l2GasLimit: l2GasParams.gasLimit,
6180
6332
  gasPerPubdata: ctx.gasPerPubdata,
6181
6333
  refundRecipient: ctx.refundRecipient,
6182
6334
  l2Contract: p.to ?? ctx.sender,
6183
- l2Value
6335
+ l2Value: p.amount
6184
6336
  });
6185
6337
  const data = new ethers.Contract(
6186
6338
  ctx.bridgehub,
6187
6339
  IBridgehub_default,
6188
6340
  ctx.client.l1
6189
- ).interface.encodeFunctionData("requestL2TransactionDirect", [req]);
6190
- const tx = {
6341
+ ).interface.encodeFunctionData("requestL2TransactionDirect", [requestStruct]);
6342
+ const l1TxCandidate = {
6191
6343
  to: ctx.bridgehub,
6192
6344
  data,
6193
6345
  value: 0n,
6194
6346
  // base token is ERC-20 ⇒ msg.value MUST be 0
6195
6347
  from: ctx.sender,
6196
- ...txOverrides
6348
+ ...ctx.gasOverrides
6197
6349
  };
6198
- if (overrideGasLimit != null) {
6199
- tx.gasLimit = overrideGasLimit;
6200
- resolvedL1GasLimit = overrideGasLimit;
6201
- } else {
6202
- try {
6203
- const est = await wrapAs4(
6204
- "RPC",
6205
- OP_DEPOSITS.base.estGas,
6206
- () => ctx.client.l1.estimateGas(tx),
6207
- {
6208
- ctx: { where: "l1.estimateGas", to: ctx.bridgehub },
6209
- message: "Failed to estimate gas for Bridgehub request."
6210
- }
6211
- );
6212
- const buffered = BigInt(est) * 115n / 100n;
6213
- tx.gasLimit = buffered;
6214
- resolvedL1GasLimit = buffered;
6215
- } catch {
6216
- }
6350
+ const l1GasParams = await quoteL1Gas2({
6351
+ ctx,
6352
+ tx: l1TxCandidate,
6353
+ overrides: ctx.gasOverrides,
6354
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
6355
+ });
6356
+ if (l1GasParams) {
6357
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
6358
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
6359
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
6217
6360
  }
6218
6361
  steps.push({
6219
6362
  key: "bridgehub:direct:erc20-base",
6220
6363
  kind: "bridgehub:direct",
6221
6364
  description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
6222
- tx
6365
+ tx: l1TxCandidate
6366
+ });
6367
+ const fees = buildFeeBreakdown({
6368
+ feeToken: baseToken,
6369
+ l1Gas: l1GasParams,
6370
+ l2Gas: l2GasParams,
6371
+ l2BaseCost: baseCost,
6372
+ operatorTip: ctx.operatorTip,
6373
+ mintValue
6223
6374
  });
6224
6375
  return {
6225
6376
  steps,
6226
6377
  approvals,
6227
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
6378
+ fees
6228
6379
  };
6229
6380
  }
6230
6381
  };
@@ -6243,37 +6394,19 @@ function createDepositsResource(client) {
6243
6394
  const ctx = await commonCtx(p, client);
6244
6395
  const route = ctx.route;
6245
6396
  await ROUTES[route].preflight?.(p, ctx);
6246
- const { steps, approvals, quoteExtras } = await ROUTES[route].build(p, ctx);
6247
- const { baseCost, mintValue } = quoteExtras;
6248
- const fallbackGasLimit = quoteExtras.l1GasLimit;
6249
- const resolveGasLimit = () => {
6250
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
6251
- for (let i = steps.length - 1; i >= 0; i--) {
6252
- const candidate = steps[i].tx.gasLimit;
6253
- if (candidate == null) continue;
6254
- if (typeof candidate === "bigint") return candidate;
6255
- try {
6256
- return BigInt(candidate.toString());
6257
- } catch {
6258
- }
6259
- }
6260
- if (fallbackGasLimit != null) return fallbackGasLimit;
6261
- return ctx.l2GasLimit;
6262
- };
6263
- const gasLimit = resolveGasLimit();
6397
+ const { steps, approvals, fees } = await ROUTES[route].build(p, ctx);
6264
6398
  return {
6265
6399
  route: ctx.route,
6266
6400
  summary: {
6267
6401
  route: ctx.route,
6268
6402
  approvalsNeeded: approvals,
6269
- baseCost,
6270
- mintValue,
6271
- gasPerPubdata: ctx.gasPerPubdata,
6272
- fees: {
6273
- gasLimit,
6274
- maxFeePerGas: ctx.fee.maxFeePerGas,
6275
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
6276
- }
6403
+ amounts: {
6404
+ transfer: { token: p.token, amount: p.amount }
6405
+ },
6406
+ fees,
6407
+ // Legacy fields (maintained for backward compatibility)
6408
+ baseCost: fees.l2?.baseCost,
6409
+ mintValue: fees.mintValue
6277
6410
  },
6278
6411
  steps
6279
6412
  };
@@ -6342,7 +6475,7 @@ function createDepositsResource(client) {
6342
6475
  step.tx.maxPriorityFeePerGas = overrides.maxPriorityFeePerGas;
6343
6476
  }
6344
6477
  }
6345
- if (!step.tx.gasLimit) {
6478
+ if (!p.l1TxOverrides?.gasLimit) {
6346
6479
  try {
6347
6480
  const est = await client.l1.estimateGas(step.tx);
6348
6481
  step.tx.gasLimit = BigInt(est) * 115n / 100n;
@@ -6533,7 +6666,7 @@ function normalizeTokenForRouting(token) {
6533
6666
  function pickWithdrawRoute(args) {
6534
6667
  const tokenNorm = normalizeTokenForRouting(args.token);
6535
6668
  const isL2BaseAlias = tokenNorm.toLowerCase() === L2_BASE_TOKEN_ADDRESS.toLowerCase();
6536
- if (isL2BaseAlias) return args.baseIsEth ? "eth-base" : "eth-nonbase";
6669
+ if (isL2BaseAlias) return "base";
6537
6670
  return "erc20-nonbase";
6538
6671
  }
6539
6672
  async function ntvBaseAssetId(l2, ntv) {
@@ -6567,10 +6700,7 @@ async function commonCtx2(p, client) {
6567
6700
  const { chainId } = await client.l2.getNetwork();
6568
6701
  const chainIdL2 = BigInt(chainId);
6569
6702
  const baseIsEth = await isEthBasedChain(client.l2, l2NativeTokenVault);
6570
- const fee = await getL2FeeOverrides(client, p.l2TxOverrides);
6571
- const route = pickWithdrawRoute({ token: p.token, baseIsEth });
6572
- const l2GasLimit = p.l2GasLimit ?? 300000n;
6573
- const gasBufferPct = 15;
6703
+ const route = pickWithdrawRoute({ token: p.token});
6574
6704
  return {
6575
6705
  client,
6576
6706
  bridgehub,
@@ -6583,29 +6713,111 @@ async function commonCtx2(p, client) {
6583
6713
  l2NativeTokenVault,
6584
6714
  l2BaseTokenSystem,
6585
6715
  baseIsEth,
6586
- l2GasLimit,
6587
- gasBufferPct,
6588
- fee
6716
+ gasOverrides: p.l2TxOverrides
6717
+ };
6718
+ }
6719
+
6720
+ // src/core/resources/withdrawals/gas.ts
6721
+ function makeGasQuote2(p) {
6722
+ return {
6723
+ gasLimit: p.gasLimit,
6724
+ maxFeePerGas: p.maxFeePerGas,
6725
+ maxPriorityFeePerGas: p.maxPriorityFeePerGas,
6726
+ maxCost: p.gasLimit * p.maxFeePerGas
6727
+ };
6728
+ }
6729
+ async function fetchFees2(estimator) {
6730
+ try {
6731
+ const fees = await estimator.estimateFeesPerGas();
6732
+ if (fees.maxFeePerGas != null) {
6733
+ return {
6734
+ maxFeePerGas: fees.maxFeePerGas,
6735
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas ?? 0n
6736
+ };
6737
+ }
6738
+ if (fees.gasPrice != null) {
6739
+ return {
6740
+ maxFeePerGas: fees.gasPrice,
6741
+ maxPriorityFeePerGas: 0n
6742
+ };
6743
+ }
6744
+ } catch {
6745
+ }
6746
+ try {
6747
+ const gp = await estimator.getGasPrice();
6748
+ return { maxFeePerGas: gp, maxPriorityFeePerGas: 0n };
6749
+ } catch {
6750
+ return { maxFeePerGas: 0n, maxPriorityFeePerGas: 0n };
6751
+ }
6752
+ }
6753
+ async function quoteL2Gas3(input) {
6754
+ const { estimator, tx, overrides } = input;
6755
+ const market = await fetchFees2(estimator);
6756
+ const o = overrides;
6757
+ const maxFeePerGas = o?.maxFeePerGas ?? (tx.maxFeePerGas != null ? BigInt(tx.maxFeePerGas) : market.maxFeePerGas);
6758
+ const maxPriorityFeePerGas = o?.maxPriorityFeePerGas ?? (tx.maxPriorityFeePerGas != null ? BigInt(tx.maxPriorityFeePerGas) : market.maxPriorityFeePerGas);
6759
+ const explicitGasLimit = o?.gasLimit ?? (tx.gasLimit != null ? BigInt(tx.gasLimit) : void 0);
6760
+ if (explicitGasLimit != null) {
6761
+ return makeGasQuote2({
6762
+ gasLimit: explicitGasLimit,
6763
+ maxFeePerGas,
6764
+ maxPriorityFeePerGas
6765
+ });
6766
+ }
6767
+ try {
6768
+ const est = await estimator.estimateGas(tx);
6769
+ const buffered = BigInt(est) * (100n + BUFFER) / 100n;
6770
+ return makeGasQuote2({
6771
+ gasLimit: buffered,
6772
+ maxFeePerGas,
6773
+ maxPriorityFeePerGas
6774
+ });
6775
+ } catch (err) {
6776
+ console.warn("Failed to estimate L2 gas for withdrawal.", err);
6777
+ return void 0;
6778
+ }
6779
+ }
6780
+
6781
+ // src/adapters/ethers/resources/withdrawals/services/gas.ts
6782
+ async function quoteL2Gas4(input) {
6783
+ const { ctx, tx } = input;
6784
+ const estimator = ethersToGasEstimator(ctx.client.l2);
6785
+ return quoteL2Gas3({
6786
+ estimator,
6787
+ tx: toCoreTx(tx),
6788
+ overrides: ctx.gasOverrides
6789
+ });
6790
+ }
6791
+
6792
+ // src/adapters/ethers/resources/withdrawals/services/fees.ts
6793
+ function buildFeeBreakdown2(p) {
6794
+ const l2Total = p.l2Gas?.maxCost ?? 0n;
6795
+ const l2 = {
6796
+ total: l2Total,
6797
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
6798
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
6799
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas
6800
+ };
6801
+ return {
6802
+ token: p.feeToken,
6803
+ maxTotal: l2Total,
6804
+ l2
6589
6805
  };
6590
6806
  }
6591
- var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
6807
+
6808
+ // src/adapters/ethers/resources/withdrawals/routes/eth.ts
6809
+ var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
6592
6810
  function routeEthBase() {
6593
6811
  return {
6594
6812
  async build(p, ctx) {
6595
6813
  const steps = [];
6596
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
6597
- const base = new ethers.Contract(
6598
- L2_BASE_TOKEN_ADDRESS,
6599
- new ethers.Interface(IBaseToken_default),
6600
- ctx.client.l2
6601
- );
6602
- const toL1 = p.to ?? ctx.sender;
6603
- const data = await wrapAs5(
6814
+ const base = (await ctx.client.contracts()).l2BaseTokenSystem;
6815
+ const data = await wrapAs6(
6604
6816
  "INTERNAL",
6605
6817
  OP_WITHDRAWALS.eth.encodeWithdraw,
6606
- () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [toL1])),
6818
+ () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [p.to ?? ctx.sender])),
6607
6819
  {
6608
- ctx: { where: "L2BaseToken.withdraw", to: toL1 },
6820
+ ctx: { where: "L2BaseToken.withdraw", to: p.to ?? ctx.sender },
6609
6821
  message: "Failed to encode ETH withdraw calldata."
6610
6822
  }
6611
6823
  );
@@ -6613,38 +6825,29 @@ function routeEthBase() {
6613
6825
  to: L2_BASE_TOKEN_ADDRESS,
6614
6826
  data,
6615
6827
  from: ctx.sender,
6616
- value: p.amount,
6617
- maxFeePerGas,
6618
- maxPriorityFeePerGas
6828
+ value: p.amount
6619
6829
  };
6620
- if (overrideGasLimit != null) {
6621
- tx.gasLimit = overrideGasLimit;
6622
- } else {
6623
- try {
6624
- const est = await wrapAs5(
6625
- "RPC",
6626
- OP_WITHDRAWALS.eth.estGas,
6627
- () => ctx.client.l2.estimateGas(tx),
6628
- {
6629
- ctx: { where: "l2.estimateGas", to: L2_BASE_TOKEN_ADDRESS },
6630
- message: "Failed to estimate gas for L2 ETH withdraw."
6631
- }
6632
- );
6633
- tx.gasLimit = BigInt(est) * 115n / 100n;
6634
- } catch {
6635
- }
6636
- }
6830
+ const gas = await quoteL2Gas4({ ctx, tx });
6831
+ if (gas) {
6832
+ tx.gasLimit = gas.gasLimit;
6833
+ tx.maxFeePerGas = gas.maxFeePerGas;
6834
+ tx.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
6835
+ }
6836
+ const fees = buildFeeBreakdown2({
6837
+ feeToken: L2_BASE_TOKEN_ADDRESS,
6838
+ l2Gas: gas
6839
+ });
6637
6840
  steps.push({
6638
6841
  key: "l2-base-token:withdraw",
6639
6842
  kind: "l2-base-token:withdraw",
6640
6843
  description: "Withdraw ETH via L2 Base Token System",
6641
6844
  tx
6642
6845
  });
6643
- return { steps, approvals: [], quoteExtras: {} };
6846
+ return { steps, approvals: [], fees };
6644
6847
  }
6645
6848
  };
6646
6849
  }
6647
- var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
6850
+ var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
6648
6851
  var SIG = {
6649
6852
  withdraw: "withdraw(bytes32,bytes)"
6650
6853
  };
@@ -6653,10 +6856,8 @@ function routeErc20NonBase2() {
6653
6856
  async build(p, ctx) {
6654
6857
  const steps = [];
6655
6858
  const approvals = [];
6656
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
6657
- const txOverrides = overrideGasLimit != null ? { maxFeePerGas, maxPriorityFeePerGas, gasLimit: overrideGasLimit } : { maxFeePerGas, maxPriorityFeePerGas };
6658
6859
  const erc20 = new ethers.Contract(p.token, IERC20_default, ctx.client.getL2Signer());
6659
- const current = await wrapAs6(
6860
+ const current = await wrapAs7(
6660
6861
  "CONTRACT",
6661
6862
  OP_WITHDRAWALS.erc20.allowance,
6662
6863
  () => erc20.allowance(ctx.sender, ctx.l2NativeTokenVault),
@@ -6676,15 +6877,26 @@ function routeErc20NonBase2() {
6676
6877
  ctx.l2NativeTokenVault,
6677
6878
  p.amount
6678
6879
  ]);
6880
+ const approveTx = {
6881
+ to: p.token,
6882
+ data,
6883
+ from: ctx.sender
6884
+ };
6885
+ const approveGas = await quoteL2Gas4({ ctx, tx: approveTx });
6886
+ if (approveGas) {
6887
+ approveTx.gasLimit = approveGas.gasLimit;
6888
+ approveTx.maxFeePerGas = approveGas.maxFeePerGas;
6889
+ approveTx.maxPriorityFeePerGas = approveGas.maxPriorityFeePerGas;
6890
+ }
6679
6891
  steps.push({
6680
6892
  key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
6681
6893
  kind: "approve:l2",
6682
6894
  description: `Approve ${p.amount} to NativeTokenVault`,
6683
- tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
6895
+ tx: approveTx
6684
6896
  });
6685
6897
  }
6686
- const ntv = new ethers.Contract(ctx.l2NativeTokenVault, L2NativeTokenVault_default, ctx.client.l2);
6687
- const assetId = await wrapAs6(
6898
+ const ntv = (await ctx.client.contracts()).l2NativeTokenVault;
6899
+ const assetId = await wrapAs7(
6688
6900
  "CONTRACT",
6689
6901
  OP_WITHDRAWALS.erc20.ensureRegistered,
6690
6902
  () => ntv.getFunction("ensureTokenIsRegistered").staticCall(p.token),
@@ -6693,22 +6905,19 @@ function routeErc20NonBase2() {
6693
6905
  message: "Failed to ensure token is registered in L2NativeTokenVault."
6694
6906
  }
6695
6907
  );
6696
- const assetData = await wrapAs6(
6908
+ const assetData = await wrapAs7(
6697
6909
  "INTERNAL",
6698
6910
  OP_WITHDRAWALS.erc20.encodeAssetData,
6699
6911
  () => Promise.resolve(
6700
- ethers.AbiCoder.defaultAbiCoder().encode(
6701
- ["uint256", "address", "address"],
6702
- [p.amount, p.to ?? ctx.sender, p.token]
6703
- )
6912
+ encodeNativeTokenVaultTransferData(p.amount, p.to ?? ctx.sender, p.token)
6704
6913
  ),
6705
6914
  {
6706
6915
  ctx: { where: "AbiCoder.encode", token: p.token, to: p.to ?? ctx.sender },
6707
6916
  message: "Failed to encode burn/withdraw asset data."
6708
6917
  }
6709
6918
  );
6710
- const l2ar = new ethers.Contract(ctx.l2AssetRouter, IL2AssetRouter_default, ctx.client.l2);
6711
- const dataWithdraw = await wrapAs6(
6919
+ const l2ar = (await ctx.client.contracts()).l2AssetRouter;
6920
+ const dataWithdraw = await wrapAs7(
6712
6921
  "INTERNAL",
6713
6922
  OP_WITHDRAWALS.erc20.encodeWithdraw,
6714
6923
  () => Promise.resolve(l2ar.interface.encodeFunctionData(SIG.withdraw, [assetId, assetData])),
@@ -6720,81 +6929,25 @@ function routeErc20NonBase2() {
6720
6929
  const withdrawTx = {
6721
6930
  to: ctx.l2AssetRouter,
6722
6931
  data: dataWithdraw,
6723
- from: ctx.sender,
6724
- ...txOverrides
6932
+ from: ctx.sender
6725
6933
  };
6934
+ const withdrawGas = await quoteL2Gas4({ ctx, tx: withdrawTx });
6935
+ if (withdrawGas) {
6936
+ withdrawTx.gasLimit = withdrawGas.gasLimit;
6937
+ withdrawTx.maxFeePerGas = withdrawGas.maxFeePerGas;
6938
+ withdrawTx.maxPriorityFeePerGas = withdrawGas.maxPriorityFeePerGas;
6939
+ }
6726
6940
  steps.push({
6727
6941
  key: "l2-asset-router:withdraw",
6728
6942
  kind: "l2-asset-router:withdraw",
6729
6943
  description: "Burn on L2 & send L2\u2192L1 message",
6730
6944
  tx: withdrawTx
6731
6945
  });
6732
- return { steps, approvals, quoteExtras: {} };
6733
- }
6734
- };
6735
- }
6736
- var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
6737
- function routeEthNonBase2() {
6738
- return {
6739
- async preflight(p, ctx) {
6740
- await wrapAs7(
6741
- "VALIDATION",
6742
- OP_WITHDRAWALS.ethNonBase.assertNonEthBase,
6743
- () => {
6744
- if (p.token.toLowerCase() !== L2_BASE_TOKEN_ADDRESS.toLowerCase()) {
6745
- throw new Error("eth-nonbase route requires the L2 base-token alias (0x\u2026800A).");
6746
- }
6747
- if (ctx.baseIsEth) {
6748
- throw new Error("eth-nonbase route requires chain base \u2260 ETH.");
6749
- }
6750
- },
6751
- { ctx: { token: p.token, baseIsEth: ctx.baseIsEth } }
6752
- );
6753
- },
6754
- async build(p, ctx) {
6755
- const steps = [];
6756
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
6757
- const toL1 = p.to ?? ctx.sender;
6758
- const iface = new ethers.Interface(IBaseToken_default);
6759
- const data = await wrapAs7(
6760
- "INTERNAL",
6761
- OP_WITHDRAWALS.eth.encodeWithdraw,
6762
- // reuse label for base-token system call
6763
- () => Promise.resolve(iface.encodeFunctionData("withdraw", [toL1])),
6764
- { ctx: { where: "L2BaseToken.withdraw", to: toL1 } }
6765
- );
6766
- const tx = {
6767
- to: L2_BASE_TOKEN_ADDRESS,
6768
- data,
6769
- from: ctx.sender,
6770
- value: p.amount,
6771
- maxFeePerGas,
6772
- maxPriorityFeePerGas
6773
- };
6774
- if (overrideGasLimit != null) {
6775
- tx.gasLimit = overrideGasLimit;
6776
- } else {
6777
- try {
6778
- const est = await wrapAs7(
6779
- "RPC",
6780
- OP_WITHDRAWALS.eth.estGas,
6781
- () => ctx.client.l2.estimateGas(tx),
6782
- {
6783
- ctx: { where: "l2.estimateGas", to: L2_BASE_TOKEN_ADDRESS },
6784
- message: "Failed to estimate gas for L2 base-token withdraw."
6785
- }
6786
- );
6787
- tx.gasLimit = BigInt(est) * 115n / 100n;
6788
- } catch {
6789
- }
6790
- }
6791
- steps.push({
6792
- key: "l2-base-token:withdraw",
6793
- kind: "l2-base-token:withdraw",
6794
- description: "Withdraw base token via L2 Base Token System (base \u2260 ETH)",
6795
- tx
6946
+ const fees = buildFeeBreakdown2({
6947
+ feeToken: await ctx.client.baseToken(ctx.chainIdL2),
6948
+ l2Gas: withdrawGas
6796
6949
  });
6797
- return { steps, approvals: [], quoteExtras: {} };
6950
+ return { steps, approvals, fees };
6798
6951
  }
6799
6952
  };
6800
6953
  }
@@ -7106,10 +7259,8 @@ function createFinalizationServices(client) {
7106
7259
 
7107
7260
  // src/adapters/ethers/resources/withdrawals/index.ts
7108
7261
  var ROUTES2 = {
7109
- "eth-base": routeEthBase(),
7262
+ base: routeEthBase(),
7110
7263
  // BaseTokenSystem.withdraw, chain base = ETH
7111
- "eth-nonbase": routeEthNonBase2(),
7112
- // BaseTokenSystem.withdraw, chain base ≠ ETH
7113
7264
  "erc20-nonbase": routeErc20NonBase2()
7114
7265
  // AssetRouter.withdraw for non-base ERC-20s
7115
7266
  };
@@ -7119,32 +7270,19 @@ function createWithdrawalsResource(client) {
7119
7270
  async function buildPlan(p) {
7120
7271
  const ctx = await commonCtx2(p, client);
7121
7272
  await ROUTES2[ctx.route].preflight?.(p, ctx);
7122
- const { steps, approvals } = await ROUTES2[ctx.route].build(p, ctx);
7123
- const resolveGasLimit = () => {
7124
- if (ctx.fee.gasLimit != null) return ctx.fee.gasLimit;
7125
- for (let i = steps.length - 1; i >= 0; i--) {
7126
- const candidate = steps[i].tx.gasLimit;
7127
- if (candidate == null) continue;
7128
- if (typeof candidate === "bigint") return candidate;
7129
- try {
7130
- return BigInt(candidate.toString());
7131
- } catch {
7132
- }
7133
- }
7134
- return void 0;
7135
- };
7136
- const gasLimit = resolveGasLimit();
7137
- const summary = {
7273
+ const { steps, approvals, fees } = await ROUTES2[ctx.route].build(p, ctx);
7274
+ return {
7138
7275
  route: ctx.route,
7139
- approvalsNeeded: approvals,
7140
- suggestedL2GasLimit: ctx.l2GasLimit,
7141
- fees: {
7142
- gasLimit,
7143
- maxFeePerGas: ctx.fee.maxFeePerGas,
7144
- maxPriorityFeePerGas: ctx.fee.maxPriorityFeePerGas
7145
- }
7276
+ summary: {
7277
+ route: ctx.route,
7278
+ approvalsNeeded: approvals,
7279
+ amounts: {
7280
+ transfer: { token: p.token, amount: p.amount }
7281
+ },
7282
+ fees
7283
+ },
7284
+ steps
7146
7285
  };
7147
- return { route: ctx.route, summary, steps };
7148
7286
  }
7149
7287
  const finalizeCache = /* @__PURE__ */ new Map();
7150
7288
  const quote = (p) => wrap2(
@@ -7509,7 +7647,6 @@ function createEthersSdk(client) {
7509
7647
  }
7510
7648
 
7511
7649
  exports.buildDirectRequestStruct = buildDirectRequestStruct;
7512
- exports.checkBaseCost = checkBaseCost;
7513
7650
  exports.classifyReadinessFromRevert = classifyReadinessFromRevert;
7514
7651
  exports.createClient = createEthersClient;
7515
7652
  exports.createDepositsResource = createDepositsResource;
@@ -7527,11 +7664,7 @@ exports.encodeSecondBridgeArgs = encodeSecondBridgeArgs;
7527
7664
  exports.encodeSecondBridgeDataV1 = encodeSecondBridgeDataV1;
7528
7665
  exports.encodeSecondBridgeErc20Args = encodeSecondBridgeErc20Args;
7529
7666
  exports.encodeSecondBridgeEthArgs = encodeSecondBridgeEthArgs;
7530
- exports.getFeeOverrides = getFeeOverrides;
7531
- exports.getGasPriceWei = getGasPriceWei;
7532
- exports.getL2FeeOverrides = getL2FeeOverrides;
7533
7667
  exports.registerErrorAbi = registerErrorAbi;
7534
- exports.scaleGasLimit = scaleGasLimit;
7535
7668
  exports.toZKsyncError = toZKsyncError;
7536
7669
  //# sourceMappingURL=index.cjs.map
7537
7670
  //# sourceMappingURL=index.cjs.map