Sfoglia il codice sorgente

added back the burning code

0xPratik 1 anno fa
parent
commit
920c71f25e

+ 3 - 3
compression/cnft-burn/Anchor.toml

@@ -4,15 +4,15 @@
 seeds = false
 skip-lint = false
 
-[programs.localnet]
+[programs.devnet]
 cnft_burn = "FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXXNn"
 
 [registry]
 url = "https://api.apr.dev"
 
 [provider]
-cluster = "Localnet"
-wallet = "/Users/pratik/.config/solana/id.json"
+cluster = "devnet"
+wallet = "~/.config/solana/id.json"
 
 [scripts]
 test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 8 - 6
compression/cnft-burn/package.json

@@ -4,16 +4,18 @@
         "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
     },
     "dependencies": {
-        "@coral-xyz/anchor": "^0.29.0"
+        "@coral-xyz/anchor": "^0.29.0",
+        "@metaplex-foundation/mpl-bubblegum": "^3.0.0",
+        "axios": "^1.6.5"
     },
     "devDependencies": {
-        "chai": "^4.3.4",
-        "mocha": "^9.0.3",
-        "ts-mocha": "^10.0.0",
         "@types/bn.js": "^5.1.0",
         "@types/chai": "^4.3.0",
         "@types/mocha": "^9.0.0",
-        "typescript": "^4.3.5",
-        "prettier": "^2.6.2"
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "prettier": "^2.6.2",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
     }
 }

+ 3 - 0
compression/cnft-burn/programs/cnft-burn/Cargo.toml

@@ -17,3 +17,6 @@ default = []
 
 [dependencies]
 anchor-lang = "0.29.0"
+mpl-bubblegum = {version="1.1.0" }
+spl-account-compression = { version="0.3.0",features = ["no-entrypoint","cpi"] }
+spl-noop = { version = "0.2.0", features = ["no-entrypoint"] }

+ 73 - 2
compression/cnft-burn/programs/cnft-burn/src/lib.rs

@@ -2,14 +2,85 @@ use anchor_lang::prelude::*;
 
 declare_id!("FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXXNn");
 
+#[derive(Clone)]
+pub struct SPLCompression;
+
+impl anchor_lang::Id for SPLCompression {
+    fn id() -> Pubkey {
+        spl_account_compression::id()
+    }
+}
+
 #[program]
 pub mod cnft_burn {
     use super::*;
 
-    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
+    pub fn burn_cnft<'info>(
+        ctx: Context<'_, '_, '_, 'info, BurnCnft<'info>>,
+        root: [u8; 32],
+        data_hash: [u8; 32],
+        creator_hash: [u8; 32],
+        nonce: u64,
+        index: u32,
+    ) -> Result<()> {
+        let tree_config = ctx.accounts.tree_authority.to_account_info();
+        let leaf_owner = ctx.accounts.leaf_owner.to_account_info();
+        let merkle_tree = ctx.accounts.merkle_tree.to_account_info();
+        let log_wrapper = ctx.accounts.log_wrapper.to_account_info();
+        let compression_program = ctx.accounts.compression_program.to_account_info();
+        let system_program = ctx.accounts.system_program.to_account_info();
+
+        let cnft_burn_cpi = mpl_bubblegum::instructions::BurnCpi::new(
+            &ctx.accounts.bubblegum_program,
+            mpl_bubblegum::instructions::BurnCpiAccounts {
+                tree_config: &tree_config,
+                leaf_owner: (&leaf_owner, true),
+                leaf_delegate: (&leaf_owner, false),
+                merkle_tree: &merkle_tree,
+                log_wrapper: &log_wrapper,
+                compression_program: &compression_program,
+                system_program: &system_program,
+            },
+            mpl_bubblegum::instructions::BurnInstructionArgs {
+                root,
+                data_hash,
+                creator_hash,
+                nonce,
+                index,
+            },
+        );
+
+        cnft_burn_cpi.invoke_with_remaining_accounts(
+            ctx.remaining_accounts
+                .iter()
+                .map(|account| (account, false, false))
+                .collect::<Vec<_>>()
+                .as_slice(),
+        )?;
+
         Ok(())
     }
 }
 
 #[derive(Accounts)]
-pub struct Initialize {}
+pub struct BurnCnft<'info> {
+    #[account(mut)]
+    pub leaf_owner: Signer<'info>,
+    #[account(mut)]
+    #[account(
+        seeds = [merkle_tree.key().as_ref()],
+        bump,
+        seeds::program = bubblegum_program.key()
+    )]
+    /// CHECK: This account is modified in the downstream program
+    pub tree_authority: UncheckedAccount<'info>,
+    #[account(mut)]
+    /// CHECK: This account is neither written to nor read from.
+    pub merkle_tree: UncheckedAccount<'info>,
+    /// CHECK: This account is neither written to nor read from.
+    pub log_wrapper: UncheckedAccount<'info>,
+    pub compression_program: Program<'info, SPLCompression>,
+    /// CHECK: This account is neither written to nor read from.
+    pub bubblegum_program: UncheckedAccount<'info>,
+    pub system_program: Program<'info, System>,
+}

+ 53 - 3
compression/cnft-burn/tests/cnft-burn.ts

@@ -1,16 +1,66 @@
 import * as anchor from "@coral-xyz/anchor";
 import { Program } from "@coral-xyz/anchor";
 import { CnftBurn } from "../target/types/cnft_burn";
+import {
+  MPL_BUBBLEGUM_PROGRAM_ID,
+  SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+  SPL_NOOP_PROGRAM_ID,
+} from "@metaplex-foundation/mpl-bubblegum";
+import { decode, mapProof } from "./utils";
+import { getAsset, getAssetProof } from "./readApi";
 
 describe("cnft-burn", () => {
   // Configure the client to use the local cluster.
   anchor.setProvider(anchor.AnchorProvider.env());
 
   const program = anchor.workspace.CnftBurn as Program<CnftBurn>;
+  const provider = anchor.AnchorProvider.env();
+  const payerWallet = provider.wallet as anchor.Wallet;
+  // this should be your tree address
+  const tree = new anchor.web3.PublicKey(
+    "Dggp3P5C7rB5crU3TnWMGYYKTy1At1dzwE5Ax9Sz46Kj"
+  );
+  const MPL_BUBBLEGUM_PROGRAM_ID_KEY = new anchor.web3.PublicKey(
+    MPL_BUBBLEGUM_PROGRAM_ID
+  );
+  const [treeAuthority, _bump2] = anchor.web3.PublicKey.findProgramAddressSync(
+    [tree.toBuffer()],
+    MPL_BUBBLEGUM_PROGRAM_ID_KEY
+  );
+  console.log("Tree Authority", treeAuthority.toString());
+  console.log(
+    "Computed tree authority",
+    "2zhktLCwGLFg6bqGxgdN5BEKT7PVsQ81XyfQ33gKVtxU"
+  );
+  // this is the assetId of the cNft you want to burn
+  const assetId = "2joTFxoKshsWXT2QAdjZVdvqVmGv6FhTZ2s5TCCYz7Eo";
 
-  it("Is initialized!", async () => {
-    // Add your test here.
-    const tx = await program.methods.initialize().rpc();
+  it("Burn cNft!", async () => {
+    const asset = await getAsset(assetId);
+
+    const proof = await getAssetProof(assetId);
+    const proofPathAsAccounts = mapProof(proof);
+    const root = decode(proof.root);
+    const dataHash = decode(asset.compression.data_hash);
+    const creatorHash = decode(asset.compression.creator_hash);
+    const nonce = new anchor.BN(asset.compression.leaf_id);
+    const index = asset.compression.leaf_id;
+    const tx = await program.methods
+      .burnCnft(root, dataHash, creatorHash, nonce, index)
+      .accounts({
+        merkleTree: tree,
+        leafOwner: payerWallet.publicKey,
+        treeAuthority: treeAuthority,
+
+        bubblegumProgram: MPL_BUBBLEGUM_PROGRAM_ID,
+        compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
+        logWrapper: SPL_NOOP_PROGRAM_ID,
+        systemProgram: anchor.web3.SystemProgram.programId,
+      })
+      .remainingAccounts(proofPathAsAccounts)
+      .rpc({
+        skipPreflight: true,
+      });
     console.log("Your transaction signature", tx);
   });
 });

+ 49 - 0
compression/cnft-burn/tests/readApi.ts

@@ -0,0 +1,49 @@
+// I recommend using a WrappedConnection for production
+// as it supports more readAPI functionality
+// this is just a subset of functions for quick availabiity
+
+import axios from "axios";
+
+// you might want to change that to your custom RPC
+const RPC_PATH = "https://api.devnet.solana.com";
+
+export async function getAsset(assetId: any, rpcUrl = RPC_PATH): Promise<any> {
+  try {
+    const axiosInstance = axios.create({
+      baseURL: rpcUrl,
+    });
+    const response = await axiosInstance.post(rpcUrl, {
+      jsonrpc: "2.0",
+      method: "getAsset",
+      id: "rpd-op-123",
+      params: {
+        id: assetId,
+      },
+    });
+    return response.data.result;
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+export async function getAssetProof(
+  assetId: any,
+  rpcUrl = RPC_PATH
+): Promise<any> {
+  try {
+    const axiosInstance = axios.create({
+      baseURL: rpcUrl,
+    });
+    const response = await axiosInstance.post(rpcUrl, {
+      jsonrpc: "2.0",
+      method: "getAssetProof",
+      id: "rpd-op-123",
+      params: {
+        id: assetId,
+      },
+    });
+    return response.data.result;
+  } catch (error) {
+    console.error(error);
+  }
+}

+ 40 - 0
compression/cnft-burn/tests/utils.ts

@@ -0,0 +1,40 @@
+import {
+  Connection,
+  Keypair,
+  PublicKey,
+  Signer,
+  TransactionInstruction,
+  TransactionMessage,
+  VersionedTransaction,
+  AccountMeta,
+} from "@solana/web3.js";
+
+import * as bs58 from "bs58";
+
+export function loadWalletKey(keypairFile: string): Keypair {
+  const fs = require("fs");
+  return Keypair.fromSecretKey(
+    new Uint8Array(JSON.parse(fs.readFileSync(keypairFile).toString()))
+  );
+}
+
+export function decode(stuff: string) {
+  return bufferToArray(bs58.decode(stuff));
+}
+function bufferToArray(buffer: Buffer): number[] {
+  const nums: number[] = [];
+  for (let i = 0; i < buffer.length; i++) {
+    nums.push(buffer[i]);
+  }
+  return nums;
+}
+export const mapProof = (assetProof: { proof: string[] }): AccountMeta[] => {
+  if (!assetProof.proof || assetProof.proof.length === 0) {
+    throw new Error("Proof is empty");
+  }
+  return assetProof.proof.map((node) => ({
+    pubkey: new PublicKey(node),
+    isSigner: false,
+    isWritable: false,
+  }));
+};