@matterlabs/zksync-js 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +12 -12
  2. package/dist/adapters/ethers/client.cjs +642 -1
  3. package/dist/adapters/ethers/client.cjs.map +1 -1
  4. package/dist/adapters/ethers/client.js +6 -5
  5. package/dist/adapters/ethers/estimator.d.ts +4 -0
  6. package/dist/adapters/ethers/index.cjs +1279 -925
  7. package/dist/adapters/ethers/index.cjs.map +1 -1
  8. package/dist/adapters/ethers/index.d.ts +1 -0
  9. package/dist/adapters/ethers/index.js +9 -8
  10. package/dist/adapters/ethers/resources/contracts/contracts.d.ts +9 -0
  11. package/dist/adapters/ethers/resources/contracts/index.d.ts +2 -0
  12. package/dist/adapters/ethers/resources/contracts/types.d.ts +60 -0
  13. package/dist/adapters/ethers/resources/deposits/context.d.ts +21 -7
  14. package/dist/adapters/ethers/resources/deposits/index.d.ts +3 -1
  15. package/dist/adapters/ethers/resources/deposits/routes/types.d.ts +2 -6
  16. package/dist/adapters/ethers/resources/deposits/services/fee.d.ts +6 -0
  17. package/dist/adapters/ethers/resources/deposits/services/gas.d.ts +41 -0
  18. package/dist/adapters/ethers/resources/tokens/index.d.ts +1 -0
  19. package/dist/adapters/ethers/resources/tokens/tokens.d.ts +10 -0
  20. package/dist/adapters/ethers/resources/utils.d.ts +3 -17
  21. package/dist/adapters/ethers/resources/withdrawals/context.d.ts +15 -7
  22. package/dist/adapters/ethers/resources/withdrawals/index.d.ts +3 -1
  23. package/dist/adapters/ethers/resources/withdrawals/routes/types.d.ts +2 -2
  24. package/dist/adapters/ethers/resources/withdrawals/services/fees.d.ts +14 -0
  25. package/dist/adapters/ethers/resources/withdrawals/services/gas.d.ts +12 -0
  26. package/dist/adapters/ethers/sdk.cjs +1388 -1501
  27. package/dist/adapters/ethers/sdk.cjs.map +1 -1
  28. package/dist/adapters/ethers/sdk.d.ts +5 -22
  29. package/dist/adapters/ethers/sdk.js +7 -6
  30. package/dist/adapters/viem/client.cjs.map +1 -1
  31. package/dist/adapters/viem/client.d.ts +1 -1
  32. package/dist/adapters/viem/client.js +4 -5
  33. package/dist/adapters/viem/estimator.d.ts +4 -0
  34. package/dist/adapters/viem/index.cjs +1233 -744
  35. package/dist/adapters/viem/index.cjs.map +1 -1
  36. package/dist/adapters/viem/index.d.ts +3 -0
  37. package/dist/adapters/viem/index.js +8 -8
  38. package/dist/adapters/viem/resources/contracts/contracts.d.ts +9 -0
  39. package/dist/adapters/viem/resources/contracts/index.d.ts +2 -0
  40. package/dist/adapters/viem/resources/contracts/types.d.ts +61 -0
  41. package/dist/adapters/viem/resources/deposits/context.d.ts +21 -7
  42. package/dist/adapters/viem/resources/deposits/index.d.ts +3 -1
  43. package/dist/adapters/viem/resources/deposits/routes/types.d.ts +2 -6
  44. package/dist/adapters/viem/resources/deposits/services/fee.d.ts +6 -0
  45. package/dist/adapters/viem/resources/deposits/services/gas.d.ts +37 -0
  46. package/dist/adapters/viem/resources/tokens/index.d.ts +1 -0
  47. package/dist/adapters/viem/resources/tokens/tokens.d.ts +3 -0
  48. package/dist/adapters/viem/resources/utils.d.ts +3 -19
  49. package/dist/adapters/viem/resources/withdrawals/context.d.ts +14 -9
  50. package/dist/adapters/viem/resources/withdrawals/index.d.ts +3 -1
  51. package/dist/adapters/viem/resources/withdrawals/routes/types.d.ts +12 -2
  52. package/dist/adapters/viem/resources/withdrawals/services/fee.d.ts +17 -0
  53. package/dist/adapters/viem/resources/withdrawals/services/gas.d.ts +12 -0
  54. package/dist/adapters/viem/sdk.cjs +1225 -699
  55. package/dist/adapters/viem/sdk.cjs.map +1 -1
  56. package/dist/adapters/viem/sdk.d.ts +5 -25
  57. package/dist/adapters/viem/sdk.js +6 -6
  58. package/dist/{chunk-3LALBFFE.js → chunk-3MRGU4HV.js} +9 -5
  59. package/dist/{chunk-CGO27P7F.js → chunk-5YWP4CZP.js} +849 -835
  60. package/dist/{chunk-4HLJJKIY.js → chunk-6K6VJQAL.js} +2 -2
  61. package/dist/{chunk-6GCT6TLS.js → chunk-F2ENUV3A.js} +13 -1
  62. package/dist/{chunk-7M4V3FMT.js → chunk-JXUFGIJG.js} +986 -678
  63. package/dist/chunk-LL3WKCFJ.js +231 -0
  64. package/dist/{chunk-Y75OMFK6.js → chunk-M5J2MM2U.js} +351 -1
  65. package/dist/{chunk-263G6636.js → chunk-NCAIVYBR.js} +1 -14
  66. package/dist/{chunk-DI2CJDPZ.js → chunk-NEC2ZKHI.js} +5 -13
  67. package/dist/chunk-NTEIA5KA.js +13 -0
  68. package/dist/chunk-XRE7H466.js +157 -0
  69. package/dist/{chunk-BD2LUO5T.js → chunk-YUK547UF.js} +3 -3
  70. package/dist/core/abi.d.ts +9 -0
  71. package/dist/core/adapters/interfaces.d.ts +25 -0
  72. package/dist/core/codec/ntv.d.ts +48 -0
  73. package/dist/core/constants.cjs +12 -0
  74. package/dist/core/constants.cjs.map +1 -1
  75. package/dist/core/constants.d.ts +6 -0
  76. package/dist/core/constants.js +1 -1
  77. package/dist/core/index.cjs +4508 -1
  78. package/dist/core/index.cjs.map +1 -1
  79. package/dist/core/index.d.ts +2 -0
  80. package/dist/core/index.js +5 -4
  81. package/dist/core/resources/deposits/fee.d.ts +15 -0
  82. package/dist/core/resources/deposits/gas.d.ts +38 -0
  83. package/dist/core/resources/withdrawals/gas.d.ts +14 -0
  84. package/dist/core/types/errors.d.ts +1 -1
  85. package/dist/core/types/fees.d.ts +40 -0
  86. package/dist/core/types/flows/base.d.ts +0 -10
  87. package/dist/core/types/flows/deposits.d.ts +20 -6
  88. package/dist/core/types/flows/route.d.ts +2 -3
  89. package/dist/core/types/flows/token.d.ts +192 -0
  90. package/dist/core/types/flows/withdrawals.d.ts +12 -6
  91. package/dist/core/utils/addr.d.ts +2 -0
  92. package/dist/index.cjs +4520 -1
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.d.ts +2 -0
  95. package/dist/index.js +5 -4
  96. package/package.json +5 -1
  97. package/dist/adapters/ethers/resources/token-info.d.ts +0 -31
  98. package/dist/adapters/ethers/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  99. package/dist/adapters/viem/resources/token-info.d.ts +0 -34
  100. package/dist/adapters/viem/resources/withdrawals/routes/eth-nonbase.d.ts +0 -2
  101. package/dist/chunk-B77GWPO5.js +0 -339
  102. package/dist/core/internal/abi-registry.d.ts +0 -9
  103. package/dist/core/utils/gas.d.ts +0 -13
@@ -1,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 { createNTVCodec, buildFeeBreakdown, quoteL2Gas, quoteL2BaseCost, quoteL1Gas, quoteL2Gas2 } from './chunk-LL3WKCFJ.js';
2
+ import { findL1MessageSentLog, messengerLogIndex, normalizeL1Token, isAddressEq, hexEq, isHash66, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-NEC2ZKHI.js';
3
+ import { createErrorHandlers, toZKsyncError, classifyReadinessFromRevert } from './chunk-XRE7H466.js';
4
+ import { OP_WITHDRAWALS, IL1Nullifier_default, createError, OP_DEPOSITS, IERC20_default, isZKsyncError, isReceiptNotFound } from './chunk-M5J2MM2U.js';
5
+ import { ETH_ADDRESS, L1_MESSENGER_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_BASE_TOKEN_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, TOPIC_CANONICAL_ASSIGNED, TOPIC_CANONICAL_SUCCESS, SAFE_L1_BRIDGE_GAS } from './chunk-F2ENUV3A.js';
6
6
  import { Interface, AbiCoder, ethers, Contract, NonceManager } from 'ethers';
7
7
 
8
8
  var I_BRIDGEHUB = new Interface([
@@ -68,105 +68,67 @@ 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";
73
- }
74
- function encodeNativeTokenVaultAssetId(chainId, address) {
75
- const abi = new AbiCoder();
76
- const hex = abi.encode(
77
- ["uint256", "address", "address"],
78
- [chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, address]
79
- );
80
- return ethers.keccak256(hex);
71
+
72
+ // src/adapters/ethers/resources/deposits/context.ts
73
+ async function commonCtx(p, client, tokens, contracts) {
74
+ const { bridgehub, l1AssetRouter } = await contracts.addresses();
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 resolvedToken = await tokens.resolve(p.token, { chain: "l1" });
81
+ const baseTokenAssetId = resolvedToken.baseTokenAssetId;
82
+ const baseTokenL1 = await tokens.l1TokenFromAssetId(baseTokenAssetId);
83
+ const baseIsEth = resolvedToken.isChainEthBased;
84
+ const route = (() => {
85
+ if (resolvedToken.kind === "eth") {
86
+ return baseIsEth ? "eth-base" : "eth-nonbase";
87
+ }
88
+ if (resolvedToken.kind === "base") {
89
+ return baseIsEth ? "eth-base" : "erc20-base";
90
+ }
91
+ return "erc20-nonbase";
92
+ })();
93
+ return {
94
+ client,
95
+ tokens,
96
+ contracts,
97
+ resolvedToken,
98
+ baseTokenAssetId,
99
+ baseTokenL1,
100
+ baseIsEth,
101
+ l1AssetRouter,
102
+ route,
103
+ bridgehub,
104
+ chainIdL2: BigInt(chainId),
105
+ sender,
106
+ gasOverrides: p.l1TxOverrides,
107
+ l2GasLimit: p.l2GasLimit,
108
+ gasPerPubdata,
109
+ operatorTip,
110
+ refundRecipient
111
+ };
81
112
  }
82
113
  function encodeNativeTokenVaultTransferData(amount, receiver, token) {
83
114
  return new AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
84
115
  }
85
116
  function encodeSecondBridgeDataV1(assetId, transferData) {
86
- const abi = new AbiCoder();
87
- const data = abi.encode(["bytes32", "bytes"], [assetId, transferData]);
117
+ const abi2 = new AbiCoder();
118
+ const data = abi2.encode(["bytes32", "bytes"], [assetId, transferData]);
88
119
  return ethers.concat(["0x01", data]);
89
120
  }
90
- function encodeNTVAssetId(chainId, address) {
91
- const abi = new AbiCoder();
92
- const hex = abi.encode(
93
- ["uint256", "address", "address"],
94
- [chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, address]
121
+ function encodeSecondBridgeArgs(token, amount, l2Receiver) {
122
+ return AbiCoder.defaultAbiCoder().encode(
123
+ ["address", "uint256", "address"],
124
+ [token, amount, l2Receiver]
95
125
  );
96
- return ethers.keccak256(hex);
97
- }
98
- function encodeNTVTransferData(amount, receiver, token) {
99
- return new AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
100
126
  }
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
- };
129
- }
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
- };
127
+ function encodeSecondBridgeErc20Args(token, amount, l2Receiver) {
128
+ return encodeSecondBridgeArgs(token, amount, l2Receiver);
164
129
  }
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");
130
+ function encodeSecondBridgeEthArgs(amount, l2Receiver, ethToken = ETH_ADDRESS) {
131
+ return encodeSecondBridgeArgs(ethToken, amount, l2Receiver);
170
132
  }
171
133
  function buildDirectRequestStruct(args) {
172
134
  return {
@@ -181,352 +143,310 @@ function buildDirectRequestStruct(args) {
181
143
  refundRecipient: args.refundRecipient
182
144
  };
183
145
  }
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
146
 
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);
147
+ // src/adapters/ethers/estimator.ts
148
+ function toCoreTx(tx) {
208
149
  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
150
+ to: tx.to,
151
+ from: tx.from,
152
+ data: tx.data,
153
+ value: tx.value ? BigInt(tx.value) : void 0,
154
+ gasLimit: tx.gasLimit ? BigInt(tx.gasLimit) : void 0,
155
+ maxFeePerGas: tx.maxFeePerGas ? BigInt(tx.maxFeePerGas) : void 0,
156
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? BigInt(tx.maxPriorityFeePerGas) : void 0
220
157
  };
221
158
  }
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
- };
159
+ function ethersToGasEstimator(provider) {
160
+ return {
161
+ async estimateGas(tx, stateOverrides) {
162
+ const ethTx = {
163
+ to: tx.to,
164
+ from: tx.from,
165
+ data: tx.data,
166
+ value: tx.value,
167
+ gasLimit: tx.gasLimit,
168
+ maxFeePerGas: tx.maxFeePerGas,
169
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas
170
+ };
171
+ if (stateOverrides && "send" in provider) {
172
+ try {
173
+ const jsonRpcProvider = provider;
174
+ const result = await jsonRpcProvider.send("eth_estimateGas", [
175
+ ethTx,
176
+ "latest",
177
+ stateOverrides
178
+ ]);
179
+ return BigInt(result);
180
+ } catch (error) {
181
+ console.warn(
182
+ "Failed to estimate gas with state overrides, falling back to standard estimation:",
183
+ error
184
+ );
185
+ }
186
+ } else if (stateOverrides) {
187
+ console.warn('Provider does not support "send", skipping state overrides estimation.');
303
188
  }
304
- } catch {
189
+ return await provider.estimateGas(ethTx);
190
+ },
191
+ async estimateFeesPerGas() {
192
+ const fd = await provider.getFeeData();
193
+ return {
194
+ maxFeePerGas: fd.maxFeePerGas != null ? BigInt(fd.maxFeePerGas) : void 0,
195
+ maxPriorityFeePerGas: fd.maxPriorityFeePerGas != null ? BigInt(fd.maxPriorityFeePerGas) : void 0,
196
+ gasPrice: fd.gasPrice != null ? BigInt(fd.gasPrice) : void 0
197
+ };
198
+ },
199
+ async getGasPrice() {
200
+ const fd = await provider.getFeeData();
201
+ if (fd.gasPrice != null) return BigInt(fd.gasPrice);
202
+ throw new Error("Could not fetch gas price");
203
+ },
204
+ async call(tx) {
205
+ const ethTx = {
206
+ to: tx.to,
207
+ data: tx.data,
208
+ value: tx.value,
209
+ from: tx.from
210
+ };
211
+ return await provider.call(ethTx);
305
212
  }
306
- }
307
- return { selector };
213
+ };
308
214
  }
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 };
215
+
216
+ // src/adapters/ethers/resources/deposits/services/fee.ts
217
+ var { wrapAs } = createErrorHandlers("deposits");
218
+ var encode = (abi2, fn, args) => {
219
+ return new Interface(abi2).encodeFunctionData(fn, args);
220
+ };
221
+ async function quoteL2BaseCost2(input) {
222
+ const { ctx, l2GasLimit } = input;
223
+ const estimator = ethersToGasEstimator(ctx.client.l1);
224
+ return wrapAs(
225
+ "RPC",
226
+ "deposits.fees.l2BaseCost",
227
+ () => quoteL2BaseCost({
228
+ estimator,
229
+ encode,
230
+ bridgehub: ctx.bridgehub,
231
+ chainIdL2: ctx.chainIdL2,
232
+ l2GasLimit,
233
+ gasPerPubdata: ctx.gasPerPubdata
234
+ }),
235
+ { ctx: { chainIdL2: ctx.chainIdL2 } }
236
+ );
325
237
  }
326
238
 
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) });
239
+ // src/adapters/ethers/resources/deposits/services/gas.ts
240
+ async function quoteL1Gas2(input) {
241
+ const { ctx, tx, overrides, fallbackGasLimit } = input;
242
+ const estimator = ethersToGasEstimator(ctx.client.l1);
243
+ return quoteL1Gas({
244
+ estimator,
245
+ tx: toCoreTx(tx),
246
+ overrides,
247
+ fallbackGasLimit
248
+ });
332
249
  }
333
- function resolveMessage(op, msg) {
334
- if (!msg) return `Error during ${op}.`;
335
- return typeof msg === "function" ? msg() : msg;
250
+ async function quoteL2Gas3(input) {
251
+ const { ctx, route, l2TxForModeling, overrideGasLimit } = input;
252
+ const estimator = ethersToGasEstimator(ctx.client.l2);
253
+ return quoteL2Gas({
254
+ estimator,
255
+ route,
256
+ tx: l2TxForModeling ? toCoreTx(l2TxForModeling) : void 0,
257
+ gasPerPubdata: ctx.gasPerPubdata,
258
+ l2GasLimit: ctx.l2GasLimit,
259
+ overrideGasLimit,
260
+ stateOverrides: input.stateOverrides
261
+ });
336
262
  }
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);
263
+ async function determineErc20L2Gas(input) {
264
+ const { ctx, l1Token } = input;
265
+ const DEFAULT_SAFE_L2_GAS_LIMIT = 3000000n;
266
+ if (ctx.l2GasLimit != null) {
267
+ return quoteL2Gas3({
268
+ ctx,
269
+ route: "erc20-nonbase",
270
+ overrideGasLimit: ctx.l2GasLimit
271
+ });
352
272
  }
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 };
273
+ try {
274
+ const l2TokenAddress = ctx.tokens ? await ctx.tokens.toL2Address(l1Token) : await (await ctx.contracts.l2NativeTokenVault()).l2TokenAddress(l1Token);
275
+ if (l2TokenAddress === "0x0000000000000000000000000000000000000000") {
276
+ return quoteL2Gas3({
277
+ ctx,
278
+ route: "erc20-nonbase",
279
+ overrideGasLimit: DEFAULT_SAFE_L2_GAS_LIMIT
280
+ });
369
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
+ });
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
- 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."
315
+ const bh = await ctx.contracts.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
+ async preflight(p, ctx) {
393
+ const resolved = ctx.resolvedToken ?? (ctx.tokens ? await ctx.tokens.resolve(p.token, { chain: "l1" }) : void 0);
394
+ const baseToken = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
474
395
  await wrapAs2(
475
396
  "VALIDATION",
476
397
  OP_DEPOSITS.nonbase.assertNonBaseToken,
477
398
  () => {
478
- if (normalizeAddrEq(baseToken, p.token)) {
399
+ if (resolved?.kind === "base" || resolved?.kind === "eth") {
479
400
  throw new Error("erc20-nonbase route requires a non-base ERC-20 deposit token.");
480
401
  }
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 = ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2);
409
+ const baseIsEth = ctx.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,96 +482,86 @@ 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.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) {
545
+ const resolved = ctx.resolvedToken ?? (ctx.tokens ? await ctx.tokens.resolve(p.token, { chain: "l1" }) : void 0);
622
546
  await wrapAs3(
623
547
  "VALIDATION",
624
548
  OP_DEPOSITS.ethNonBase.assertEthAsset,
625
549
  () => {
626
- if (!isETH(p.token)) {
550
+ if (resolved?.kind !== "eth" && !isETH(p.token)) {
627
551
  throw new Error("eth-nonbase route requires ETH as the deposit asset.");
628
552
  }
629
553
  },
630
554
  { ctx: { token: p.token } }
631
555
  );
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
- );
642
556
  await wrapAs3(
643
557
  "VALIDATION",
644
558
  OP_DEPOSITS.ethNonBase.assertNonEthBase,
645
559
  () => {
646
- if (isETH(baseToken)) {
560
+ if (ctx.baseIsEth) {
647
561
  throw new Error("eth-nonbase route requires target chain base token \u2260 ETH.");
648
562
  }
649
563
  },
650
- { ctx: { baseToken, chainIdL2: ctx.chainIdL2 } }
564
+ { ctx: { baseIsEth: ctx.baseIsEth, chainIdL2: ctx.chainIdL2 } }
651
565
  );
652
566
  const ethBal = await wrapAs3(
653
567
  "RPC",
@@ -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 = ctx.baseTokenL1;
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,92 +640,78 @@ 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(
754
- ctx.bridgehub,
755
- IBridgehub_default,
756
- ctx.client.l1
757
- ).interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
758
- let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
759
- const bridgeTx = {
654
+ const bridgehub = await ctx.contracts.bridgehub();
655
+ const data = bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [
656
+ requestStruct
657
+ ]);
658
+ const l1TxCandidate = {
760
659
  to: ctx.bridgehub,
761
- data: dataTwo,
660
+ data,
762
661
  value: p.amount,
763
662
  // base ≠ ETH ⇒ msg.value == secondBridgeValue
764
663
  from: ctx.sender,
765
- ...txOverrides
664
+ ...ctx.gasOverrides
766
665
  };
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
- }
666
+ const l1GasParams = await quoteL1Gas2({
667
+ ctx,
668
+ tx: l1TxCandidate,
669
+ overrides: ctx.gasOverrides,
670
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
671
+ });
672
+ if (l1GasParams) {
673
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
674
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
675
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
786
676
  }
787
677
  steps.push({
788
678
  key: "bridgehub:two-bridges:eth-nonbase",
789
679
  kind: "bridgehub:two-bridges",
790
680
  description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
791
- tx: bridgeTx
681
+ tx: l1TxCandidate
682
+ });
683
+ const fees = buildFeeBreakdown({
684
+ feeToken: baseToken,
685
+ l1Gas: l1GasParams,
686
+ l2Gas: l2GasParams,
687
+ l2BaseCost: baseCost,
688
+ operatorTip: ctx.operatorTip,
689
+ mintValue
792
690
  });
793
691
  return {
794
692
  steps,
795
693
  approvals,
796
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
694
+ fees
797
695
  };
798
696
  }
799
697
  };
800
698
  }
801
699
  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
700
  function routeErc20Base() {
806
701
  return {
807
702
  async preflight(p, ctx) {
703
+ const resolved = ctx.resolvedToken ?? (ctx.tokens ? await ctx.tokens.resolve(p.token, { chain: "l1" }) : void 0);
808
704
  await wrapAs4(
809
705
  "VALIDATION",
810
706
  OP_DEPOSITS.base.assertErc20Asset,
811
707
  () => {
812
- if (isETH(p.token)) {
708
+ if (resolved?.kind === "eth" || isETH(p.token)) {
813
709
  throw new Error("erc20-base route requires an ERC-20 token (not ETH).");
814
710
  }
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 = ctx.baseTokenL1 ?? 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 = ctx.baseTokenL1 ?? 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,74 +757,333 @@ 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
- const data = new Contract(
909
- ctx.bridgehub,
910
- IBridgehub_default,
911
- ctx.client.l1
912
- ).interface.encodeFunctionData("requestL2TransactionDirect", [req]);
913
- const tx = {
782
+ const bridgehub = await ctx.contracts.bridgehub();
783
+ const data = bridgehub.interface.encodeFunctionData("requestL2TransactionDirect", [
784
+ requestStruct
785
+ ]);
786
+ const l1TxCandidate = {
914
787
  to: ctx.bridgehub,
915
788
  data,
916
789
  value: 0n,
917
790
  // base token is ERC-20 ⇒ msg.value MUST be 0
918
791
  from: ctx.sender,
919
- ...txOverrides
792
+ ...ctx.gasOverrides
920
793
  };
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
- }
794
+ const l1GasParams = await quoteL1Gas2({
795
+ ctx,
796
+ tx: l1TxCandidate,
797
+ overrides: ctx.gasOverrides,
798
+ fallbackGasLimit: SAFE_L1_BRIDGE_GAS
799
+ });
800
+ if (l1GasParams) {
801
+ l1TxCandidate.gasLimit = l1GasParams.gasLimit;
802
+ l1TxCandidate.maxFeePerGas = l1GasParams.maxFeePerGas;
803
+ l1TxCandidate.maxPriorityFeePerGas = l1GasParams.maxPriorityFeePerGas;
940
804
  }
941
805
  steps.push({
942
806
  key: "bridgehub:direct:erc20-base",
943
807
  kind: "bridgehub:direct",
944
808
  description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
945
- tx
809
+ tx: l1TxCandidate
810
+ });
811
+ const fees = buildFeeBreakdown({
812
+ feeToken: baseToken,
813
+ l1Gas: l1GasParams,
814
+ l2Gas: l2GasParams,
815
+ l2BaseCost: baseCost,
816
+ operatorTip: ctx.operatorTip,
817
+ mintValue
946
818
  });
947
819
  return {
948
820
  steps,
949
821
  approvals,
950
- quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
822
+ fees
951
823
  };
952
824
  }
953
825
  };
954
826
  }
827
+ var { wrapAs: wrapAs5 } = createErrorHandlers("tokens");
828
+ var abi = AbiCoder.defaultAbiCoder();
829
+ var ntvCodec = createNTVCodec({
830
+ encode: (types, values) => abi.encode(types, values),
831
+ keccak256: (data) => ethers.keccak256(data)
832
+ });
833
+ function createTokensResource(client) {
834
+ let l2NtvL1ChainIdPromise = null;
835
+ let baseTokenAssetIdPromise = null;
836
+ let wethL1Promise = null;
837
+ let wethL2Promise = null;
838
+ async function getL1ChainId() {
839
+ if (!l2NtvL1ChainIdPromise) {
840
+ l2NtvL1ChainIdPromise = wrapAs5("INTERNAL", "getL1ChainId", async () => {
841
+ const { l2NativeTokenVault } = await client.contracts();
842
+ const chainId = await l2NativeTokenVault.L1_CHAIN_ID();
843
+ return chainId;
844
+ });
845
+ }
846
+ return l2NtvL1ChainIdPromise;
847
+ }
848
+ async function getBaseTokenAssetId() {
849
+ if (!baseTokenAssetIdPromise) {
850
+ baseTokenAssetIdPromise = wrapAs5("INTERNAL", "baseTokenAssetId", async () => {
851
+ const { l2NativeTokenVault } = await client.contracts();
852
+ const assetId = await l2NativeTokenVault.BASE_TOKEN_ASSET_ID();
853
+ return assetId;
854
+ });
855
+ }
856
+ return baseTokenAssetIdPromise;
857
+ }
858
+ async function getWethL1() {
859
+ if (!wethL1Promise) {
860
+ wethL1Promise = wrapAs5("INTERNAL", "wethL1", async () => {
861
+ const { l1NativeTokenVault } = await client.contracts();
862
+ const weth = await l1NativeTokenVault.WETH_TOKEN();
863
+ return weth;
864
+ });
865
+ }
866
+ return wethL1Promise;
867
+ }
868
+ async function getWethL2() {
869
+ if (!wethL2Promise) {
870
+ wethL2Promise = wrapAs5("INTERNAL", "wethL2", async () => {
871
+ const { l2NativeTokenVault } = await client.contracts();
872
+ const weth = await l2NativeTokenVault.WETH_TOKEN();
873
+ return weth;
874
+ });
875
+ }
876
+ return wethL2Promise;
877
+ }
878
+ async function toL2Address(l1Token) {
879
+ return wrapAs5("CONTRACT", "tokens.toL2Address", async () => {
880
+ const normalized = normalizeL1Token(l1Token);
881
+ const { chainId } = await client.l2.getNetwork();
882
+ const baseToken = await client.baseToken(BigInt(chainId));
883
+ if (isAddressEq(normalized, baseToken)) {
884
+ return L2_BASE_TOKEN_ADDRESS;
885
+ }
886
+ const { l2NativeTokenVault } = await client.contracts();
887
+ const l2Token = await l2NativeTokenVault.l2TokenAddress(normalized);
888
+ return l2Token;
889
+ });
890
+ }
891
+ async function toL1Address(l2Token) {
892
+ return wrapAs5("CONTRACT", "tokens.toL1Address", async () => {
893
+ if (isAddressEq(l2Token, ETH_ADDRESS)) {
894
+ return ETH_ADDRESS;
895
+ }
896
+ if (isAddressEq(l2Token, L2_BASE_TOKEN_ADDRESS)) {
897
+ const { chainId } = await client.l2.getNetwork();
898
+ return await client.baseToken(BigInt(chainId));
899
+ }
900
+ const { l2AssetRouter } = await client.contracts();
901
+ const l1Token = await l2AssetRouter.l1TokenAddress(l2Token);
902
+ return l1Token;
903
+ });
904
+ }
905
+ async function assetIdOfL1(l1Token) {
906
+ return wrapAs5("CONTRACT", "tokens.assetIdOfL1", async () => {
907
+ const normalized = normalizeL1Token(l1Token);
908
+ const { l1NativeTokenVault } = await client.contracts();
909
+ const assetId = await l1NativeTokenVault.assetId(normalized);
910
+ return assetId;
911
+ });
912
+ }
913
+ async function assetIdOfL2(l2Token) {
914
+ return wrapAs5("CONTRACT", "tokens.assetIdOfL2", async () => {
915
+ const { l2NativeTokenVault } = await client.contracts();
916
+ const assetId = await l2NativeTokenVault.assetId(l2Token);
917
+ return assetId;
918
+ });
919
+ }
920
+ async function l2TokenFromAssetId(assetId) {
921
+ return wrapAs5("CONTRACT", "tokens.l2TokenFromAssetId", async () => {
922
+ const { l2NativeTokenVault } = await client.contracts();
923
+ const tokenAddr = await l2NativeTokenVault.tokenAddress(assetId);
924
+ return tokenAddr;
925
+ });
926
+ }
927
+ async function l1TokenFromAssetId(assetId) {
928
+ return wrapAs5("CONTRACT", "tokens.l1TokenFromAssetId", async () => {
929
+ const { l1NativeTokenVault } = await client.contracts();
930
+ const tokenAddr = await l1NativeTokenVault.tokenAddress(assetId);
931
+ return tokenAddr;
932
+ });
933
+ }
934
+ async function originChainId(assetId) {
935
+ return wrapAs5("CONTRACT", "tokens.originChainId", async () => {
936
+ const { l2NativeTokenVault } = await client.contracts();
937
+ const chainId = await l2NativeTokenVault.originChainId(assetId);
938
+ return chainId;
939
+ });
940
+ }
941
+ async function baseTokenAssetId() {
942
+ return getBaseTokenAssetId();
943
+ }
944
+ async function isChainEthBased() {
945
+ return wrapAs5("CONTRACT", "tokens.isChainEthBased", async () => {
946
+ const baseAssetId = await getBaseTokenAssetId();
947
+ const l1ChainId = await getL1ChainId();
948
+ const ethAssetId = ntvCodec.encodeAssetId(
949
+ l1ChainId,
950
+ L2_NATIVE_TOKEN_VAULT_ADDRESS,
951
+ ETH_ADDRESS
952
+ );
953
+ return hexEq(baseAssetId, ethAssetId);
954
+ });
955
+ }
956
+ async function wethL1() {
957
+ return getWethL1();
958
+ }
959
+ async function wethL2() {
960
+ return getWethL2();
961
+ }
962
+ async function computeL2BridgedAddress(args) {
963
+ return wrapAs5("CONTRACT", "tokens.computeL2BridgedAddress", async () => {
964
+ const normalized = normalizeL1Token(args.l1Token);
965
+ const { l2NativeTokenVault } = await client.contracts();
966
+ const predicted = await l2NativeTokenVault.calculateCreate2TokenAddress(
967
+ args.originChainId,
968
+ normalized
969
+ );
970
+ return predicted;
971
+ });
972
+ }
973
+ async function resolve(ref, opts) {
974
+ return wrapAs5("CONTRACT", "tokens.resolve", async () => {
975
+ let chain;
976
+ let address;
977
+ if (typeof ref === "string") {
978
+ chain = opts?.chain ?? "l1";
979
+ address = ref;
980
+ } else {
981
+ chain = ref.chain;
982
+ address = ref.address;
983
+ }
984
+ let l1;
985
+ let l2;
986
+ if (chain === "l1") {
987
+ l1 = normalizeL1Token(address);
988
+ l2 = await toL2Address(address);
989
+ } else {
990
+ l2 = address;
991
+ l1 = await toL1Address(address);
992
+ }
993
+ const assetId = await assetIdOfL1(l1);
994
+ const originChainIdVal = await originChainId(assetId);
995
+ const [baseAssetId, wethL1Addr, wethL2Addr, ethBased] = await Promise.all([
996
+ baseTokenAssetId(),
997
+ wethL1(),
998
+ wethL2(),
999
+ isChainEthBased()
1000
+ ]);
1001
+ let kind;
1002
+ if (isAddressEq(l1, ETH_ADDRESS)) {
1003
+ kind = "eth";
1004
+ } else if (hexEq(assetId, baseAssetId)) {
1005
+ kind = "base";
1006
+ } else {
1007
+ kind = "erc20";
1008
+ }
1009
+ return {
1010
+ kind,
1011
+ l1,
1012
+ l2,
1013
+ assetId,
1014
+ originChainId: originChainIdVal,
1015
+ isChainEthBased: ethBased,
1016
+ baseTokenAssetId: baseAssetId,
1017
+ wethL1: wethL1Addr,
1018
+ wethL2: wethL2Addr
1019
+ };
1020
+ });
1021
+ }
1022
+ return {
1023
+ resolve,
1024
+ toL2Address,
1025
+ toL1Address,
1026
+ assetIdOfL1,
1027
+ assetIdOfL2,
1028
+ l2TokenFromAssetId,
1029
+ l1TokenFromAssetId,
1030
+ originChainId,
1031
+ baseTokenAssetId,
1032
+ isChainEthBased,
1033
+ wethL1,
1034
+ wethL2,
1035
+ computeL2BridgedAddress
1036
+ };
1037
+ }
1038
+
1039
+ // src/adapters/ethers/resources/contracts/contracts.ts
1040
+ function createContractsResource(client) {
1041
+ async function addresses() {
1042
+ return client.ensureAddresses();
1043
+ }
1044
+ async function instances() {
1045
+ return client.contracts();
1046
+ }
1047
+ async function bridgehub() {
1048
+ const { bridgehub: bridgehub2 } = await instances();
1049
+ return bridgehub2;
1050
+ }
1051
+ async function l1AssetRouter() {
1052
+ const { l1AssetRouter: l1AssetRouter2 } = await instances();
1053
+ return l1AssetRouter2;
1054
+ }
1055
+ async function l1NativeTokenVault() {
1056
+ const { l1NativeTokenVault: l1NativeTokenVault2 } = await instances();
1057
+ return l1NativeTokenVault2;
1058
+ }
1059
+ async function l1Nullifier() {
1060
+ const { l1Nullifier: l1Nullifier2 } = await instances();
1061
+ return l1Nullifier2;
1062
+ }
1063
+ async function l2AssetRouter() {
1064
+ const { l2AssetRouter: l2AssetRouter2 } = await instances();
1065
+ return l2AssetRouter2;
1066
+ }
1067
+ async function l2NativeTokenVault() {
1068
+ const { l2NativeTokenVault: l2NativeTokenVault2 } = await instances();
1069
+ return l2NativeTokenVault2;
1070
+ }
1071
+ async function l2BaseTokenSystem() {
1072
+ const { l2BaseTokenSystem: l2BaseTokenSystem2 } = await instances();
1073
+ return l2BaseTokenSystem2;
1074
+ }
1075
+ return {
1076
+ addresses,
1077
+ instances,
1078
+ bridgehub,
1079
+ l1AssetRouter,
1080
+ l1NativeTokenVault,
1081
+ l1Nullifier,
1082
+ l2AssetRouter,
1083
+ l2NativeTokenVault,
1084
+ l2BaseTokenSystem
1085
+ };
1086
+ }
955
1087
 
956
1088
  // src/adapters/ethers/resources/deposits/index.ts
957
1089
  var { wrap, toResult } = createErrorHandlers("deposits");
@@ -961,42 +1093,26 @@ var ROUTES = {
961
1093
  "erc20-nonbase": routeErc20NonBase(),
962
1094
  "erc20-base": routeErc20Base()
963
1095
  };
964
- function createDepositsResource(client) {
1096
+ function createDepositsResource(client, tokens, contracts) {
1097
+ const tokensResource = tokens ?? createTokensResource(client);
1098
+ const contractsResource = contracts ?? createContractsResource(client);
965
1099
  async function buildPlan(p) {
966
- const ctx = await commonCtx(p, client);
1100
+ const ctx = await commonCtx(p, client, tokensResource, contractsResource);
967
1101
  const route = ctx.route;
968
1102
  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();
1103
+ const { steps, approvals, fees } = await ROUTES[route].build(p, ctx);
987
1104
  return {
988
1105
  route: ctx.route,
989
1106
  summary: {
990
1107
  route: ctx.route,
991
1108
  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
- }
1109
+ amounts: {
1110
+ transfer: { token: p.token, amount: p.amount }
1111
+ },
1112
+ fees,
1113
+ // Legacy fields (maintained for backward compatibility)
1114
+ baseCost: fees.l2?.baseCost,
1115
+ mintValue: fees.mintValue
1000
1116
  },
1001
1117
  steps
1002
1118
  };
@@ -1065,7 +1181,7 @@ function createDepositsResource(client) {
1065
1181
  step.tx.maxPriorityFeePerGas = overrides.maxPriorityFeePerGas;
1066
1182
  }
1067
1183
  }
1068
- if (!step.tx.gasLimit) {
1184
+ if (!p.l1TxOverrides?.gasLimit) {
1069
1185
  try {
1070
1186
  const est = await client.l1.estimateGas(step.tx);
1071
1187
  step.tx.gasLimit = BigInt(est) * 115n / 100n;
@@ -1246,25 +1362,9 @@ function createDepositsResource(client) {
1246
1362
  );
1247
1363
  return { quote, tryQuote, prepare, tryPrepare, create, tryCreate, status, wait, tryWait };
1248
1364
  }
1249
- async function ntvBaseAssetId(l2, ntv) {
1250
- const c = new Contract(ntv, L2NativeTokenVault_default, l2);
1251
- return await c.BASE_TOKEN_ASSET_ID();
1252
- }
1253
- async function ntvL1ChainId(l2, ntv) {
1254
- const c = new Contract(ntv, L2NativeTokenVault_default, l2);
1255
- return await c.L1_CHAIN_ID();
1256
- }
1257
- async function isEthBasedChain(l2, ntv) {
1258
- const [baseAssetId, l1ChainId] = await Promise.all([
1259
- ntvBaseAssetId(l2, ntv),
1260
- ntvL1ChainId(l2, ntv)
1261
- ]);
1262
- const ethAssetId = encodeNativeTokenVaultAssetId(l1ChainId, ETH_ADDRESS);
1263
- return baseAssetId.toLowerCase() === ethAssetId.toLowerCase();
1264
- }
1265
1365
 
1266
1366
  // src/adapters/ethers/resources/withdrawals/context.ts
1267
- async function commonCtx2(p, client) {
1367
+ async function commonCtx2(p, client, tokens, contracts) {
1268
1368
  const sender = await client.signer.getAddress();
1269
1369
  const {
1270
1370
  bridgehub,
@@ -1273,16 +1373,21 @@ async function commonCtx2(p, client) {
1273
1373
  l2AssetRouter,
1274
1374
  l2NativeTokenVault,
1275
1375
  l2BaseTokenSystem
1276
- } = await client.ensureAddresses();
1376
+ } = await contracts.addresses();
1277
1377
  const { chainId } = await client.l2.getNetwork();
1278
1378
  const chainIdL2 = BigInt(chainId);
1279
- const baseIsEth = await isEthBasedChain(client.l2, l2NativeTokenVault);
1280
- const fee = await getL2FeeOverrides(client, p.l2TxOverrides);
1379
+ const resolvedToken = await tokens.resolve(p.token, { chain: "l2" });
1380
+ const baseTokenAssetId = resolvedToken.baseTokenAssetId;
1381
+ const baseTokenL1 = await tokens.l1TokenFromAssetId(baseTokenAssetId);
1382
+ const baseIsEth = resolvedToken.isChainEthBased;
1281
1383
  const route = pickWithdrawRoute({ token: p.token, baseIsEth });
1282
- const l2GasLimit = p.l2GasLimit ?? 300000n;
1283
- const gasBufferPct = 15;
1284
1384
  return {
1285
1385
  client,
1386
+ tokens,
1387
+ contracts,
1388
+ resolvedToken,
1389
+ baseTokenAssetId,
1390
+ baseTokenL1,
1286
1391
  bridgehub,
1287
1392
  chainIdL2,
1288
1393
  sender,
@@ -1293,29 +1398,50 @@ async function commonCtx2(p, client) {
1293
1398
  l2NativeTokenVault,
1294
1399
  l2BaseTokenSystem,
1295
1400
  baseIsEth,
1296
- l2GasLimit,
1297
- gasBufferPct,
1298
- fee
1401
+ gasOverrides: p.l2TxOverrides
1299
1402
  };
1300
1403
  }
1301
- var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
1404
+
1405
+ // src/adapters/ethers/resources/withdrawals/services/gas.ts
1406
+ async function quoteL2Gas4(input) {
1407
+ const { ctx, tx } = input;
1408
+ const estimator = ethersToGasEstimator(ctx.client.l2);
1409
+ return quoteL2Gas2({
1410
+ estimator,
1411
+ tx: toCoreTx(tx),
1412
+ overrides: ctx.gasOverrides
1413
+ });
1414
+ }
1415
+
1416
+ // src/adapters/ethers/resources/withdrawals/services/fees.ts
1417
+ function buildFeeBreakdown2(p) {
1418
+ const l2Total = p.l2Gas?.maxCost ?? 0n;
1419
+ const l2 = {
1420
+ total: l2Total,
1421
+ gasLimit: p.l2Gas?.gasLimit ?? 0n,
1422
+ maxFeePerGas: p.l2Gas?.maxFeePerGas ?? 0n,
1423
+ maxPriorityFeePerGas: p.l2Gas?.maxPriorityFeePerGas
1424
+ };
1425
+ return {
1426
+ token: p.feeToken,
1427
+ maxTotal: l2Total,
1428
+ l2
1429
+ };
1430
+ }
1431
+
1432
+ // src/adapters/ethers/resources/withdrawals/routes/eth.ts
1433
+ var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
1302
1434
  function routeEthBase() {
1303
1435
  return {
1304
1436
  async build(p, ctx) {
1305
1437
  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;
1313
- const data = await wrapAs5(
1438
+ const base = await ctx.contracts.l2BaseTokenSystem();
1439
+ const data = await wrapAs6(
1314
1440
  "INTERNAL",
1315
1441
  OP_WITHDRAWALS.eth.encodeWithdraw,
1316
- () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [toL1])),
1442
+ () => Promise.resolve(base.interface.encodeFunctionData("withdraw", [p.to ?? ctx.sender])),
1317
1443
  {
1318
- ctx: { where: "L2BaseToken.withdraw", to: toL1 },
1444
+ ctx: { where: "L2BaseToken.withdraw", to: p.to ?? ctx.sender },
1319
1445
  message: "Failed to encode ETH withdraw calldata."
1320
1446
  }
1321
1447
  );
@@ -1323,38 +1449,29 @@ function routeEthBase() {
1323
1449
  to: L2_BASE_TOKEN_ADDRESS,
1324
1450
  data,
1325
1451
  from: ctx.sender,
1326
- value: p.amount,
1327
- maxFeePerGas,
1328
- maxPriorityFeePerGas
1452
+ value: p.amount
1329
1453
  };
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
- }
1454
+ const gas = await quoteL2Gas4({ ctx, tx });
1455
+ if (gas) {
1456
+ tx.gasLimit = gas.gasLimit;
1457
+ tx.maxFeePerGas = gas.maxFeePerGas;
1458
+ tx.maxPriorityFeePerGas = gas.maxPriorityFeePerGas;
1346
1459
  }
1460
+ const fees = buildFeeBreakdown2({
1461
+ feeToken: L2_BASE_TOKEN_ADDRESS,
1462
+ l2Gas: gas
1463
+ });
1347
1464
  steps.push({
1348
1465
  key: "l2-base-token:withdraw",
1349
1466
  kind: "l2-base-token:withdraw",
1350
1467
  description: "Withdraw ETH via L2 Base Token System",
1351
1468
  tx
1352
1469
  });
1353
- return { steps, approvals: [], quoteExtras: {} };
1470
+ return { steps, approvals: [], fees };
1354
1471
  }
1355
1472
  };
1356
1473
  }
1357
- var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
1474
+ var { wrapAs: wrapAs7 } = createErrorHandlers("withdrawals");
1358
1475
  var SIG = {
1359
1476
  withdraw: "withdraw(bytes32,bytes)"
1360
1477
  };
@@ -1363,10 +1480,8 @@ function routeErc20NonBase2() {
1363
1480
  async build(p, ctx) {
1364
1481
  const steps = [];
1365
1482
  const approvals = [];
1366
- const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
1367
- const txOverrides = overrideGasLimit != null ? { maxFeePerGas, maxPriorityFeePerGas, gasLimit: overrideGasLimit } : { maxFeePerGas, maxPriorityFeePerGas };
1368
1483
  const erc20 = new Contract(p.token, IERC20_default, ctx.client.getL2Signer());
1369
- const current = await wrapAs6(
1484
+ const current = await wrapAs7(
1370
1485
  "CONTRACT",
1371
1486
  OP_WITHDRAWALS.erc20.allowance,
1372
1487
  () => erc20.allowance(ctx.sender, ctx.l2NativeTokenVault),
@@ -1386,39 +1501,51 @@ function routeErc20NonBase2() {
1386
1501
  ctx.l2NativeTokenVault,
1387
1502
  p.amount
1388
1503
  ]);
1504
+ const approveTx = {
1505
+ to: p.token,
1506
+ data,
1507
+ from: ctx.sender
1508
+ };
1509
+ const approveGas = await quoteL2Gas4({ ctx, tx: approveTx });
1510
+ if (approveGas) {
1511
+ approveTx.gasLimit = approveGas.gasLimit;
1512
+ approveTx.maxFeePerGas = approveGas.maxFeePerGas;
1513
+ approveTx.maxPriorityFeePerGas = approveGas.maxPriorityFeePerGas;
1514
+ }
1389
1515
  steps.push({
1390
1516
  key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
1391
1517
  kind: "approve:l2",
1392
1518
  description: `Approve ${p.amount} to NativeTokenVault`,
1393
- tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
1519
+ tx: approveTx
1394
1520
  });
1395
1521
  }
1396
- const ntv = new Contract(ctx.l2NativeTokenVault, L2NativeTokenVault_default, ctx.client.l2);
1397
- const assetId = await wrapAs6(
1522
+ const resolved = ctx.resolvedToken ?? (ctx.tokens ? await ctx.tokens.resolve(p.token, { chain: "l2" }) : void 0);
1523
+ const assetId = resolved?.assetId ?? await wrapAs7(
1398
1524
  "CONTRACT",
1399
1525
  OP_WITHDRAWALS.erc20.ensureRegistered,
1400
- () => ntv.getFunction("ensureTokenIsRegistered").staticCall(p.token),
1526
+ async () => {
1527
+ const ntv = await ctx.contracts.l2NativeTokenVault();
1528
+ const ensured = await ntv.getFunction("ensureTokenIsRegistered").staticCall(p.token);
1529
+ return ensured;
1530
+ },
1401
1531
  {
1402
1532
  ctx: { where: "L2NativeTokenVault.ensureTokenIsRegistered", token: p.token },
1403
1533
  message: "Failed to ensure token is registered in L2NativeTokenVault."
1404
1534
  }
1405
1535
  );
1406
- const assetData = await wrapAs6(
1536
+ const assetData = await wrapAs7(
1407
1537
  "INTERNAL",
1408
1538
  OP_WITHDRAWALS.erc20.encodeAssetData,
1409
1539
  () => Promise.resolve(
1410
- AbiCoder.defaultAbiCoder().encode(
1411
- ["uint256", "address", "address"],
1412
- [p.amount, p.to ?? ctx.sender, p.token]
1413
- )
1540
+ encodeNativeTokenVaultTransferData(p.amount, p.to ?? ctx.sender, p.token)
1414
1541
  ),
1415
1542
  {
1416
1543
  ctx: { where: "AbiCoder.encode", token: p.token, to: p.to ?? ctx.sender },
1417
1544
  message: "Failed to encode burn/withdraw asset data."
1418
1545
  }
1419
1546
  );
1420
- const l2ar = new Contract(ctx.l2AssetRouter, IL2AssetRouter_default, ctx.client.l2);
1421
- const dataWithdraw = await wrapAs6(
1547
+ const l2ar = await ctx.contracts.l2AssetRouter();
1548
+ const dataWithdraw = await wrapAs7(
1422
1549
  "INTERNAL",
1423
1550
  OP_WITHDRAWALS.erc20.encodeWithdraw,
1424
1551
  () => Promise.resolve(l2ar.interface.encodeFunctionData(SIG.withdraw, [assetId, assetData])),
@@ -1430,81 +1557,25 @@ function routeErc20NonBase2() {
1430
1557
  const withdrawTx = {
1431
1558
  to: ctx.l2AssetRouter,
1432
1559
  data: dataWithdraw,
1433
- from: ctx.sender,
1434
- ...txOverrides
1560
+ from: ctx.sender
1435
1561
  };
1562
+ const withdrawGas = await quoteL2Gas4({ ctx, tx: withdrawTx });
1563
+ if (withdrawGas) {
1564
+ withdrawTx.gasLimit = withdrawGas.gasLimit;
1565
+ withdrawTx.maxFeePerGas = withdrawGas.maxFeePerGas;
1566
+ withdrawTx.maxPriorityFeePerGas = withdrawGas.maxPriorityFeePerGas;
1567
+ }
1436
1568
  steps.push({
1437
1569
  key: "l2-asset-router:withdraw",
1438
1570
  kind: "l2-asset-router:withdraw",
1439
1571
  description: "Burn on L2 & send L2\u2192L1 message",
1440
1572
  tx: withdrawTx
1441
1573
  });
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
1574
+ const fees = buildFeeBreakdown2({
1575
+ feeToken: ctx.baseTokenL1 ?? await ctx.client.baseToken(ctx.chainIdL2),
1576
+ l2Gas: withdrawGas
1506
1577
  });
1507
- return { steps, approvals: [], quoteExtras: {} };
1578
+ return { steps, approvals, fees };
1508
1579
  }
1509
1580
  };
1510
1581
  }
@@ -1782,45 +1853,32 @@ function createFinalizationServices(client) {
1782
1853
 
1783
1854
  // src/adapters/ethers/resources/withdrawals/index.ts
1784
1855
  var ROUTES2 = {
1785
- "eth-base": routeEthBase(),
1856
+ base: routeEthBase(),
1786
1857
  // BaseTokenSystem.withdraw, chain base = ETH
1787
- "eth-nonbase": routeEthNonBase2(),
1788
- // BaseTokenSystem.withdraw, chain base ≠ ETH
1789
1858
  "erc20-nonbase": routeErc20NonBase2()
1790
1859
  // AssetRouter.withdraw for non-base ERC-20s
1791
1860
  };
1792
- function createWithdrawalsResource(client) {
1861
+ function createWithdrawalsResource(client, tokens, contracts) {
1793
1862
  const svc = createFinalizationServices(client);
1794
1863
  const { wrap: wrap2, toResult: toResult2 } = createErrorHandlers("withdrawals");
1864
+ const tokensResource = tokens ?? createTokensResource(client);
1865
+ const contractsResource = contracts ?? createContractsResource(client);
1795
1866
  async function buildPlan(p) {
1796
- const ctx = await commonCtx2(p, client);
1867
+ const ctx = await commonCtx2(p, client, tokensResource, contractsResource);
1797
1868
  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 = {
1869
+ const { steps, approvals, fees } = await ROUTES2[ctx.route].build(p, ctx);
1870
+ return {
1814
1871
  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
- }
1872
+ summary: {
1873
+ route: ctx.route,
1874
+ approvalsNeeded: approvals,
1875
+ amounts: {
1876
+ transfer: { token: p.token, amount: p.amount }
1877
+ },
1878
+ fees
1879
+ },
1880
+ steps
1822
1881
  };
1823
- return { route: ctx.route, summary, steps };
1824
1882
  }
1825
1883
  const finalizeCache = /* @__PURE__ */ new Map();
1826
1884
  const quote = (p) => wrap2(
@@ -2130,58 +2188,14 @@ function createWithdrawalsResource(client) {
2130
2188
 
2131
2189
  // src/adapters/ethers/sdk.ts
2132
2190
  function createEthersSdk(client) {
2191
+ const tokens = createTokensResource(client);
2192
+ const contracts = createContractsResource(client);
2133
2193
  return {
2134
- deposits: createDepositsResource(client),
2135
- withdrawals: createWithdrawalsResource(client),
2136
- // TODO: might update to create dedicated resources for these
2137
- helpers: {
2138
- addresses: () => client.ensureAddresses(),
2139
- contracts: () => client.contracts(),
2140
- async l1AssetRouter() {
2141
- const { l1AssetRouter } = await client.contracts();
2142
- return l1AssetRouter;
2143
- },
2144
- async l1NativeTokenVault() {
2145
- const { l1NativeTokenVault } = await client.contracts();
2146
- return l1NativeTokenVault;
2147
- },
2148
- async l1Nullifier() {
2149
- const { l1Nullifier } = await client.contracts();
2150
- return l1Nullifier;
2151
- },
2152
- async baseToken(chainId) {
2153
- const id = chainId ?? BigInt((await client.l2.getNetwork()).chainId);
2154
- return client.baseToken(id);
2155
- },
2156
- async l2TokenAddress(l1Token) {
2157
- if (isAddressEq(l1Token, FORMAL_ETH_ADDRESS)) {
2158
- return ETH_ADDRESS;
2159
- }
2160
- const { chainId } = await client.l2.getNetwork();
2161
- const base = await client.baseToken(BigInt(chainId));
2162
- if (isAddressEq(l1Token, base)) {
2163
- return L2_BASE_TOKEN_ADDRESS;
2164
- }
2165
- const { l2NativeTokenVault } = await client.contracts();
2166
- const addr = await l2NativeTokenVault.l2TokenAddress(l1Token);
2167
- return addr;
2168
- },
2169
- async l1TokenAddress(l2Token) {
2170
- if (isAddressEq(l2Token, ETH_ADDRESS)) {
2171
- return ETH_ADDRESS;
2172
- }
2173
- const { l2AssetRouter } = await client.contracts();
2174
- const addr = await l2AssetRouter.l1TokenAddress(l2Token);
2175
- return addr;
2176
- },
2177
- async assetId(l1Token) {
2178
- const norm = isAddressEq(l1Token, FORMAL_ETH_ADDRESS) ? ETH_ADDRESS : l1Token;
2179
- const { l1NativeTokenVault } = await client.contracts();
2180
- const id = await l1NativeTokenVault.assetId(norm);
2181
- return id;
2182
- }
2183
- }
2194
+ deposits: createDepositsResource(client, tokens, contracts),
2195
+ withdrawals: createWithdrawalsResource(client, tokens, contracts),
2196
+ tokens,
2197
+ contracts
2184
2198
  };
2185
2199
  }
2186
2200
 
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 };
2201
+ export { buildDirectRequestStruct, createDepositsResource, createEthersSdk, createFinalizationServices, createTokensResource, createWithdrawalsResource, encodeNativeTokenVaultTransferData, encodeSecondBridgeArgs, encodeSecondBridgeDataV1, encodeSecondBridgeErc20Args, encodeSecondBridgeEthArgs };