fix: transaction button gas estimation for pay (#3178)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"thirdweb": patch
|
||||
---
|
||||
|
||||
Fix gas estimation in transaction button
|
||||
@@ -3,6 +3,8 @@ import { Button } from "@/components/ui/button";
|
||||
import { metadataBase } from "@/lib/constants";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import { CodeExample } from "../../../components/code/code-example";
|
||||
import { StyledPayTransaction } from "../../../components/styled-pay-transaction";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase,
|
||||
@@ -55,6 +57,46 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section className="container px-4 md:px-6 space-y-8">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-4xl font-semibold tracking-tight">
|
||||
Transaction Button
|
||||
</h2>
|
||||
<p className="max-w-[600px]">
|
||||
Transaction Button is a handy component that handles transactions.
|
||||
<br />
|
||||
If your user doesn't have enough funds for that transaction, a
|
||||
pre-filled pay modal will appear with the exact amount needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<CodeExample
|
||||
preview={<StyledPayTransaction />}
|
||||
code={`import { TransactionButton } from "thirdweb/react";
|
||||
|
||||
|
||||
function App() {
|
||||
const account = useActiveAccount();
|
||||
|
||||
return (
|
||||
<TransactionButton
|
||||
transaction={() => {
|
||||
// any transaction works
|
||||
return claimTo({
|
||||
contract,
|
||||
quantity: 1n,
|
||||
tokenId: 0n,
|
||||
to: account?.address,
|
||||
});
|
||||
}}
|
||||
>
|
||||
Buy for 10 MATIC
|
||||
</TransactionButton>
|
||||
);
|
||||
};`}
|
||||
lang="tsx"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import { THIRDWEB_CLIENT } from "@/lib/client";
|
||||
import { useTheme } from "next-themes";
|
||||
import { getContract } from "thirdweb";
|
||||
import { polygon } from "thirdweb/chains";
|
||||
import { claimTo } from "thirdweb/extensions/erc1155";
|
||||
import { TransactionButton, useActiveAccount } from "thirdweb/react";
|
||||
|
||||
const contract = getContract({
|
||||
address: "0x96B30d36f783c7BC68535De23147e2ce65788e93",
|
||||
chain: polygon,
|
||||
client: THIRDWEB_CLIENT,
|
||||
});
|
||||
|
||||
export function StyledPayTransaction() {
|
||||
const account = useActiveAccount();
|
||||
const { theme } = useTheme();
|
||||
|
||||
return (
|
||||
<TransactionButton
|
||||
transaction={() => {
|
||||
if (!account) throw new Error("No active account");
|
||||
return claimTo({
|
||||
contract,
|
||||
quantity: 1n,
|
||||
tokenId: 0n,
|
||||
to: account?.address,
|
||||
});
|
||||
}}
|
||||
payModal={{
|
||||
theme: theme === "light" ? "light" : "dark",
|
||||
}}
|
||||
>
|
||||
Buy for 10 MATIC
|
||||
</TransactionButton>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
@@ -22,19 +18,10 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"target": "ES2017"
|
||||
"target": "ES2020"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { sendTransaction } from "../../../../transaction/actions/send-transactio
|
||||
import type { WaitForReceiptOptions } from "../../../../transaction/actions/wait-for-tx-receipt.js";
|
||||
import type { PreparedTransaction } from "../../../../transaction/prepare-transaction.js";
|
||||
import { resolvePromisedValue } from "../../../../utils/promise/resolve-promised-value.js";
|
||||
import type { Account } from "../../../../wallets/interfaces/wallet.js";
|
||||
import {
|
||||
type GetWalletBalanceResult,
|
||||
getWalletBalance,
|
||||
@@ -105,7 +104,7 @@ export function useSendTransactionCore(
|
||||
chain: tx.chain,
|
||||
client: tx.client,
|
||||
}),
|
||||
getTotalTxCostForBuy(tx, account),
|
||||
getTotalTxCostForBuy(tx, account?.address),
|
||||
]);
|
||||
|
||||
const walletBalanceWei = walletBalance.value;
|
||||
@@ -139,18 +138,31 @@ export function useSendTransactionCore(
|
||||
|
||||
export async function getTotalTxCostForBuy(
|
||||
tx: PreparedTransaction,
|
||||
account?: Account,
|
||||
from?: string,
|
||||
) {
|
||||
const gasCost = await estimateGasCost({
|
||||
transaction: tx,
|
||||
account,
|
||||
});
|
||||
try {
|
||||
const gasCost = await estimateGasCost({
|
||||
transaction: tx,
|
||||
from,
|
||||
});
|
||||
|
||||
const bufferCost = gasCost.wei / 10n;
|
||||
const bufferCost = gasCost.wei / 10n;
|
||||
|
||||
// Note: get tx.value AFTER estimateGasCost
|
||||
const txValue = await resolvePromisedValue(tx.value);
|
||||
// Note: get tx.value AFTER estimateGasCost
|
||||
const txValue = await resolvePromisedValue(tx.value);
|
||||
|
||||
// add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
|
||||
return gasCost.wei + bufferCost + (txValue || 0n);
|
||||
// add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
|
||||
return gasCost.wei + bufferCost + (txValue || 0n);
|
||||
} catch (e) {
|
||||
if (from) {
|
||||
// try again without passing from
|
||||
return await getTotalTxCostForBuy(tx);
|
||||
}
|
||||
// fallback if both fail, use the tx value + 1% buffer
|
||||
const value = await resolvePromisedValue(tx.value);
|
||||
if (!value) {
|
||||
return 0n;
|
||||
}
|
||||
return value + value / 100n;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,10 @@ export function useBuyTxStates(options: {
|
||||
}
|
||||
|
||||
try {
|
||||
const totalCost = await getTotalTxCostForBuy(buyForTx.tx, account);
|
||||
const totalCost = await getTotalTxCostForBuy(
|
||||
buyForTx.tx,
|
||||
account?.address,
|
||||
);
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
|
||||
@@ -23,7 +23,10 @@ export type EstimateGasOptions = Prettify<
|
||||
|
||||
export type EstimateGasResult = bigint;
|
||||
|
||||
const cache = new WeakMap<PreparedTransaction, Promise<EstimateGasResult>>();
|
||||
const cache = new WeakMap<
|
||||
PreparedTransaction & { from: string | undefined },
|
||||
Promise<EstimateGasResult>
|
||||
>();
|
||||
|
||||
/**
|
||||
* Estimates the gas required to execute a transaction. The gas is returned as a `bigint` and in gwei units.
|
||||
@@ -41,9 +44,15 @@ const cache = new WeakMap<PreparedTransaction, Promise<EstimateGasResult>>();
|
||||
export async function estimateGas(
|
||||
options: EstimateGasOptions,
|
||||
): Promise<EstimateGasResult> {
|
||||
if (cache.has(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 ?? undefined;
|
||||
const txWithFrom = { ...options.transaction, from };
|
||||
if (cache.has(txWithFrom)) {
|
||||
// biome-ignore lint/style/noNonNullAssertion: the `has` above ensures that this will always be set
|
||||
return cache.get(options.transaction)!;
|
||||
return cache.get(txWithFrom)!;
|
||||
}
|
||||
const { account } = options;
|
||||
const promise = (async () => {
|
||||
@@ -84,11 +93,6 @@ 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 ?? undefined;
|
||||
try {
|
||||
let gas = await eth_estimateGas(
|
||||
rpcRequest,
|
||||
@@ -110,6 +114,6 @@ export async function estimateGas(
|
||||
});
|
||||
}
|
||||
})();
|
||||
cache.set(options.transaction, promise);
|
||||
cache.set(txWithFrom, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user