Skip to content

Commit

Permalink
sendTx, estimateGas and simulate now all accept wallet OR account
Browse files Browse the repository at this point in the history
  • Loading branch information
jnsdls committed Feb 20, 2024
1 parent 7e947b6 commit ecb3d1e
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 42 deletions.
3 changes: 3 additions & 0 deletions packages/thirdweb/src/chains/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export type ChainOptions = {
apiUrl?: string;
}>;
testnet?: true;
experimental?: {
increaseZeroByteCount?: boolean;
};
};

type Icon = {
Expand Down
47 changes: 40 additions & 7 deletions packages/thirdweb/src/gas/fee-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from "../rpc/index.js";
import { parseUnits } from "../utils/units.js";
import type { PreparedTransaction } from "../transaction/prepare-transaction.js";
import { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js";
import { roundUpGas } from "./op-gas-fee-reducer.js";

type FeeData = {
maxFeePerGas: null | bigint;
Expand All @@ -34,23 +36,54 @@ export async function getGasOverridesForTransaction(
transaction: PreparedTransaction,
): Promise<FeeDataParams> {
// if we have a `gasPrice` param in the transaction, use that.
if ("gasPrice" in transaction && !transaction.gasPrice) {
return { gasPrice: transaction.gasPrice };
if ("gasPrice" in transaction) {
const resolvedGasPrice = await resolvePromisedValue(transaction.gasPrice);
// if the value ends up being "undefined" -> continue to getting the real data
if (resolvedGasPrice !== undefined) {
return { gasPrice: resolvedGasPrice };
}
}
// if we have a maxFeePerGas and maxPriorityFeePerGas, use those
if (
"maxFeePerGas" in transaction &&
"maxPriorityFeePerGas" in transaction &&
!transaction.maxFeePerGas &&
!transaction.maxPriorityFeePerGas
transaction.maxFeePerGas &&
transaction.maxPriorityFeePerGas
) {
const [resolvedMaxFee, resolvedMaxPriorityFee] = await Promise.all([
resolvePromisedValue(transaction.maxFeePerGas),
resolvePromisedValue(transaction.maxPriorityFeePerGas),
]);
return {
maxFeePerGas: transaction.maxFeePerGas,
maxPriorityFeePerGas: transaction.maxPriorityFeePerGas,
maxFeePerGas: resolvedMaxFee,
maxPriorityFeePerGas: resolvedMaxPriorityFee,
};
}
// otherwise call getDefaultGasOverrides
return getDefaultGasOverrides(transaction.client, transaction.chain);
const defaultGasOverrides = await getDefaultGasOverrides(
transaction.client,
transaction.chain,
);
if (!transaction.chain.experimental?.increaseZeroByteCount) {
// return as is
return defaultGasOverrides;
}
// otherwise adjust each value
if (defaultGasOverrides.gasPrice) {
return { gasPrice: roundUpGas(defaultGasOverrides.gasPrice) };
} else if (
defaultGasOverrides.maxFeePerGas &&
defaultGasOverrides.maxPriorityFeePerGas
) {
return {
maxFeePerGas: roundUpGas(defaultGasOverrides.maxFeePerGas),
maxPriorityFeePerGas: roundUpGas(
defaultGasOverrides.maxPriorityFeePerGas,
),
};
}
// this should never happen
return defaultGasOverrides;
}

/**
Expand Down
10 changes: 10 additions & 0 deletions packages/thirdweb/src/gas/op-gas-fee-reducer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { describe, test, expect } from "vitest";
import { roundUpGas } from "./op-gas-fee-reducer.js";
import { hexToBigInt, numberToHex } from "viem";

describe("opGasFeeReducer", () => {
test("should turn '0x3F1234' into '0x400000'", () => {
const result = roundUpGas(hexToBigInt("0x3F1234"));
expect(numberToHex(result)).toBe("0x400000");
});
});
24 changes: 24 additions & 0 deletions packages/thirdweb/src/gas/op-gas-fee-reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Via: https://twitter.com/0xjustadev/status/1758973668011434062
*
* Increases the gas fee value to the nearest power of 2.
* If the value is already a power of 2 or 0, it returns the value as is.
* Otherwise, it finds the highest power of 2 that is bigger than the given value.
* @param value - The gas fee value to be "rounded up".
* @returns The *increased* gas value which will result in a lower L1 gas fee, overall reducing the gas fee.
* @internal
*/
export function roundUpGas(value: bigint): bigint {
if (value === 0n || (value & (value - 1n)) === 0n) {
return value;
}

Check warning on line 14 in packages/thirdweb/src/gas/op-gas-fee-reducer.ts

View check run for this annotation

Codecov / codecov/patch

packages/thirdweb/src/gas/op-gas-fee-reducer.ts#L13-L14

Added lines #L13 - L14 were not covered by tests

// Find the highest set bit by shifting until the value is 0.
let highestBit = 1n;
while (value > 0n) {
value >>= 1n;
highestBit <<= 1n;
}

return highestBit;
}
44 changes: 34 additions & 10 deletions packages/thirdweb/src/transaction/actions/estimate-gas.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
import { formatTransactionRequest } from "viem";
import type { Wallet } from "../../wallets/interfaces/wallet.js";
import type { Account, Wallet } from "../../wallets/interfaces/wallet.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { extractError as parseEstimationError } from "../extract-error.js";
import type { Prettify } from "../../utils/type-utils.js";
import { roundUpGas } from "../../gas/op-gas-fee-reducer.js";

type EstimateGasOptions = Prettify<
export type EstimateGasOptions = Prettify<
{
transaction: PreparedTransaction;
} & (
| {
account: Account;
from?: never;
wallet?: never;
}
| {
account?: never;
from?: string;
wallet?: never;
}
| { from?: never; wallet?: Wallet }
| {
account?: never;
from?: never;
wallet?: Wallet;
}
)
>;

Expand Down Expand Up @@ -51,7 +62,11 @@ export async function estimateGas(
// if the wallet itself overrides the estimateGas function, use that
if (options.wallet && options.wallet.estimateGas) {
try {
return await options.wallet.estimateGas(options.transaction);
let gas = await options.wallet.estimateGas(options.transaction);
if (options.transaction.chain.experimental?.increaseZeroByteCount) {
gas = roundUpGas(gas);
}
return gas;
} catch (error) {
throw await parseEstimationError({
error,
Expand All @@ -74,19 +89,28 @@ export async function estimateGas(
]);

const rpcRequest = getRpcClient(options.transaction);
// from is:
// 1. the user specified from address
// 2. the passed in account address
// 3. the passed in wallet's account address
const from =
options.from ??
options.account?.address ??
options.wallet?.getAccount()?.address ??
undefined;
try {
return await eth_estimateGas(
let gas = await eth_estimateGas(
rpcRequest,
formatTransactionRequest({
to: toAddress,
data: encodedData,
from:
// if the user has specified a from address, use that
// otherwise use the wallet's account address
// if the wallet is not provided, use undefined
options.from ?? options.wallet?.getAccount()?.address ?? undefined,
from,
}),
);
if (options.transaction.chain.experimental?.increaseZeroByteCount) {
gas = roundUpGas(gas);
}
return gas;
} catch (error) {
throw await parseEstimationError({
error,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import type { WaitForReceiptOptions } from "./wait-for-tx-receipt.js";
import type {
Account,
SendTransactionOption,
Wallet,
} from "../../wallets/interfaces/wallet.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import { encode } from "./encode.js";
import type { Prettify } from "../../utils/type-utils.js";

type SendBatchTransactionOptions = {
transactions: PreparedTransaction[];
wallet: Wallet;
};
export type SendBatchTransactionOptions = Prettify<
{
transactions: PreparedTransaction[];
} & (
| { account?: never; wallet: Wallet }
| { account: Account; wallet?: never }
)
>;

/**
* Sends a transaction using the provided wallet.
Expand All @@ -30,7 +36,7 @@ type SendBatchTransactionOptions = {
export async function sendBatchTransaction(
options: SendBatchTransactionOptions,
): Promise<WaitForReceiptOptions> {
const account = options.wallet.getAccount();
const account = options.account ?? options.wallet.getAccount();
if (!account) {
throw new Error("not connected");
}
Expand Down
34 changes: 21 additions & 13 deletions packages/thirdweb/src/transaction/actions/send-transaction.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
import type { TransactionSerializable } from "viem";
import type { WaitForReceiptOptions } from "./wait-for-tx-receipt.js";
import type { Wallet } from "../../wallets/interfaces/wallet.js";
import type { Account, Wallet } from "../../wallets/interfaces/wallet.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import type { Prettify } from "../../utils/type-utils.js";

type SendTransactionOptions = {
transaction: PreparedTransaction;
wallet: Wallet;
};
export type SendTransactionOptions = Prettify<
{
transaction: PreparedTransaction;
} & (
| {
account?: never;
wallet: Wallet;
}
| {
account: Account;
wallet?: never;
}
)
>;

/**
* Sends a transaction using the provided wallet.
Expand All @@ -27,7 +38,7 @@ type SendTransactionOptions = {
export async function sendTransaction(
options: SendTransactionOptions,
): Promise<WaitForReceiptOptions> {
const account = options.wallet.getAccount();
const account = options.account ?? options.wallet.getAccount();
if (!account) {
throw new Error("not connected");
}
Expand Down Expand Up @@ -56,26 +67,23 @@ export async function sendTransaction(
address: account.address,
blockTag: "pending",
}),
// if user has specified a gas value, use that
estimateGas({
transaction: options.transaction,
wallet: options.wallet,
}),
// takes the same options as the sendTransaction function thankfully!
estimateGas(options),
getGasOverridesForTransaction(options.transaction),
resolvePromisedValue(options.transaction.to),
resolvePromisedValue(options.transaction.accessList),
resolvePromisedValue(options.transaction.value),
]);

const walletChainId = options.wallet.getChain()?.id;
const walletChainId = options.wallet?.getChain()?.id;
const chainId = options.transaction.chain.id;
// only if:
// 1. the wallet has a chainId
// 2. the wallet has a switchChain method
// 3. the wallet's chainId is not the same as the transaction's chainId
// => switch tot he wanted chain
if (
options.wallet.switchChain &&
options.wallet?.switchChain &&
walletChainId &&
walletChainId !== chainId
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/thirdweb/src/transaction/actions/simulate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ describe("transaction: simulate", () => {
});
const result = await simulateTransaction({
transaction: tx,
account: { address: TEST_WALLET_A },
from: TEST_WALLET_A,
});
expect(result).toMatchInlineSnapshot(`true`);

Expand Down
40 changes: 34 additions & 6 deletions packages/thirdweb/src/transaction/actions/simulate.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import { formatTransactionRequest } from "viem";
import type { Account } from "../../wallets/interfaces/wallet.js";
import type { Account, Wallet } from "../../wallets/interfaces/wallet.js";
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
import type { PreparedTransaction } from "../prepare-transaction.js";
import { eth_call } from "../../rpc/index.js";
import type { Abi, AbiFunction } from "abitype";
import type { ReadContractResult } from "../read-contract.js";
import { decodeFunctionResult } from "../../abi/decode.js";
import { extractError } from "../extract-error.js";
import type { Prettify } from "../../utils/type-utils.js";

type SimulateOptions<abi extends Abi, abiFn extends AbiFunction> = {
transaction: PreparedTransaction<abi, abiFn>;
account?: Partial<Account> | undefined;
};
type SimulateOptions<abi extends Abi, abiFn extends AbiFunction> = Prettify<
{
transaction: PreparedTransaction<abi, abiFn>;
} & (
| {
account: Account;
from?: never;
wallet?: never;
}
| {
account?: never;
from?: string;
wallet?: never;
}
| {
account?: never;
from?: never;
wallet?: Wallet;
}
)
>;

/**
* Simulates the execution of a transaction.
Expand Down Expand Up @@ -44,9 +62,19 @@ export async function simulateTransaction<
resolvePromisedValue(options.transaction.value),
]);

// from is:
// 1. the user specified from address
// 2. the passed in account address
// 3. the passed in wallet's account address
const from =
options.from ??
options.account?.address ??
options.wallet?.getAccount()?.address ??
undefined;

const serializedTx = formatTransactionRequest({
data,
from: options.account?.address,
from,
to,
value,
accessList,
Expand Down

0 comments on commit ecb3d1e

Please sign in to comment.