Skip to content

Commit 5ca4d29

Browse files
authored
feat: Enforce maxFeePerGas as a gas ceiling and add timeouts (#692)
* wip * move all gas settings to overrides * remove gasPrice override * add logging * fix formatting, expand range
1 parent 26397db commit 5ca4d29

File tree

23 files changed

+390
-474
lines changed

23 files changed

+390
-474
lines changed

src/db/transactions/queueTx.ts

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import type { DeployTransaction, Transaction } from "@thirdweb-dev/sdk";
2-
import { ERC4337EthersSigner } from "@thirdweb-dev/wallets/dist/declarations/src/evm/connectors/smart-wallet/lib/erc4337-signer";
3-
import { Address, ZERO_ADDRESS } from "thirdweb";
2+
import type { ERC4337EthersSigner } from "@thirdweb-dev/wallets/dist/declarations/src/evm/connectors/smart-wallet/lib/erc4337-signer";
3+
import { ZERO_ADDRESS, type Address } from "thirdweb";
44
import type { ContractExtension } from "../../schema/extension";
5+
import { parseTransactionOverrides } from "../../server/utils/transactionOverrides";
56
import { maybeBigInt, normalizeAddress } from "../../utils/primitiveTypes";
67
import { insertTransaction } from "../../utils/transaction/insertTransaction";
8+
import type { InsertedTransaction } from "../../utils/transaction/types";
79

810
interface QueueTxParams {
911
// we should move away from Transaction type (v4 SDK)
@@ -49,10 +51,8 @@ export const queueTx = async ({
4951
functionName,
5052
functionArgs,
5153
extension,
52-
gas: maybeBigInt(txOverrides?.gas),
53-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
54-
maxPriorityFeePerGas: maybeBigInt(txOverrides?.maxPriorityFeePerGas),
55-
};
54+
...parseTransactionOverrides(txOverrides),
55+
} satisfies Partial<InsertedTransaction>;
5656

5757
// TODO: We need a much safer way of detecting if the transaction should be a user operation
5858
const isUserOp = !!(tx.getSigner as ERC4337EthersSigner).erc4337provider;
@@ -76,27 +76,25 @@ export const queueTx = async ({
7676
idempotencyKey,
7777
shouldSimulate: simulateTx,
7878
});
79-
} else {
80-
const isPublishedContractDeploy =
81-
tx.getTarget() === ZERO_ADDRESS &&
82-
functionName === "deploy" &&
83-
extension === "deploy-published";
79+
}
8480

85-
const queueId = await insertTransaction({
86-
insertedTransaction: {
87-
...baseTransaction,
88-
isUserOp: false,
89-
deployedContractAddress,
90-
deployedContractType,
91-
from: normalizeAddress(await tx.getSignerAddress()),
92-
to: normalizeAddress(
93-
isPublishedContractDeploy ? undefined : tx.getTarget(),
94-
),
95-
},
96-
idempotencyKey,
97-
shouldSimulate: simulateTx,
98-
});
81+
const isPublishedContractDeploy =
82+
tx.getTarget() === ZERO_ADDRESS &&
83+
functionName === "deploy" &&
84+
extension === "deploy-published";
9985

100-
return queueId;
101-
}
86+
return await insertTransaction({
87+
insertedTransaction: {
88+
...baseTransaction,
89+
isUserOp: false,
90+
deployedContractAddress,
91+
deployedContractType,
92+
from: normalizeAddress(await tx.getSignerAddress()),
93+
to: normalizeAddress(
94+
isPublishedContractDeploy ? undefined : tx.getTarget(),
95+
),
96+
},
97+
idempotencyKey,
98+
shouldSimulate: simulateTx,
99+
});
102100
};

src/server/routes/backend-wallet/sendTransaction.ts

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Static, Type } from "@sinclair/typebox";
2-
import { FastifyInstance } from "fastify";
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
4-
import { Address, Hex } from "thirdweb";
5-
import { maybeBigInt } from "../../../utils/primitiveTypes";
4+
import type { Address, Hex } from "thirdweb";
65
import { insertTransaction } from "../../../utils/transaction/insertTransaction";
76
import { AddressSchema } from "../../schemas/address";
87
import {
@@ -17,6 +16,7 @@ import {
1716
walletWithAAHeaderSchema,
1817
} from "../../schemas/wallet";
1918
import { getChainIdFromChain } from "../../utils/chain";
19+
import { parseTransactionOverrides } from "../../utils/transactionOverrides";
2020

2121
const requestBodySchema = Type.Object({
2222
toAddress: Type.Optional(AddressSchema),
@@ -93,11 +93,7 @@ export async function sendTransaction(fastify: FastifyInstance) {
9393
accountFactoryAddress,
9494
"x-account-factory-address",
9595
),
96-
gas: maybeBigInt(txOverrides?.gas),
97-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
98-
maxPriorityFeePerGas: maybeBigInt(
99-
txOverrides?.maxPriorityFeePerGas,
100-
),
96+
...parseTransactionOverrides(txOverrides),
10197
},
10298
shouldSimulate: simulateTx,
10399
idempotencyKey,
@@ -111,12 +107,7 @@ export async function sendTransaction(fastify: FastifyInstance) {
111107
to: toAddress as Address | undefined,
112108
data: data as Hex,
113109
value: BigInt(value),
114-
115-
gas: maybeBigInt(txOverrides?.gas),
116-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
117-
maxPriorityFeePerGas: maybeBigInt(
118-
txOverrides?.maxPriorityFeePerGas,
119-
),
110+
...parseTransactionOverrides(txOverrides),
120111
},
121112
shouldSimulate: simulateTx,
122113
idempotencyKey,

src/server/routes/backend-wallet/sendTransactionBatch.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Static, Type } from "@sinclair/typebox";
2-
import { FastifyInstance } from "fastify";
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
4-
import { Address, Hex } from "thirdweb";
5-
import { maybeBigInt } from "../../../utils/primitiveTypes";
4+
import type { Address, Hex } from "thirdweb";
65
import { insertTransaction } from "../../../utils/transaction/insertTransaction";
76
import { AddressSchema } from "../../schemas/address";
87
import { standardResponseSchema } from "../../schemas/sharedApiSchemas";
@@ -12,6 +11,7 @@ import {
1211
walletHeaderSchema,
1312
} from "../../schemas/wallet";
1413
import { getChainIdFromChain } from "../../utils/chain";
14+
import { parseTransactionOverrides } from "../../utils/transactionOverrides";
1515

1616
const requestBodySchema = Type.Array(
1717
Type.Object({
@@ -74,12 +74,7 @@ export async function sendTransactionBatch(fastify: FastifyInstance) {
7474
to: toAddress as Address | undefined,
7575
data: data as Hex,
7676
value: BigInt(value),
77-
78-
gas: maybeBigInt(txOverrides?.gas),
79-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
80-
maxPriorityFeePerGas: maybeBigInt(
81-
txOverrides?.maxPriorityFeePerGas,
82-
),
77+
...parseTransactionOverrides(txOverrides),
8378
},
8479
});
8580
queueIds.push(queueId);

src/server/routes/backend-wallet/transfer.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import { transfer as transferERC20 } from "thirdweb/extensions/erc20";
1212
import { isContractDeployed, resolvePromisedValue } from "thirdweb/utils";
1313
import { getChain } from "../../../utils/chain";
14-
import { maybeBigInt, normalizeAddress } from "../../../utils/primitiveTypes";
14+
import { normalizeAddress } from "../../../utils/primitiveTypes";
1515
import { thirdwebClient } from "../../../utils/sdk";
1616
import { insertTransaction } from "../../../utils/transaction/insertTransaction";
1717
import type { InsertedTransaction } from "../../../utils/transaction/types";
@@ -29,6 +29,7 @@ import {
2929
walletWithAddressParamSchema,
3030
} from "../../schemas/wallet";
3131
import { getChainIdFromChain } from "../../utils/chain";
32+
import { parseTransactionOverrides } from "../../utils/transactionOverrides";
3233

3334
const requestSchema = Type.Omit(walletWithAddressParamSchema, [
3435
"walletAddress",
@@ -93,11 +94,6 @@ export async function transfer(fastify: FastifyInstance) {
9394
// Resolve inputs.
9495
const currencyAddress = normalizeAddress(_currencyAddress);
9596
const chainId = await getChainIdFromChain(chain);
96-
const gasOverrides = {
97-
gas: maybeBigInt(txOverrides?.gas),
98-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
99-
maxPriorityFeePerGas: maybeBigInt(txOverrides?.maxPriorityFeePerGas),
100-
};
10197

10298
let insertedTransaction: InsertedTransaction;
10399
if (
@@ -113,7 +109,7 @@ export async function transfer(fastify: FastifyInstance) {
113109
value: toWei(amount),
114110
extension: "none",
115111
functionName: "transfer",
116-
...gasOverrides,
112+
...parseTransactionOverrides(txOverrides),
117113
};
118114
} else {
119115
const contract = getContract({
@@ -147,7 +143,7 @@ export async function transfer(fastify: FastifyInstance) {
147143
extension: "erc20",
148144
functionName: "transfer",
149145
functionArgs: [to, amount, currencyAddress],
150-
...gasOverrides,
146+
...parseTransactionOverrides(txOverrides),
151147
};
152148
}
153149

src/server/routes/backend-wallet/withdraw.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import { getWalletBalance } from "thirdweb/wallets";
1212
import { getAccount } from "../../../utils/account";
1313
import { getChain } from "../../../utils/chain";
1414
import { logger } from "../../../utils/logger";
15-
import { getChecksumAddress, maybeBigInt } from "../../../utils/primitiveTypes";
15+
import { getChecksumAddress } from "../../../utils/primitiveTypes";
1616
import { thirdwebClient } from "../../../utils/sdk";
17+
import type { PopulatedTransaction } from "../../../utils/transaction/types";
1718
import { createCustomError } from "../../middleware/error";
1819
import { AddressSchema, TransactionHashSchema } from "../../schemas/address";
1920
import { TokenAmountStringSchema } from "../../schemas/number";
@@ -27,6 +28,7 @@ import {
2728
walletWithAddressParamSchema,
2829
} from "../../schemas/wallet";
2930
import { getChainIdFromChain } from "../../utils/chain";
31+
import { parseTransactionOverrides } from "../../utils/transactionOverrides";
3032

3133
const ParamsSchema = Type.Omit(walletWithAddressParamSchema, ["walletAddress"]);
3234

@@ -88,9 +90,7 @@ export async function withdraw(fastify: FastifyInstance) {
8890
data: "0x",
8991
// Dummy value, replaced below.
9092
value: 1n,
91-
gas: maybeBigInt(txOverrides?.gas),
92-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
93-
maxPriorityFeePerGas: maybeBigInt(txOverrides?.maxPriorityFeePerGas),
93+
...parseTransactionOverrides(txOverrides).overrides,
9494
},
9595
});
9696

@@ -130,7 +130,7 @@ export async function withdraw(fastify: FastifyInstance) {
130130

131131
const getWithdrawValue = async (
132132
from: Address,
133-
populatedTransaction: Awaited<ReturnType<typeof toSerializableTransaction>>,
133+
populatedTransaction: PopulatedTransaction,
134134
): Promise<bigint> => {
135135
const chain = await getChain(populatedTransaction.chainId);
136136

src/server/routes/contract/extensions/erc721/write/signatureMint.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { resolvePromisedValue } from "thirdweb/utils";
99
import { queueTx } from "../../../../../../db/transactions/queueTx";
1010
import { getContract } from "../../../../../../utils/cache/getContract";
1111
import { getContractV5 } from "../../../../../../utils/cache/getContractv5";
12-
import { maybeBigInt } from "../../../../../../utils/primitiveTypes";
1312
import { insertTransaction } from "../../../../../../utils/transaction/insertTransaction";
1413
import { thirdwebSdkVersionSchema } from "../../../../../schemas/httpHeaders/thirdwebSdkVersion";
1514
import { signature721OutputSchema } from "../../../../../schemas/nft";
@@ -23,6 +22,7 @@ import {
2322
import { txOverridesWithValueSchema } from "../../../../../schemas/txOverrides";
2423
import { walletWithAAHeaderSchema } from "../../../../../schemas/wallet";
2524
import { getChainIdFromChain } from "../../../../../utils/chain";
25+
import { parseTransactionOverrides } from "../../../../../utils/transactionOverrides";
2626

2727
// INPUTS
2828
const requestSchema = contractParamSchema;
@@ -122,11 +122,9 @@ export async function erc721SignatureMint(fastify: FastifyInstance) {
122122
from: fromAddress as Address,
123123
to: contractAddress as Address | undefined,
124124
data: (await resolvePromisedValue(transaction.data)) as Hex,
125-
value: maybeBigInt(txOverrides?.value),
126-
gas: maybeBigInt(txOverrides?.gas),
127-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
128-
maxPriorityFeePerGas: maybeBigInt(txOverrides?.maxPriorityFeePerGas),
125+
...parseTransactionOverrides(txOverrides),
129126
};
127+
130128
if (accountAddress) {
131129
queueId = await insertTransaction({
132130
insertedTransaction: {

src/server/routes/contract/write/write.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { Type, type Static } from "@sinclair/typebox";
22
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { prepareContractCall, resolveMethod } from "thirdweb";
5+
import type { AbiFunction } from "thirdweb/utils";
56
import { getContractV5 } from "../../../../utils/cache/getContractv5";
6-
import { maybeBigInt } from "../../../../utils/primitiveTypes";
77
import { queueTransaction } from "../../../../utils/transaction/queueTransation";
88
import { createCustomError } from "../../../middleware/error";
99
import { abiSchema } from "../../../schemas/contract";
@@ -19,6 +19,7 @@ import {
1919
walletWithAAHeaderSchema,
2020
} from "../../../schemas/wallet";
2121
import { getChainIdFromChain } from "../../../utils/chain";
22+
import { parseTransactionOverrides } from "../../../utils/transactionOverrides";
2223

2324
// INPUT
2425
const writeRequestBodySchema = Type.Object({
@@ -78,7 +79,7 @@ export async function writeToContract(fastify: FastifyInstance) {
7879
// 2. functionName passed as function name + passed in ABI
7980
// 3. functionName passed as function name + inferred ABI (fetched at encode time)
8081
// this is all handled inside the `resolveMethod` function
81-
let method;
82+
let method: AbiFunction;
8283
try {
8384
method = await resolveMethod(functionName)(contract);
8485
} catch (e: any) {
@@ -88,10 +89,7 @@ export async function writeToContract(fastify: FastifyInstance) {
8889
contract,
8990
method,
9091
params: args,
91-
gas: maybeBigInt(txOverrides?.gas),
92-
value: maybeBigInt(txOverrides?.value),
93-
maxFeePerGas: maybeBigInt(txOverrides?.maxFeePerGas),
94-
maxPriorityFeePerGas: maybeBigInt(txOverrides?.maxPriorityFeePerGas),
92+
...parseTransactionOverrides(txOverrides),
9593
});
9694

9795
const queueId = await queueTransaction({

src/server/routes/transaction/cancel.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { Static, Type } from "@sinclair/typebox";
2-
import { FastifyInstance } from "fastify";
1+
import { Type, type Static } from "@sinclair/typebox";
2+
import type { FastifyInstance } from "fastify";
33
import { StatusCodes } from "http-status-codes";
44
import { TransactionDB } from "../../../db/transactions/db";
55
import { getBlockNumberish } from "../../../utils/block";
66
import { getConfig } from "../../../utils/cache/getConfig";
77
import { getChain } from "../../../utils/chain";
88
import { msSince } from "../../../utils/date";
99
import { sendCancellationTransaction } from "../../../utils/transaction/cancelTransaction";
10-
import { CancelledTransaction } from "../../../utils/transaction/types";
10+
import type { CancelledTransaction } from "../../../utils/transaction/types";
1111
import { enqueueTransactionWebhook } from "../../../utils/transaction/webhook";
1212
import { reportUsage } from "../../../utils/usage";
1313
import { SendTransactionQueue } from "../../../worker/queues/sendTransactionQueue";
@@ -98,10 +98,13 @@ export async function cancelTransaction(fastify: FastifyInstance) {
9898
...transaction,
9999
status: "cancelled",
100100
cancelledAt: new Date(),
101+
102+
// Dummy data since the transaction was never sent.
101103
sentAt: new Date(),
102104
sentAtBlock: await getBlockNumberish(transaction.chainId),
103105

104106
isUserOp: false,
107+
gas: 0n,
105108
nonce: -1,
106109
sentTransactionHashes: [],
107110
};

src/server/routes/transaction/syncRetry.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,10 @@ export async function syncRetryTransaction(fastify: FastifyInstance) {
8888
client: thirdwebClient,
8989
chain: await getChain(chainId),
9090
...transaction,
91+
// Explicitly reuse the same nonce the transaction had previously acquired.
92+
nonce: transaction.nonce,
9193
maxFeePerGas: maybeBigInt(maxFeePerGas),
9294
maxPriorityFeePerGas: maybeBigInt(maxPriorityFeePerGas),
93-
nonce: transaction.nonce,
9495
},
9596
});
9697

src/server/schemas/number.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ export const TokenAmountStringSchema = Type.RegExp(/^\d+(\.\d+)?$/, {
77

88
export const WeiAmountStringSchema = Type.RegExp(/^\d+$/, {
99
description: 'An amount in wei (no decimals). Example: "100000000"',
10-
examples: ["0"],
10+
examples: ["50000000000"],
1111
});

0 commit comments

Comments
 (0)