Skip to content

Commit

Permalink
[SDK] Feature: Add support for forcing legacy transactions in chain c…
Browse files Browse the repository at this point in the history
…onfiguration (#6180)

Signed-off-by: Prithvish Baidya <[email protected]>
Co-authored-by: Joaquim Verges <[email protected]>
  • Loading branch information
d4mr and joaquim-verges authored Feb 27, 2025
1 parent 39cb2a2 commit 862db40
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 55 deletions.
6 changes: 5 additions & 1 deletion packages/thirdweb/src/adapters/ethers5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import type { ThirdwebClient } from "../client/client.js";
import { type ThirdwebContract, getContract } from "../contract/contract.js";
import { toSerializableTransaction } from "../transaction/actions/to-serializable-transaction.js";
import { waitForReceipt } from "../transaction/actions/wait-for-tx-receipt.js";
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
import {
type PreparedTransaction,
TransactionTypeMap,
} from "../transaction/prepare-transaction.js";
import type { SerializableTransaction } from "../transaction/serialize-transaction.js";
import { toHex } from "../utils/encoding/hex.js";
import type { Account } from "../wallets/interfaces/wallet.js";
Expand Down Expand Up @@ -483,6 +486,7 @@ export async function toEthersSigner(

const response: ethers5.ethers.providers.TransactionResponse = {
...serialized,
type: serialized.type ? TransactionTypeMap[serialized.type] : undefined,
nonce: Number(serialized.nonce ?? 0),
from: account.address,
maxFeePerGas: serialized.maxFeePerGas
Expand Down
3 changes: 3 additions & 0 deletions packages/thirdweb/src/chains/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { FeeType } from "../gas/fee-data.js";

/**
* @chain
*/
Expand Down Expand Up @@ -26,6 +28,7 @@ export type ChainOptions = {
increaseZeroByteCount?: boolean;
};
faucets?: Array<string>;
feeType?: FeeType;
};

/**
Expand Down
51 changes: 32 additions & 19 deletions packages/thirdweb/src/gas/fee-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export async function getGasOverridesForTransaction(
transaction: PreparedTransaction,
): Promise<FeeDataParams> {
// first check for explicit values
const [maxFeePerGas, maxPriorityFeePerGas, gasPrice] = await Promise.all([
resolvePromisedValue(transaction.maxFeePerGas),
resolvePromisedValue(transaction.maxPriorityFeePerGas),
resolvePromisedValue(transaction.gasPrice),
]);
const [maxFeePerGas, maxPriorityFeePerGas, gasPrice, type] =
await Promise.all([
resolvePromisedValue(transaction.maxFeePerGas),
resolvePromisedValue(transaction.maxPriorityFeePerGas),
resolvePromisedValue(transaction.gasPrice),
resolvePromisedValue(transaction.type),
]);

// Exit early if the user explicitly provided enough options
if (maxFeePerGas !== undefined && maxPriorityFeePerGas !== undefined) {
Expand All @@ -65,6 +67,7 @@ export async function getGasOverridesForTransaction(
maxPriorityFeePerGas,
};
}

if (typeof gasPrice === "bigint") {
return { gasPrice };
}
Expand All @@ -73,6 +76,7 @@ export async function getGasOverridesForTransaction(
const defaultGasOverrides = await getDefaultGasOverrides(
transaction.client,
transaction.chain,
type === "legacy" ? "legacy" : "eip1559", // 7702, 2930, and eip1559 all qualify as "eip1559" fee type
);

if (transaction.chain.experimental?.increaseZeroByteCount) {
Expand Down Expand Up @@ -103,6 +107,8 @@ export async function getGasOverridesForTransaction(
};
}

export type FeeType = "legacy" | "eip1559";

/**
* Retrieves the default gas overrides for a given client and chain ID.
* If the fee data contains both maxFeePerGas and maxPriorityFeePerGas, it returns an object with those values.
Expand All @@ -115,20 +121,27 @@ export async function getGasOverridesForTransaction(
export async function getDefaultGasOverrides(
client: ThirdwebClient,
chain: Chain,
feeType?: FeeType,
) {
// if chain is in the force gas price list, always use gas price
if (!FORCE_GAS_PRICE_CHAIN_IDS.includes(chain.id)) {
const feeData = await getDynamicFeeData(client, chain);
if (
feeData.maxFeePerGas !== null &&
feeData.maxPriorityFeePerGas !== null
) {
return {
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
};
}
// give priority to the transaction fee type over the chain fee type
const resolvedFeeType = feeType ?? chain.feeType;
// if chain is configured to force legacy transactions or is in the legacy chain list
if (
resolvedFeeType === "legacy" ||
FORCE_GAS_PRICE_CHAIN_IDS.includes(chain.id)
) {
return {
gasPrice: await getGasPrice({ client, chain, percentMultiplier: 10 }),
};
}
const feeData = await getDynamicFeeData(client, chain);
if (feeData.maxFeePerGas !== null && feeData.maxPriorityFeePerGas !== null) {
return {
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
};
}
// TODO: resolvedFeeType here could be "EIP1559", but we could not get EIP1559 fee data. should we throw?
return {
gasPrice: await getGasPrice({ client, chain, percentMultiplier: 10 }),
};
Expand Down Expand Up @@ -156,7 +169,7 @@ async function getDynamicFeeData(
eth_maxPriorityFeePerGas(rpcRequest).catch(() => null),
]);

const baseBlockFee = block?.baseFeePerGas ?? 0n;
const baseBlockFee = block?.baseFeePerGas;

const chainId = chain.id;
// flag chain testnet & flag chain
Expand All @@ -174,7 +187,7 @@ async function getDynamicFeeData(
maxPriorityFeePerGas_ = maxPriorityFeePerGas;
}

if (maxPriorityFeePerGas_ == null) {
if (maxPriorityFeePerGas_ == null || baseBlockFee == null) {
// chain does not support eip-1559, return null for both
return { maxFeePerGas: null, maxPriorityFeePerGas: null };
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,42 +75,52 @@ export async function toSerializableTransaction(
const rpcRequest = getRpcClient(options.transaction);
const chainId = options.transaction.chain.id;
const from = options.from;
let [data, nonce, gas, feeData, to, accessList, value, authorizationList] =
await Promise.all([
encode(options.transaction),
(async () => {
// if the user has specified a nonce, use that
const resolvedNonce = await resolvePromisedValue(
options.transaction.nonce,
);
if (resolvedNonce !== undefined) {
return resolvedNonce;
}
let [
data,
nonce,
gas,
feeData,
to,
accessList,
value,
authorizationList,
type,
] = await Promise.all([
encode(options.transaction),
(async () => {
// if the user has specified a nonce, use that
const resolvedNonce = await resolvePromisedValue(
options.transaction.nonce,
);
if (resolvedNonce !== undefined) {
return resolvedNonce;
}

return from // otherwise get the next nonce (import the method to do so)
? await import("../../rpc/actions/eth_getTransactionCount.js").then(
({ eth_getTransactionCount }) =>
eth_getTransactionCount(rpcRequest, {
address:
typeof from === "string"
? getAddress(from)
: getAddress(from.address),
blockTag: "pending",
}),
)
: undefined;
})(),
// takes the same options as the sendTransaction function thankfully!
estimateGas({
...options,
from: options.from,
}),
getGasOverridesForTransaction(options.transaction),
resolvePromisedValue(options.transaction.to),
resolvePromisedValue(options.transaction.accessList),
resolvePromisedValue(options.transaction.value),
resolvePromisedValue(options.transaction.authorizationList),
]);
return from // otherwise get the next nonce (import the method to do so)
? await import("../../rpc/actions/eth_getTransactionCount.js").then(
({ eth_getTransactionCount }) =>
eth_getTransactionCount(rpcRequest, {
address:
typeof from === "string"
? getAddress(from)
: getAddress(from.address),
blockTag: "pending",
}),
)
: undefined;
})(),
// takes the same options as the sendTransaction function thankfully!
estimateGas({
...options,
from: options.from,
}),
getGasOverridesForTransaction(options.transaction),
resolvePromisedValue(options.transaction.to),
resolvePromisedValue(options.transaction.accessList),
resolvePromisedValue(options.transaction.value),
resolvePromisedValue(options.transaction.authorizationList),
resolvePromisedValue(options.transaction.type),
]);

const extraGas = await resolvePromisedValue(options.transaction.extraGas);
if (extraGas) {
Expand All @@ -126,6 +136,7 @@ export async function toSerializableTransaction(
accessList,
value,
authorizationList,
type,
...feeData,
} satisfies SerializableTransaction;
}
11 changes: 11 additions & 0 deletions packages/thirdweb/src/transaction/prepare-transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type StaticPrepareTransactionOptions = {
maxFeePerGas?: bigint | undefined;
maxPriorityFeePerGas?: bigint | undefined;
maxFeePerBlobGas?: bigint | undefined;
type?: undefined | TransactionType;
nonce?: number | undefined;
extraGas?: bigint | undefined;
// eip7702
Expand All @@ -34,6 +35,16 @@ export type StaticPrepareTransactionOptions = {
};
};

type TransactionType = "legacy" | "eip1559" | "eip2930" | "eip4844" | "eip7702";

export const TransactionTypeMap: Record<TransactionType, number> = {
legacy: 0,
eip1559: 1,
eip2930: 2,
eip4844: 3,
eip7702: 4,
};

export type EIP712TransactionOptions = {
// constant or user input
gasPerPubdata?: bigint | undefined;
Expand Down

0 comments on commit 862db40

Please sign in to comment.