Skip to content

Commit dd63c03

Browse files
committed
changes to run the test completely
1 parent aeb95fc commit dd63c03

File tree

11 files changed

+1461
-48
lines changed

11 files changed

+1461
-48
lines changed

compression/cnft-burn/Anchor.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ cluster = "devnet"
1515
wallet = "~/.config/solana/id.json"
1616

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

compression/cnft-burn/README.md

+3-4
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@ The program is deployed on devnet at `FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXX
1313

1414
## How to run
1515

16-
1. Configure RPC path in utils/readAPI.ts. Personal preference: Helius RPCs.
16+
1. Configure RPC path in cnft-burn.ts. Personal preference: Helius RPCs.
1717
2. run `anchor build` at the root of the project i.e cnft-burn in this case.
1818
3. run `anchor deploy` to deploy and test the program on your own cluster.
19-
4. to run the tests you need to have the tree address and the assetId of the cNFT you want to burn. You need to make sure the signer of the instruction is also the owner of that cNFT. if you don't have the tree and assetId you can create the tree and mint the cNFT using [compressed-nfts](https://github.com/solana-developers/compressed-nfts) and use the scripts `createAndMint.ts` to create the tree and get the tree address and `fetchNFTsByCollection.ts` to get the assetId based on the collection mint address which will be provided in the createAndMint script.
20-
5. run `anchor test` to run the tests.
19+
4. run `anchor test` to run the tests.
2120

2221
## Acknowledgements
2322

2423
This Example program would not have been possible without the work of:
2524

2625
- [Metaplex](https://github.com/metaplex-foundation/) for providing the Bubblegum program with ix builders.
27-
- [@nickfrosty](https://twitter.com/nickfrosty) for providing the same code for fetching and creating cNFTs.
26+
- [@nickfrosty](https://twitter.com/nickfrosty) for providing the sample code for fetching and creating cNFTs.

compression/cnft-burn/package.json

+24-20
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,26 @@
11
{
2-
"scripts": {
3-
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
4-
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
5-
},
6-
"dependencies": {
7-
"@coral-xyz/anchor": "^0.29.0",
8-
"@metaplex-foundation/mpl-bubblegum": "^3.0.0",
9-
"@metaplex-foundation/umi": "^0.9.0",
10-
"axios": "^1.6.5"
11-
},
12-
"devDependencies": {
13-
"@types/bn.js": "^5.1.0",
14-
"@types/chai": "^4.3.0",
15-
"@types/mocha": "^9.0.0",
16-
"chai": "^4.3.4",
17-
"mocha": "^9.0.3",
18-
"prettier": "^2.6.2",
19-
"ts-mocha": "^10.0.0",
20-
"typescript": "^4.3.5"
21-
}
2+
"scripts": {
3+
"lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w",
4+
"lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check"
5+
},
6+
"dependencies": {
7+
"@coral-xyz/anchor": "^0.29.0",
8+
"@metaplex-foundation/js": "^0.19.4",
9+
"@metaplex-foundation/mpl-bubblegum": "^0.7.0",
10+
"@metaplex-foundation/mpl-token-metadata": "^2.12.0",
11+
"@metaplex-foundation/umi": "^0.9.0",
12+
"@solana/spl-account-compression": "^0.2.0",
13+
"@solana/web3.js": "^1.89.0",
14+
"axios": "^1.6.5"
15+
},
16+
"devDependencies": {
17+
"@types/bn.js": "^5.1.0",
18+
"@types/chai": "^4.3.0",
19+
"@types/mocha": "^9.0.0",
20+
"chai": "^4.3.4",
21+
"mocha": "^9.0.3",
22+
"prettier": "^2.6.2",
23+
"ts-mocha": "^10.0.0",
24+
"typescript": "^4.3.5"
25+
}
2226
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { Commitment, Connection, ConnectionConfig, PublicKey } from "@solana/web3.js";
2+
// local imports for the ReadApi types
3+
import type {
4+
GetAssetProofRpcInput,
5+
GetAssetProofRpcResponse,
6+
GetAssetRpcInput,
7+
GetAssetsByGroupRpcInput,
8+
GetAssetsByOwnerRpcInput,
9+
ReadApiAsset,
10+
ReadApiAssetList,
11+
} from "@/ReadApi/types";
12+
import type { Metadata, Mint, NftOriginalEdition, SplTokenCurrency } from "@metaplex-foundation/js";
13+
// import from the `@metaplex-foundation/js`
14+
import { MetaplexError, Pda, amount, toBigNumber } from "@metaplex-foundation/js";
15+
16+
import BN from "bn.js";
17+
import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID } from "@metaplex-foundation/mpl-bubblegum";
18+
import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
19+
20+
type JsonRpcParams<ReadApiMethodParams> = {
21+
method: string;
22+
id?: string;
23+
params: ReadApiMethodParams;
24+
};
25+
26+
type JsonRpcOutput<ReadApiJsonOutput> = {
27+
result: ReadApiJsonOutput;
28+
};
29+
30+
/** @group Errors */
31+
export class ReadApiError extends MetaplexError {
32+
readonly name: string = "ReadApiError";
33+
constructor(message: string, cause?: Error) {
34+
super(message, "rpc", undefined, cause);
35+
}
36+
}
37+
38+
/**
39+
* Convert a ReadApi asset (e.g. compressed NFT) into an NftEdition
40+
*/
41+
export const toNftEditionFromReadApiAsset = (input: ReadApiAsset): NftOriginalEdition => {
42+
return {
43+
model: "nftEdition",
44+
isOriginal: true,
45+
address: new PublicKey(input.id),
46+
supply: toBigNumber(input.supply.print_current_supply),
47+
maxSupply: toBigNumber(input.supply.print_max_supply),
48+
};
49+
};
50+
51+
/**
52+
* Convert a ReadApi asset (e.g. compressed NFT) into an NFT mint
53+
*/
54+
export const toMintFromReadApiAsset = (input: ReadApiAsset): Mint => {
55+
const currency: SplTokenCurrency = {
56+
symbol: "Token",
57+
decimals: 0,
58+
namespace: "spl-token",
59+
};
60+
61+
return {
62+
model: "mint",
63+
address: new PublicKey(input.id),
64+
mintAuthorityAddress: new PublicKey(input.id),
65+
freezeAuthorityAddress: new PublicKey(input.id),
66+
decimals: 0,
67+
supply: amount(1, currency),
68+
isWrappedSol: false,
69+
currency,
70+
};
71+
};
72+
73+
/**
74+
* Convert a ReadApi asset's data into standard Metaplex `Metadata`
75+
*/
76+
export const toMetadataFromReadApiAsset = (input: ReadApiAsset): Metadata => {
77+
const updateAuthority = input.authorities?.find(authority => authority.scopes.includes("full"));
78+
79+
const collection = input.grouping.find(({ group_key }) => group_key === "collection");
80+
81+
return {
82+
model: "metadata",
83+
/**
84+
* We technically don't have a metadata address anymore.
85+
* So we are using the asset's id as the address
86+
*/
87+
address: Pda.find(BUBBLEGUM_PROGRAM_ID, [
88+
Buffer.from("asset", "utf-8"),
89+
new PublicKey(input.compression.tree).toBuffer(),
90+
Uint8Array.from(new BN(input.compression.leaf_id).toArray("le", 8)),
91+
]),
92+
mintAddress: new PublicKey(input.id),
93+
updateAuthorityAddress: new PublicKey(updateAuthority!.address),
94+
95+
name: input.content.metadata?.name ?? "",
96+
symbol: input.content.metadata?.symbol ?? "",
97+
98+
json: input.content.metadata,
99+
jsonLoaded: true,
100+
uri: input.content.json_uri,
101+
isMutable: input.mutable,
102+
103+
primarySaleHappened: input.royalty.primary_sale_happened,
104+
sellerFeeBasisPoints: input.royalty.basis_points,
105+
creators: input.creators,
106+
107+
editionNonce: input.supply.edition_nonce,
108+
tokenStandard: TokenStandard.NonFungible,
109+
110+
collection: collection
111+
? { address: new PublicKey(collection.group_value), verified: false }
112+
: null,
113+
114+
// Current regular `Metadata` does not currently have a `compression` value
115+
// @ts-ignore
116+
compression: input.compression,
117+
118+
// Read API doesn't return this info, yet
119+
collectionDetails: null,
120+
// Read API doesn't return this info, yet
121+
uses: null,
122+
// Read API doesn't return this info, yet
123+
programmableConfig: null,
124+
};
125+
};
126+
127+
/**
128+
* Wrapper class to add additional methods on top the standard Connection from `@solana/web3.js`
129+
* Specifically, adding the RPC methods used by the Digital Asset Standards (DAS) ReadApi
130+
* for state compression and compressed NFTs
131+
*/
132+
export class WrapperConnection extends Connection {
133+
constructor(endpoint: string, commitmentOrConfig?: Commitment | ConnectionConfig) {
134+
super(endpoint, commitmentOrConfig);
135+
}
136+
137+
private callReadApi = async <ReadApiMethodParams, ReadApiJsonOutput>(
138+
jsonRpcParams: JsonRpcParams<ReadApiMethodParams>,
139+
): Promise<JsonRpcOutput<ReadApiJsonOutput>> => {
140+
const response = await fetch(this.rpcEndpoint, {
141+
method: "POST",
142+
headers: {
143+
"Content-Type": "application/json",
144+
},
145+
body: JSON.stringify({
146+
jsonrpc: "2.0",
147+
method: jsonRpcParams.method,
148+
id: jsonRpcParams.id ?? "rpd-op-123",
149+
params: jsonRpcParams.params,
150+
}),
151+
});
152+
153+
return await response.json() as JsonRpcOutput<ReadApiJsonOutput>;
154+
};
155+
156+
// Asset id can be calculated via Bubblegum#getLeafAssetId
157+
// It is a PDA with the following seeds: ["asset", tree, leafIndex]
158+
async getAsset(assetId: PublicKey): Promise<ReadApiAsset> {
159+
const { result: asset } = await this.callReadApi<GetAssetRpcInput, ReadApiAsset>({
160+
method: "getAsset",
161+
params: {
162+
id: assetId.toBase58(),
163+
},
164+
});
165+
166+
if (!asset) throw new ReadApiError("No asset returned");
167+
168+
return asset;
169+
}
170+
171+
// Asset id can be calculated via Bubblegum#getLeafAssetId
172+
// It is a PDA with the following seeds: ["asset", tree, leafIndex]
173+
async getAssetProof(assetId: PublicKey): Promise<GetAssetProofRpcResponse> {
174+
const { result: proof } = await this.callReadApi<
175+
GetAssetProofRpcInput,
176+
GetAssetProofRpcResponse
177+
>({
178+
method: "getAssetProof",
179+
params: {
180+
id: assetId.toBase58(),
181+
},
182+
});
183+
184+
if (!proof) throw new ReadApiError("No asset proof returned");
185+
186+
return proof;
187+
}
188+
189+
//
190+
async getAssetsByGroup({
191+
groupKey,
192+
groupValue,
193+
page,
194+
limit,
195+
sortBy,
196+
before,
197+
after,
198+
}: GetAssetsByGroupRpcInput): Promise<ReadApiAssetList> {
199+
// `page` cannot be supplied with `before` or `after`
200+
if (typeof page == "number" && (before || after))
201+
throw new ReadApiError(
202+
"Pagination Error. Only one pagination parameter supported per query.",
203+
);
204+
205+
// a pagination method MUST be selected, but we are defaulting to using `page=0`
206+
207+
const { result } = await this.callReadApi<GetAssetsByGroupRpcInput, ReadApiAssetList>({
208+
method: "getAssetsByGroup",
209+
params: {
210+
groupKey,
211+
groupValue,
212+
after: after ?? null,
213+
before: before ?? null,
214+
limit: limit ?? null,
215+
page: page ?? 1,
216+
sortBy: sortBy ?? null,
217+
},
218+
});
219+
220+
if (!result) throw new ReadApiError("No results returned");
221+
222+
return result;
223+
}
224+
225+
//
226+
async getAssetsByOwner({
227+
ownerAddress,
228+
page,
229+
limit,
230+
sortBy,
231+
before,
232+
after,
233+
}: GetAssetsByOwnerRpcInput): Promise<ReadApiAssetList> {
234+
// `page` cannot be supplied with `before` or `after`
235+
if (typeof page == "number" && (before || after))
236+
throw new ReadApiError(
237+
"Pagination Error. Only one pagination parameter supported per query.",
238+
);
239+
240+
// a pagination method MUST be selected, but we are defaulting to using `page=0`
241+
242+
const { result } = await this.callReadApi<GetAssetsByOwnerRpcInput, ReadApiAssetList>({
243+
method: "getAssetsByOwner",
244+
params: {
245+
ownerAddress,
246+
after: after ?? null,
247+
before: before ?? null,
248+
limit: limit ?? null,
249+
page: page ?? 1,
250+
sortBy: sortBy ?? null,
251+
},
252+
});
253+
254+
if (!result) throw new ReadApiError("No results returned");
255+
256+
return result;
257+
}
258+
}

0 commit comments

Comments
 (0)