solana-foundation / developer-content

Collection of Developer content for exploring, learning, and building in the Solana ecosystem.
https://solana.com/developers
81 stars 96 forks source link

Combine Immutable Owner extension and Non-transferable extension together fail. #215

Open andrewzhang97 opened 1 month ago

andrewzhang97 commented 1 month ago

Following guides, https://solana.com/developers/guides/token-extensions/non-transferable#build-instructions and https://solana.com/developers/guides/token-extensions/immutable-owner#conclusion

They can work separately well. However, if I put them together, immutable-owner always show Error processing Instruction 2: invalid account data for instruction. My code is based on guide.

import * as anchor from "@coral-xyz/anchor";
import {Program, web3} from "@coral-xyz/anchor";
import { ZkmeSol } from "../target/types/zkme_sol";
import {it} from "mocha";
import {Connection, Keypair, sendAndConfirmTransaction, SystemProgram, Transaction} from "@solana/web3.js";
import {
    AuthorityType,
    createInitializeAccountInstruction,
    createInitializeImmutableOwnerInstruction,
    createInitializeMintInstruction,
    createInitializeNonTransferableMintInstruction,
    createMint,
    ExtensionType,
    getAccountLen, getMintLen, setAuthority,
    TOKEN_2022_PROGRAM_ID
} from "@solana/spl-token";
import {min} from "bn.js";

//anchor test
describe("zkme_sol", () => {
    // Configure the client to use the local cluster.
    anchor.setProvider(anchor.AnchorProvider.env());

    const program = anchor.workspace.ZkmeSol as Program<ZkmeSol>;
    const zkmeSeed = anchor.utils.bytes.utf8.encode("zkme_conf");

    let adminPubKey;

    before( async () => {
        [adminPubKey] = await anchor.web3.PublicKey.findProgramAddress(
            [zkmeSeed, anchor.AnchorProvider.env().wallet.publicKey.toBytes()],
            program.programId
        );
    });
    it( "Is createAdmin", async () => {
        const payer = web3.Keypair.generate();

// Connection to devnet cluster
        const connection = new Connection('http://127.0.0.1:8899', 'confirmed');

// Transaction signature returned from sent transaction
        let transactionSignature: string;

        const airdropSignature = await connection.requestAirdrop(
            payer.publicKey,
            web3.LAMPORTS_PER_SOL, // 1 SOL
        );
        await connection.confirmTransaction(airdropSignature);
        const balanceA = await connection.getBalance(payer.publicKey)
        console.log("airdropSignature success", balanceA);

        // Authority that can mint new tokens
        const mintAuthority = payer.publicKey;
// Decimals for Mint Account
        const decimals = 2;

        const mintLen = getMintLen([ExtensionType.NonTransferable]);
// Minimum lamports required for Mint Account
        let lamports = await connection.getMinimumBalanceForRentExemption(mintLen);

        const mintKeypair = Keypair.generate();
// Address for Mint Account
        const mint = mintKeypair.publicKey;

        const createAccountInstructionA = SystemProgram.createAccount({
            fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
            newAccountPubkey: mint, // Address of the account to create
            space: mintLen, // Amount of bytes to allocate to the created account
            lamports, // Amount of lamports transferred to created account
            programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
        });
        console.log("createAccountInstruction",createAccountInstructionA)

// Instruction to initialize the NonTransferable Extension
        const initializeNonTransferableMintInstructionA =
            createInitializeNonTransferableMintInstruction(
                mint, // Mint Account address
                TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
            );
        console.log("initializeNonTransferableMintInstruction",initializeNonTransferableMintInstructionA)

        // Instruction to initialize Mint Account data
        const initializeMintInstructionA = createInitializeMintInstruction(
            mint, // Mint Account Address
            decimals, // Decimals of Mint
            anchor.AnchorProvider.env().publicKey, // Designated Mint Authority
            null, // Optional Freeze Authority
            TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
        );
        console.log("initializeMintInstruction",initializeMintInstructionA)
        // Add instructions to new transaction
        const transactionA = new Transaction().add(
            createAccountInstructionA,
            initializeNonTransferableMintInstructionA,
            initializeMintInstructionA,
        );
        console.log("mint:",mint)

// Send transaction
        transactionSignature = await sendAndConfirmTransaction(
            connection,
            transactionA,
            [payer, mintKeypair], // Signers
        );

        console.log(
            "transactionSignature",transactionSignature
        )

        // Random keypair to use as owner of Token Account
        const tokenAccountKeypair = Keypair.generate();
// Address for Token Account
        const tokenAccount = tokenAccountKeypair.publicKey;

        // Size of Token Account with extension
        const accountLen = getAccountLen([ExtensionType.ImmutableOwner]);
// Minimum lamports required for Token Account
         lamports = await connection.getMinimumBalanceForRentExemption(accountLen);

        // Instruction to invoke System Program to create new account
        const createAccountInstruction = SystemProgram.createAccount({
            fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
            newAccountPubkey: tokenAccount, // Address of the account to create
            space: accountLen, // Amount of bytes to allocate to the created account
            lamports, // Amount of lamports transferred to created account
            programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
        });

        // Instruction to initialize the ImmutableOwner Extension
        const initializeImmutableOwnerInstruction =
            createInitializeImmutableOwnerInstruction(
                tokenAccount, // Token Account address
                TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
            );

        // Instruction to initialize Token Account data
        const initializeAccountInstruction = createInitializeAccountInstruction(
            tokenAccount, // Token Account Address
            mint, // Mint Account
            payer.publicKey, // Token Account Owner
            TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
        );
        console.log("mint:",mint)

        // Add instructions to new transaction
        const transaction = new Transaction().add(
            createAccountInstruction,
           // initializeImmutableOwnerInstruction,
            initializeAccountInstruction,
        );

// Send transaction
        transactionSignature = await sendAndConfirmTransaction(
            connection,
            transaction,
            [payer, tokenAccountKeypair], // Signers
        );

        console.log(
            "\nCreate Token Account:",
            `https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`,
        );

    })

    });

Proposed Solution