2

I’m using react typescript, not next js, to make a candy machine v3 minting dapp using metaplex js. The mint is paid in Spl-token instead of Sol.

How do you check and flag if user does not have enough tokens before attempting to mint? The default alert in phantom wallet is vague and might lead users to believe the site doesn’t work.

2 Answers 2

2

There are a few ways to do this. Generally, you'll need to find the associated token account (the user's account that hold's the token, which is derived from the user's wallet address and the mint address). You'll then want to compare the balance of that account to the mint cost. One way to do that is (connection.getTokenAccountBalance). In your dapp, you'll need to fetch this, store the state, and maybe apply a disabled feature to your button with a text notice. Example:

import React, { useState, useEffect } from "react";
import { PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddress } from "@solana/spl-token";
import { findAssociatedTokenAccountPda } from "@metaplex-foundation/js";
import { clusterApiUrl, Connection, RpcResponseAndContext, TokenAmount } from "@solana/web3.js";

let connection = new Connection(clusterApiUrl("devnet"), "confirmed"); // or useConnection or whatever you're using

async function sufficientBalance(owner: PublicKey, mint: PublicKey, price: number): Promise<boolean> {
  const ata = await getAssociatedTokenAddress(mint, owner); // or await findAssociatedTokenAccountPda(mint, owner) (if you are using @metaplex-foundation/js)
  try {
    const result: RpcResponseAndContext<TokenAmount> = (
      await connection.getTokenAccountBalance(ata, "confirmed")
    );
    if (result.value.uiAmount === null || result.value.uiAmount === undefined) {
      console.warn(`User (${owner.toBase58()}) may not have a USDC account or balance is unavailable.`);
      return false;
    }
    return result.value.uiAmount > price;
  } catch (error) {
    console.error(`Error while checking the token account balance for user (${owner.toBase58()}):`, error);
    return false;
  }
}

const App = () => {
  const [buttonEnabled, setButtonEnabled] = useState(false);

  useEffect(() => {
    async function checkBalance() {
      const ownerPublicKey = new PublicKey("YOUR_OWNER_PUBLIC_KEY"); // or use useWallet or whatever you're using
      const mintPublicKey = new PublicKey("YOUR_MINT_PUBLIC_KEY"); // update with your mint
      const price = 10; // update with your price

      const hasSufficientBalance = await sufficientBalance(ownerPublicKey, mintPublicKey, price);
      setButtonEnabled(hasSufficientBalance);
    }

    checkBalance();
  }, []);
  const displayText = buttonEnabled ? "Mint" : "Disabled";

  return (
    <div>
      <button disabled={!buttonEnabled} onClick={() => alert("Minted!")}>
        {displayText}
      </button>
    </div>
  );
};

export default App;
4
  • Thanks for the help. How would you get ownerPublickey using useWallet? I’m getting an error. Apr 2, 2023 at 2:30
  • Also, findAssociatedTokenAccountPda isn’t available in metaplex-foundation/js 0.18.3 thankfully there was 2 options. Apr 2, 2023 at 2:37
  • I believe you can get the ownerkey from something like const {publicKey} = useWallet();
    – AMilz
    Apr 3, 2023 at 16:23
  • try const ata = await getAssociatedTokenAddress(mint, owner); import getAssociatedTokenAddress from @solana/spl-token
    – AMilz
    Apr 3, 2023 at 16:26
0

I trying to modify Amilz's answer to fit my app. Instead of disabling the button, I want the button dialogue to alert the user. The only problem is that the function reads false even if true. I suspect it's how I have ownerPublicKey set up.

const Home = (props: HomeProps) => {
  const { connect, connected, publicKey, wallet } = useWallet();
  const [buttonEnabled, setButtonEnabled] = useState(false);
  
  const metaplex = new Metaplex(props.connection);

  async function sufficientBalance(owner: PublicKey, mint: PublicKey, price: number): Promise<boolean> {
    const ata = await getAssociatedTokenAddress(mint, owner); // or await findAssociatedTokenAccountPda(mint, owner) (if you are using @metaplex-foundation/js)
    try {
      const result: RpcResponseAndContext<TokenAmount> = (
        await props.connection.getTokenAccountBalance(ata, "confirmed")
      );
      if (result.value.uiAmount === null || result.value.uiAmount === undefined) {
        console.warn(`User (${owner.toBase58()}) may not have a USDC account or balance is unavailable.`);
        return false;
      }
      return result.value.uiAmount > price;
    } catch (error) {
      console.error(`Error while checking the token account balance for user (${owner.toBase58()}):`, error);
      return false;
    }
  }

  const getPayMintButtonContent = () => {
    if (!candyMachine) {
      return "Loading...";
    }

    if (isUserMinting) {
      return "Minting in progress..";
    } else if (candyMachine.itemsRemaining === 0) {
      return "Sold out";
    } else {
      return displayText;
    }
  };

  useEffect(() => {
    async function checkBalance() {
      const ownerPublicKey = new PublicKey(wallet!.adapter.connected); // or use useWallet or whatever you're using
      const mintPublicKey = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // update with your mint
      const price = 1; // update with your price

      const hasSufficientBalance = await sufficientBalance(ownerPublicKey, mintPublicKey, price);
      setButtonEnabled(hasSufficientBalance);
    }

    checkBalance();
  }, [wallet]);
  const displayText = buttonEnabled ? "USDC" : "Ya broke? Maybe ask an uncle";

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.