4

I'm trying to create a qr code so people can scan it and mint a cNFT. I'm using a transaction request as shown in the Solana Pay docs and using Metaplex Bubblegum for state compression. This is on Devnet for now.

My question here is that I'm signing a transaction with the authority Keypair, but the API logs return 2 signatures, and I don’t understand why...nor why a null one (the null one corresponds to the pubkey of the wallet requesting the transaction) ?!?!?!

Can someone explain to me what the problem is?

Thanks a lot :)

Here’s the relevant part of my code:

let accountField = req.body?.account;
if (!accountField) throw new Error('missing account');
const user = new PublicKey(accountField)

const authoritySecret = JSON.parse(process.env.AUTHORITY_KEY ?? "") as number[]
const authoritySecretKey = Uint8Array.from(authoritySecret)
const authority = Keypair.fromSecretKey(authoritySecretKey)

const tree = new PublicKey("ERkz...X97T");
const collectionMint = new PublicKey("3Xfk...Fvfz");
const ix = await createMintCNFTInstruction(tree, user, authority.publicKey, collectionMint);

let transaction = new Transaction();
transaction.add(ix);

const connection = new Connection(devnet url here)
const bh = await connection.getLatestBlockhash();
transaction.recentBlockhash = bh.blockhash;
transaction.feePayer = user

transaction = Transaction.from(transaction.serialize({
    verifySignatures: false,
    requireAllSignatures: false,
}));

transaction.sign(authority)

Here's the createMintCNFTInstruction function:

async function createMintCNFTInstruction(merkleTree: PublicKey, user: PublicKey, authority: PublicKey, collectionMint: PublicKey) {

    const [treeAuthority, _bump] = PublicKey.findProgramAddressSync(
        [merkleTree.toBuffer()],
        BUBBLEGUM_PROGRAM_ID,
    );

    const [collectionMetadataAccount, _b1] = PublicKey.findProgramAddressSync(
        [
            Buffer.from("metadata", "utf8"),
            TOKEN_METADATA_PROGRAM_ID.toBuffer(),
            collectionMint.toBuffer(),
        ],
        TOKEN_METADATA_PROGRAM_ID
    );

    const [collectionEditionAccount, _b2] = PublicKey.findProgramAddressSync(
        [
            Buffer.from("metadata", "utf8"),
            TOKEN_METADATA_PROGRAM_ID.toBuffer(),
            collectionMint.toBuffer(),
            Buffer.from("edition", "utf8"),
        ],
        TOKEN_METADATA_PROGRAM_ID
    );

    const [bgumSigner, __] = PublicKey.findProgramAddressSync(
        [Buffer.from("collection_cpi", "utf8")],
        BUBBLEGUM_PROGRAM_ID
    );

    const tix = await createMintToCollectionV1Instruction({
        treeAuthority: treeAuthority,
        leafOwner: user,
        leafDelegate: user,
        merkleTree: merkleTree,
        payer: user,
        treeDelegate: authority,
        logWrapper: SPL_NOOP_PROGRAM_ID,
        compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
        collectionAuthority: authority,
        collectionAuthorityRecordPda: BUBBLEGUM_PROGRAM_ID,
        collectionMint: collectionMint,
        collectionMetadata: collectionMetadataAccount,
        editionAccount: collectionEditionAccount,
        bubblegumSigner: bgumSigner,
        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
    }, {
        metadataArgs: {
            collection: { key: collectionMint, verified: false },
            creators: [],
            isMutable: true,
            name: "NameHere",
            primarySaleHappened: true,
            sellerFeeBasisPoints: 0,
            symbol: "CAS",
            uri: "https://shdw-drive.genesysgo.net/....json",
            uses: null,
            tokenStandard: TokenStandard.NonFungible,
            editionNonce: null,
            tokenProgramVersion: TokenProgramVersion.Original
        }
    });
    return tix;


}

Here’s the API logs:

the transaction.signature:  [
  {
    signature: null,
    publicKey: PublicKey [PublicKey(c9M…GHK)] {
      _bn: <BN: 900c8…ecce>
    }
  },
  {
    signature: <Buffer 2a b1 ... 14 more bytes>,
    publicKey: PublicKey [PublicKey(E8a…pD)] {
      _bn: <BN: c3189…196>
    }
  }
]
2
  • ritchbeau: I'm much interested if you figure out to get a soloution on your question/problem here with Solana Pay QR code. I'm not so much into the stuf about Solana cNFTS and minting just like you. Then it would be a big learning opportunity for me to try and understand your code regarding this sucject. Can i ask you if yu might share your full code - not just only the relevant code by any reason - if you have a GitHub repo or other filesharing option ?
    – TomCatzy
    Commented Jun 25, 2023 at 23:24
  • Are you trying to send this transaction yourself? If so, that would be the problem. Like many of the other comments, the user scanning the Solana Pay QR code is the fee payer. So they must sign the transaction. Your app should be returning a base64 version of the serialized transaction back to the user scanning the QR code. Then the user will be able to sign and send the transaction
    – nickfrosty
    Commented Jul 14, 2023 at 1:27

2 Answers 2

2

You are adding the user as feepayer, which means it needs to sign, but looks like you are not signing with it. So the signature would be empty.

So you could probably use the authority key as feepayer which should solve it. Or partially sign with the user keypair as well.

For the second signature: Can you add the code from the createMintCNFTInstruction its probably adding another signer there or its the signature of the authority.

1
  • 1
    Yes I set up the user as payer. I'll change it to make the authority pay to see. I've edited my post to add the createMintCNFTInstruction function
    – ritchbeau
    Commented Jun 16, 2023 at 15:17
0

If you're encountering an error when scanning on mobile (Solflare) that says, Invalid transaction: transaction failed to sanitize accounts offsets correctly, the issue may be due to trying to add a signer after serializing the transaction. Try adding the additional signer before serializing the tx like this:

transaction.feePayer = user
transaction.sign(authority)

transaction = Transaction.from(transaction.serialize({
    verifySignatures: false,
    requireAllSignatures: false,
}));

Since you're setting the feepayer as the user scanning the qrcode, the first signature should be null as you're seeing. This is because the user hasn't signed yet when the transaction is built.

For your transaction, two signatures are expected since the payer is the user and the treeDelegate and collectionAuthority both the authority. createMintToCollectionV1Instruction requires the payer, treeDelegate, and collectionAuthority to be signers.

Also, if you're testing Solana Pay QrCodes locally, you'll need something like https://ngrok.com/ for an https URL. (If you just use http://localhost:3000/ scanning won't work)

Lastly, it seems that compressed nfts don't currently show up on mobile wallets (solflare/phantom) when connected to devnet (they work fine for mainnet though). So if you don't see them show up in your mobile wallet after scanning but the transaction goes through, try adding the wallet to a browser wallet instead to check.

5
  • If this possible, my problem is with const [treeAuthority, _bump] = PublicKey.findProgramAddressSync( [merkleTree.toBuffer()], BUBBLEGUM_PROGRAM_ID, ); I don’t understand why the merkleTree.toBase58() = BYcDWRYDZsCQeU9Ew82PG5Gnkxkq2b9N8GWeorN5gdeU which seems to be an address that does not exist.
    – ritchbeau
    Commented Jun 16, 2023 at 22:02
  • You'll need to first create a new tree. Creating a tree requires two instructions, createAllocTreeIx from @solana/spl-account-compression and createCreateTreeInstruction from @metaplex-foundation/mpl-bubblegum. You can use this as a reference: github.com/Unboxed-Software/cnft-client/blob/master/src/…
    – john
    Commented Jun 16, 2023 at 22:09
  • I've created the tree already : ERkzt2Zyau5nnSf877FCQNzQRRxW5xaMJEt4DQhYX97T and in the code I use const tree = new PublicKey("ERkzt2Zyau5nnSf877FCQNzQRRxW5xaMJEt4DQhYX97T")
    – ritchbeau
    Commented Jun 16, 2023 at 22:12
  • Is BYcDWRYDZsCQeU9Ew82PG5Gnkxkq2b9N8GWeorN5gdeU the treeAuthority then? If so, its a PDA that's used by the Bubblegum program as both the authority of the tree, and to create a TreeConfig account. Did you use the createCreateTreeInstruction after creating the account for the tree?
    – john
    Commented Jun 16, 2023 at 22:17
  • Here is a repo you reference for minting cnfts with Solana Pay
    – john
    Commented Jun 16, 2023 at 23:22

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.