@matterlabs/zksync-js 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +124 -0
- package/dist/adapters/ethers/client.cjs +4548 -0
- package/dist/adapters/ethers/client.cjs.map +1 -0
- package/dist/adapters/ethers/client.d.ts +61 -0
- package/dist/adapters/ethers/client.js +5 -0
- package/dist/adapters/ethers/errors/error-ops.d.ts +20 -0
- package/dist/adapters/ethers/errors/revert.d.ts +28 -0
- package/dist/adapters/ethers/index.cjs +7537 -0
- package/dist/adapters/ethers/index.cjs.map +1 -0
- package/dist/adapters/ethers/index.d.ts +12 -0
- package/dist/adapters/ethers/index.js +8 -0
- package/dist/adapters/ethers/resources/deposits/context.d.ts +27 -0
- package/dist/adapters/ethers/resources/deposits/index.d.ts +46 -0
- package/dist/adapters/ethers/resources/deposits/routes/erc20-base.d.ts +2 -0
- package/dist/adapters/ethers/resources/deposits/routes/erc20-nonbase.d.ts +2 -0
- package/dist/adapters/ethers/resources/deposits/routes/eth-nonbase.d.ts +2 -0
- package/dist/adapters/ethers/resources/deposits/routes/eth.d.ts +2 -0
- package/dist/adapters/ethers/resources/deposits/routes/types.d.ts +10 -0
- package/dist/adapters/ethers/resources/deposits/services/verification.d.ts +9 -0
- package/dist/adapters/ethers/resources/token-info.d.ts +31 -0
- package/dist/adapters/ethers/resources/utils.d.ts +39 -0
- package/dist/adapters/ethers/resources/withdrawals/context.d.ts +19 -0
- package/dist/adapters/ethers/resources/withdrawals/index.d.ts +56 -0
- package/dist/adapters/ethers/resources/withdrawals/routes/erc20-nonbase.d.ts +2 -0
- package/dist/adapters/ethers/resources/withdrawals/routes/eth-nonbase.d.ts +2 -0
- package/dist/adapters/ethers/resources/withdrawals/routes/eth.d.ts +2 -0
- package/dist/adapters/ethers/resources/withdrawals/routes/types.d.ts +18 -0
- package/dist/adapters/ethers/resources/withdrawals/services/finalization.d.ts +33 -0
- package/dist/adapters/ethers/rpc.d.ts +4 -0
- package/dist/adapters/ethers/sdk.cjs +6245 -0
- package/dist/adapters/ethers/sdk.cjs.map +1 -0
- package/dist/adapters/ethers/sdk.d.ts +29 -0
- package/dist/adapters/ethers/sdk.js +6 -0
- package/dist/adapters/ethers/typechain/IAssetRouterBase.d.ts +198 -0
- package/dist/adapters/ethers/typechain/IBridgehub.d.ts +731 -0
- package/dist/adapters/ethers/typechain/IERC20.d.ts +108 -0
- package/dist/adapters/ethers/typechain/IL1AssetRouter.d.ts +570 -0
- package/dist/adapters/ethers/typechain/IL1NativeTokenVault.d.ts +154 -0
- package/dist/adapters/ethers/typechain/IL1Nullifier.d.ts +305 -0
- package/dist/adapters/ethers/typechain/IL2AssetRouter.d.ts +288 -0
- package/dist/adapters/ethers/typechain/IL2NativeTokenVault.d.ts +380 -0
- package/dist/adapters/ethers/typechain/common.d.ts +46 -0
- package/dist/adapters/ethers/typechain/factories/IAssetRouterBase__factory.d.ts +203 -0
- package/dist/adapters/ethers/typechain/factories/IBridgehub__factory.d.ts +998 -0
- package/dist/adapters/ethers/typechain/factories/IERC20__factory.d.ts +177 -0
- package/dist/adapters/ethers/typechain/factories/IL1AssetRouter__factory.d.ts +666 -0
- package/dist/adapters/ethers/typechain/factories/IL1NativeTokenVault__factory.d.ts +234 -0
- package/dist/adapters/ethers/typechain/factories/IL1Nullifier__factory.d.ts +382 -0
- package/dist/adapters/ethers/typechain/factories/IL2AssetRouter__factory.d.ts +327 -0
- package/dist/adapters/ethers/typechain/factories/IL2NativeTokenVault__factory.d.ts +696 -0
- package/dist/adapters/ethers/typechain/factories/index.d.ts +8 -0
- package/dist/adapters/ethers/typechain/index.d.ts +17 -0
- package/dist/adapters/viem/client.cjs +4534 -0
- package/dist/adapters/viem/client.cjs.map +1 -0
- package/dist/adapters/viem/client.d.ts +44 -0
- package/dist/adapters/viem/client.js +5 -0
- package/dist/adapters/viem/errors/error-ops.d.ts +20 -0
- package/dist/adapters/viem/errors/revert.d.ts +25 -0
- package/dist/adapters/viem/index.cjs +7772 -0
- package/dist/adapters/viem/index.cjs.map +1 -0
- package/dist/adapters/viem/index.d.ts +11 -0
- package/dist/adapters/viem/index.js +8 -0
- package/dist/adapters/viem/resources/deposits/context.d.ts +27 -0
- package/dist/adapters/viem/resources/deposits/index.d.ts +46 -0
- package/dist/adapters/viem/resources/deposits/routes/erc20-base.d.ts +2 -0
- package/dist/adapters/viem/resources/deposits/routes/erc20-nonbase.d.ts +2 -0
- package/dist/adapters/viem/resources/deposits/routes/eth-nonbase.d.ts +2 -0
- package/dist/adapters/viem/resources/deposits/routes/eth.d.ts +2 -0
- package/dist/adapters/viem/resources/deposits/routes/types.d.ts +20 -0
- package/dist/adapters/viem/resources/deposits/services/verification.d.ts +7 -0
- package/dist/adapters/viem/resources/token-info.d.ts +34 -0
- package/dist/adapters/viem/resources/utils.d.ts +42 -0
- package/dist/adapters/viem/resources/withdrawals/context.d.ts +22 -0
- package/dist/adapters/viem/resources/withdrawals/index.d.ts +56 -0
- package/dist/adapters/viem/resources/withdrawals/routes/erc20-nonbase.d.ts +2 -0
- package/dist/adapters/viem/resources/withdrawals/routes/eth-nonbase.d.ts +2 -0
- package/dist/adapters/viem/resources/withdrawals/routes/eth.d.ts +2 -0
- package/dist/adapters/viem/resources/withdrawals/routes/types.d.ts +19 -0
- package/dist/adapters/viem/resources/withdrawals/services/finalization.d.ts +33 -0
- package/dist/adapters/viem/rpc.d.ts +2 -0
- package/dist/adapters/viem/sdk.cjs +6481 -0
- package/dist/adapters/viem/sdk.cjs.map +1 -0
- package/dist/adapters/viem/sdk.d.ts +32 -0
- package/dist/adapters/viem/sdk.js +6 -0
- package/dist/chunk-263G6636.js +36 -0
- package/dist/chunk-3LALBFFE.js +138 -0
- package/dist/chunk-4HLJJKIY.js +262 -0
- package/dist/chunk-6GCT6TLS.js +45 -0
- package/dist/chunk-7M4V3FMT.js +2444 -0
- package/dist/chunk-B77GWPO5.js +339 -0
- package/dist/chunk-BD2LUO5T.js +123 -0
- package/dist/chunk-CGO27P7F.js +2187 -0
- package/dist/chunk-DI2CJDPZ.js +76 -0
- package/dist/chunk-Y75OMFK6.js +4489 -0
- package/dist/core/constants.cjs +39 -0
- package/dist/core/constants.cjs.map +1 -0
- package/dist/core/constants.d.ts +36 -0
- package/dist/core/constants.js +1 -0
- package/dist/core/errors/factory.d.ts +10 -0
- package/dist/core/errors/formatter.d.ts +2 -0
- package/dist/core/errors/rpc.d.ts +4 -0
- package/dist/core/errors/withdrawal-revert-map.d.ts +3 -0
- package/dist/core/index.cjs +552 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/index.js +4 -0
- package/dist/core/internal/abi-registry.d.ts +9 -0
- package/dist/core/internal/abis/IAssetRouterBase.d.ts +198 -0
- package/dist/core/internal/abis/IBaseToken.d.ts +162 -0
- package/dist/core/internal/abis/IBridgehub.d.ts +994 -0
- package/dist/core/internal/abis/IERC20.d.ts +224 -0
- package/dist/core/internal/abis/IL1AssetRouter.d.ts +661 -0
- package/dist/core/internal/abis/IL1Nullifier.d.ts +377 -0
- package/dist/core/internal/abis/IL2AssetRouter.d.ts +690 -0
- package/dist/core/internal/abis/L1NativeTokenVault.d.ts +719 -0
- package/dist/core/internal/abis/L2NativeTokenVault.d.ts +735 -0
- package/dist/core/internal/abis/Mailbox.d.ts +779 -0
- package/dist/core/resources/deposits/route.d.ts +6 -0
- package/dist/core/resources/withdrawals/events.d.ts +9 -0
- package/dist/core/resources/withdrawals/logs.d.ts +5 -0
- package/dist/core/resources/withdrawals/route.d.ts +6 -0
- package/dist/core/rpc/transport.d.ts +10 -0
- package/dist/core/rpc/types.d.ts +40 -0
- package/dist/core/rpc/zks.d.ts +12 -0
- package/dist/core/types/errors.d.ts +177 -0
- package/dist/core/types/flows/base.d.ts +51 -0
- package/dist/core/types/flows/deposits.d.ts +43 -0
- package/dist/core/types/flows/route.d.ts +12 -0
- package/dist/core/types/flows/withdrawals.d.ts +83 -0
- package/dist/core/types/index.d.ts +2 -0
- package/dist/core/types/primitives.d.ts +3 -0
- package/dist/core/utils/addr.d.ts +5 -0
- package/dist/core/utils/gas.d.ts +13 -0
- package/dist/index.cjs +571 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +4 -0
- package/package.json +177 -0
|
@@ -0,0 +1,2187 @@
|
|
|
1
|
+
import { assertNoLegacyGas, assertPriorityFeeBounds, REVERT_TO_READINESS } from './chunk-263G6636.js';
|
|
2
|
+
import { findL1MessageSentLog, messengerLogIndex, isAddressEq, isHash66, pickDepositRoute, isETH, normalizeAddrEq, pickWithdrawRoute } from './chunk-DI2CJDPZ.js';
|
|
3
|
+
import { IL1Nullifier_default, IERC20_default, L1NativeTokenVault_default, L2NativeTokenVault_default, Mailbox_default, IBridgehub_default, IL2AssetRouter_default, IBaseToken_default } from './chunk-Y75OMFK6.js';
|
|
4
|
+
import { isZKsyncError, createError, shapeCause, OP_WITHDRAWALS, OP_DEPOSITS, isReceiptNotFound } from './chunk-B77GWPO5.js';
|
|
5
|
+
import { ETH_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L1_FEE_ESTIMATION_COEF_NUMERATOR, L1_FEE_ESTIMATION_COEF_DENOMINATOR, L1_MESSENGER_ADDRESS, L2_ASSET_ROUTER_ADDRESS, FORMAL_ETH_ADDRESS, L2_BASE_TOKEN_ADDRESS, TOPIC_CANONICAL_ASSIGNED, TOPIC_CANONICAL_SUCCESS } from './chunk-6GCT6TLS.js';
|
|
6
|
+
import { Interface, AbiCoder, ethers, Contract, NonceManager } from 'ethers';
|
|
7
|
+
|
|
8
|
+
var I_BRIDGEHUB = new Interface([
|
|
9
|
+
"event NewPriorityRequest(uint256 indexed chainId, address indexed sender, bytes32 txHash, uint256 txId, bytes data)"
|
|
10
|
+
]);
|
|
11
|
+
var TOPIC_BRIDGEHUB_NPR = I_BRIDGEHUB.getEvent("NewPriorityRequest").topicHash;
|
|
12
|
+
function extractL2TxHashFromL1Logs(logs) {
|
|
13
|
+
for (const lg of logs) {
|
|
14
|
+
if ((lg.topics?.[0] ?? "").toLowerCase() === TOPIC_BRIDGEHUB_NPR.toLowerCase()) {
|
|
15
|
+
try {
|
|
16
|
+
const ev = I_BRIDGEHUB.decodeEventLog("NewPriorityRequest", lg.data, lg.topics);
|
|
17
|
+
const h = ev.txHash;
|
|
18
|
+
if (isHash66(h)) return h;
|
|
19
|
+
} catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const lg of logs) {
|
|
24
|
+
const t0 = (lg.topics?.[0] ?? "").toLowerCase();
|
|
25
|
+
if (t0 === TOPIC_CANONICAL_ASSIGNED.toLowerCase()) {
|
|
26
|
+
const h = lg.topics?.[2];
|
|
27
|
+
if (isHash66(h)) return h;
|
|
28
|
+
}
|
|
29
|
+
if (t0 === TOPIC_CANONICAL_SUCCESS.toLowerCase()) {
|
|
30
|
+
const h = lg.topics?.[3];
|
|
31
|
+
if (isHash66(h)) return h;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
async function waitForL2ExecutionFromL1Tx(l1, l2, l1TxHash) {
|
|
37
|
+
const l1Receipt = await l1.waitForTransaction(l1TxHash);
|
|
38
|
+
if (!l1Receipt) throw new Error("No L1 receipt found");
|
|
39
|
+
const l2TxHash = extractL2TxHashFromL1Logs(l1Receipt.logs);
|
|
40
|
+
if (!l2TxHash) {
|
|
41
|
+
throw createError("VERIFICATION", {
|
|
42
|
+
message: "Failed to extract L2 transaction hash from L1 logs",
|
|
43
|
+
resource: "deposits",
|
|
44
|
+
operation: "deposits.wait",
|
|
45
|
+
context: { l1TxHash, logCount: l1Receipt.logs?.length ?? 0 }
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const l2Receipt = await l2.waitForTransaction(l2TxHash);
|
|
49
|
+
if (!l2Receipt) {
|
|
50
|
+
const maybe = await l2.getTransactionReceipt(l2TxHash).catch(() => null);
|
|
51
|
+
if (!maybe) {
|
|
52
|
+
throw createError("VERIFICATION", {
|
|
53
|
+
message: "L2 transaction was not found after waiting for its execution",
|
|
54
|
+
resource: "deposits",
|
|
55
|
+
operation: "deposits.wait",
|
|
56
|
+
context: { l1TxHash, l2TxHash, where: "l2.waitForTransaction" }
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (maybe.status !== 1) {
|
|
60
|
+
throw createError("VERIFICATION", {
|
|
61
|
+
message: "L2 transaction execution failed",
|
|
62
|
+
resource: "deposits",
|
|
63
|
+
operation: "deposits.wait",
|
|
64
|
+
context: { l1TxHash, l2TxHash, status: maybe.status }
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return { l2Receipt: maybe, l2TxHash };
|
|
68
|
+
}
|
|
69
|
+
return { l2Receipt, l2TxHash };
|
|
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);
|
|
81
|
+
}
|
|
82
|
+
function encodeNativeTokenVaultTransferData(amount, receiver, token) {
|
|
83
|
+
return new AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
|
|
84
|
+
}
|
|
85
|
+
function encodeSecondBridgeDataV1(assetId, transferData) {
|
|
86
|
+
const abi = new AbiCoder();
|
|
87
|
+
const data = abi.encode(["bytes32", "bytes"], [assetId, transferData]);
|
|
88
|
+
return ethers.concat(["0x01", data]);
|
|
89
|
+
}
|
|
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]
|
|
95
|
+
);
|
|
96
|
+
return ethers.keccak256(hex);
|
|
97
|
+
}
|
|
98
|
+
function encodeNTVTransferData(amount, receiver, token) {
|
|
99
|
+
return new AbiCoder().encode(["uint256", "address", "address"], [amount, receiver, token]);
|
|
100
|
+
}
|
|
101
|
+
function scaleGasLimit(gasLimit) {
|
|
102
|
+
return gasLimit * BigInt(L1_FEE_ESTIMATION_COEF_NUMERATOR) / BigInt(L1_FEE_ESTIMATION_COEF_DENOMINATOR);
|
|
103
|
+
}
|
|
104
|
+
async function checkBaseCost(baseCost, value) {
|
|
105
|
+
const resolvedValue = await value;
|
|
106
|
+
if (baseCost > resolvedValue) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`The base cost of performing the priority operation is higher than the provided value parameter for the transaction: baseCost: ${String(baseCost)}, provided value: ${String(resolvedValue)}!`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function getFeeOverrides(client, overrides) {
|
|
113
|
+
assertNoLegacyGas(overrides);
|
|
114
|
+
const fd = await client.l1.getFeeData();
|
|
115
|
+
const maxFeeFromProvider = fd.maxFeePerGas ?? void 0;
|
|
116
|
+
const maxPriorityFromProvider = fd.maxPriorityFeePerGas ?? void 0;
|
|
117
|
+
const gasPriceFallback = fd.gasPrice ?? void 0;
|
|
118
|
+
const maxFeePerGas = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback;
|
|
119
|
+
if (maxFeePerGas == null) throw new Error("provider returned no gas price data");
|
|
120
|
+
const maxPriorityFeePerGas = overrides?.maxPriorityFeePerGas ?? maxPriorityFromProvider ?? maxFeePerGas;
|
|
121
|
+
assertPriorityFeeBounds({ maxFeePerGas, maxPriorityFeePerGas });
|
|
122
|
+
const gasPriceForBaseCost = overrides?.maxFeePerGas ?? maxFeeFromProvider ?? gasPriceFallback ?? maxFeePerGas;
|
|
123
|
+
return {
|
|
124
|
+
gasLimit: overrides?.gasLimit,
|
|
125
|
+
maxFeePerGas,
|
|
126
|
+
maxPriorityFeePerGas,
|
|
127
|
+
gasPriceForBaseCost
|
|
128
|
+
};
|
|
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
|
+
};
|
|
164
|
+
}
|
|
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");
|
|
170
|
+
}
|
|
171
|
+
function buildDirectRequestStruct(args) {
|
|
172
|
+
return {
|
|
173
|
+
chainId: args.chainId,
|
|
174
|
+
l2Contract: args.l2Contract,
|
|
175
|
+
mintValue: args.mintValue,
|
|
176
|
+
l2Value: args.l2Value,
|
|
177
|
+
l2Calldata: "0x",
|
|
178
|
+
l2GasLimit: args.l2GasLimit,
|
|
179
|
+
l2GasPerPubdataByteLimit: args.gasPerPubdata,
|
|
180
|
+
factoryDeps: [],
|
|
181
|
+
refundRecipient: args.refundRecipient
|
|
182
|
+
};
|
|
183
|
+
}
|
|
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
|
+
|
|
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);
|
|
208
|
+
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
|
|
220
|
+
};
|
|
221
|
+
}
|
|
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
|
+
};
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return { selector };
|
|
308
|
+
}
|
|
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 };
|
|
325
|
+
}
|
|
326
|
+
|
|
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) });
|
|
332
|
+
}
|
|
333
|
+
function resolveMessage(op, msg) {
|
|
334
|
+
if (!msg) return `Error during ${op}.`;
|
|
335
|
+
return typeof msg === "function" ? msg() : msg;
|
|
336
|
+
}
|
|
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);
|
|
352
|
+
}
|
|
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 };
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return { wrap: wrap2, wrapAs: wrapAs9, toResult: toResult2 };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// src/adapters/ethers/resources/deposits/routes/eth.ts
|
|
375
|
+
var { wrapAs } = createErrorHandlers("deposits");
|
|
376
|
+
function routeEthDirect() {
|
|
377
|
+
return {
|
|
378
|
+
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."
|
|
393
|
+
}
|
|
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;
|
|
399
|
+
const req = buildDirectRequestStruct({
|
|
400
|
+
chainId: ctx.chainIdL2,
|
|
401
|
+
mintValue,
|
|
402
|
+
l2GasLimit: ctx.l2GasLimit,
|
|
403
|
+
gasPerPubdata: ctx.gasPerPubdata,
|
|
404
|
+
refundRecipient: ctx.refundRecipient,
|
|
405
|
+
l2Contract,
|
|
406
|
+
l2Value
|
|
407
|
+
});
|
|
408
|
+
const data = bh.interface.encodeFunctionData("requestL2TransactionDirect", [req]);
|
|
409
|
+
let resolvedL1GasLimit = overrideGasLimit ?? ctx.l2GasLimit;
|
|
410
|
+
const tx = {
|
|
411
|
+
to: ctx.bridgehub,
|
|
412
|
+
data,
|
|
413
|
+
value: mintValue,
|
|
414
|
+
from: ctx.sender,
|
|
415
|
+
...txFeeOverrides
|
|
416
|
+
};
|
|
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
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const steps = [
|
|
438
|
+
{
|
|
439
|
+
key: "bridgehub:direct",
|
|
440
|
+
kind: "bridgehub:direct",
|
|
441
|
+
description: "Bridge ETH via Bridgehub.requestL2TransactionDirect",
|
|
442
|
+
tx
|
|
443
|
+
}
|
|
444
|
+
];
|
|
445
|
+
return {
|
|
446
|
+
steps,
|
|
447
|
+
approvals: [],
|
|
448
|
+
quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
var { wrapAs: wrapAs2 } = createErrorHandlers("deposits");
|
|
454
|
+
var MIN_L2_GAS_FOR_ERC20 = 2500000n;
|
|
455
|
+
function routeErc20NonBase() {
|
|
456
|
+
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
|
+
);
|
|
474
|
+
await wrapAs2(
|
|
475
|
+
"VALIDATION",
|
|
476
|
+
OP_DEPOSITS.nonbase.assertNonBaseToken,
|
|
477
|
+
() => {
|
|
478
|
+
if (normalizeAddrEq(baseToken, p.token)) {
|
|
479
|
+
throw new Error("erc20-nonbase route requires a non-base ERC-20 deposit token.");
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
{ ctx: { depositToken: p.token, baseToken } }
|
|
483
|
+
);
|
|
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."
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
const baseCost = BigInt(rawBaseCost);
|
|
500
|
+
const mintValue = baseCost + ctx.operatorTip;
|
|
501
|
+
const approvals = [];
|
|
502
|
+
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
|
+
});
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
const baseIsEth = isETH(baseToken);
|
|
530
|
+
if (!baseIsEth) {
|
|
531
|
+
const erc20Base = new Contract(baseToken, IERC20_default, l1Signer);
|
|
532
|
+
const allowanceBase = await wrapAs2(
|
|
533
|
+
"RPC",
|
|
534
|
+
OP_DEPOSITS.nonbase.allowanceBase,
|
|
535
|
+
() => erc20Base.allowance(ctx.sender, assetRouter),
|
|
536
|
+
{
|
|
537
|
+
ctx: { where: "erc20.allowance", token: baseToken, spender: assetRouter },
|
|
538
|
+
message: "Failed to read base-token allowance."
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
if (allowanceBase < mintValue) {
|
|
542
|
+
approvals.push({ token: baseToken, spender: assetRouter, amount: mintValue });
|
|
543
|
+
const data = erc20Base.interface.encodeFunctionData("approve", [assetRouter, mintValue]);
|
|
544
|
+
steps.push({
|
|
545
|
+
key: `approve:${baseToken}:${assetRouter}`,
|
|
546
|
+
kind: "approve",
|
|
547
|
+
description: `Approve base token for mintValue`,
|
|
548
|
+
tx: { to: baseToken, data, from: ctx.sender, ...txOverrides }
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
const secondBridgeCalldata = await wrapAs2(
|
|
553
|
+
"INTERNAL",
|
|
554
|
+
OP_DEPOSITS.nonbase.encodeCalldata,
|
|
555
|
+
() => Promise.resolve(encodeSecondBridgeErc20Args(p.token, p.amount, p.to ?? ctx.sender)),
|
|
556
|
+
{
|
|
557
|
+
ctx: { where: "encodeSecondBridgeErc20Args" },
|
|
558
|
+
message: "Failed to encode bridging calldata."
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
const outer = {
|
|
562
|
+
chainId: ctx.chainIdL2,
|
|
563
|
+
mintValue,
|
|
564
|
+
// fees (in ETH if base=ETH, else pulled as base ERC-20)
|
|
565
|
+
l2Value: 0n,
|
|
566
|
+
l2GasLimit: l2GasLimitUsed,
|
|
567
|
+
l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
|
|
568
|
+
refundRecipient: ctx.refundRecipient,
|
|
569
|
+
secondBridgeAddress: assetRouter,
|
|
570
|
+
secondBridgeValue: 0n,
|
|
571
|
+
secondBridgeCalldata
|
|
572
|
+
};
|
|
573
|
+
const dataTwo = bh.interface.encodeFunctionData("requestL2TransactionTwoBridges", [outer]);
|
|
574
|
+
const bridgeTx = {
|
|
575
|
+
to: ctx.bridgehub,
|
|
576
|
+
data: dataTwo,
|
|
577
|
+
value: baseIsEth ? mintValue : 0n,
|
|
578
|
+
from: ctx.sender,
|
|
579
|
+
...txOverrides
|
|
580
|
+
};
|
|
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
|
+
}
|
|
600
|
+
}
|
|
601
|
+
steps.push({
|
|
602
|
+
key: "bridgehub:two-bridges:nonbase",
|
|
603
|
+
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
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
steps,
|
|
609
|
+
approvals,
|
|
610
|
+
quoteExtras: { baseCost, mintValue, baseToken, baseIsEth, l1GasLimit: resolvedL1GasLimit }
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
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
|
+
function routeEthNonBase() {
|
|
620
|
+
return {
|
|
621
|
+
async preflight(p, ctx) {
|
|
622
|
+
await wrapAs3(
|
|
623
|
+
"VALIDATION",
|
|
624
|
+
OP_DEPOSITS.ethNonBase.assertEthAsset,
|
|
625
|
+
() => {
|
|
626
|
+
if (!isETH(p.token)) {
|
|
627
|
+
throw new Error("eth-nonbase route requires ETH as the deposit asset.");
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
{ ctx: { token: p.token } }
|
|
631
|
+
);
|
|
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
|
+
await wrapAs3(
|
|
643
|
+
"VALIDATION",
|
|
644
|
+
OP_DEPOSITS.ethNonBase.assertNonEthBase,
|
|
645
|
+
() => {
|
|
646
|
+
if (isETH(baseToken)) {
|
|
647
|
+
throw new Error("eth-nonbase route requires target chain base token \u2260 ETH.");
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
{ ctx: { baseToken, chainIdL2: ctx.chainIdL2 } }
|
|
651
|
+
);
|
|
652
|
+
const ethBal = await wrapAs3(
|
|
653
|
+
"RPC",
|
|
654
|
+
OP_DEPOSITS.ethNonBase.ethBalance,
|
|
655
|
+
() => ctx.client.l1.getBalance(ctx.sender),
|
|
656
|
+
{
|
|
657
|
+
ctx: { where: "l1.getBalance", sender: ctx.sender },
|
|
658
|
+
message: "Failed to read L1 ETH balance."
|
|
659
|
+
}
|
|
660
|
+
);
|
|
661
|
+
await wrapAs3(
|
|
662
|
+
"VALIDATION",
|
|
663
|
+
OP_DEPOSITS.ethNonBase.assertEthBalance,
|
|
664
|
+
() => {
|
|
665
|
+
if (ethBal < p.amount) {
|
|
666
|
+
throw new Error("Insufficient L1 ETH balance to cover deposit amount.");
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
{ ctx: { required: p.amount.toString(), balance: ethBal.toString() } }
|
|
670
|
+
);
|
|
671
|
+
return;
|
|
672
|
+
},
|
|
673
|
+
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(
|
|
687
|
+
"RPC",
|
|
688
|
+
OP_DEPOSITS.ethNonBase.baseCost,
|
|
689
|
+
() => bh.l2TransactionBaseCost(
|
|
690
|
+
ctx.chainIdL2,
|
|
691
|
+
gasPriceForBaseCost,
|
|
692
|
+
ctx.l2GasLimit,
|
|
693
|
+
ctx.gasPerPubdata
|
|
694
|
+
),
|
|
695
|
+
{
|
|
696
|
+
ctx: { where: "l2TransactionBaseCost", chainIdL2: ctx.chainIdL2 },
|
|
697
|
+
message: "Could not fetch L2 base cost."
|
|
698
|
+
}
|
|
699
|
+
);
|
|
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."
|
|
714
|
+
}
|
|
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
|
+
}
|
|
729
|
+
}
|
|
730
|
+
const secondBridgeCalldata = await wrapAs3(
|
|
731
|
+
"INTERNAL",
|
|
732
|
+
OP_DEPOSITS.ethNonBase.encodeCalldata,
|
|
733
|
+
() => Promise.resolve(encodeSecondBridgeEthArgs(p.amount, p.to ?? ctx.sender)),
|
|
734
|
+
{
|
|
735
|
+
ctx: {
|
|
736
|
+
where: "encodeSecondBridgeEthArgs",
|
|
737
|
+
amount: p.amount.toString(),
|
|
738
|
+
to: p.to ?? ctx.sender
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
);
|
|
742
|
+
const outer = {
|
|
743
|
+
chainId: ctx.chainIdL2,
|
|
744
|
+
mintValue,
|
|
745
|
+
l2Value: 0n,
|
|
746
|
+
l2GasLimit: ctx.l2GasLimit,
|
|
747
|
+
l2GasPerPubdataByteLimit: ctx.gasPerPubdata,
|
|
748
|
+
refundRecipient: ctx.refundRecipient,
|
|
749
|
+
secondBridgeAddress: ctx.l1AssetRouter,
|
|
750
|
+
secondBridgeValue: p.amount,
|
|
751
|
+
secondBridgeCalldata
|
|
752
|
+
};
|
|
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 = {
|
|
760
|
+
to: ctx.bridgehub,
|
|
761
|
+
data: dataTwo,
|
|
762
|
+
value: p.amount,
|
|
763
|
+
// base ≠ ETH ⇒ msg.value == secondBridgeValue
|
|
764
|
+
from: ctx.sender,
|
|
765
|
+
...txOverrides
|
|
766
|
+
};
|
|
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
|
+
}
|
|
786
|
+
}
|
|
787
|
+
steps.push({
|
|
788
|
+
key: "bridgehub:two-bridges:eth-nonbase",
|
|
789
|
+
kind: "bridgehub:two-bridges",
|
|
790
|
+
description: "Bridge ETH (fees in base ERC-20) via Bridgehub.requestL2TransactionTwoBridges",
|
|
791
|
+
tx: bridgeTx
|
|
792
|
+
});
|
|
793
|
+
return {
|
|
794
|
+
steps,
|
|
795
|
+
approvals,
|
|
796
|
+
quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
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
|
+
function routeErc20Base() {
|
|
806
|
+
return {
|
|
807
|
+
async preflight(p, ctx) {
|
|
808
|
+
await wrapAs4(
|
|
809
|
+
"VALIDATION",
|
|
810
|
+
OP_DEPOSITS.base.assertErc20Asset,
|
|
811
|
+
() => {
|
|
812
|
+
if (isETH(p.token)) {
|
|
813
|
+
throw new Error("erc20-base route requires an ERC-20 token (not ETH).");
|
|
814
|
+
}
|
|
815
|
+
},
|
|
816
|
+
{ ctx: { token: p.token } }
|
|
817
|
+
);
|
|
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
|
+
);
|
|
828
|
+
await wrapAs4(
|
|
829
|
+
"VALIDATION",
|
|
830
|
+
OP_DEPOSITS.base.assertMatchesBase,
|
|
831
|
+
() => {
|
|
832
|
+
if (!normalizeAddrEq(baseToken, p.token)) {
|
|
833
|
+
throw new Error("Provided token is not the base token for the target chain.");
|
|
834
|
+
}
|
|
835
|
+
},
|
|
836
|
+
{ ctx: { baseToken, provided: p.token, chainIdL2: ctx.chainIdL2 } }
|
|
837
|
+
);
|
|
838
|
+
return;
|
|
839
|
+
},
|
|
840
|
+
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);
|
|
872
|
+
const approvals = [];
|
|
873
|
+
const steps = [];
|
|
874
|
+
{
|
|
875
|
+
const erc20 = new Contract(baseToken, IERC20_default, ctx.client.getL1Signer());
|
|
876
|
+
const allowance = await wrapAs4(
|
|
877
|
+
"RPC",
|
|
878
|
+
OP_DEPOSITS.base.allowance,
|
|
879
|
+
() => erc20.allowance(ctx.sender, ctx.l1AssetRouter),
|
|
880
|
+
{
|
|
881
|
+
ctx: { where: "erc20.allowance", token: baseToken, spender: ctx.l1AssetRouter },
|
|
882
|
+
message: "Failed to read base-token allowance."
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
if (allowance < mintValue) {
|
|
886
|
+
approvals.push({ token: baseToken, spender: ctx.l1AssetRouter, amount: mintValue });
|
|
887
|
+
const data2 = erc20.interface.encodeFunctionData("approve", [
|
|
888
|
+
ctx.l1AssetRouter,
|
|
889
|
+
mintValue
|
|
890
|
+
]);
|
|
891
|
+
steps.push({
|
|
892
|
+
key: `approve:${baseToken}:${ctx.l1AssetRouter}`,
|
|
893
|
+
kind: "approve",
|
|
894
|
+
description: "Approve base token for mintValue",
|
|
895
|
+
tx: { to: baseToken, data: data2, from: ctx.sender, ...txOverrides }
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
const req = buildDirectRequestStruct({
|
|
900
|
+
chainId: ctx.chainIdL2,
|
|
901
|
+
mintValue,
|
|
902
|
+
l2GasLimit: ctx.l2GasLimit,
|
|
903
|
+
gasPerPubdata: ctx.gasPerPubdata,
|
|
904
|
+
refundRecipient: ctx.refundRecipient,
|
|
905
|
+
l2Contract: p.to ?? ctx.sender,
|
|
906
|
+
l2Value
|
|
907
|
+
});
|
|
908
|
+
const data = new Contract(
|
|
909
|
+
ctx.bridgehub,
|
|
910
|
+
IBridgehub_default,
|
|
911
|
+
ctx.client.l1
|
|
912
|
+
).interface.encodeFunctionData("requestL2TransactionDirect", [req]);
|
|
913
|
+
const tx = {
|
|
914
|
+
to: ctx.bridgehub,
|
|
915
|
+
data,
|
|
916
|
+
value: 0n,
|
|
917
|
+
// base token is ERC-20 ⇒ msg.value MUST be 0
|
|
918
|
+
from: ctx.sender,
|
|
919
|
+
...txOverrides
|
|
920
|
+
};
|
|
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
|
+
}
|
|
940
|
+
}
|
|
941
|
+
steps.push({
|
|
942
|
+
key: "bridgehub:direct:erc20-base",
|
|
943
|
+
kind: "bridgehub:direct",
|
|
944
|
+
description: "Bridge base ERC-20 via Bridgehub.requestL2TransactionDirect",
|
|
945
|
+
tx
|
|
946
|
+
});
|
|
947
|
+
return {
|
|
948
|
+
steps,
|
|
949
|
+
approvals,
|
|
950
|
+
quoteExtras: { baseCost, mintValue, l1GasLimit: resolvedL1GasLimit }
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// src/adapters/ethers/resources/deposits/index.ts
|
|
957
|
+
var { wrap, toResult } = createErrorHandlers("deposits");
|
|
958
|
+
var ROUTES = {
|
|
959
|
+
"eth-base": routeEthDirect(),
|
|
960
|
+
"eth-nonbase": routeEthNonBase(),
|
|
961
|
+
"erc20-nonbase": routeErc20NonBase(),
|
|
962
|
+
"erc20-base": routeErc20Base()
|
|
963
|
+
};
|
|
964
|
+
function createDepositsResource(client) {
|
|
965
|
+
async function buildPlan(p) {
|
|
966
|
+
const ctx = await commonCtx(p, client);
|
|
967
|
+
const route = ctx.route;
|
|
968
|
+
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();
|
|
987
|
+
return {
|
|
988
|
+
route: ctx.route,
|
|
989
|
+
summary: {
|
|
990
|
+
route: ctx.route,
|
|
991
|
+
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
|
+
}
|
|
1000
|
+
},
|
|
1001
|
+
steps
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
const quote = async (p) => wrap(
|
|
1005
|
+
OP_DEPOSITS.quote,
|
|
1006
|
+
async () => {
|
|
1007
|
+
const plan = await buildPlan(p);
|
|
1008
|
+
return plan.summary;
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
message: "Internal error while preparing a deposit quote.",
|
|
1012
|
+
ctx: { token: p.token, where: "deposits.quote" }
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
const tryQuote = (p) => toResult(OP_DEPOSITS.tryQuote, () => quote(p), {
|
|
1016
|
+
message: "Internal error while preparing a deposit quote.",
|
|
1017
|
+
ctx: { token: p.token, where: "deposits.tryQuote" }
|
|
1018
|
+
});
|
|
1019
|
+
const prepare = (p) => wrap(OP_DEPOSITS.prepare, () => buildPlan(p), {
|
|
1020
|
+
message: "Internal error while preparing a deposit plan.",
|
|
1021
|
+
ctx: { token: p.token, where: "deposits.prepare" }
|
|
1022
|
+
});
|
|
1023
|
+
const tryPrepare = (p) => toResult(OP_DEPOSITS.tryPrepare, () => prepare(p), {
|
|
1024
|
+
ctx: { token: p.token, where: "deposits.tryPrepare" }
|
|
1025
|
+
});
|
|
1026
|
+
const create = (p) => wrap(
|
|
1027
|
+
OP_DEPOSITS.create,
|
|
1028
|
+
async () => {
|
|
1029
|
+
const plan = await prepare(p);
|
|
1030
|
+
const stepHashes = {};
|
|
1031
|
+
const managed = new NonceManager(client.signer);
|
|
1032
|
+
const from = await managed.getAddress();
|
|
1033
|
+
let next = await client.l1.getTransactionCount(from, "latest");
|
|
1034
|
+
for (const step of plan.steps) {
|
|
1035
|
+
if (step.kind === "approve") {
|
|
1036
|
+
try {
|
|
1037
|
+
const [, token, router] = step.key.split(":");
|
|
1038
|
+
const erc20 = new Contract(token, IERC20_default, client.signer);
|
|
1039
|
+
const target = plan.summary.approvalsNeeded.find(
|
|
1040
|
+
(need) => need.token.toLowerCase() === (token ?? "").toLowerCase() && need.spender.toLowerCase() === (router ?? "").toLowerCase()
|
|
1041
|
+
)?.amount ?? 0n;
|
|
1042
|
+
const current = await erc20.allowance(from, router);
|
|
1043
|
+
if (current >= target) {
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
throw toZKsyncError(
|
|
1048
|
+
"CONTRACT",
|
|
1049
|
+
{
|
|
1050
|
+
resource: "deposits",
|
|
1051
|
+
operation: "deposits.create.erc20-allowance-recheck",
|
|
1052
|
+
context: { where: "erc20.allowance(recheck)", step: step.key, from },
|
|
1053
|
+
message: "Failed to read ERC-20 allowance during deposit step."
|
|
1054
|
+
},
|
|
1055
|
+
e
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
step.tx.nonce = next++;
|
|
1060
|
+
if (p.l1TxOverrides) {
|
|
1061
|
+
const overrides = p.l1TxOverrides;
|
|
1062
|
+
if (overrides.gasLimit != null) step.tx.gasLimit = overrides.gasLimit;
|
|
1063
|
+
if (overrides.maxFeePerGas != null) step.tx.maxFeePerGas = overrides.maxFeePerGas;
|
|
1064
|
+
if (overrides.maxPriorityFeePerGas != null) {
|
|
1065
|
+
step.tx.maxPriorityFeePerGas = overrides.maxPriorityFeePerGas;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (!step.tx.gasLimit) {
|
|
1069
|
+
try {
|
|
1070
|
+
const est = await client.l1.estimateGas(step.tx);
|
|
1071
|
+
step.tx.gasLimit = BigInt(est) * 115n / 100n;
|
|
1072
|
+
} catch {
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
let hash;
|
|
1076
|
+
try {
|
|
1077
|
+
const sent = await managed.sendTransaction(step.tx);
|
|
1078
|
+
hash = sent.hash;
|
|
1079
|
+
stepHashes[step.key] = hash;
|
|
1080
|
+
const rcpt = await sent.wait();
|
|
1081
|
+
if (rcpt?.status === 0) {
|
|
1082
|
+
throw createError("EXECUTION", {
|
|
1083
|
+
resource: "deposits",
|
|
1084
|
+
operation: "deposits.create.sendTransaction",
|
|
1085
|
+
message: "Deposit transaction reverted on L1 during a step.",
|
|
1086
|
+
context: { step: step.key, txHash: hash }
|
|
1087
|
+
});
|
|
1088
|
+
}
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
if (isZKsyncError(e)) throw e;
|
|
1091
|
+
throw toZKsyncError(
|
|
1092
|
+
"EXECUTION",
|
|
1093
|
+
{
|
|
1094
|
+
resource: "deposits",
|
|
1095
|
+
operation: "deposits.create.sendTransaction",
|
|
1096
|
+
context: { step: step.key, txHash: hash, nonce: Number(step.tx.nonce ?? -1) },
|
|
1097
|
+
message: "Failed to send or confirm a deposit transaction step."
|
|
1098
|
+
},
|
|
1099
|
+
e
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const ordered = Object.entries(stepHashes);
|
|
1104
|
+
const last = ordered[ordered.length - 1][1];
|
|
1105
|
+
return { kind: "deposit", l1TxHash: last, stepHashes, plan };
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
message: "Internal error while creating a deposit.",
|
|
1109
|
+
ctx: { token: p.token, amount: p.amount, to: p.to, where: "deposits.create" }
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
const tryCreate = (p) => toResult(OP_DEPOSITS.tryCreate, () => create(p), {
|
|
1113
|
+
message: "Internal error while creating a deposit.",
|
|
1114
|
+
ctx: { token: p.token, amount: p.amount, to: p.to, where: "deposits.tryCreate" }
|
|
1115
|
+
});
|
|
1116
|
+
const status = (h) => wrap(
|
|
1117
|
+
OP_DEPOSITS.status,
|
|
1118
|
+
async () => {
|
|
1119
|
+
const l1TxHash = typeof h === "string" ? h : h.l1TxHash;
|
|
1120
|
+
if (!l1TxHash) {
|
|
1121
|
+
return { phase: "UNKNOWN", l1TxHash: "0x" };
|
|
1122
|
+
}
|
|
1123
|
+
let l1Rcpt;
|
|
1124
|
+
try {
|
|
1125
|
+
l1Rcpt = await client.l1.getTransactionReceipt(l1TxHash);
|
|
1126
|
+
} catch (e) {
|
|
1127
|
+
throw toZKsyncError(
|
|
1128
|
+
"RPC",
|
|
1129
|
+
{
|
|
1130
|
+
resource: "deposits",
|
|
1131
|
+
operation: "deposits.status.getTransactionReceipt",
|
|
1132
|
+
context: { where: "l1.getTransactionReceipt", l1TxHash },
|
|
1133
|
+
message: "Failed to fetch L1 transaction receipt."
|
|
1134
|
+
},
|
|
1135
|
+
e
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
if (!l1Rcpt) return { phase: "L1_PENDING", l1TxHash };
|
|
1139
|
+
let l2TxHash;
|
|
1140
|
+
try {
|
|
1141
|
+
l2TxHash = extractL2TxHashFromL1Logs(l1Rcpt.logs) ?? void 0;
|
|
1142
|
+
} catch (e) {
|
|
1143
|
+
throw toZKsyncError(
|
|
1144
|
+
"INTERNAL",
|
|
1145
|
+
{
|
|
1146
|
+
resource: "deposits",
|
|
1147
|
+
operation: "deposits.status.extractL2TxHashFromL1Logs",
|
|
1148
|
+
context: { where: "extractL2TxHashFromL1Logs", l1TxHash },
|
|
1149
|
+
message: "Failed to derive L2 transaction hash from L1 logs."
|
|
1150
|
+
},
|
|
1151
|
+
e
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
if (!l2TxHash) return { phase: "L1_INCLUDED", l1TxHash };
|
|
1155
|
+
let l2Rcpt;
|
|
1156
|
+
try {
|
|
1157
|
+
l2Rcpt = await client.l2.getTransactionReceipt(l2TxHash);
|
|
1158
|
+
} catch (e) {
|
|
1159
|
+
if (isReceiptNotFound(e)) {
|
|
1160
|
+
return { phase: "L2_PENDING", l1TxHash, l2TxHash };
|
|
1161
|
+
}
|
|
1162
|
+
throw toZKsyncError(
|
|
1163
|
+
"RPC",
|
|
1164
|
+
{
|
|
1165
|
+
resource: "deposits",
|
|
1166
|
+
operation: "deposits.status.getTransactionReceipt",
|
|
1167
|
+
message: "Failed to fetch L2 transaction receipt.",
|
|
1168
|
+
context: { l2TxHash, where: "l2.getTransactionReceipt" }
|
|
1169
|
+
},
|
|
1170
|
+
e
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
if (!l2Rcpt) return { phase: "L2_PENDING", l1TxHash, l2TxHash };
|
|
1174
|
+
const ok = l2Rcpt.status === 1;
|
|
1175
|
+
return ok ? { phase: "L2_EXECUTED", l1TxHash, l2TxHash } : { phase: "L2_FAILED", l1TxHash, l2TxHash };
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
message: "Internal error while checking deposit status.",
|
|
1179
|
+
ctx: { input: h, where: "deposits.status" }
|
|
1180
|
+
}
|
|
1181
|
+
);
|
|
1182
|
+
const wait = (h, opts) => wrap(
|
|
1183
|
+
OP_DEPOSITS.wait,
|
|
1184
|
+
async () => {
|
|
1185
|
+
const l1Hash = typeof h === "string" ? h : "l1TxHash" in h ? h.l1TxHash : void 0;
|
|
1186
|
+
if (!l1Hash) return null;
|
|
1187
|
+
let l1Receipt;
|
|
1188
|
+
try {
|
|
1189
|
+
l1Receipt = await client.l1.waitForTransaction(l1Hash);
|
|
1190
|
+
} catch (e) {
|
|
1191
|
+
throw toZKsyncError(
|
|
1192
|
+
"RPC",
|
|
1193
|
+
{
|
|
1194
|
+
resource: "deposits",
|
|
1195
|
+
operation: "deposits.waitForTransaction",
|
|
1196
|
+
context: { where: "l1.waitForTransaction", l1TxHash: l1Hash, for: opts.for },
|
|
1197
|
+
message: "Failed while waiting for L1 transaction."
|
|
1198
|
+
},
|
|
1199
|
+
e
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
if (!l1Receipt) return null;
|
|
1203
|
+
if (opts.for === "l1") return l1Receipt;
|
|
1204
|
+
try {
|
|
1205
|
+
const { l2Receipt } = await waitForL2ExecutionFromL1Tx(client.l1, client.l2, l1Hash);
|
|
1206
|
+
return l2Receipt ?? null;
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
if (isZKsyncError(e)) throw e;
|
|
1209
|
+
throw toZKsyncError(
|
|
1210
|
+
"INTERNAL",
|
|
1211
|
+
{
|
|
1212
|
+
resource: "deposits",
|
|
1213
|
+
operation: "deposits.waitForL2ExecutionFromL1Tx",
|
|
1214
|
+
context: { where: "waitForL2ExecutionFromL1Tx", l1TxHash: l1Hash },
|
|
1215
|
+
message: "Internal error while waiting for L2 execution."
|
|
1216
|
+
},
|
|
1217
|
+
e
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
message: "Internal error while waiting for deposit.",
|
|
1223
|
+
ctx: { input: h, for: opts?.for, where: "deposits.wait" }
|
|
1224
|
+
}
|
|
1225
|
+
);
|
|
1226
|
+
const tryWait = (h, opts) => toResult(
|
|
1227
|
+
OP_DEPOSITS.tryWait,
|
|
1228
|
+
async () => {
|
|
1229
|
+
const v = await wait(h, opts);
|
|
1230
|
+
if (v) return v;
|
|
1231
|
+
throw createError("STATE", {
|
|
1232
|
+
resource: "deposits",
|
|
1233
|
+
operation: "deposits.tryWait",
|
|
1234
|
+
message: opts.for === "l2" ? "No L2 receipt yet; the deposit has not executed on L2." : "No L1 receipt yet; the deposit has not been included on L1.",
|
|
1235
|
+
context: {
|
|
1236
|
+
for: opts.for,
|
|
1237
|
+
l1TxHash: typeof h === "string" ? h : "l1TxHash" in h ? h.l1TxHash : void 0,
|
|
1238
|
+
where: "deposits.tryWait"
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
message: "Internal error while waiting for deposit.",
|
|
1244
|
+
ctx: { input: h, for: opts?.for, where: "deposits.tryWait" }
|
|
1245
|
+
}
|
|
1246
|
+
);
|
|
1247
|
+
return { quote, tryQuote, prepare, tryPrepare, create, tryCreate, status, wait, tryWait };
|
|
1248
|
+
}
|
|
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
|
+
|
|
1266
|
+
// src/adapters/ethers/resources/withdrawals/context.ts
|
|
1267
|
+
async function commonCtx2(p, client) {
|
|
1268
|
+
const sender = await client.signer.getAddress();
|
|
1269
|
+
const {
|
|
1270
|
+
bridgehub,
|
|
1271
|
+
l1AssetRouter,
|
|
1272
|
+
l1Nullifier,
|
|
1273
|
+
l2AssetRouter,
|
|
1274
|
+
l2NativeTokenVault,
|
|
1275
|
+
l2BaseTokenSystem
|
|
1276
|
+
} = await client.ensureAddresses();
|
|
1277
|
+
const { chainId } = await client.l2.getNetwork();
|
|
1278
|
+
const chainIdL2 = BigInt(chainId);
|
|
1279
|
+
const baseIsEth = await isEthBasedChain(client.l2, l2NativeTokenVault);
|
|
1280
|
+
const fee = await getL2FeeOverrides(client, p.l2TxOverrides);
|
|
1281
|
+
const route = pickWithdrawRoute({ token: p.token, baseIsEth });
|
|
1282
|
+
const l2GasLimit = p.l2GasLimit ?? 300000n;
|
|
1283
|
+
const gasBufferPct = 15;
|
|
1284
|
+
return {
|
|
1285
|
+
client,
|
|
1286
|
+
bridgehub,
|
|
1287
|
+
chainIdL2,
|
|
1288
|
+
sender,
|
|
1289
|
+
route,
|
|
1290
|
+
l1AssetRouter,
|
|
1291
|
+
l1Nullifier,
|
|
1292
|
+
l2AssetRouter,
|
|
1293
|
+
l2NativeTokenVault,
|
|
1294
|
+
l2BaseTokenSystem,
|
|
1295
|
+
baseIsEth,
|
|
1296
|
+
l2GasLimit,
|
|
1297
|
+
gasBufferPct,
|
|
1298
|
+
fee
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
var { wrapAs: wrapAs5 } = createErrorHandlers("withdrawals");
|
|
1302
|
+
function routeEthBase() {
|
|
1303
|
+
return {
|
|
1304
|
+
async build(p, ctx) {
|
|
1305
|
+
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(
|
|
1314
|
+
"INTERNAL",
|
|
1315
|
+
OP_WITHDRAWALS.eth.encodeWithdraw,
|
|
1316
|
+
() => Promise.resolve(base.interface.encodeFunctionData("withdraw", [toL1])),
|
|
1317
|
+
{
|
|
1318
|
+
ctx: { where: "L2BaseToken.withdraw", to: toL1 },
|
|
1319
|
+
message: "Failed to encode ETH withdraw calldata."
|
|
1320
|
+
}
|
|
1321
|
+
);
|
|
1322
|
+
const tx = {
|
|
1323
|
+
to: L2_BASE_TOKEN_ADDRESS,
|
|
1324
|
+
data,
|
|
1325
|
+
from: ctx.sender,
|
|
1326
|
+
value: p.amount,
|
|
1327
|
+
maxFeePerGas,
|
|
1328
|
+
maxPriorityFeePerGas
|
|
1329
|
+
};
|
|
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
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
steps.push({
|
|
1348
|
+
key: "l2-base-token:withdraw",
|
|
1349
|
+
kind: "l2-base-token:withdraw",
|
|
1350
|
+
description: "Withdraw ETH via L2 Base Token System",
|
|
1351
|
+
tx
|
|
1352
|
+
});
|
|
1353
|
+
return { steps, approvals: [], quoteExtras: {} };
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
var { wrapAs: wrapAs6 } = createErrorHandlers("withdrawals");
|
|
1358
|
+
var SIG = {
|
|
1359
|
+
withdraw: "withdraw(bytes32,bytes)"
|
|
1360
|
+
};
|
|
1361
|
+
function routeErc20NonBase2() {
|
|
1362
|
+
return {
|
|
1363
|
+
async build(p, ctx) {
|
|
1364
|
+
const steps = [];
|
|
1365
|
+
const approvals = [];
|
|
1366
|
+
const { gasLimit: overrideGasLimit, maxFeePerGas, maxPriorityFeePerGas } = ctx.fee;
|
|
1367
|
+
const txOverrides = overrideGasLimit != null ? { maxFeePerGas, maxPriorityFeePerGas, gasLimit: overrideGasLimit } : { maxFeePerGas, maxPriorityFeePerGas };
|
|
1368
|
+
const erc20 = new Contract(p.token, IERC20_default, ctx.client.getL2Signer());
|
|
1369
|
+
const current = await wrapAs6(
|
|
1370
|
+
"CONTRACT",
|
|
1371
|
+
OP_WITHDRAWALS.erc20.allowance,
|
|
1372
|
+
() => erc20.allowance(ctx.sender, ctx.l2NativeTokenVault),
|
|
1373
|
+
{
|
|
1374
|
+
ctx: {
|
|
1375
|
+
where: "erc20.allowance",
|
|
1376
|
+
chain: "L2",
|
|
1377
|
+
token: p.token,
|
|
1378
|
+
spender: ctx.l2NativeTokenVault
|
|
1379
|
+
},
|
|
1380
|
+
message: "Failed to read L2 ERC-20 allowance."
|
|
1381
|
+
}
|
|
1382
|
+
);
|
|
1383
|
+
if (current < p.amount) {
|
|
1384
|
+
approvals.push({ token: p.token, spender: ctx.l2NativeTokenVault, amount: p.amount });
|
|
1385
|
+
const data = erc20.interface.encodeFunctionData("approve", [
|
|
1386
|
+
ctx.l2NativeTokenVault,
|
|
1387
|
+
p.amount
|
|
1388
|
+
]);
|
|
1389
|
+
steps.push({
|
|
1390
|
+
key: `approve:l2:${p.token}:${ctx.l2NativeTokenVault}`,
|
|
1391
|
+
kind: "approve:l2",
|
|
1392
|
+
description: `Approve ${p.amount} to NativeTokenVault`,
|
|
1393
|
+
tx: { to: p.token, data, from: ctx.sender, ...txOverrides }
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
const ntv = new Contract(ctx.l2NativeTokenVault, L2NativeTokenVault_default, ctx.client.l2);
|
|
1397
|
+
const assetId = await wrapAs6(
|
|
1398
|
+
"CONTRACT",
|
|
1399
|
+
OP_WITHDRAWALS.erc20.ensureRegistered,
|
|
1400
|
+
() => ntv.getFunction("ensureTokenIsRegistered").staticCall(p.token),
|
|
1401
|
+
{
|
|
1402
|
+
ctx: { where: "L2NativeTokenVault.ensureTokenIsRegistered", token: p.token },
|
|
1403
|
+
message: "Failed to ensure token is registered in L2NativeTokenVault."
|
|
1404
|
+
}
|
|
1405
|
+
);
|
|
1406
|
+
const assetData = await wrapAs6(
|
|
1407
|
+
"INTERNAL",
|
|
1408
|
+
OP_WITHDRAWALS.erc20.encodeAssetData,
|
|
1409
|
+
() => Promise.resolve(
|
|
1410
|
+
AbiCoder.defaultAbiCoder().encode(
|
|
1411
|
+
["uint256", "address", "address"],
|
|
1412
|
+
[p.amount, p.to ?? ctx.sender, p.token]
|
|
1413
|
+
)
|
|
1414
|
+
),
|
|
1415
|
+
{
|
|
1416
|
+
ctx: { where: "AbiCoder.encode", token: p.token, to: p.to ?? ctx.sender },
|
|
1417
|
+
message: "Failed to encode burn/withdraw asset data."
|
|
1418
|
+
}
|
|
1419
|
+
);
|
|
1420
|
+
const l2ar = new Contract(ctx.l2AssetRouter, IL2AssetRouter_default, ctx.client.l2);
|
|
1421
|
+
const dataWithdraw = await wrapAs6(
|
|
1422
|
+
"INTERNAL",
|
|
1423
|
+
OP_WITHDRAWALS.erc20.encodeWithdraw,
|
|
1424
|
+
() => Promise.resolve(l2ar.interface.encodeFunctionData(SIG.withdraw, [assetId, assetData])),
|
|
1425
|
+
{
|
|
1426
|
+
ctx: { where: "L2AssetRouter.withdraw", assetId },
|
|
1427
|
+
message: "Failed to encode withdraw calldata."
|
|
1428
|
+
}
|
|
1429
|
+
);
|
|
1430
|
+
const withdrawTx = {
|
|
1431
|
+
to: ctx.l2AssetRouter,
|
|
1432
|
+
data: dataWithdraw,
|
|
1433
|
+
from: ctx.sender,
|
|
1434
|
+
...txOverrides
|
|
1435
|
+
};
|
|
1436
|
+
steps.push({
|
|
1437
|
+
key: "l2-asset-router:withdraw",
|
|
1438
|
+
kind: "l2-asset-router:withdraw",
|
|
1439
|
+
description: "Burn on L2 & send L2\u2192L1 message",
|
|
1440
|
+
tx: withdrawTx
|
|
1441
|
+
});
|
|
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
|
|
1506
|
+
});
|
|
1507
|
+
return { steps, approvals: [], quoteExtras: {} };
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
var { wrapAs: wrapAs8 } = createErrorHandlers("withdrawals");
|
|
1512
|
+
var IL1NullifierMini = [
|
|
1513
|
+
"function isWithdrawalFinalized(uint256,uint256,uint256) view returns (bool)"
|
|
1514
|
+
];
|
|
1515
|
+
function createFinalizationServices(client) {
|
|
1516
|
+
const { l1, l2, signer } = client;
|
|
1517
|
+
return {
|
|
1518
|
+
async fetchFinalizeDepositParams(l2TxHash) {
|
|
1519
|
+
const parsed = await wrapAs8(
|
|
1520
|
+
"RPC",
|
|
1521
|
+
OP_WITHDRAWALS.finalize.fetchParams.receipt,
|
|
1522
|
+
() => client.zks.getReceiptWithL2ToL1(l2TxHash),
|
|
1523
|
+
{
|
|
1524
|
+
ctx: { where: "getReceiptWithL2ToL1", l2TxHash },
|
|
1525
|
+
message: "Failed to fetch L2 receipt (with L2\u2192L1 logs)."
|
|
1526
|
+
}
|
|
1527
|
+
);
|
|
1528
|
+
if (!parsed) {
|
|
1529
|
+
throw createError("STATE", {
|
|
1530
|
+
resource: "withdrawals",
|
|
1531
|
+
operation: OP_WITHDRAWALS.finalize.fetchParams.receipt,
|
|
1532
|
+
message: "L2 receipt not found.",
|
|
1533
|
+
context: { l2TxHash }
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
const ev = await wrapAs8(
|
|
1537
|
+
"INTERNAL",
|
|
1538
|
+
OP_WITHDRAWALS.finalize.fetchParams.findMessage,
|
|
1539
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
1540
|
+
() => Promise.resolve(findL1MessageSentLog(parsed, { index: 0 })),
|
|
1541
|
+
{
|
|
1542
|
+
ctx: { l2TxHash, index: 0 },
|
|
1543
|
+
message: "Failed to locate L1MessageSent event in L2 receipt."
|
|
1544
|
+
}
|
|
1545
|
+
);
|
|
1546
|
+
const message = await wrapAs8(
|
|
1547
|
+
"INTERNAL",
|
|
1548
|
+
OP_WITHDRAWALS.finalize.fetchParams.decodeMessage,
|
|
1549
|
+
() => Promise.resolve(AbiCoder.defaultAbiCoder().decode(["bytes"], ev.data)[0]),
|
|
1550
|
+
{
|
|
1551
|
+
ctx: { where: "decode L1MessageSent", data: ev.data },
|
|
1552
|
+
message: "Failed to decode withdrawal message."
|
|
1553
|
+
}
|
|
1554
|
+
);
|
|
1555
|
+
const raw = await wrapAs8(
|
|
1556
|
+
"RPC",
|
|
1557
|
+
OP_WITHDRAWALS.finalize.fetchParams.rawReceipt,
|
|
1558
|
+
() => client.zks.getReceiptWithL2ToL1(l2TxHash),
|
|
1559
|
+
{
|
|
1560
|
+
ctx: { where: "getReceiptWithL2ToL1 (raw)", l2TxHash },
|
|
1561
|
+
message: "Failed to fetch raw L2 receipt."
|
|
1562
|
+
}
|
|
1563
|
+
);
|
|
1564
|
+
if (!raw) {
|
|
1565
|
+
throw createError("STATE", {
|
|
1566
|
+
resource: "withdrawals",
|
|
1567
|
+
operation: OP_WITHDRAWALS.finalize.fetchParams.rawReceipt,
|
|
1568
|
+
message: "Raw L2 receipt not found.",
|
|
1569
|
+
context: { l2TxHash }
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
const idx = await wrapAs8(
|
|
1573
|
+
"INTERNAL",
|
|
1574
|
+
OP_WITHDRAWALS.finalize.fetchParams.messengerIndex,
|
|
1575
|
+
() => Promise.resolve(messengerLogIndex(raw, { index: 0, messenger: L1_MESSENGER_ADDRESS })),
|
|
1576
|
+
{
|
|
1577
|
+
ctx: { where: "derive messenger log index", l2TxHash, receipt: raw },
|
|
1578
|
+
message: "Failed to derive messenger log index."
|
|
1579
|
+
}
|
|
1580
|
+
);
|
|
1581
|
+
const proof = await wrapAs8(
|
|
1582
|
+
"RPC",
|
|
1583
|
+
OP_WITHDRAWALS.finalize.fetchParams.proof,
|
|
1584
|
+
() => client.zks.getL2ToL1LogProof(l2TxHash, idx),
|
|
1585
|
+
{
|
|
1586
|
+
ctx: { where: "get L2\u2192L1 log proof", l2TxHash, messengerLogIndex: idx },
|
|
1587
|
+
message: "Failed to fetch L2\u2192L1 log proof."
|
|
1588
|
+
}
|
|
1589
|
+
);
|
|
1590
|
+
const { chainId } = await wrapAs8(
|
|
1591
|
+
"RPC",
|
|
1592
|
+
OP_WITHDRAWALS.finalize.fetchParams.network,
|
|
1593
|
+
() => l2.getNetwork(),
|
|
1594
|
+
{
|
|
1595
|
+
ctx: { where: "l2.getNetwork" },
|
|
1596
|
+
message: "Failed to read L2 network."
|
|
1597
|
+
}
|
|
1598
|
+
);
|
|
1599
|
+
const txIndex = Number(parsed.transactionIndex ?? 0);
|
|
1600
|
+
const params = {
|
|
1601
|
+
chainId: BigInt(chainId),
|
|
1602
|
+
l2BatchNumber: proof.batchNumber,
|
|
1603
|
+
l2MessageIndex: proof.id,
|
|
1604
|
+
l2Sender: L2_ASSET_ROUTER_ADDRESS,
|
|
1605
|
+
l2TxNumberInBatch: txIndex,
|
|
1606
|
+
message,
|
|
1607
|
+
merkleProof: proof.proof
|
|
1608
|
+
};
|
|
1609
|
+
const { l1Nullifier } = await wrapAs8(
|
|
1610
|
+
"INTERNAL",
|
|
1611
|
+
OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
|
|
1612
|
+
() => client.ensureAddresses(),
|
|
1613
|
+
{
|
|
1614
|
+
ctx: { where: "ensureAddresses" },
|
|
1615
|
+
message: "Failed to ensure L1 Nullifier address."
|
|
1616
|
+
}
|
|
1617
|
+
);
|
|
1618
|
+
return { params, nullifier: l1Nullifier };
|
|
1619
|
+
},
|
|
1620
|
+
async simulateFinalizeReadiness(params) {
|
|
1621
|
+
const { l1Nullifier } = await wrapAs8(
|
|
1622
|
+
"INTERNAL",
|
|
1623
|
+
OP_WITHDRAWALS.finalize.readiness.ensureAddresses,
|
|
1624
|
+
() => client.ensureAddresses(),
|
|
1625
|
+
{
|
|
1626
|
+
ctx: { where: "ensureAddresses" },
|
|
1627
|
+
message: "Failed to ensure L1 Nullifier address."
|
|
1628
|
+
}
|
|
1629
|
+
);
|
|
1630
|
+
const done = await (async () => {
|
|
1631
|
+
try {
|
|
1632
|
+
const cMini = new Contract(l1Nullifier, IL1NullifierMini, l1);
|
|
1633
|
+
const isFinalized = await wrapAs8(
|
|
1634
|
+
"RPC",
|
|
1635
|
+
OP_WITHDRAWALS.finalize.readiness.isFinalized,
|
|
1636
|
+
() => cMini.isWithdrawalFinalized(
|
|
1637
|
+
params.chainId,
|
|
1638
|
+
params.l2BatchNumber,
|
|
1639
|
+
params.l2MessageIndex
|
|
1640
|
+
),
|
|
1641
|
+
{
|
|
1642
|
+
ctx: { where: "isWithdrawalFinalized", params },
|
|
1643
|
+
message: "Failed to read finalization status."
|
|
1644
|
+
}
|
|
1645
|
+
);
|
|
1646
|
+
return Boolean(isFinalized);
|
|
1647
|
+
} catch {
|
|
1648
|
+
return false;
|
|
1649
|
+
}
|
|
1650
|
+
})();
|
|
1651
|
+
if (done) return { kind: "FINALIZED" };
|
|
1652
|
+
const c = new Contract(l1Nullifier, IL1Nullifier_default, l1);
|
|
1653
|
+
try {
|
|
1654
|
+
await c.finalizeDeposit.staticCall(params);
|
|
1655
|
+
return { kind: "READY" };
|
|
1656
|
+
} catch (e) {
|
|
1657
|
+
return classifyReadinessFromRevert(e);
|
|
1658
|
+
}
|
|
1659
|
+
},
|
|
1660
|
+
async isWithdrawalFinalized(key) {
|
|
1661
|
+
const { l1Nullifier } = await wrapAs8(
|
|
1662
|
+
"INTERNAL",
|
|
1663
|
+
OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
|
|
1664
|
+
() => client.ensureAddresses(),
|
|
1665
|
+
{
|
|
1666
|
+
ctx: { where: "ensureAddresses" },
|
|
1667
|
+
message: "Failed to ensure L1 Nullifier address."
|
|
1668
|
+
}
|
|
1669
|
+
);
|
|
1670
|
+
const c = new Contract(l1Nullifier, IL1NullifierMini, l1);
|
|
1671
|
+
return await wrapAs8(
|
|
1672
|
+
"RPC",
|
|
1673
|
+
OP_WITHDRAWALS.finalize.isFinalized,
|
|
1674
|
+
() => c.isWithdrawalFinalized(key.chainIdL2, key.l2BatchNumber, key.l2MessageIndex),
|
|
1675
|
+
{
|
|
1676
|
+
ctx: { where: "isWithdrawalFinalized", key },
|
|
1677
|
+
message: "Failed to read finalization status."
|
|
1678
|
+
}
|
|
1679
|
+
);
|
|
1680
|
+
},
|
|
1681
|
+
async estimateFinalization(params) {
|
|
1682
|
+
const { l1Nullifier } = await wrapAs8(
|
|
1683
|
+
"INTERNAL",
|
|
1684
|
+
OP_WITHDRAWALS.finalize.estimate,
|
|
1685
|
+
() => client.ensureAddresses(),
|
|
1686
|
+
{
|
|
1687
|
+
ctx: { where: "ensureAddresses" },
|
|
1688
|
+
message: "Failed to ensure L1 Nullifier address."
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
const signer2 = client.getL1Signer();
|
|
1692
|
+
const c = new Contract(l1Nullifier, IL1Nullifier_default, signer2);
|
|
1693
|
+
const gasLimit = await wrapAs8(
|
|
1694
|
+
"RPC",
|
|
1695
|
+
OP_WITHDRAWALS.finalize.estimate,
|
|
1696
|
+
() => c.finalizeDeposit.estimateGas(params),
|
|
1697
|
+
{
|
|
1698
|
+
ctx: {
|
|
1699
|
+
where: "estimateGas(finalizeDeposit)",
|
|
1700
|
+
chainIdL2: params.chainId,
|
|
1701
|
+
l2BatchNumber: params.l2BatchNumber,
|
|
1702
|
+
l2MessageIndex: params.l2MessageIndex,
|
|
1703
|
+
l1Nullifier
|
|
1704
|
+
},
|
|
1705
|
+
message: "Failed to estimate gas for finalizeDeposit."
|
|
1706
|
+
}
|
|
1707
|
+
);
|
|
1708
|
+
const feeData = await wrapAs8("RPC", OP_WITHDRAWALS.finalize.estimate, () => l1.getFeeData(), {
|
|
1709
|
+
ctx: { where: "l1.getFeeData" },
|
|
1710
|
+
message: "Failed to estimate fee data for finalizeDeposit."
|
|
1711
|
+
});
|
|
1712
|
+
const maxFeePerGas = feeData.maxFeePerGas ?? feeData.gasPrice ?? // legacy-style gas price if present
|
|
1713
|
+
(() => {
|
|
1714
|
+
throw createError("RPC", {
|
|
1715
|
+
resource: "withdrawals",
|
|
1716
|
+
operation: OP_WITHDRAWALS.finalize.estimate,
|
|
1717
|
+
message: "Provider did not return gas price or EIP-1559 fields.",
|
|
1718
|
+
context: { feeData }
|
|
1719
|
+
});
|
|
1720
|
+
})();
|
|
1721
|
+
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ?? 0n;
|
|
1722
|
+
return {
|
|
1723
|
+
gasLimit,
|
|
1724
|
+
maxFeePerGas,
|
|
1725
|
+
maxPriorityFeePerGas
|
|
1726
|
+
};
|
|
1727
|
+
},
|
|
1728
|
+
async finalizeDeposit(params) {
|
|
1729
|
+
const { l1Nullifier } = await wrapAs8(
|
|
1730
|
+
"INTERNAL",
|
|
1731
|
+
OP_WITHDRAWALS.finalize.fetchParams.ensureAddresses,
|
|
1732
|
+
() => client.ensureAddresses(),
|
|
1733
|
+
{
|
|
1734
|
+
ctx: { where: "ensureAddresses" },
|
|
1735
|
+
message: "Failed to ensure L1 Nullifier address."
|
|
1736
|
+
}
|
|
1737
|
+
);
|
|
1738
|
+
const c = new Contract(l1Nullifier, IL1Nullifier_default, signer);
|
|
1739
|
+
try {
|
|
1740
|
+
const receipt = await c.finalizeDeposit(params);
|
|
1741
|
+
const hash = receipt.hash;
|
|
1742
|
+
return {
|
|
1743
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
1744
|
+
hash,
|
|
1745
|
+
wait: async () => {
|
|
1746
|
+
try {
|
|
1747
|
+
return await receipt.wait();
|
|
1748
|
+
} catch (e) {
|
|
1749
|
+
throw toZKsyncError(
|
|
1750
|
+
"EXECUTION",
|
|
1751
|
+
{
|
|
1752
|
+
resource: "withdrawals",
|
|
1753
|
+
operation: OP_WITHDRAWALS.finalize.wait,
|
|
1754
|
+
message: "Failed while waiting for finalizeDeposit transaction.",
|
|
1755
|
+
context: { txHash: hash }
|
|
1756
|
+
},
|
|
1757
|
+
e
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
};
|
|
1762
|
+
} catch (e) {
|
|
1763
|
+
throw toZKsyncError(
|
|
1764
|
+
"EXECUTION",
|
|
1765
|
+
{
|
|
1766
|
+
resource: "withdrawals",
|
|
1767
|
+
operation: OP_WITHDRAWALS.finalize.send,
|
|
1768
|
+
message: "Failed to send finalizeDeposit transaction.",
|
|
1769
|
+
context: {
|
|
1770
|
+
chainIdL2: params.chainId,
|
|
1771
|
+
l2BatchNumber: params.l2BatchNumber,
|
|
1772
|
+
l2MessageIndex: params.l2MessageIndex,
|
|
1773
|
+
l1Nullifier
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1776
|
+
e
|
|
1777
|
+
);
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
// src/adapters/ethers/resources/withdrawals/index.ts
|
|
1784
|
+
var ROUTES2 = {
|
|
1785
|
+
"eth-base": routeEthBase(),
|
|
1786
|
+
// BaseTokenSystem.withdraw, chain base = ETH
|
|
1787
|
+
"eth-nonbase": routeEthNonBase2(),
|
|
1788
|
+
// BaseTokenSystem.withdraw, chain base ≠ ETH
|
|
1789
|
+
"erc20-nonbase": routeErc20NonBase2()
|
|
1790
|
+
// AssetRouter.withdraw for non-base ERC-20s
|
|
1791
|
+
};
|
|
1792
|
+
function createWithdrawalsResource(client) {
|
|
1793
|
+
const svc = createFinalizationServices(client);
|
|
1794
|
+
const { wrap: wrap2, toResult: toResult2 } = createErrorHandlers("withdrawals");
|
|
1795
|
+
async function buildPlan(p) {
|
|
1796
|
+
const ctx = await commonCtx2(p, client);
|
|
1797
|
+
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 = {
|
|
1814
|
+
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
|
+
}
|
|
1822
|
+
};
|
|
1823
|
+
return { route: ctx.route, summary, steps };
|
|
1824
|
+
}
|
|
1825
|
+
const finalizeCache = /* @__PURE__ */ new Map();
|
|
1826
|
+
const quote = (p) => wrap2(
|
|
1827
|
+
OP_WITHDRAWALS.quote,
|
|
1828
|
+
async () => {
|
|
1829
|
+
const plan = await buildPlan(p);
|
|
1830
|
+
return plan.summary;
|
|
1831
|
+
},
|
|
1832
|
+
{
|
|
1833
|
+
message: "Internal error while preparing a withdrawal quote.",
|
|
1834
|
+
ctx: { token: p.token, where: "withdrawals.quote" }
|
|
1835
|
+
}
|
|
1836
|
+
);
|
|
1837
|
+
const tryQuote = (p) => toResult2(
|
|
1838
|
+
OP_WITHDRAWALS.tryQuote,
|
|
1839
|
+
async () => {
|
|
1840
|
+
const plan = await buildPlan(p);
|
|
1841
|
+
return plan.summary;
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
message: "Internal error while preparing a withdrawal quote.",
|
|
1845
|
+
ctx: { token: p.token, where: "withdrawals.tryQuote" }
|
|
1846
|
+
}
|
|
1847
|
+
);
|
|
1848
|
+
const prepare = (p) => wrap2(OP_WITHDRAWALS.prepare, () => buildPlan(p), {
|
|
1849
|
+
message: "Internal error while preparing a withdrawal plan.",
|
|
1850
|
+
ctx: { token: p.token, where: "withdrawals.prepare" }
|
|
1851
|
+
});
|
|
1852
|
+
const tryPrepare = (p) => toResult2(OP_WITHDRAWALS.tryPrepare, () => buildPlan(p), {
|
|
1853
|
+
message: "Internal error while preparing a withdrawal plan.",
|
|
1854
|
+
ctx: { token: p.token, where: "withdrawals.tryPrepare" }
|
|
1855
|
+
});
|
|
1856
|
+
const create = (p) => wrap2(
|
|
1857
|
+
OP_WITHDRAWALS.create,
|
|
1858
|
+
async () => {
|
|
1859
|
+
const plan = await prepare(p);
|
|
1860
|
+
const stepHashes = {};
|
|
1861
|
+
const managed = new NonceManager(client.getL2Signer());
|
|
1862
|
+
const from = await managed.getAddress();
|
|
1863
|
+
let next = await client.l2.getTransactionCount(from, "pending");
|
|
1864
|
+
for (const step of plan.steps) {
|
|
1865
|
+
step.tx.nonce = next++;
|
|
1866
|
+
if (p.l2TxOverrides) {
|
|
1867
|
+
const overrides = p.l2TxOverrides;
|
|
1868
|
+
if (overrides.gasLimit != null) step.tx.gasLimit = overrides.gasLimit;
|
|
1869
|
+
if (overrides.maxFeePerGas != null) step.tx.maxFeePerGas = overrides.maxFeePerGas;
|
|
1870
|
+
if (overrides.maxPriorityFeePerGas != null) {
|
|
1871
|
+
step.tx.maxPriorityFeePerGas = overrides.maxPriorityFeePerGas;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
if (!step.tx.gasLimit) {
|
|
1875
|
+
try {
|
|
1876
|
+
const est = await client.l2.estimateGas(step.tx);
|
|
1877
|
+
step.tx.gasLimit = BigInt(est) * 115n / 100n;
|
|
1878
|
+
} catch {
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
let hash;
|
|
1882
|
+
try {
|
|
1883
|
+
const sent = await managed.sendTransaction(step.tx);
|
|
1884
|
+
hash = sent.hash;
|
|
1885
|
+
stepHashes[step.key] = hash;
|
|
1886
|
+
const rcpt = await sent.wait();
|
|
1887
|
+
if (rcpt?.status === 0) {
|
|
1888
|
+
throw createError("EXECUTION", {
|
|
1889
|
+
resource: "withdrawals",
|
|
1890
|
+
operation: "withdrawals.create.sendTransaction",
|
|
1891
|
+
message: "Withdrawal transaction reverted on L2 during a step.",
|
|
1892
|
+
context: { step: step.key, txHash: hash, nonce: Number(step.tx.nonce ?? -1) }
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
} catch (e) {
|
|
1896
|
+
throw toZKsyncError(
|
|
1897
|
+
"EXECUTION",
|
|
1898
|
+
{
|
|
1899
|
+
resource: "withdrawals",
|
|
1900
|
+
operation: "withdrawals.create.sendTransaction",
|
|
1901
|
+
message: "Failed to send or confirm a withdrawal transaction step.",
|
|
1902
|
+
context: { step: step.key, txHash: hash, nonce: Number(step.tx.nonce ?? -1) }
|
|
1903
|
+
},
|
|
1904
|
+
e
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
const keys = Object.keys(stepHashes);
|
|
1909
|
+
const l2TxHash = stepHashes[keys[keys.length - 1]];
|
|
1910
|
+
return { kind: "withdrawal", l2TxHash, stepHashes, plan };
|
|
1911
|
+
},
|
|
1912
|
+
{
|
|
1913
|
+
message: "Internal error while creating withdrawal transactions.",
|
|
1914
|
+
ctx: { token: p.token, amount: p.amount, to: p.to, where: "withdrawals.create" }
|
|
1915
|
+
}
|
|
1916
|
+
);
|
|
1917
|
+
const tryCreate = (p) => toResult2(OP_WITHDRAWALS.tryCreate, () => create(p), {
|
|
1918
|
+
message: "Internal error while creating withdrawal transactions.",
|
|
1919
|
+
ctx: { token: p.token, amount: p.amount, to: p.to, where: "withdrawals.tryCreate" }
|
|
1920
|
+
});
|
|
1921
|
+
const status = (h) => wrap2(
|
|
1922
|
+
OP_WITHDRAWALS.status,
|
|
1923
|
+
async () => {
|
|
1924
|
+
const l2TxHash = typeof h === "string" ? h : "l2TxHash" in h && h.l2TxHash ? h.l2TxHash : "0x";
|
|
1925
|
+
if (!l2TxHash || l2TxHash === "0x") {
|
|
1926
|
+
return { phase: "UNKNOWN", l2TxHash: "0x" };
|
|
1927
|
+
}
|
|
1928
|
+
let l2Rcpt;
|
|
1929
|
+
try {
|
|
1930
|
+
l2Rcpt = await client.l2.getTransactionReceipt(l2TxHash);
|
|
1931
|
+
} catch (e) {
|
|
1932
|
+
if (isReceiptNotFound(e)) {
|
|
1933
|
+
return { phase: "L2_PENDING", l2TxHash };
|
|
1934
|
+
}
|
|
1935
|
+
throw toZKsyncError(
|
|
1936
|
+
"RPC",
|
|
1937
|
+
{
|
|
1938
|
+
resource: "withdrawals",
|
|
1939
|
+
operation: "withdrawals.status.getTransactionReceipt",
|
|
1940
|
+
message: "Failed to fetch L2 transaction receipt.",
|
|
1941
|
+
context: { l2TxHash, where: "l2.getTransactionReceipt" }
|
|
1942
|
+
},
|
|
1943
|
+
e
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
if (!l2Rcpt) return { phase: "L2_PENDING", l2TxHash };
|
|
1947
|
+
let pack;
|
|
1948
|
+
try {
|
|
1949
|
+
pack = await svc.fetchFinalizeDepositParams(l2TxHash);
|
|
1950
|
+
} catch {
|
|
1951
|
+
return { phase: "PENDING", l2TxHash };
|
|
1952
|
+
}
|
|
1953
|
+
const key = {
|
|
1954
|
+
chainIdL2: pack.params.chainId,
|
|
1955
|
+
l2BatchNumber: pack.params.l2BatchNumber,
|
|
1956
|
+
l2MessageIndex: pack.params.l2MessageIndex
|
|
1957
|
+
};
|
|
1958
|
+
try {
|
|
1959
|
+
const done = await svc.isWithdrawalFinalized(key);
|
|
1960
|
+
if (done) return { phase: "FINALIZED", l2TxHash, key };
|
|
1961
|
+
} catch {
|
|
1962
|
+
}
|
|
1963
|
+
const readiness = await svc.simulateFinalizeReadiness(pack.params);
|
|
1964
|
+
if (readiness.kind === "FINALIZED") return { phase: "FINALIZED", l2TxHash, key };
|
|
1965
|
+
if (readiness.kind === "READY") return { phase: "READY_TO_FINALIZE", l2TxHash, key };
|
|
1966
|
+
return { phase: "PENDING", l2TxHash, key };
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
message: "Internal error while checking withdrawal status.",
|
|
1970
|
+
ctx: { where: "withdrawals.status", l2TxHash: typeof h === "string" ? h : h.l2TxHash }
|
|
1971
|
+
}
|
|
1972
|
+
);
|
|
1973
|
+
const wait = (h, opts = {
|
|
1974
|
+
for: "l2",
|
|
1975
|
+
pollMs: 5500
|
|
1976
|
+
}) => wrap2(
|
|
1977
|
+
OP_WITHDRAWALS.wait,
|
|
1978
|
+
async () => {
|
|
1979
|
+
const l2Hash = typeof h === "string" ? h : "l2TxHash" in h && h.l2TxHash ? h.l2TxHash : "0x";
|
|
1980
|
+
if (!l2Hash || l2Hash === "0x") return null;
|
|
1981
|
+
if (opts.for === "l2") {
|
|
1982
|
+
let rcpt;
|
|
1983
|
+
try {
|
|
1984
|
+
rcpt = await client.l2.waitForTransaction(l2Hash);
|
|
1985
|
+
} catch (e) {
|
|
1986
|
+
throw toZKsyncError(
|
|
1987
|
+
"RPC",
|
|
1988
|
+
{
|
|
1989
|
+
resource: "withdrawals",
|
|
1990
|
+
operation: "withdrawals.wait.l2.waitForTransaction",
|
|
1991
|
+
message: "Failed while waiting for L2 transaction.",
|
|
1992
|
+
context: { l2TxHash: l2Hash }
|
|
1993
|
+
},
|
|
1994
|
+
e
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
if (!rcpt) return null;
|
|
1998
|
+
try {
|
|
1999
|
+
const raw = await client.zks.getReceiptWithL2ToL1(l2Hash);
|
|
2000
|
+
rcpt.l2ToL1Logs = raw?.l2ToL1Logs ?? [];
|
|
2001
|
+
} catch {
|
|
2002
|
+
rcpt.l2ToL1Logs = rcpt.l2ToL1Logs ?? [];
|
|
2003
|
+
}
|
|
2004
|
+
return rcpt;
|
|
2005
|
+
}
|
|
2006
|
+
const poll = Math.max(1e3, opts.pollMs ?? 2500);
|
|
2007
|
+
const deadline = opts.timeoutMs ? Date.now() + opts.timeoutMs : void 0;
|
|
2008
|
+
while (true) {
|
|
2009
|
+
const s = await status(l2Hash);
|
|
2010
|
+
if (opts.for === "ready") {
|
|
2011
|
+
if (s.phase === "READY_TO_FINALIZE" || s.phase === "FINALIZED") return null;
|
|
2012
|
+
} else {
|
|
2013
|
+
if (s.phase === "FINALIZED") {
|
|
2014
|
+
const l1Hash = finalizeCache.get(l2Hash);
|
|
2015
|
+
if (l1Hash) {
|
|
2016
|
+
try {
|
|
2017
|
+
const l1Rcpt = await client.l1.getTransactionReceipt(l1Hash);
|
|
2018
|
+
if (l1Rcpt) {
|
|
2019
|
+
finalizeCache.delete(l2Hash);
|
|
2020
|
+
return l1Rcpt;
|
|
2021
|
+
}
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
return null;
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
if (deadline && Date.now() > deadline) return null;
|
|
2029
|
+
await new Promise((r) => setTimeout(r, poll));
|
|
2030
|
+
}
|
|
2031
|
+
},
|
|
2032
|
+
{
|
|
2033
|
+
message: "Internal error while waiting for withdrawal.",
|
|
2034
|
+
ctx: {
|
|
2035
|
+
where: "withdrawals.wait",
|
|
2036
|
+
l2TxHash: typeof h === "string" ? h : h.l2TxHash,
|
|
2037
|
+
for: opts.for
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
);
|
|
2041
|
+
const finalize = (l2TxHash) => wrap2(
|
|
2042
|
+
OP_WITHDRAWALS.finalize.send,
|
|
2043
|
+
async () => {
|
|
2044
|
+
const pack = await (async () => {
|
|
2045
|
+
try {
|
|
2046
|
+
return await svc.fetchFinalizeDepositParams(l2TxHash);
|
|
2047
|
+
} catch (e) {
|
|
2048
|
+
throw createError("STATE", {
|
|
2049
|
+
resource: "withdrawals",
|
|
2050
|
+
operation: OP_WITHDRAWALS.finalize.fetchParams.receipt,
|
|
2051
|
+
message: "Withdrawal not ready: finalize params unavailable.",
|
|
2052
|
+
context: { l2TxHash },
|
|
2053
|
+
cause: e
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
})();
|
|
2057
|
+
const { params } = pack;
|
|
2058
|
+
const key = {
|
|
2059
|
+
chainIdL2: params.chainId,
|
|
2060
|
+
l2BatchNumber: params.l2BatchNumber,
|
|
2061
|
+
l2MessageIndex: params.l2MessageIndex
|
|
2062
|
+
};
|
|
2063
|
+
try {
|
|
2064
|
+
const done = await svc.isWithdrawalFinalized(key);
|
|
2065
|
+
if (done) {
|
|
2066
|
+
const statusNow = await status(l2TxHash);
|
|
2067
|
+
return { status: statusNow };
|
|
2068
|
+
}
|
|
2069
|
+
} catch {
|
|
2070
|
+
}
|
|
2071
|
+
const readiness = await svc.simulateFinalizeReadiness(params);
|
|
2072
|
+
if (readiness.kind === "FINALIZED") {
|
|
2073
|
+
const statusNow = await status(l2TxHash);
|
|
2074
|
+
return { status: statusNow };
|
|
2075
|
+
}
|
|
2076
|
+
if (readiness.kind === "NOT_READY") {
|
|
2077
|
+
throw createError("STATE", {
|
|
2078
|
+
resource: "withdrawals",
|
|
2079
|
+
operation: OP_WITHDRAWALS.finalize.readiness.simulate,
|
|
2080
|
+
message: "Withdrawal not ready to finalize.",
|
|
2081
|
+
context: readiness
|
|
2082
|
+
});
|
|
2083
|
+
}
|
|
2084
|
+
try {
|
|
2085
|
+
const tx = await svc.finalizeDeposit(params);
|
|
2086
|
+
finalizeCache.set(l2TxHash, tx.hash);
|
|
2087
|
+
const rcpt = await tx.wait();
|
|
2088
|
+
const statusNow = await status(l2TxHash);
|
|
2089
|
+
return { status: statusNow, receipt: rcpt };
|
|
2090
|
+
} catch (e) {
|
|
2091
|
+
const statusNow = await status(l2TxHash);
|
|
2092
|
+
if (statusNow.phase === "FINALIZED") return { status: statusNow };
|
|
2093
|
+
try {
|
|
2094
|
+
const again = await svc.simulateFinalizeReadiness(params);
|
|
2095
|
+
if (again.kind === "NOT_READY") {
|
|
2096
|
+
throw createError("STATE", {
|
|
2097
|
+
resource: "withdrawals",
|
|
2098
|
+
operation: OP_WITHDRAWALS.finalize.readiness.simulate,
|
|
2099
|
+
message: "Withdrawal not ready to finalize.",
|
|
2100
|
+
context: again
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
} catch {
|
|
2104
|
+
}
|
|
2105
|
+
throw e;
|
|
2106
|
+
}
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
message: "Internal error while attempting to finalize withdrawal.",
|
|
2110
|
+
ctx: { l2TxHash, where: "withdrawals.finalize" }
|
|
2111
|
+
}
|
|
2112
|
+
);
|
|
2113
|
+
const tryFinalize = (l2TxHash) => toResult2("withdrawals.tryFinalize", () => finalize(l2TxHash), {
|
|
2114
|
+
message: "Internal error while attempting to tryFinalize withdrawal.",
|
|
2115
|
+
ctx: { l2TxHash, where: "withdrawals.tryFinalize" }
|
|
2116
|
+
});
|
|
2117
|
+
return {
|
|
2118
|
+
quote,
|
|
2119
|
+
tryQuote,
|
|
2120
|
+
prepare,
|
|
2121
|
+
tryPrepare,
|
|
2122
|
+
create,
|
|
2123
|
+
tryCreate,
|
|
2124
|
+
status,
|
|
2125
|
+
wait,
|
|
2126
|
+
finalize,
|
|
2127
|
+
tryFinalize
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
// src/adapters/ethers/sdk.ts
|
|
2132
|
+
function createEthersSdk(client) {
|
|
2133
|
+
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
|
+
}
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
|
|
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 };
|