Skip to content

Commit 920c71f

Browse files
committed
added back the burning code
1 parent 9fe5a03 commit 920c71f

File tree

7 files changed

+229
-14
lines changed

7 files changed

+229
-14
lines changed

compression/cnft-burn/Anchor.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
seeds = false
55
skip-lint = false
66

7-
[programs.localnet]
7+
[programs.devnet]
88
cnft_burn = "FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXXNn"
99

1010
[registry]
1111
url = "https://api.apr.dev"
1212

1313
[provider]
14-
cluster = "Localnet"
15-
wallet = "/Users/pratik/.config/solana/id.json"
14+
cluster = "devnet"
15+
wallet = "~/.config/solana/id.json"
1616

1717
[scripts]
1818
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

compression/cnft-burn/package.json

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
55
},
66
"dependencies": {
7-
"@coral-xyz/anchor": "^0.29.0"
7+
"@coral-xyz/anchor": "^0.29.0",
8+
"@metaplex-foundation/mpl-bubblegum": "^3.0.0",
9+
"axios": "^1.6.5"
810
},
911
"devDependencies": {
10-
"chai": "^4.3.4",
11-
"mocha": "^9.0.3",
12-
"ts-mocha": "^10.0.0",
1312
"@types/bn.js": "^5.1.0",
1413
"@types/chai": "^4.3.0",
1514
"@types/mocha": "^9.0.0",
16-
"typescript": "^4.3.5",
17-
"prettier": "^2.6.2"
15+
"chai": "^4.3.4",
16+
"mocha": "^9.0.3",
17+
"prettier": "^2.6.2",
18+
"ts-mocha": "^10.0.0",
19+
"typescript": "^4.3.5"
1820
}
1921
}

compression/cnft-burn/programs/cnft-burn/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@ default = []
1717

1818
[dependencies]
1919
anchor-lang = "0.29.0"
20+
mpl-bubblegum = {version="1.1.0" }
21+
spl-account-compression = { version="0.3.0",features = ["no-entrypoint","cpi"] }
22+
spl-noop = { version = "0.2.0", features = ["no-entrypoint"] }

compression/cnft-burn/programs/cnft-burn/src/lib.rs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,85 @@ use anchor_lang::prelude::*;
22

33
declare_id!("FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXXNn");
44

5+
#[derive(Clone)]
6+
pub struct SPLCompression;
7+
8+
impl anchor_lang::Id for SPLCompression {
9+
fn id() -> Pubkey {
10+
spl_account_compression::id()
11+
}
12+
}
13+
514
#[program]
615
pub mod cnft_burn {
716
use super::*;
817

9-
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
18+
pub fn burn_cnft<'info>(
19+
ctx: Context<'_, '_, '_, 'info, BurnCnft<'info>>,
20+
root: [u8; 32],
21+
data_hash: [u8; 32],
22+
creator_hash: [u8; 32],
23+
nonce: u64,
24+
index: u32,
25+
) -> Result<()> {
26+
let tree_config = ctx.accounts.tree_authority.to_account_info();
27+
let leaf_owner = ctx.accounts.leaf_owner.to_account_info();
28+
let merkle_tree = ctx.accounts.merkle_tree.to_account_info();
29+
let log_wrapper = ctx.accounts.log_wrapper.to_account_info();
30+
let compression_program = ctx.accounts.compression_program.to_account_info();
31+
let system_program = ctx.accounts.system_program.to_account_info();
32+
33+
let cnft_burn_cpi = mpl_bubblegum::instructions::BurnCpi::new(
34+
&ctx.accounts.bubblegum_program,
35+
mpl_bubblegum::instructions::BurnCpiAccounts {
36+
tree_config: &tree_config,
37+
leaf_owner: (&leaf_owner, true),
38+
leaf_delegate: (&leaf_owner, false),
39+
merkle_tree: &merkle_tree,
40+
log_wrapper: &log_wrapper,
41+
compression_program: &compression_program,
42+
system_program: &system_program,
43+
},
44+
mpl_bubblegum::instructions::BurnInstructionArgs {
45+
root,
46+
data_hash,
47+
creator_hash,
48+
nonce,
49+
index,
50+
},
51+
);
52+
53+
cnft_burn_cpi.invoke_with_remaining_accounts(
54+
ctx.remaining_accounts
55+
.iter()
56+
.map(|account| (account, false, false))
57+
.collect::<Vec<_>>()
58+
.as_slice(),
59+
)?;
60+
1061
Ok(())
1162
}
1263
}
1364

1465
#[derive(Accounts)]
15-
pub struct Initialize {}
66+
pub struct BurnCnft<'info> {
67+
#[account(mut)]
68+
pub leaf_owner: Signer<'info>,
69+
#[account(mut)]
70+
#[account(
71+
seeds = [merkle_tree.key().as_ref()],
72+
bump,
73+
seeds::program = bubblegum_program.key()
74+
)]
75+
/// CHECK: This account is modified in the downstream program
76+
pub tree_authority: UncheckedAccount<'info>,
77+
#[account(mut)]
78+
/// CHECK: This account is neither written to nor read from.
79+
pub merkle_tree: UncheckedAccount<'info>,
80+
/// CHECK: This account is neither written to nor read from.
81+
pub log_wrapper: UncheckedAccount<'info>,
82+
pub compression_program: Program<'info, SPLCompression>,
83+
/// CHECK: This account is neither written to nor read from.
84+
pub bubblegum_program: UncheckedAccount<'info>,
85+
pub system_program: Program<'info, System>,
86+
}
Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,66 @@
11
import * as anchor from "@coral-xyz/anchor";
22
import { Program } from "@coral-xyz/anchor";
33
import { CnftBurn } from "../target/types/cnft_burn";
4+
import {
5+
MPL_BUBBLEGUM_PROGRAM_ID,
6+
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
7+
SPL_NOOP_PROGRAM_ID,
8+
} from "@metaplex-foundation/mpl-bubblegum";
9+
import { decode, mapProof } from "./utils";
10+
import { getAsset, getAssetProof } from "./readApi";
411

512
describe("cnft-burn", () => {
613
// Configure the client to use the local cluster.
714
anchor.setProvider(anchor.AnchorProvider.env());
815

916
const program = anchor.workspace.CnftBurn as Program<CnftBurn>;
17+
const provider = anchor.AnchorProvider.env();
18+
const payerWallet = provider.wallet as anchor.Wallet;
19+
// this should be your tree address
20+
const tree = new anchor.web3.PublicKey(
21+
"Dggp3P5C7rB5crU3TnWMGYYKTy1At1dzwE5Ax9Sz46Kj"
22+
);
23+
const MPL_BUBBLEGUM_PROGRAM_ID_KEY = new anchor.web3.PublicKey(
24+
MPL_BUBBLEGUM_PROGRAM_ID
25+
);
26+
const [treeAuthority, _bump2] = anchor.web3.PublicKey.findProgramAddressSync(
27+
[tree.toBuffer()],
28+
MPL_BUBBLEGUM_PROGRAM_ID_KEY
29+
);
30+
console.log("Tree Authority", treeAuthority.toString());
31+
console.log(
32+
"Computed tree authority",
33+
"2zhktLCwGLFg6bqGxgdN5BEKT7PVsQ81XyfQ33gKVtxU"
34+
);
35+
// this is the assetId of the cNft you want to burn
36+
const assetId = "2joTFxoKshsWXT2QAdjZVdvqVmGv6FhTZ2s5TCCYz7Eo";
1037

11-
it("Is initialized!", async () => {
12-
// Add your test here.
13-
const tx = await program.methods.initialize().rpc();
38+
it("Burn cNft!", async () => {
39+
const asset = await getAsset(assetId);
40+
41+
const proof = await getAssetProof(assetId);
42+
const proofPathAsAccounts = mapProof(proof);
43+
const root = decode(proof.root);
44+
const dataHash = decode(asset.compression.data_hash);
45+
const creatorHash = decode(asset.compression.creator_hash);
46+
const nonce = new anchor.BN(asset.compression.leaf_id);
47+
const index = asset.compression.leaf_id;
48+
const tx = await program.methods
49+
.burnCnft(root, dataHash, creatorHash, nonce, index)
50+
.accounts({
51+
merkleTree: tree,
52+
leafOwner: payerWallet.publicKey,
53+
treeAuthority: treeAuthority,
54+
55+
bubblegumProgram: MPL_BUBBLEGUM_PROGRAM_ID,
56+
compressionProgram: SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
57+
logWrapper: SPL_NOOP_PROGRAM_ID,
58+
systemProgram: anchor.web3.SystemProgram.programId,
59+
})
60+
.remainingAccounts(proofPathAsAccounts)
61+
.rpc({
62+
skipPreflight: true,
63+
});
1464
console.log("Your transaction signature", tx);
1565
});
1666
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// I recommend using a WrappedConnection for production
2+
// as it supports more readAPI functionality
3+
// this is just a subset of functions for quick availabiity
4+
5+
import axios from "axios";
6+
7+
// you might want to change that to your custom RPC
8+
const RPC_PATH = "https://api.devnet.solana.com";
9+
10+
export async function getAsset(assetId: any, rpcUrl = RPC_PATH): Promise<any> {
11+
try {
12+
const axiosInstance = axios.create({
13+
baseURL: rpcUrl,
14+
});
15+
const response = await axiosInstance.post(rpcUrl, {
16+
jsonrpc: "2.0",
17+
method: "getAsset",
18+
id: "rpd-op-123",
19+
params: {
20+
id: assetId,
21+
},
22+
});
23+
return response.data.result;
24+
} catch (error) {
25+
console.error(error);
26+
}
27+
}
28+
29+
export async function getAssetProof(
30+
assetId: any,
31+
rpcUrl = RPC_PATH
32+
): Promise<any> {
33+
try {
34+
const axiosInstance = axios.create({
35+
baseURL: rpcUrl,
36+
});
37+
const response = await axiosInstance.post(rpcUrl, {
38+
jsonrpc: "2.0",
39+
method: "getAssetProof",
40+
id: "rpd-op-123",
41+
params: {
42+
id: assetId,
43+
},
44+
});
45+
return response.data.result;
46+
} catch (error) {
47+
console.error(error);
48+
}
49+
}

compression/cnft-burn/tests/utils.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
Connection,
3+
Keypair,
4+
PublicKey,
5+
Signer,
6+
TransactionInstruction,
7+
TransactionMessage,
8+
VersionedTransaction,
9+
AccountMeta,
10+
} from "@solana/web3.js";
11+
12+
import * as bs58 from "bs58";
13+
14+
export function loadWalletKey(keypairFile: string): Keypair {
15+
const fs = require("fs");
16+
return Keypair.fromSecretKey(
17+
new Uint8Array(JSON.parse(fs.readFileSync(keypairFile).toString()))
18+
);
19+
}
20+
21+
export function decode(stuff: string) {
22+
return bufferToArray(bs58.decode(stuff));
23+
}
24+
function bufferToArray(buffer: Buffer): number[] {
25+
const nums: number[] = [];
26+
for (let i = 0; i < buffer.length; i++) {
27+
nums.push(buffer[i]);
28+
}
29+
return nums;
30+
}
31+
export const mapProof = (assetProof: { proof: string[] }): AccountMeta[] => {
32+
if (!assetProof.proof || assetProof.proof.length === 0) {
33+
throw new Error("Proof is empty");
34+
}
35+
return assetProof.proof.map((node) => ({
36+
pubkey: new PublicKey(node),
37+
isSigner: false,
38+
isWritable: false,
39+
}));
40+
};

0 commit comments

Comments
 (0)