Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/tests/sendTransactionWorker.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Hex } from "thirdweb";
import { describe, expect, it } from "vitest";
import { _updateGasFees } from "../worker/tasks/sendTransactionWorker";

describe("_updateGasFees", () => {
const base = {
// Irrelevant values for testing.
chainId: 1,
data: "0x0" as Hex,
gas: 21000n,
to: undefined,
nonce: undefined,
accessList: undefined,
value: undefined,
};

it("returns the original transaction on first send (resendCount = 0)", () => {
let result = _updateGasFees({ ...base, gasPrice: 100n }, 0, undefined);
expect(result.gasPrice).toEqual(100n);

result = _updateGasFees(
{ ...base, maxFeePerGas: 100n, maxPriorityFeePerGas: 10n },
0,
undefined,
);
expect(result.maxFeePerGas).toEqual(100n);
expect(result.maxPriorityFeePerGas).toEqual(10n);
});

it("doubles gasPrice for legacy transactions", () => {
const result = _updateGasFees({ ...base, gasPrice: 100n }, 1, {});
expect(result.gasPrice).toBe(200n);
});

it("caps gasPrice multiplier at 10x", () => {
const result = _updateGasFees({ ...base, gasPrice: 100n }, 10, {});
expect(result.gasPrice).toBe(1000n);
});

it("updates maxPriorityFeePerGas and maxFeePerGas for EIP-1559 transactions", () => {
const result = _updateGasFees(
{ ...base, maxFeePerGas: 100n, maxPriorityFeePerGas: 10n },
3,
{},
);
expect(result.maxPriorityFeePerGas).toBe(60n);
expect(result.maxFeePerGas).toBe(260n);
});

it("respects overrides for maxPriorityFeePerGas", () => {
const result = _updateGasFees(
{ ...base, maxFeePerGas: 100n, maxPriorityFeePerGas: 10n },
3,
{ maxPriorityFeePerGas: 10n },
);
expect(result.maxPriorityFeePerGas).toBe(10n); // matches override
expect(result.maxFeePerGas).toBe(210n);
});

it("respects overrides for maxFeePerGas", () => {
const result = _updateGasFees(
{ ...base, maxFeePerGas: 100n, maxPriorityFeePerGas: 10n },
3,
{ maxFeePerGas: 100n },
);
expect(result.maxPriorityFeePerGas).toBe(60n);
expect(result.maxFeePerGas).toBe(100n); // matches override
});

it("returns correct values when only maxPriorityFeePerGas is set", () => {
const result = _updateGasFees(
{ ...base, maxPriorityFeePerGas: 10n },
3,
{},
);
expect(result.maxPriorityFeePerGas).toBe(60n);
expect(result.maxFeePerGas).toBeUndefined();
});

it("returns correct values when only maxFeePerGas is set", () => {
const result = _updateGasFees({ ...base, maxFeePerGas: 80n }, 3, {});
expect(result.maxFeePerGas).toBe(160n);
});
});
63 changes: 49 additions & 14 deletions src/worker/tasks/sendTransactionWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ const _resendTransaction = async (

// Populate the transaction with double gas.
const { chainId, from, overrides, sentTransactionHashes } = sentTransaction;
const populatedTransaction = await toSerializableTransaction({
let populatedTransaction = await toSerializableTransaction({
from: getChecksumAddress(from),
transaction: {
client: thirdwebClient,
Expand All @@ -436,19 +436,12 @@ const _resendTransaction = async (
},
});

// Double gas fee settings if they were not provded in `overrides`.
if (populatedTransaction.gasPrice) {
populatedTransaction.gasPrice *= 2n;
}
if (populatedTransaction.maxFeePerGas && !overrides?.maxFeePerGas) {
populatedTransaction.maxFeePerGas *= 2n;
}
if (
populatedTransaction.maxPriorityFeePerGas &&
!overrides?.maxPriorityFeePerGas
) {
populatedTransaction.maxPriorityFeePerGas *= 2n;
}
// Increase gas fees for this resend attempt.
populatedTransaction = _updateGasFees(
populatedTransaction,
sentTransaction.resendCount + 1,
sentTransaction.overrides,
);

job.log(`Populated transaction: ${stringify(populatedTransaction)}`);

Expand Down Expand Up @@ -559,6 +552,48 @@ const _hasExceededTimeout = (
const _minutesFromNow = (minutes: number) =>
new Date(Date.now() + minutes * 60_000);

/**
* Computes aggressive gas fees when resending a transaction.
*
* For legacy transactions (pre-EIP1559):
* - Gas price = (2 * attempt) * estimatedGasPrice, capped at 10x.
*
* For other transactions:
* - maxPriorityFeePerGas = (2 * attempt) * estimatedMaxPriorityFeePerGas, capped at 10x.
* - maxFeePerGas = (2 * estimatedMaxFeePerGas) + maxPriorityFeePerGas.
*
* @param populatedTransaction The transaction with estimated gas from RPC.
* @param resendCount The resend attempt #. Example: 2 = the transaction was initially sent, then resent once. This is the second resend attempt.
*/
export const _updateGasFees = (
populatedTransaction: PopulatedTransaction,
resendCount: number,
overrides: SentTransaction["overrides"],
): PopulatedTransaction => {
if (resendCount === 0) {
return populatedTransaction;
}

const multiplier = BigInt(Math.min(10, resendCount * 2));

const updated = { ...populatedTransaction };

// Update gas fees (unless they were explicitly overridden).

if (updated.gasPrice && !overrides?.gasPrice) {
updated.gasPrice *= multiplier;
}
if (updated.maxPriorityFeePerGas && !overrides?.maxPriorityFeePerGas) {
updated.maxPriorityFeePerGas *= multiplier;
}
if (updated.maxFeePerGas && !overrides?.maxFeePerGas) {
updated.maxFeePerGas =
updated.maxFeePerGas * 2n + (updated.maxPriorityFeePerGas ?? 0n);
}

return updated;
};

// Must be explicitly called for the worker to run on this host.
export const initSendTransactionWorker = () => {
const _worker = new Worker(SendTransactionQueue.q.name, handler, {
Expand Down
Loading