Solana transfer instruction for programmable NFTs

340 views Asked by At

Using the following transfer instruction, I get Error: Account is frozen when transferring programmable NFTs.

Is there another method on the spl-token library or do I need to manually create the instruction i.e. https://github.com/metaplex-foundation/mpl-token-metadata/blob/main/programs/token-metadata/js/src/generated/instructions/Transfer.ts#L94

export const formTransaction = async (
  tokenAddress: PublicKey,
  recipientAddress: string,
  sendAmount: bigint,
  publicKey: PublicKey,
  connection: Connection,
) => {
    const { associatedAddress: fromTokenAccount, instruction: fromIxs } =
      await getOrCreateAssociatedTokenAccountIxs(
        connection,
        publicKey,
        publicKey,
        tokenAddress,
      );

    const { associatedAddress: toTokenAddress, instruction: toIxs } =
      await getOrCreateAssociatedTokenAccountIxs(
        connection,
        publicKey,
        new PublicKey(recipientAddress),
        tokenAddress,
      );

    const txsInstruction = createTransferInstruction(
      fromTokenAccount,
      toTokenAddress,
      publicKey,
      sendAmount,
    );

    const allIxs = [fromIxs, toIxs, txsInstruction].filter(
      (ixs) => ixs,
    ) as TransactionInstruction[];

   return new Transaction().add(...allIxs);
};
const getOrCreateAssociatedTokenAccountIxs = async (
  connection: Connection,
  payerKey: PublicKey,
  ownerKey: PublicKey,
  mintKey: PublicKey,
): Promise<{
  associatedAddress: PublicKey;
  instruction: TransactionInstruction | null;
}> => {
  const associatedAddress = await getAssociatedTokenAddress(mintKey, ownerKey);
  const accountInfo = await connection.getAccountInfo(associatedAddress);
  if (accountInfo !== null) {
    return { associatedAddress, instruction: null };
  }
  const createAssociatedAccountIx = createAssociatedTokenAccountInstruction(
    payerKey,
    associatedAddress,
    ownerKey,
    mintKey,
  );
  return { associatedAddress, instruction: createAssociatedAccountIx };
};
1

There are 1 answers

0
Hyetigran On

To send a PNFT, you'll need to create the instruction by feeding additional accounts

export const formTransactionPnft = async ({
  wallet,
  nft,
  amount,
  destPublicKey,
}: {
  wallet: AnchorWallet;
  nft: {
    mintAddress: PublicKey;
    masterEditionAddress: PublicKey;
    metadataAddress: PublicKey;
    ruleSetAddress: PublicKey | null;
  };
  amount: bigint;
  destPublicKey: PublicKey;
}) => {
  const { TOKEN_AUTH_RULES_PROGRAM_ID } = getClusterConstants(
    'TOKEN_AUTH_RULES_PROGRAM_ID',
  );
  const owner = getAssociatedTokenAddressSync(
    nft.mintAddress,
    wallet.publicKey,
    true,
  );

  const destination = getAssociatedTokenAddressSync(
    nft.mintAddress,
    destPublicKey,
  );

  const ownerRecord = getTokenRecordAddress(nft.mintAddress, owner);
  const destinationRecord = getTokenRecordAddress(nft.mintAddress, destination);

  const transferAcccounts: TransferInstructionAccounts = {
    authority: wallet.publicKey,
    tokenOwner: wallet.publicKey,
    token: owner,
    metadata: nft.metadataAddress,
    mint: nft.mintAddress,
    edition: nft.masterEditionAddress,
    destinationOwner: destPublicKey,
    destination,
    payer: wallet.publicKey,
    splTokenProgram: TOKEN_PROGRAM_ID,
    splAtaProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
    systemProgram: SystemProgram.programId,
    sysvarInstructions: SYSVAR_INSTRUCTIONS_PUBKEY,
    authorizationRules: nft.ruleSetAddress ?? undefined,
    authorizationRulesProgram: new PublicKey(TOKEN_AUTH_RULES_PROGRAM_ID),
    ownerTokenRecord: ownerRecord,
    destinationTokenRecord: destinationRecord,
  };

  const transferArgs: TransferInstructionArgs = {
    transferArgs: {
      __kind: 'V1',
      amount: Number(amount),
      authorizationData: null,
    },
  };

  const instruction = createTransferInstructionProgrammableNft(
    transferAcccounts,
    transferArgs,
  );

  const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
    units: 400_000,
  });

  return new Transaction().add(modifyComputeUnits).add(instruction);
};

export const createTransferInstructionProgrammableNft = (
  accounts: TransferInstructionAccounts,
  args: TransferInstructionArgs,
  programId = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'),
) => {
  const [data] = TransferStruct.serialize({
    instructionDiscriminator: transferInstructionDiscriminator,
    ...args,
  });

  const keys: AccountMeta[] = [
    {
      pubkey: accounts.token,
      isWritable: true,
      isSigner: false,
    },
    {
      pubkey: accounts.tokenOwner,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.destination,
      isWritable: true,
      isSigner: false,
    },
    {
      pubkey: accounts.destinationOwner,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.mint,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.metadata,
      isWritable: true,
      isSigner: false,
    },
    {
      pubkey: accounts.edition ?? programId,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.ownerTokenRecord ?? programId,
      isWritable: accounts.ownerTokenRecord != null,
      isSigner: false,
    },
    {
      pubkey: accounts.destinationTokenRecord ?? programId,
      isWritable: accounts.destinationTokenRecord != null,
      isSigner: false,
    },
    {
      pubkey: accounts.authority,
      isWritable: false,
      isSigner: true,
    },
    {
      pubkey: accounts.payer,
      isWritable: true,
      isSigner: true,
    },
    {
      pubkey: accounts.systemProgram ?? SystemProgram.programId,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.sysvarInstructions,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.splTokenProgram,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.splAtaProgram,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.authorizationRulesProgram ?? programId,
      isWritable: false,
      isSigner: false,
    },
    {
      pubkey: accounts.authorizationRules ?? programId,
      isWritable: false,
      isSigner: false,
    },
  ];

  return new TransactionInstruction({
    programId,
    keys,
    data,
  });
};