Skip to content

Commit 6390052

Browse files
authored
Add fallback for gas fee estimation in zkSync (#8637)
1 parent 621f187 commit 6390052

File tree

3 files changed

+93
-20
lines changed

3 files changed

+93
-20
lines changed

.changeset/spotty-experts-rest.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
newer zkos chains no longer support zks\_ rpc endpoints, fallback to evm std

packages/thirdweb/src/transaction/actions/zksync/send-eip712-transaction.test.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import {
55
TEST_ACCOUNT_B,
66
} from "../../../../test/src/test-wallets.js";
77
import { zkSyncSepolia } from "../../../chains/chain-definitions/zksync-sepolia.js";
8+
import { defineChain } from "../../../chains/utils.js";
89
import { deployPublishedContract } from "../../../extensions/prebuilts/deploy-published.js";
910
import { prepareTransaction } from "../../prepare-transaction.js";
10-
import { sendEip712Transaction } from "./send-eip712-transaction.js";
11+
import {
12+
getZkGasFees,
13+
sendEip712Transaction,
14+
} from "./send-eip712-transaction.js";
1115

1216
describe("sendEip712Transaction", () => {
1317
// re-enable for testing, but disable for CI since it requires testnet funds
@@ -52,4 +56,34 @@ describe("sendEip712Transaction", () => {
5256
expect(address).toBeDefined();
5357
expect(address.length).toBe(42);
5458
});
59+
60+
it("should fallback to standard EVM methods when zks_estimateFee is not available", async () => {
61+
// Chain 278701 is a zkSync chain that doesn't support zks_estimateFee
62+
const zkSyncChainWithoutZksSupport = defineChain(278701);
63+
64+
// Use a transaction with pre-defined gas to skip estimation (which requires balance)
65+
// This tests that the fallback path is taken for fee estimation
66+
const transaction = prepareTransaction({
67+
chain: zkSyncChainWithoutZksSupport,
68+
client: TEST_CLIENT,
69+
gas: 21000n, // pre-define gas to skip eth_estimateGas
70+
to: TEST_ACCOUNT_B.address,
71+
value: 0n,
72+
});
73+
74+
const gasFees = await getZkGasFees({
75+
transaction,
76+
from: TEST_ACCOUNT_A.address as `0x${string}`,
77+
});
78+
79+
// Verify fallback worked - should have valid gas values
80+
expect(gasFees.gas).toBeDefined();
81+
expect(gasFees.gas).toBeGreaterThan(0n);
82+
expect(gasFees.maxFeePerGas).toBeDefined();
83+
expect(gasFees.maxFeePerGas).toBeGreaterThan(0n);
84+
expect(gasFees.maxPriorityFeePerGas).toBeDefined();
85+
expect(gasFees.maxPriorityFeePerGas).toBeGreaterThan(0n);
86+
// Fallback should use 100k for gasPerPubdata
87+
expect(gasFees.gasPerPubdata).toBe(100000n);
88+
});
5589
});

packages/thirdweb/src/transaction/actions/zksync/send-eip712-transaction.ts

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -180,25 +180,59 @@ export async function getZkGasFees(args: {
180180
) {
181181
const rpc = getRpcClient(transaction);
182182
const params = await formatTransaction({ from, transaction });
183-
const result = (await rpc({
184-
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
185-
method: "zks_estimateFee" as any,
186-
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
187-
params: [replaceBigInts(params, toHex)] as any,
188-
})) as {
189-
gas_limit: string;
190-
max_fee_per_gas: string;
191-
max_priority_fee_per_gas: string;
192-
gas_per_pubdata_limit: string;
193-
};
194-
gas = toBigInt(result.gas_limit) * 2n; // overestimating to avoid issues when not accounting for paymaster extra gas ( we should really pass the paymaster input above for better accuracy )
195-
const baseFee = toBigInt(result.max_fee_per_gas);
196-
maxFeePerGas = baseFee * 2n; // bumping the base fee per gas to ensure fast inclusion
197-
maxPriorityFeePerGas = toBigInt(result.max_priority_fee_per_gas) || 1n;
198-
gasPerPubdata = toBigInt(result.gas_per_pubdata_limit) * 2n; // doubling for fast inclusion;
199-
if (gasPerPubdata < 50000n) {
200-
// enforce a minimum gas per pubdata limit
201-
gasPerPubdata = 50000n;
183+
184+
// Try zkSync-specific fee estimation first, fallback to standard EVM methods
185+
try {
186+
const result = (await rpc({
187+
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
188+
method: "zks_estimateFee" as any,
189+
// biome-ignore lint/suspicious/noExplicitAny: TODO add to RPC method types
190+
params: [replaceBigInts(params, toHex)] as any,
191+
})) as {
192+
gas_limit: string;
193+
max_fee_per_gas: string;
194+
max_priority_fee_per_gas: string;
195+
gas_per_pubdata_limit: string;
196+
};
197+
gas = toBigInt(result.gas_limit) * 2n; // overestimating to avoid issues when not accounting for paymaster extra gas ( we should really pass the paymaster input above for better accuracy )
198+
const baseFee = toBigInt(result.max_fee_per_gas);
199+
maxFeePerGas = baseFee * 2n; // bumping the base fee per gas to ensure fast inclusion
200+
maxPriorityFeePerGas = toBigInt(result.max_priority_fee_per_gas) || 1n;
201+
gasPerPubdata = toBigInt(result.gas_per_pubdata_limit) * 2n; // doubling for fast inclusion;
202+
if (gasPerPubdata < 50000n) {
203+
// enforce a minimum gas per pubdata limit
204+
gasPerPubdata = 50000n;
205+
}
206+
} catch {
207+
// Fallback to standard EVM methods if zks_estimateFee is not available
208+
const [{ estimateGas }, { getDefaultGasOverrides }] = await Promise.all([
209+
import("../estimate-gas.js"),
210+
import("../../../gas/fee-data.js"),
211+
]);
212+
213+
const [estimatedGas, gasOverrides] = await Promise.all([
214+
gas === undefined
215+
? estimateGas({ transaction, from })
216+
: Promise.resolve(gas),
217+
getDefaultGasOverrides(transaction.client, transaction.chain),
218+
]);
219+
220+
gas = estimatedGas * 2n; // overestimating similar to zkSync estimation
221+
if ("maxFeePerGas" in gasOverrides && gasOverrides.maxFeePerGas) {
222+
maxFeePerGas = gasOverrides.maxFeePerGas * 2n; // bumping for fast inclusion
223+
} else if ("gasPrice" in gasOverrides && gasOverrides.gasPrice) {
224+
maxFeePerGas = gasOverrides.gasPrice * 2n;
225+
}
226+
if (
227+
"maxPriorityFeePerGas" in gasOverrides &&
228+
gasOverrides.maxPriorityFeePerGas
229+
) {
230+
maxPriorityFeePerGas = gasOverrides.maxPriorityFeePerGas;
231+
} else {
232+
maxPriorityFeePerGas = 1n;
233+
}
234+
// Use 100k as default gasPerPubdata for non-zkSync chains
235+
gasPerPubdata = 100000n;
202236
}
203237
}
204238
return {

0 commit comments

Comments
 (0)