11"use client" ;
22import { useRef } from "react" ;
3- import { encode , getContract , type ThirdwebClient } from "thirdweb" ;
3+ import {
4+ encode ,
5+ estimateGasCost ,
6+ getContract ,
7+ getGasPrice ,
8+ type PreparedTransaction ,
9+ type ThirdwebClient ,
10+ toEther ,
11+ } from "thirdweb" ;
412import { deployERC721Contract , deployERC1155Contract } from "thirdweb/deploys" ;
513import { multicall } from "thirdweb/extensions/common" ;
614import {
@@ -14,7 +22,8 @@ import {
1422} from "thirdweb/extensions/erc1155" ;
1523import { grantRole } from "thirdweb/extensions/permissions" ;
1624import { useActiveAccount } from "thirdweb/react" ;
17- import { maxUint256 } from "thirdweb/utils" ;
25+ import { maxUint256 , resolvePromisedValue } from "thirdweb/utils" ;
26+ import { getWalletBalance } from "thirdweb/wallets" ;
1827import { create7702MinimalAccount } from "thirdweb/wallets/smart" ;
1928import { revalidatePathAction } from "@/actions/revalidate" ;
2029import { reportContractDeployed } from "@/analytics/report" ;
@@ -39,6 +48,10 @@ export function CreateNFTPage(props: {
3948 const contractAddressRef = useRef < string | undefined > ( undefined ) ;
4049 const getChain = useGetV5DashboardChain ( ) ;
4150 const sendAndConfirmTx = useSendAndConfirmTx ( ) ;
51+ const sendAndConfirmTxNoPayModal = useSendAndConfirmTx ( {
52+ payModal : false ,
53+ } ) ;
54+
4255 function getAccount ( params : { gasless : boolean } ) {
4356 if ( ! activeAccount ) {
4457 throw new Error ( "Wallet is not connected" ) ;
@@ -218,6 +231,10 @@ export function CreateNFTPage(props: {
218231 count : number ;
219232 } ;
220233 gasless : boolean ;
234+ onNotEnoughFunds : ( data : {
235+ requiredAmount : string ;
236+ balance : string ;
237+ } ) => void ;
221238 } ) {
222239 const { values, batch } = params ;
223240 const contract = getDeployedContract ( {
@@ -277,7 +294,44 @@ export function CreateNFTPage(props: {
277294 data : encodedTransactions ,
278295 } ) ;
279296
280- await sendAndConfirmTx . mutateAsync ( tx ) ;
297+ // if there are more than one batches, on the first batch, we check if the user has enough funds to cover the cost of all the batches ->
298+ // calculate the cost of one batch, multiply by the number of batches to get the total cost
299+ // if the user does not have enough funds, call the onNotEnoughFunds callback
300+
301+ const totalBatches = Math . ceil ( values . nfts . length / batch . count ) ;
302+
303+ if ( batch . startIndex === 0 && totalBatches > 1 && ! params . gasless ) {
304+ if ( ! activeAccount ) {
305+ throw new Error ( "Wallet is not connected" ) ;
306+ }
307+
308+ const costPerBatch = await getTotalTransactionCost ( {
309+ tx : tx ,
310+ from : activeAccount . address ,
311+ } ) ;
312+
313+ const totalCost = costPerBatch * BigInt ( totalBatches ) ;
314+
315+ const walletBalance = await getWalletBalance ( {
316+ address : activeAccount . address ,
317+ chain : contract . chain ,
318+ client : contract . client ,
319+ } ) ;
320+
321+ if ( walletBalance . value < totalCost ) {
322+ params . onNotEnoughFunds ( {
323+ balance : toEther ( walletBalance . value ) ,
324+ requiredAmount : toEther ( totalCost ) ,
325+ } ) ;
326+ throw new Error (
327+ `Not enough funds: Required ${ toEther ( totalCost ) } , Balance ${ toEther ( walletBalance . value ) } ` ,
328+ ) ;
329+ }
330+
331+ await sendAndConfirmTxNoPayModal . mutateAsync ( tx ) ;
332+ } else {
333+ await sendAndConfirmTx . mutateAsync ( tx ) ;
334+ }
281335 }
282336
283337 async function handleSetAdmins ( params : {
@@ -390,3 +444,41 @@ function transformSocialUrls(
390444 { } as Record < string , string > ,
391445 ) ;
392446}
447+
448+ async function getTransactionGasCost ( tx : PreparedTransaction , from ?: string ) {
449+ try {
450+ const gasCost = await estimateGasCost ( {
451+ from,
452+ transaction : tx ,
453+ } ) ;
454+
455+ const bufferCost = gasCost . wei / 10n ;
456+
457+ // Note: get tx.value AFTER estimateGasCost
458+ // add 10% extra gas cost to the estimate to ensure user buys enough to cover the tx cost
459+ return gasCost . wei + bufferCost ;
460+ } catch {
461+ if ( from ) {
462+ // try again without passing from
463+ return await getTransactionGasCost ( tx ) ;
464+ }
465+ // fallback if both fail, use the tx value + 1M * gas price
466+ const gasPrice = await getGasPrice ( {
467+ chain : tx . chain ,
468+ client : tx . client ,
469+ } ) ;
470+
471+ return 1_000_000n * gasPrice ;
472+ }
473+ }
474+
475+ async function getTotalTransactionCost ( params : {
476+ tx : PreparedTransaction ;
477+ from ?: string ;
478+ } ) {
479+ const [ txValue , txGasCost ] = await Promise . all ( [
480+ resolvePromisedValue ( params . tx . value ) ,
481+ getTransactionGasCost ( params . tx , params . from ) ,
482+ ] ) ;
483+ return ( txValue || 0n ) + txGasCost ;
484+ }
0 commit comments