From 3233f4e34cfc62ba6eb2c664958a1dac6922d396 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Thu, 6 Feb 2025 06:34:48 +0530 Subject: [PATCH 1/7] feat: Add support for forcing legacy transactions in chain configuration --- packages/thirdweb/src/chains/types.ts | 6 ++++++ packages/thirdweb/src/gas/fee-data.ts | 27 +++++++++++++++------------ 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/thirdweb/src/chains/types.ts b/packages/thirdweb/src/chains/types.ts index 7a9298c92eb..387a3c621da 100644 --- a/packages/thirdweb/src/chains/types.ts +++ b/packages/thirdweb/src/chains/types.ts @@ -26,6 +26,12 @@ export type ChainOptions = { increaseZeroByteCount?: boolean; }; faucets?: Array; + fees?: { + /** + * Whether to force legacy transactions (pre EIP-1559) for this chain + */ + forceLegacyTransactions?: boolean; + }; }; /** diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts index ee0357015d4..77e3f3f8596 100644 --- a/packages/thirdweb/src/gas/fee-data.ts +++ b/packages/thirdweb/src/gas/fee-data.ts @@ -114,18 +114,21 @@ export async function getDefaultGasOverrides( client: ThirdwebClient, chain: Chain, ) { - // 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, - }; - } + // if chain is configured to force legacy transactions or is in the legacy chain list + if ( + chain.fees?.forceLegacyTransactions || + 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, + }; } return { gasPrice: await getGasPrice({ client, chain, percentMultiplier: 10 }), From e10bb39329b9861741f9d18bf0bb077d1d7b9d04 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Thu, 6 Feb 2025 08:18:46 +0530 Subject: [PATCH 2/7] support general feeType on chain and prepareTransaction --- packages/thirdweb/src/chains/types.ts | 9 +++---- packages/thirdweb/src/gas/fee-data.ts | 25 +++++++++++++------ .../src/transaction/prepare-transaction.ts | 3 +++ 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/packages/thirdweb/src/chains/types.ts b/packages/thirdweb/src/chains/types.ts index 387a3c621da..f9ed7b52658 100644 --- a/packages/thirdweb/src/chains/types.ts +++ b/packages/thirdweb/src/chains/types.ts @@ -1,3 +1,5 @@ +import type { FeeType } from "src/transaction/prepare-transaction.js"; + /** * @chain */ @@ -26,12 +28,7 @@ export type ChainOptions = { increaseZeroByteCount?: boolean; }; faucets?: Array; - fees?: { - /** - * Whether to force legacy transactions (pre EIP-1559) for this chain - */ - forceLegacyTransactions?: boolean; - }; + feeType?: FeeType; }; /** diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts index 77e3f3f8596..0bcb2f894a7 100644 --- a/packages/thirdweb/src/gas/fee-data.ts +++ b/packages/thirdweb/src/gas/fee-data.ts @@ -3,7 +3,10 @@ import type { ThirdwebClient } from "../client/client.js"; import { eth_getBlockByNumber } from "../rpc/actions/eth_getBlockByNumber.js"; import { eth_maxPriorityFeePerGas } from "../rpc/actions/eth_maxPriorityFeePerGas.js"; import { getRpcClient } from "../rpc/rpc.js"; -import type { PreparedTransaction } from "../transaction/prepare-transaction.js"; +import type { + FeeType, + PreparedTransaction, +} from "../transaction/prepare-transaction.js"; import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js"; import { toUnits } from "../utils/units.js"; import { getGasPrice } from "./get-gas-price.js"; @@ -50,11 +53,13 @@ export async function getGasOverridesForTransaction( transaction: PreparedTransaction, ): Promise { // 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, feeType] = + await Promise.all([ + resolvePromisedValue(transaction.maxFeePerGas), + resolvePromisedValue(transaction.maxPriorityFeePerGas), + resolvePromisedValue(transaction.gasPrice), + resolvePromisedValue(transaction.feeType), + ]); // Exit early if the user explicitly provided enough options if (maxFeePerGas !== undefined && maxPriorityFeePerGas !== undefined) { @@ -63,6 +68,7 @@ export async function getGasOverridesForTransaction( maxPriorityFeePerGas, }; } + if (typeof gasPrice === "bigint") { return { gasPrice }; } @@ -71,6 +77,7 @@ export async function getGasOverridesForTransaction( const defaultGasOverrides = await getDefaultGasOverrides( transaction.client, transaction.chain, + feeType, ); if (transaction.chain.experimental?.increaseZeroByteCount) { @@ -113,10 +120,13 @@ export async function getGasOverridesForTransaction( export async function getDefaultGasOverrides( client: ThirdwebClient, chain: Chain, + feeType?: FeeType, ) { + // 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 ( - chain.fees?.forceLegacyTransactions || + resolvedFeeType === "legacy" || FORCE_GAS_PRICE_CHAIN_IDS.includes(chain.id) ) { return { @@ -130,6 +140,7 @@ export async function getDefaultGasOverrides( 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 }), }; diff --git a/packages/thirdweb/src/transaction/prepare-transaction.ts b/packages/thirdweb/src/transaction/prepare-transaction.ts index aef9a876a82..ca54cf1d2f1 100644 --- a/packages/thirdweb/src/transaction/prepare-transaction.ts +++ b/packages/thirdweb/src/transaction/prepare-transaction.ts @@ -17,6 +17,7 @@ export type StaticPrepareTransactionOptions = { maxFeePerGas?: bigint | undefined; maxPriorityFeePerGas?: bigint | undefined; maxFeePerBlobGas?: bigint | undefined; + feeType?: FeeType | undefined; nonce?: number | undefined; extraGas?: bigint | undefined; // eip7702 @@ -34,6 +35,8 @@ export type StaticPrepareTransactionOptions = { }; }; +export type FeeType = "legacy" | "eip1559"; + export type EIP712TransactionOptions = { // constant or user input gasPerPubdata?: bigint | undefined; From 32813757a197f65667b5c4f3607d252a0f2e2623 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Thu, 6 Feb 2025 08:22:15 +0530 Subject: [PATCH 3/7] fixed import --- packages/thirdweb/src/chains/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/thirdweb/src/chains/types.ts b/packages/thirdweb/src/chains/types.ts index f9ed7b52658..c75efdf11df 100644 --- a/packages/thirdweb/src/chains/types.ts +++ b/packages/thirdweb/src/chains/types.ts @@ -1,4 +1,4 @@ -import type { FeeType } from "src/transaction/prepare-transaction.js"; +import type { FeeType } from "../transaction/prepare-transaction.js"; /** * @chain From 6fa3332aef4817a7f60b88068f68f0450284d778 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Thu, 6 Feb 2025 21:43:09 +1300 Subject: [PATCH 4/7] add chain to hardcoded list --- packages/thirdweb/src/gas/fee-data.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts index 0bcb2f894a7..95a7da0356e 100644 --- a/packages/thirdweb/src/gas/fee-data.ts +++ b/packages/thirdweb/src/gas/fee-data.ts @@ -43,6 +43,7 @@ const FORCE_GAS_PRICE_CHAIN_IDS = [ 1942999413, // Humanity Testnet 1952959480, // Lumia Testnet 994873017, // Lumia Mainnet + 1942999413, // Humanity Mainnet ]; /** From 842c37e196d64a7ac088f1241f29e9218fce2b95 Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 19 Feb 2025 02:22:27 +0530 Subject: [PATCH 5/7] update prepare transaction to use type instead of feetype --- packages/thirdweb/src/adapters/ethers5.ts | 6 +- packages/thirdweb/src/chains/types.ts | 2 +- packages/thirdweb/src/gas/fee-data.ts | 13 ++- .../actions/to-serializable-transaction.ts | 81 +++++++++++-------- .../src/transaction/prepare-transaction.ts | 17 +++- 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/packages/thirdweb/src/adapters/ethers5.ts b/packages/thirdweb/src/adapters/ethers5.ts index ec7669bc078..405e708b653 100644 --- a/packages/thirdweb/src/adapters/ethers5.ts +++ b/packages/thirdweb/src/adapters/ethers5.ts @@ -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"; @@ -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 diff --git a/packages/thirdweb/src/chains/types.ts b/packages/thirdweb/src/chains/types.ts index c75efdf11df..d4741fb47f9 100644 --- a/packages/thirdweb/src/chains/types.ts +++ b/packages/thirdweb/src/chains/types.ts @@ -1,4 +1,4 @@ -import type { FeeType } from "../transaction/prepare-transaction.js"; +import type { FeeType } from "../gas/fee-data.js"; /** * @chain diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts index 95a7da0356e..3a3459e472b 100644 --- a/packages/thirdweb/src/gas/fee-data.ts +++ b/packages/thirdweb/src/gas/fee-data.ts @@ -3,10 +3,7 @@ import type { ThirdwebClient } from "../client/client.js"; import { eth_getBlockByNumber } from "../rpc/actions/eth_getBlockByNumber.js"; import { eth_maxPriorityFeePerGas } from "../rpc/actions/eth_maxPriorityFeePerGas.js"; import { getRpcClient } from "../rpc/rpc.js"; -import type { - FeeType, - PreparedTransaction, -} from "../transaction/prepare-transaction.js"; +import type { PreparedTransaction } from "../transaction/prepare-transaction.js"; import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js"; import { toUnits } from "../utils/units.js"; import { getGasPrice } from "./get-gas-price.js"; @@ -54,12 +51,12 @@ export async function getGasOverridesForTransaction( transaction: PreparedTransaction, ): Promise { // first check for explicit values - const [maxFeePerGas, maxPriorityFeePerGas, gasPrice, feeType] = + const [maxFeePerGas, maxPriorityFeePerGas, gasPrice, type] = await Promise.all([ resolvePromisedValue(transaction.maxFeePerGas), resolvePromisedValue(transaction.maxPriorityFeePerGas), resolvePromisedValue(transaction.gasPrice), - resolvePromisedValue(transaction.feeType), + resolvePromisedValue(transaction.type), ]); // Exit early if the user explicitly provided enough options @@ -78,7 +75,7 @@ export async function getGasOverridesForTransaction( const defaultGasOverrides = await getDefaultGasOverrides( transaction.client, transaction.chain, - feeType, + type === "legacy" ? "legacy" : "eip1559", // 7702, 2930, and eip1559 all qualify as "eip1559" fee type ); if (transaction.chain.experimental?.increaseZeroByteCount) { @@ -109,6 +106,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. diff --git a/packages/thirdweb/src/transaction/actions/to-serializable-transaction.ts b/packages/thirdweb/src/transaction/actions/to-serializable-transaction.ts index c8ddfc9f5f1..f2ed26a5039 100644 --- a/packages/thirdweb/src/transaction/actions/to-serializable-transaction.ts +++ b/packages/thirdweb/src/transaction/actions/to-serializable-transaction.ts @@ -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) { @@ -126,6 +136,7 @@ export async function toSerializableTransaction( accessList, value, authorizationList, + type, ...feeData, } satisfies SerializableTransaction; } diff --git a/packages/thirdweb/src/transaction/prepare-transaction.ts b/packages/thirdweb/src/transaction/prepare-transaction.ts index ca54cf1d2f1..b022ff16669 100644 --- a/packages/thirdweb/src/transaction/prepare-transaction.ts +++ b/packages/thirdweb/src/transaction/prepare-transaction.ts @@ -17,7 +17,7 @@ export type StaticPrepareTransactionOptions = { maxFeePerGas?: bigint | undefined; maxPriorityFeePerGas?: bigint | undefined; maxFeePerBlobGas?: bigint | undefined; - feeType?: FeeType | undefined; + type?: undefined | TransactionType; nonce?: number | undefined; extraGas?: bigint | undefined; // eip7702 @@ -35,7 +35,20 @@ export type StaticPrepareTransactionOptions = { }; }; -export type FeeType = "legacy" | "eip1559"; +export type TransactionType = + | "legacy" + | "eip1559" + | "eip2930" + | "eip4844" + | "eip7702"; + +export const TransactionTypeMap: Record = { + legacy: 0, + eip1559: 1, + eip2930: 2, + eip4844: 3, + eip7702: 4, +}; export type EIP712TransactionOptions = { // constant or user input From f4a26e18c0c4eb5b251cbb02e2272bc31abceb3b Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 26 Feb 2025 16:41:19 +0530 Subject: [PATCH 6/7] remove export (lint error) --- packages/thirdweb/src/transaction/prepare-transaction.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/thirdweb/src/transaction/prepare-transaction.ts b/packages/thirdweb/src/transaction/prepare-transaction.ts index b022ff16669..f309be24dcd 100644 --- a/packages/thirdweb/src/transaction/prepare-transaction.ts +++ b/packages/thirdweb/src/transaction/prepare-transaction.ts @@ -35,12 +35,7 @@ export type StaticPrepareTransactionOptions = { }; }; -export type TransactionType = - | "legacy" - | "eip1559" - | "eip2930" - | "eip4844" - | "eip7702"; +type TransactionType = "legacy" | "eip1559" | "eip2930" | "eip4844" | "eip7702"; export const TransactionTypeMap: Record = { legacy: 0, From 66fbe309aeec02667c45f1d2cb0e4f2b36f3e65b Mon Sep 17 00:00:00 2001 From: Prithvish Baidya Date: Wed, 26 Feb 2025 17:13:52 +0530 Subject: [PATCH 7/7] fallback to legacy if no baseBlockFee in last block --- packages/thirdweb/src/gas/fee-data.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/thirdweb/src/gas/fee-data.ts b/packages/thirdweb/src/gas/fee-data.ts index e1fc691c547..b351193e05b 100644 --- a/packages/thirdweb/src/gas/fee-data.ts +++ b/packages/thirdweb/src/gas/fee-data.ts @@ -169,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 @@ -187,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 }; }