metaplex-foundation / umi-hotline

2 stars 0 forks source link

Burn pNFT using UMI #7

Closed joefitter closed 1 year ago

joefitter commented 1 year ago

Umi version

0.7.5

Code

const instructions = await burnNft(umi, {
        metadata: fromWeb3JsPublicKey(nft.metadataAddress),
        /** NFT owner */
        owner: umi.identity,
        /** Mint of the NFT */
        mint: fromWeb3JsPublicKey(nft.mint.address),
        /** Token account to close */
        tokenAccount: fromWeb3JsPublicKey(await getAssociatedTokenAddress(nft.mint.address, wallet.publicKey!)),
        /** MasterEdition2 of the NFT */
        masterEditionAccount: fromWeb3JsPublicKey(nft.edition.address),
        /** SPL Token Program */
        /** Metadata of the Collection */
        collectionMetadata: nft.collection ? fromWeb3JsPublicKey(nft.collection?.address): undefined
      }).sendAndConfirm(umi)

Error

Token Metadata |    InstructionNotSupported: Instruction not supported for ProgrammableNonFungible assets
Done!
joefitter commented 1 year ago

Hi! Trying to burn a pNFT using UMI - coming up against 0x99 Instruction not supported for pNFT

Is there an alternative method to be used for burning pNFTs?

lorisleiva commented 1 year ago

Yes, you may use burnV1 for this.

https://github.com/metaplex-foundation/mpl-token-metadata/blob/main/clients/js/test/burnV1.test.ts

joefitter commented 1 year ago

Fantastic thank you, I missed this method! <3

joefitter commented 1 year ago

Hey @lorisleiva, thanks again for this - I have it working with everything apart from editions - is there a way with UMI to get the masterEditionMint from the edition parent without scraping the transaction history for the edition? thanks!

joefitter commented 1 year ago

loosely related: is there a UMI method to get the editionMarker PDA? I am currently using the pdas() method from the NftBuilder in the JS SDK, but wondered if this had been implemented in UMI yet?

lorisleiva commented 1 year ago

Hey Joe,

is there a way with UMI to get the masterEditionMint from the edition parent without scraping the transaction history for the edition?

Unfortunately not. That's an issue at the program level where the child edition stores the edition PDA of its parent instead of its mint address which could be used to infer the latter. There might have been a reason for doing this back then but, if so, I don't know that it is haha.

is there a UMI method to get the editionMarker PDA?

That's a good point, I don't think there is. I'll make a PR for this. Would you mind sharing your use case for needing the marker in the first place? I'm trying to make low-level things like that transparent.

lorisleiva commented 1 year ago

Okay you should now have the following helpers on version v3.0.0-alpha.21 of @metaplex-foundation/mpl-token-metadata:

joefitter commented 1 year ago

amazing - thanks so much! My use case for this is just burning any type of token. Particularly NFT Editions in this case.

You can see I needed to switch to JS SDK land at end of this snippet to get the marker (the program was throwing an error when it was omitted)


const digitalAsset = await fetchDigitalAssetWithAssociatedToken(
  umi,
  publicKey(n.nftMint),
  umi.identity.publicKey
)

let masterEditionMint: UmiPublicKey | undefined = undefined
let masterEditionToken
const isEdition =
  isSome(digitalAsset.metadata.tokenStandard) &&
  digitalAsset.metadata.tokenStandard.value === TokenStandard.NonFungibleEdition &&
  !digitalAsset.edition?.isOriginal
if (isEdition) {
  const connection = new Connection(process.env.NEXT_PUBLIC_TXN_RPC_HOST!, { commitment: "confirmed" })
  const sigs = await connection.getSignaturesForAddress(
    toWeb3JsPublicKey(
      !digitalAsset.edition?.isOriginal ? digitalAsset.edition?.parent! : digitalAsset.edition.publicKey
    )
  )
  const sig = sigs[sigs.length - 1]
  const txn = await connection.getTransaction(sig.signature)
  const key = txn?.transaction.message.accountKeys[1]!

  const masterEditionDigitalAsset = await fetchDigitalAssetWithTokenByMint(umi, fromWeb3JsPublicKey(key))
  masterEditionMint = masterEditionDigitalAsset.mint.publicKey
  console.log(base58PublicKey(masterEditionMint))
  masterEditionToken = masterEditionDigitalAsset.token.publicKey
}

let amount = BigInt(1)

if (
  [TokenStandard.Fungible, TokenStandard.FungibleAsset].includes(
    unwrapSome(digitalAsset.metadata.tokenStandard)!
  )
) {
  const ata = await getAssociatedTokenAddress(
    toWeb3JsPublicKey(digitalAsset.mint.publicKey),
    toWeb3JsPublicKey(umi.identity.publicKey)
  )

  const balance = await connection.getTokenAccountBalance(ata)
  amount = BigInt(balance.value.amount)
}

const burnInstruction = burnV1(umi, {
  mint: digitalAsset.mint.publicKey,
  authority: umi.identity,
  tokenOwner: umi.identity.publicKey,
  tokenStandard: isSome(digitalAsset.metadata.tokenStandard)
    ? digitalAsset.metadata.tokenStandard.value
    : 0,
  metadata: digitalAsset.metadata.publicKey,
  collectionMetadata: isSome(digitalAsset.metadata.collection)
    ? findMetadataPda(umi, { mint: digitalAsset.metadata.collection.value.key })
    : undefined,
  edition: isEdition ? digitalAsset.edition?.publicKey : undefined,
  token: isEdition ? digitalAsset.token.publicKey : undefined,
  tokenRecord: isEdition ? digitalAsset.tokenRecord?.publicKey : undefined,
  masterEdition: isEdition
    ? digitalAsset.edition?.isOriginal
      ? digitalAsset.edition.publicKey
      : digitalAsset.edition?.parent
    : undefined,
  masterEditionMint,
  masterEditionToken,
  amount,
  editionMarker:
    isEdition && masterEditionMint
      ? fromWeb3JsPublicKey(
          metaplex
            .nfts()
            .pdas()
            .editionMarker({
              mint: toWeb3JsPublicKey(masterEditionMint!),
              edition: !digitalAsset.edition?.isOriginal
                ? toBigNumber(digitalAsset.edition?.edition.toString()!)
                : toBigNumber(0),
            })
        )
      : undefined,
})
lorisleiva commented 1 year ago

Oh I see yeah that makes sense. If there was an editionNumber argument I could have generated a default value for that account but it should be easier now with the helpers. 🙂