diff --git a/.gitignore b/.gitignore
index c5362743..30327dc1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
**/target/*
!**/target/types
+.cursorrules
+
local-scripts
.anchor
diff --git a/.mocharc.json b/.mocharc.json
new file mode 100644
index 00000000..21a5f6d9
--- /dev/null
+++ b/.mocharc.json
@@ -0,0 +1,3 @@
+{
+ "timeout": 1000000
+}
diff --git a/.prettierrc.js b/.prettierrc.js
index 57b18d55..13ee303a 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -1,6 +1,8 @@
module.exports = {
- trailingComma: 'es5',
- tabWidth: 2,
- semi: true,
- singleQuote: true,
+ trailingComma: 'es5',
+ tabWidth: 2,
+ printWidth: 160,
+
+ semi: true,
+ singleQuote: true,
};
diff --git a/Anchor.toml b/Anchor.toml
index 056f7951..2fc8db5a 100644
--- a/Anchor.toml
+++ b/Anchor.toml
@@ -5,18 +5,20 @@ resolution = true
skip-lint = false
[programs.localnet]
-libreplex_editions = "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
-libreplex_editions_controls = "5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs"
+libreplex_editions = "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"
+libreplex_editions_controls = "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN"
[registry]
url = "https://api.apr.dev"
[provider]
cluster = "Localnet"
+### @dev testing wallet
+# wallet = "~/.config/solana/id.json"
wallet = "usb://ledger"
[scripts]
-test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 js-tests/**/*.ts"
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 test/tests/**/*.ts"
[test]
startup_wait = 100000
@@ -25,6 +27,8 @@ upgradeable = false
[test.validator]
bind_address = "0.0.0.0"
+### @dev: Utilize the testnet cluster for running the testsuite
+# url = "https://api.testnet.solana.com"
url = "https://api.mainnet-beta.solana.com"
ledger = ".anchor/test-ledger"
rpc_port = 8899
@@ -40,3 +44,7 @@ address = "cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK"
[[test.validator.clone]]
address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
+
+[[test.validator.clone]]
+address = "5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V"
+
diff --git a/Cargo.lock b/Cargo.lock
index 500435b3..7551fc2d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2207,6 +2207,7 @@ dependencies = [
"arrayref",
"libreplex_editions",
"libreplex_shared",
+ "rarible-merkle-verify",
"solana-program",
"solana-program-test",
"solana-sdk",
@@ -3173,6 +3174,15 @@ dependencies = [
"rand_core 0.6.4",
]
+[[package]]
+name = "rarible-merkle-verify"
+version = "0.0.1"
+dependencies = [
+ "anchor-lang",
+ "anchor-spl",
+ "solana-program",
+]
+
[[package]]
name = "rayon"
version = "1.10.0"
diff --git a/Cargo.toml b/Cargo.toml
index 436fcebf..eaaf8cf1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,4 +11,4 @@ codegen-units = 1
[profile.release.build-override]
opt-level = 3
incremental = false
-codegen-units = 1
+codegen-units = 1
\ No newline at end of file
diff --git a/README.md b/README.md
index 7af55bf1..1dc6f546 100644
--- a/README.md
+++ b/README.md
@@ -1,51 +1,66 @@
-
-
+# Eclipse Program Library Testing Guide
+
+This guide provides step-by-step instructions for setting up and running tests for the Eclipse Program Library.
+
+## Prerequisites
+
+- [Rust](https://www.rust-lang.org/tools/install)
+- [Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools)
+- [Anchor](https://www.anchor-lang.com/docs/installation)
+- [Node.js and npm](https://nodejs.org/en/download/)
+- [Yarn](https://classic.yarnpkg.com/en/docs/install)
+
+## Setup and Testing Steps
+
+1. Clone the repository:
+ ```
+ git clone https://github.com/rarible/eclipse-program-library.git
+ cd eclipse-program-library
+ ```
+
+2. Install dependencies:
+ ```
+ yarn install
+ ```
+
+3. Sync program IDs:
+ - Open `Anchor.toml` and `constants.ts`
+ - Ensure that the program IDs in both files match
+ - Example:
+ ```toml
+ # In Anchor.toml
+ [programs.localnet]
+ libreplex_editions = "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
+ libreplex_editions_controls = "5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs"
+ ```
+ ```typescript
+ // In constants.ts
+ export const LIBREPLEX_EDITIONS_PROGRAM_ID = new PublicKey("587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq");
+ export const LIBREPLEX_EDITIONS_CONTROLS_PROGRAM_ID = new PublicKey("5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs");
+ ```
+
+4. Run tests:
+ - To build and run tests:
+ ```
+ anchor test
+ ```
+ - To skip building and only run tests (faster for repeated testing):
+ ```
+ anchor test --skip-build
+ ```
+
+## Notes
+
+- If you encounter wallet-related errors, ensure your `Anchor.toml` is configured to use your local keypair:
+ ```toml
+ [provider]
+ cluster = "localnet"
+ wallet = "~/.config/solana/id.json"
+ ```
+
+- For any issues or questions, please open an issue in the GitHub repository.
+
+## Contributing
+
+We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to contribute to this project.
-
Libreplex
-
-
-
-
-
-
-The mission of Libreplex is to provide a community-driven, open license protocol to the Solana SPL Token and NFT community. The protocol must meet the following criteria:
-
-1) Distributed deployment keys
-
-To ensure that no single entity can unilaterally make changes that impact or jeopardise the integrity of the applications that depend on the protocol.
-
-2) Open license held by a Trust / Foundation
-
-The licensing must ensure that any applications utilising the protocol can do so knowing that the nature of the protocol remains constant, to minimise uncertainty and maximise transparency.
-
-3) Guaranteed fees-free for life
-
-The fee-free nature of the protocol ensures that even though applications built on top of the protocol may introduce fees, the protocol itself will never do so. This establishes a level playing field to all and enforces predictability and transparency.
-
- 4) Open source
-
-The source of the protocol will be made available on github or similar. After initial launch, any changes will be subject to 30-day vetting and a community vote.
-
-INSTRUCTIONS:
-
-Install dependencies
-
-```
-yarn
-```
-
-Build
-
-```
-anchor build
-```
-
-To run unit tests (cargo):
-
-`cargo test`
-
-To run unit tests (cargo, for a single program):
-
-`cargo test libreplex_metadata`
diff --git a/constants.ts b/constants.ts
new file mode 100644
index 00000000..e612cfe6
--- /dev/null
+++ b/constants.ts
@@ -0,0 +1,4 @@
+// @dev: sync with Anchor.toml to ensure correct program ids are used on tests
+export const EDITIONS_PROGRAM_ID = 'GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD';
+export const EDITIONS_CONTROLS_PROGRAM_ID = 'CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN';
+export const TOKEN_GROUP_EXTENSION_PROGRAM_ID = '5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V';
diff --git a/docs/book.toml b/docs/book.toml
deleted file mode 100644
index 62fad5f9..00000000
--- a/docs/book.toml
+++ /dev/null
@@ -1,9 +0,0 @@
-[book]
-authors = ["FallingHazard"]
-language = "en"
-multilingual = false
-src = "src"
-title = "Libreplex Docs"
-
-[output.html]
-default-theme = "rust"
\ No newline at end of file
diff --git a/docs/pdf/20231130-fairlaunch-base-architecture.pdf b/docs/pdf/20231130-fairlaunch-base-architecture.pdf
deleted file mode 100644
index b5003328..00000000
Binary files a/docs/pdf/20231130-fairlaunch-base-architecture.pdf and /dev/null differ
diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md
deleted file mode 100644
index bcba8905..00000000
--- a/docs/src/SUMMARY.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# Summary
-
-- [Quickstart](./chapter_1.md)
-- [Programs](./programs.md)
-- [Data Model](./data_model.md)
-- [Creator Machine](./creator_machine.md)
diff --git a/docs/src/chapter_1.md b/docs/src/chapter_1.md
deleted file mode 100644
index e23e9800..00000000
--- a/docs/src/chapter_1.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# Quickstart
-
-
-Install the [SDK](https://www.npmjs.com/package/@libreplex/sdk)
-
-
-```
-import {mintSingle} from "@libreplex/sdk"
-import * as anchor from "@coral-xyz/anchor";
-
-const provider = anchor.AnchorProvider.env()
-
-const {method, mint} = (await mintSingle({
- provider,
- mintData: {
- assetUrl: {
- type: "jsonUrl",
- value: "COOL.com"
- },
- name: "COOL",
- symbol: "COOL",
- }
-}))
-
-await method.rpc()
-
-```
-
-## Creating a collection/group.
-
-
-
-```
-import {mintSingle} from "@libreplex/sdk"
-import * as anchor from "@coral-xyz/anchor";
-
-const provider = anchor.AnchorProvider.env()
-
-const {method, group} = await setupGroup({
- connector: {
- type: "provider",
- provider,
- },
- groupAuthority: me,
- input: {
- description: "A very cool group",
- name: "COOLIO",
- symbol: "GRP",
- url: "COOL.com",
- royalties: {
- bps: 0,
- shares: [{
- recipient: me,
- share: 100,
- }],
- },
- permittedSigners: [],
- onChainAttributes: [],
- },
- })
-
-await method.rpc()
-
-```
-
-## Minting to a group.
-
-```
-import {mintSingle} from "@libreplex/sdk"
-import * as anchor from "@coral-xyz/anchor";
-
-const provider = anchor.AnchorProvider.env()
-
-
-const group = "....Some Public Key..."
-
-const {method, mint} = (await mintSingle({
- provider,
- mintData: {
- assetUrl: {
- type: "jsonUrl",
- value: "COOL.com"
- },
- name: "COOL",
- symbol: "COOL",
- },
- mintToGroup: {
- group,
- checkValidGroup: false,
- }
-}))
-
-await method.rpc()
-
-```
diff --git a/docs/src/creator_machine.md b/docs/src/creator_machine.md
deleted file mode 100644
index 2adec254..00000000
--- a/docs/src/creator_machine.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Creator Machine
-
-Work in progress...
\ No newline at end of file
diff --git a/docs/src/data_model.md b/docs/src/data_model.md
deleted file mode 100644
index 9bc591f4..00000000
--- a/docs/src/data_model.md
+++ /dev/null
@@ -1,17 +0,0 @@
-# Data Model
-
-Libreplex provides Metadata for [Token-2022](https://spl.solana.com/token-2022) mints.
-
-## Metadata structure
-
-| Field Name | Type | Extra Info |
-|:-------------------------------------------|:--------------------------------------------------------------------------|--------------------------------------------------------------------------|
-| Mint | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) |
-| Update Authority | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) |
-| Creator | [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) | Cannot be changed
-| Is Mutable | Bool |
-| Group ( Collection ) | Optional [Pubkey](https://docs.rs/solana-program/latest/solana_program/pubkey/struct.Pubkey.html) |
-| Name | String |
-| Symbol | String |
-| Asset | |
-| Extension | |
\ No newline at end of file
diff --git a/docs/src/programs.md b/docs/src/programs.md
deleted file mode 100644
index 603a46e8..00000000
--- a/docs/src/programs.md
+++ /dev/null
@@ -1,10 +0,0 @@
-# Programs
-
-| Name | Address |
-|:-------------------------------------------|:--------------------------------------------------------------------------|
-| Metadata | LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p |
-| Creator | 78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM |
-| Creator Controls | G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV |
-| Shop | ListjawGEdhxuAErSyYwcTEGWQswFoi6FScnGG1RKSB |
-| Inscriptions | inscokhJarcjaEs59QbQ7hYjrKz25LEPRfCbP8EmdUp |
-| NFT | 9SXDHUdtfvBGT3H2uPCNEkxmWREoqdeS1qdBudLDD6KX |
diff --git a/js-tests/creator.ts b/js-tests/creator.ts
deleted file mode 100644
index 459b2040..00000000
--- a/js-tests/creator.ts
+++ /dev/null
@@ -1,176 +0,0 @@
-import * as anchor from '@coral-xyz/anchor';
-import { Program } from '@coral-xyz/anchor';
-import {
- Keypair,
- PublicKey,
- SYSVAR_EPOCH_SCHEDULE_PUBKEY
-} from '@solana/web3.js';
-import { LibreplexCreator } from '../target/types/libreplex_creator';
-import { LibreplexCreatorControls } from '../target/types/libreplex_creator_controls';
-import { LibreplexMetadata } from '../target/types/libreplex_metadata';
-import { LibreplexNft } from '../target/types/libreplex_nft';
-
-import {
- LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID,
- mintFromCreatorController,
- setupCreatorWithCustomSalePhases,
- setupCollection
-} from '@libreplex/sdk';
-import { sha256 } from 'js-sha256';
-
-describe('libreplex creator', () => {
- const provider = anchor.AnchorProvider.env();
- anchor.setProvider(provider);
-
- const program = anchor.workspace
- .LibreplexCreator as Program;
- const metadataProgram = anchor.workspace
- .LibreplexMetadata as Program;
- const nftProgram = anchor.workspace.LibreplexNft as Program;
- const controllerProgram = anchor.workspace
- .LibreplexCreatorControls as Program;
-
- console.log(Object.keys(anchor.workspace));
-
- const authority = anchor.getProvider().publicKey;
-
- it('has minted', async () => {
- const groupSeed = Keypair.generate();
-
- console.log('Setting up group');
- const grpSetupCtx = await setupCollection({
- connector: {
- type: 'provider',
- provider,
- },
- groupSeedKp: groupSeed,
- groupAuthority: program.provider.publicKey as PublicKey,
- input: {
- description: 'A very cool group',
- name: 'COOL GROUP',
- symbol: 'COOL',
- url: 'COOL.com/group',
- royalties: {
- bps: 100,
- shares: [
- {
- recipient: program.provider.publicKey as PublicKey,
- share: 100,
- },
- ],
- },
- },
- });
-
- const collection = grpSetupCtx.collection;
- await grpSetupCtx.method.rpc({
- skipPreflight: false,
- });
-
- const startTime = new Date();
- startTime.setDate(startTime.getDate() - 1);
-
- const pingDiscrim = Buffer.from(sha256.digest('global:ping')).slice(0, 8);
-
- console.log('Setting up controller');
- const creatorControllerCtx = await setupCreatorWithCustomSalePhases(
- {
- collection,
- metadataProgram,
- mintAuthority: program.provider.publicKey as PublicKey,
- program,
- creatorData: {
- baseName: 'COOL #',
- baseUrl: {
- type: 'json-prefix',
- url: 'COOL.com/',
- },
- description: 'The coolest metadatas',
- ordered: false,
- symbol: 'COOL',
- supply: 2000,
- },
- },
- controllerProgram,
- [
- {
- end: null,
- start: startTime,
- label: 'Public',
- /* No controls anyone can mint and it's free*/
- control: [
- {
- name: 'CustomProgram',
- instructionData: pingDiscrim,
- label: 'Ping',
- programId: LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID,
- remainingAccountsMetas: [
- {
- isSigner: false,
- isWritable: true,
- key: {
- type: 'key',
- value: Keypair.generate().publicKey,
- },
- },
- {
- isSigner: false,
- isWritable: true,
- key: {
- type: 'seedDerivation',
- programId: SYSVAR_EPOCH_SCHEDULE_PUBKEY,
- seeds: [
- {
- type: 'mintPlaceHolder',
- },
- ],
- },
- },
- ],
- },
- ],
- },
- ]
- );
-
- await creatorControllerCtx.method.rpc();
-
- const { creator, minterNumbers, creatorController } = creatorControllerCtx;
-
- // const controllerData = await controllerProgram.account.creatorController.fetch(creatorController)
-
- console.log('Creator initialised');
-
- {
- // Set some dummy values for transfer hook.
- const mintMethod = await mintFromCreatorController({
- addTransferHookToMint: {
- authority: program.provider.publicKey as PublicKey,
- programId: program.provider.publicKey as PublicKey,
- },
- creatorController: creatorControllerCtx.creatorController,
- creatorControllerProgram: controllerProgram,
- creatorProgram: program,
- });
-
- const txId = await mintMethod.method.rpc({
- skipPreflight: true,
- });
-
- console.log(txId);
- }
-
- {
- // Mint without transfer hook
- const mintMethod = await mintFromCreatorController({
- creatorController: creatorControllerCtx.creatorController,
- creatorControllerProgram: controllerProgram,
- creatorProgram: program,
- });
-
- await mintMethod.method.rpc({
- skipPreflight: true,
- });
- }
- });
-});
diff --git a/js-tests/mint.ts b/js-tests/mint.ts
deleted file mode 100644
index c8a06eae..00000000
--- a/js-tests/mint.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import * as anchor from "@coral-xyz/anchor";
-import { BN, Program } from "@coral-xyz/anchor";
-import { AnchorProvider, getProvider } from "@coral-xyz/anchor";
-import {mintSingle, setupCollection, setUserPermissionsForGroup, UserPermission, updateCollectionAuthority} from "@libreplex/sdk"
-import { Keypair } from "@solana/web3.js";
-
-
-describe("mint", () => {
- const provider = anchor.AnchorProvider.env()
- anchor.setProvider(provider);
-
- it("has minted", async () => {
- const mintCtx = (await mintSingle({
- provider,
- mintData: {
- assetUrl: {
- type: "jsonUrl",
- value: "COOL.com"
- },
- name: "COOL",
- symbol: "COOL",
- }
- }))
-
- await mintCtx.method.rpc()
- })
-
- it ("has minted to a collection", async () => {
- const me = provider.publicKey
-
- const grpCtx = await setupCollection({
- connector: {
- type: "provider",
- provider,
- },
- groupAuthority: me,
- input: {
- description: "A very cool group",
- name: "COOLIO",
- symbol: "GRP",
- url: "COOL.com",
- royalties: {
- bps: 0,
- shares: [{
- recipient: me,
- share: 100,
- }],
- },
- permittedSigners: [],
- onChainAttributes: [],
- },
- })
-
- await grpCtx.method.rpc()
-
- const collection = grpCtx.collection
-
- const mintCtx = (await mintSingle({
- provider,
- mintData: {
- assetUrl: {
- type: "jsonUrl",
- value: "COOL.com"
- },
- name: "COOL",
- symbol: "COOL",
- },
- mintToCollection: {
- collection,
- checkValidGroup: true,
- }
- }))
-
- await mintCtx.method.rpc()
- })
-
- it ("has minted to a collection where I am not the authority", async () => {
- const me = provider.publicKey
-
- const grpCtx = await setupCollection({
- connector: {
- type: "provider",
- provider,
- },
- groupAuthority: me,
- input: {
- description: "A very cool group",
- name: "COOLIO",
- symbol: "GRP",
- url: "COOL.com",
- royalties: {
- bps: 0,
- shares: [],
- },
- permittedSigners: [],
- onChainAttributes: [],
- },
- })
-
- await grpCtx.method.rpc();
-
- const collection = grpCtx.collection;
-
- await (await setUserPermissionsForGroup({
- connector: {
- type: "provider",
- provider,
- },
- collection,
- user: me,
- groupUpdateAuthority: me,
- permissions: [UserPermission.AddToGroup]
- })).rpc()
-
- await (await updateCollectionAuthority({
- collection,
- new_authority: Keypair.generate().publicKey,
- connector: {
- type: "provider",
- provider,
- },
- })).rpc()
-
- const mintCtx = (await mintSingle({
- provider,
- mintData: {
- assetUrl: {
- type: "jsonUrl",
- value: "COOL.com"
- },
- name: "COOL",
- symbol: "COOL",
- },
- mintToCollection: {
- collection,
- checkValidGroup: true,
- }
- }))
-
- await mintCtx.method.rpc()
- })
-
-
-})
\ No newline at end of file
diff --git a/libraries/rarible-merkle-verify/Cargo.toml b/libraries/rarible-merkle-verify/Cargo.toml
new file mode 100644
index 00000000..b2db1128
--- /dev/null
+++ b/libraries/rarible-merkle-verify/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rarible-merkle-verify"
+version = "0.0.1"
+edition = "2021"
+
+[dependencies]
+anchor-lang = {version = "~0.30"}
+anchor-spl = {version = "~0.30"}
+solana-program = {version = "1.17.13"}
+
+[features]
+idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
diff --git a/libraries/rarible-merkle-verify/src/lib.rs b/libraries/rarible-merkle-verify/src/lib.rs
new file mode 100644
index 00000000..1a86fdbc
--- /dev/null
+++ b/libraries/rarible-merkle-verify/src/lib.rs
@@ -0,0 +1,23 @@
+use solana_program::hash::hashv;
+
+/// modified version of https://github.com/saber-hq/merkle-distributor/blob/ac937d1901033ecb7fa3b0db22f7b39569c8e052/programs/merkle-distributor/src/merkle_proof.rs#L8
+/// This function deals with verification of Merkle trees (hash trees).
+/// Direct port of https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/cryptography/MerkleProof.sol
+/// Returns true if a `leaf` can be proved to be a part of a Merkle tree
+/// defined by `root`. For this, a `proof` must be provided, containing
+/// sibling hashes on the branch from the leaf to the root of the tree. Each
+/// pair of leaves and each pair of pre-images are assumed to be sorted.
+pub fn verify(proof: Vec<[u8; 32]>, root: [u8; 32], leaf: [u8; 32]) -> bool {
+ let mut computed_hash = leaf;
+ for proof_element in proof.into_iter() {
+ if computed_hash <= proof_element {
+ // Hash(current computed hash + current element of the proof)
+ computed_hash = hashv(&[&[1u8], &computed_hash, &proof_element]).to_bytes();
+ } else {
+ // Hash(current element of the proof + current computed hash)
+ computed_hash = hashv(&[&[1u8], &proof_element, &computed_hash]).to_bytes();
+ }
+ }
+ // Check if the computed hash (root) is equal to the provided root
+ computed_hash == root
+}
diff --git a/package.json b/package.json
index 5753d190..858ffa5e 100644
--- a/package.json
+++ b/package.json
@@ -8,49 +8,22 @@
"build-interfaces": "cd packages/libreplex-idls && yarn package && yarn build && cd .. && cd libreplex-sdk && yarn build && cd ../..",
"build": "lerna run clean && lerna run build",
"libre-cli": "ts-node src/cli/index.ts",
- "libreplex_nifty_hybrid:deploy:devnet": "anchor deploy -p libreplex_nifty_hybrid --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_monoswap:deploy:devnet": "anchor deploy -p libreplex_monoswap --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_monoswap:deploy:mainnet": "anchor deploy -p libreplex_monoswap --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_pipelines:deploy:devnet": "anchor deploy -p libreplex_pipelines --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_pipelines:deploy:mainnet": "anchor deploy -p libreplex_pipelines --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_editions:deploy:devnet": "anchor deploy -p libreplex_editions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_editions_controls:deploy:devnet": "anchor deploy -p libreplex_editions_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json",
- "libreplex_editions:deploy:mainnet": "anchor deploy -p libreplex_editions --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_editions:deploy:eclipse": "anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_editions:deploy:eclipse-dev": "anchor deploy -p libreplex_editions --provider.cluster https://staging-rpc.dev.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_liquidity:deploy:devnet": "anchor deploy -p libreplex_liquidity --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_liquidity:deploy:mainnet": "anchor build -p libreplex_liquidity&& anchor deploy -p libreplex_liquidity --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_inscriptions:deploy:eclipse-testnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_inscriptions:deploy:localnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster http://localhost:8899 --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_metadata:deploy:localnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster http://localhost:8899 --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_creator:deploy:devnet": "anchor build -p libreplex_creator && anchor deploy -p libreplex_creator --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_creator_controls:deploy:devnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_inscriptions:deploy:devnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_metadata:deploy:devnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_shop:deploy:devnet": "anchor build -p libreplex_shop && anchor deploy -p libreplex_shop --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_legacy:deploy:devnet": "anchor build -p libreplex_legacy && anchor deploy -p libreplex_legacy --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_legacy:deploy:mainnet": "anchor build -p libreplex_legacy && anchor deploy -p libreplex_legacy --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_metadata:deploy:mainnet": "anchor build -p libreplex_metadata && anchor deploy -p libreplex_metadata --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_creator:deploy:mainnet": "anchor build -p libreplex_creator && anchor deploy -p libreplex_creator --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_inscriptions:deploy:mainnet": "anchor build -p libreplex_inscriptions && anchor deploy -p libreplex_inscriptions --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_creator_controls:deploy:mainnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
- "libreplex_shop:deploy:mainnet": "anchor build -p libreplex_creator_controls && anchor deploy -p libreplex_creator_controls --provider.cluster https://api.mainnet-beta.solana.com --provider.wallet ~/.config/solana/libreplex-deployer.json",
+ "lint": "next lint",
"libreplex_editions:deploy:eclipse-dev2": "anchor deploy -p libreplex_editions --provider.cluster https://staging-rpc.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json",
"libreplex_editions_controls:deploy:eclipse-dev2": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://staging-rpc.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json",
+
+ "libreplex_editions:deploy:eclipse-testnet": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json",
+ "libreplex_editions_controls:deploy:eclipse-testnet": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/id.json",
"libreplex_editions:deploy:solana-dev-net": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json",
"libreplex_editions_controls:solana-dev-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.devnet.solana.com --provider.wallet ~/.config/solana/id.json",
+
"transfer-program-owner:solana-dev-net": "solana program set-upgrade-authority --skip-new-upgrade-authority-signer-check 6g8ZaBxx2tng8EhVqvaad948Vht9YFJFGG5gvAejrJj9 --new-upgrade-authority 674s1Sap3KVnr8WGrY5KGQ69oTYjjgr1disKJo6GpTYw -k ~/.config/solana/id.json -u https://api.devnet.solana.com",
"transfer-program-owner-back:solana-dev-net": "solana program set-upgrade-authority --skip-new-upgrade-authority-signer-check 6g8ZaBxx2tng8EhVqvaad948Vht9YFJFGG5gvAejrJj9 --new-upgrade-authority QjzRL6VwKGnpco8wx3cPjtq8ZPhewy7ohq7F5mv2eeR -k usb://ledger -u https://api.devnet.solana.com",
-
"libreplex_editions:deploy:solana-test-net": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://api.testnet.solana.com --provider.wallet ~/.config/solana/prod-keypair.json",
"libreplex_editions_controls:solana-test-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.testnet.solana.com --provider.wallet ~/.config/solana/prod-keypair.json",
-
- "libreplex_editions:deploy:eclipse-testnet": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/prod-keypair.json",
- "libreplex_editions_controls:deploy:eclipse-testnet": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://testnet.dev2.eclipsenetwork.xyz --provider.wallet ~/.config/solana/prod-keypair.json",
- "lint": "next lint",
"libreplex_editions:deploy:ledger:solana-dev-net": "anchor build -p libreplex_editions && anchor deploy -p libreplex_editions --provider.cluster https://api.devnet.solana.com --provider.wallet usb://ledger",
"libreplex_editions_controls:deploy:ledger:solana-dev-net": "anchor build -p libreplex_editions_controls && anchor deploy -p libreplex_editions_controls --provider.cluster https://api.devnet.solana.com --provider.wallet usb://ledger",
@@ -64,8 +37,9 @@
},
"dependencies": {
"@coral-xyz/anchor": "0.30.0",
- "@solana/spl-token": "^0.3.8",
- "@solana/web3.js": "^1.73.0"
+ "@solana/spl-token": "^0.4.8",
+ "@solana/web3.js": "^1.73.0",
+ "@solana/spl-token-metadata": "^0.1.5"
},
"devDependencies": {
"@types/chai": "^4.3.5",
diff --git a/packages/libreplex-idls/package.json b/packages/libreplex-idls/package.json
deleted file mode 100644
index 63935387..00000000
--- a/packages/libreplex-idls/package.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "name": "@libreplex/idls",
- "version": "0.5.2",
- "description": "Idls for libreplex programs",
- "sideEffects": false,
- "files": [
- "lib"
- ],
- "types": "./lib/types/index.d.ts",
- "exports": {
- "./lib/esm/*": "./lib/esm/*",
- "./lib/cjs/*": "./lib/cjs/*.js",
- "./lib/types/*": "./lib/types/*"
- },
- "scripts": {
- "clean": "npx shx mkdir -p lib && npx shx rm -rf lib",
- "package": "npx shx mkdir -p lib/cjs lib/esm lib/types",
- "build": "tsc -b ./tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
-}
diff --git a/packages/libreplex-idls/tsconfig.cjs.json b/packages/libreplex-idls/tsconfig.cjs.json
deleted file mode 100644
index ff528415..00000000
--- a/packages/libreplex-idls/tsconfig.cjs.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "include": ["../../target/types/**/*"],
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "target": "ES2016",
- "module": "CommonJS",
- "sourceMap": false,
- "outDir": "lib/cjs",
- "rootDir": "../../target/types",
- }
- }
\ No newline at end of file
diff --git a/packages/libreplex-idls/tsconfig.esm.json b/packages/libreplex-idls/tsconfig.esm.json
deleted file mode 100644
index d1625681..00000000
--- a/packages/libreplex-idls/tsconfig.esm.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "include": ["../../target/types"],
- "compilerOptions": {
- "rootDir": "../../target/types",
- "outDir": "lib/esm",
- "declarationDir": "lib/types",
- "target": "ESNext",
- "module": "ESNext",
- "sourceMap": false,
- "declaration": true,
- "composite": true
- }
-}
diff --git a/packages/libreplex-idls/tsconfig.json b/packages/libreplex-idls/tsconfig.json
deleted file mode 100644
index d01bd73a..00000000
--- a/packages/libreplex-idls/tsconfig.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "include": [],
- "compilerOptions": {
- "lib": ["ES2021.String"],
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "Node",
- "esModuleInterop": true,
- "isolatedModules": true,
- "noImplicitAny": false,
- "noEmitOnError": true,
- "resolveJsonModule": true,
- "strict": true,
- "stripInternal": true,
- "skipLibCheck": true,
- "noEmit": false,
- "allowSyntheticDefaultImports": true,
- },
- "references": [
- {
- "path": "./tsconfig.esm.json"
- },
- {
- "path": "./tsconfig.cjs.json"
- },
- ]
-}
diff --git a/packages/libreplex-idls/tsconfig.types.json b/packages/libreplex-idls/tsconfig.types.json
deleted file mode 100644
index 0c118a89..00000000
--- a/packages/libreplex-idls/tsconfig.types.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "include": ["../../target/types/**/*"],
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "outDir": "./lib/types",
- "declaration": true,
- "emitDeclarationOnly": true
- }
-}
diff --git a/packages/libreplex-sdk/README.md b/packages/libreplex-sdk/README.md
deleted file mode 100644
index 455182ec..00000000
--- a/packages/libreplex-sdk/README.md
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
Libreplex
-
-
-
\ No newline at end of file
diff --git a/packages/libreplex-sdk/package.json b/packages/libreplex-sdk/package.json
deleted file mode 100644
index 096c92d0..00000000
--- a/packages/libreplex-sdk/package.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "name": "@libreplex/sdk",
- "version": "0.3.5",
- "description": "",
- "main": "./lib/cjs/src/index.js",
- "module": "./lib/esm/src/index.js",
- "types": "./lib/types/src/index.d.ts",
- "sideEffects": false,
- "files": [
- "lib"
- ],
- "dependencies": {
- "@libreplex/idls": "0.5.2",
- "@solana/spl-token": "0.3.8"
- },
- "devDependencies": {
- "shx": "^0.3.4"
- },
- "exports": {
- "import": "./lib/esm/src/index.js",
- "require": "./lib/cjs/src/index.js",
- "types": "./lib/types/src/index.d.ts"
- },
- "scripts": {
- "clean": "npx shx mkdir -p lib && npx shx rm -rf lib",
- "build": "tsc --build"
- },
- "keywords": [],
- "author": "",
- "license": "ISC"
-}
diff --git a/packages/libreplex-sdk/src/constants.ts b/packages/libreplex-sdk/src/constants.ts
deleted file mode 100644
index 9ef6b4a7..00000000
--- a/packages/libreplex-sdk/src/constants.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { PublicKey } from "@solana/web3.js";
-
-export const LIBREPLEX_METADATA_PROGRAM_ID = new PublicKey("LibrQsXf9V1DmTtJLkEghoaF1kjJcAzWiEGoJn8mz7p")
-export const LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID = new PublicKey("G9whLiLT9nSkxwWzWvbiKKrTL6yWxvzh2UXqNht5VXqV")
-export const LIBREPLEX_CREATOR_PROGRAM_ID = new PublicKey("78deTr7qycJ6498vSd3pNMhdCKKWxMipniitVHQcM8RM")
-export const LIBREPLEX_NFT_PROGRAM_ID = new PublicKey("9SXDHUdtfvBGT3H2uPCNEkxmWREoqdeS1qdBudLDD6KX")
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/createCollection.ts b/packages/libreplex-sdk/src/createCollection.ts
deleted file mode 100644
index 13b3e2d9..00000000
--- a/packages/libreplex-sdk/src/createCollection.ts
+++ /dev/null
@@ -1,93 +0,0 @@
-import { Program, BN, Provider } from "@coral-xyz/anchor";
-import { Keypair, PublicKey, SystemProgram } from "@solana/web3.js";
-import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata"
-import { getCollectionAddress } from "./pda";
-import { loadMetadataProgram } from "./programs";
-
-type AttributeType = {
- name: string,
- possibleValues: (string | BN | number)[]
-}
-
-export type RoyaltyConfig = {
- bps: number,
- shares: {
- recipient: PublicKey,
- share: number,
- }[]
-}
-
-type SetupCollectionInput = {
- name: string,
- symbol: string,
- url: string,
- royalties: RoyaltyConfig
- description: string,
-
- /**
- * The set of possible attributes for metadatas in your collection.
- */
- onChainAttributes?: AttributeType[]
-
- /**
- * The set of all addresses that are allowed to sign your collection.
- */
- permittedSigners?: PublicKey[]
-}
-
-
-export type Connector = {
- type: "provider",
- provider: Provider,
-} | {
- type: "program",
- metadataProgram: Program,
-}
-
-
-export async function setupCollection(
- groupInfo: {
- connector: Connector,
- input: SetupCollectionInput,
- collectionAuthority: PublicKey,
- groupSeedKp?: Keypair
- }
- ) {
- const {
- connector,
- input,
- collectionAuthority,
- groupSeedKp = Keypair.generate()
- } = groupInfo
- const collection = getCollectionAddress(groupSeedKp.publicKey)
-
- const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider)
-
- return {
- method: metadataProgram.methods.createCollection({
- permittedSigners: input.permittedSigners || [],
- attributeTypes: input.onChainAttributes?.map(v => {
- return {
- permittedValues: v.possibleValues,
- continuedFromIndex: null,
- continuedAtIndex: null,
- deleted: false,
- name: v.name,
- }
- }) || [],
- description: input.description,
- name: input.name,
- symbol: input.symbol,
- url: input.url,
- royalties: input.royalties
- }).accounts({
- authority: collectionAuthority,
- seed: groupSeedKp.publicKey,
- systemProgram: SystemProgram.programId,
- collection,
- }),
-
- collection,
- }
- }
-
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/creatorControls.ts b/packages/libreplex-sdk/src/creatorControls.ts
deleted file mode 100644
index 441a3725..00000000
--- a/packages/libreplex-sdk/src/creatorControls.ts
+++ /dev/null
@@ -1,306 +0,0 @@
-import {BN, IdlAccounts, IdlTypes} from "@coral-xyz/anchor"
-import { PublicKey } from "@solana/web3.js"
-import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls"
-
-export type CreatorControl = SolPaymentControl |
- MintLimitControl |
- SplPaymentControl |
- AllowListControl |
- CustomProgramControl
-
-export type SolPaymentControl = {
- name: "SolPayment",
- price: BN,
- receiver: PublicKey,
-}
-
-export type MintLimitControl = {
- name: "MintLimitControl",
-
- amount: number,
-
- // Is this a global limit or does each buy have their own limit
- scopedToBuyer: boolean,
-
- extraSeeds: PublicKey[]
-}
-
-
-export type SplPaymentControl = {
- name: "SplPayment",
- amount: BN,
- mint: PublicKey,
- recepient: PublicKey,
- tokenProgram: PublicKey,
-}
-
-export type AllowListControl = {
- label: string,
- name: "AllowList",
- merkleRoot: number[],
-}
-
-
-type CustomProgramAccountMetaKeySeed = {
- type: "bytes",
- value: Buffer
-} | {
- type: "mintPlaceHolder",
-} | {
- type: "receiverPlaceHolder",
-} | {
- type: "payerPlaceHolder",
-}
-
-type CustomProgramAccountMetaKey = {
- type: "key",
- value: PublicKey,
-} | {
- type: "seedDerivation",
- programId: PublicKey,
- seeds: CustomProgramAccountMetaKeySeed[],
-}
-
-export type CustomProgramControl = {
- label: string,
- name: "CustomProgram",
- programId: PublicKey,
- instructionData: Buffer,
- remainingAccountsMetas: {
- isSigner: boolean,
- isWritable: boolean,
- key: CustomProgramAccountMetaKey,
- }[],
-}
-
-export function anchorToControl(c: IdlAccounts["creatorController"]["phases"][0]["controls"][0]): CreatorControl {
- if (c.allowList) {
- return {
- name: "AllowList",
- label: c.allowList[0].label,
- merkleRoot: c.allowList[0].root
- }
- }
-
- if (c.customProgram) {
- return {
- name: "CustomProgram",
- instructionData: c.customProgram[0].instructionData,
- label: c.customProgram[0].label,
- programId: c.customProgram[0].programId,
- remainingAccountsMetas: c.customProgram[0].remainingAccountMetas.map(m => {
- let anchorKey: IdlTypes["CustomProgramAcountMetaKey"] = m.key as any;
-
- let key: CustomProgramAccountMetaKey;
- if (anchorKey.pubkey) {
- key = {
- type: "key",
- value: anchorKey.pubkey[0]
- }
- }
- else if (anchorKey.derivedFromSeeds) {
- const anchorSeeds = anchorKey.derivedFromSeeds[0].seeds;
-
- const seeds: CustomProgramAccountMetaKeySeed[] = []
- for (let anchorSeed of anchorSeeds) {
- if (anchorSeed.bytes) {
- seeds.push({
- type: "bytes",
- value: anchorSeed.bytes[0]
- })
- }
- else if (anchorSeed.mintPlaceHolder) {
- seeds.push({
- type: "mintPlaceHolder",
- })
- }
- else if (anchorSeed.payerPlaceHolder) {
- seeds.push({
- type: "payerPlaceHolder",
- })
- }
- else if (anchorSeed.receiverPlaceHolder) {
- seeds.push({
- type: "receiverPlaceHolder",
- })
- }
- else {
- throw new Error("Unsupported custom program anchor seed.")
- }
- }
-
- key = {
- type: "seedDerivation",
- seeds,
- programId: anchorKey.derivedFromSeeds[0].programId,
- }
- }
- else {
- throw new Error("Unsupported custom program anchor key.")
- }
-
-
- return {
- isSigner: m.isSigner,
- isWritable: m.isWritable,
- key,
- }
- }),
- }
- }
-
- if (c.mintLimit) {
- return {
- name: "MintLimitControl",
- amount: c.mintLimit[0].limit,
- extraSeeds: c.mintLimit[0].accountKey,
- scopedToBuyer: c.mintLimit[0].scopedToBuyer,
- }
- }
-
- if (c.payment) {
- const control = c.payment[0];
-
- return {
- name: "SolPayment",
- price: control.amount,
- receiver: control.recepient
- }
- }
-
- if (c.splPayment) {
- const control = c.splPayment[0]
-
- return {
- name: "SplPayment",
- amount: control.amount,
- mint: control.mint,
- recepient: control.recepient,
- tokenProgram: control.tokenProgram
- }
- }
-
- throw new Error("Tried to convert invalid anchor control")
-}
-
-export function controlToAnchor(c: CreatorControl): IdlTypes["ControlType"] {
- switch (c.name) {
- case "SolPayment":
- return {
- payment: {
- 0: {
- amount: c.price,
- recepient: c.receiver,
- }
- }
- }
-
- case "AllowList":
- return {
- allowList: {
- "0": {
- root: c.merkleRoot,
- label: c.label,
- }
- }
- }
-
- case "CustomProgram":
-
- const remainingAccountMetas: IdlTypes["CustomProgramAccountMeta"][] = []
-
- for (let meta of c.remainingAccountsMetas) {
- let key: IdlTypes["CustomProgramAcountMetaKey"];
-
- switch (meta.key.type) {
- case "key":
- key = {
- pubkey: {
- "0": meta.key.value,
- }
- }
- break;
- case "seedDerivation":
- const seeds: IdlTypes["Seed"][] = []
-
- for (const seed of meta.key.seeds) {
- switch (seed.type) {
- case "bytes":
- seeds.push({
- bytes: {
- "0": seed.value
- }
- })
- break;
- case "mintPlaceHolder":
- seeds.push({
- mintPlaceHolder: {}
- })
- break;
- case "receiverPlaceHolder":
- seeds.push({
- receiverPlaceHolder: {}
- })
- break;
- case "payerPlaceHolder":
- seeds.push({
- payerPlaceHolder: {}
- })
- break;
-
- default:
- throw new Error(`Invalid seed ${seed}`)
- }
- }
-
- key = {
- derivedFromSeeds: {
- "0": {
- programId: meta.key.programId,
- seeds,
- }
- }
- }
- break;
- default:
- throw new Error(`Invalid custom program account meta key type. ${meta.key}`)
- }
-
- remainingAccountMetas.push({
- isSigner: meta.isSigner,
- isWritable: meta.isWritable,
- key,
- })
- }
-
- return {
- customProgram: {
- 0: {
- ...c,
- remainingAccountMetas
- }
- }
- }
-
- case "MintLimitControl":
- return {
- mintLimit: {
- "0": {
- limit: c.amount,
- scopedToBuyer: c.scopedToBuyer,
- accountKey: c.extraSeeds,
- }
- }
- }
-
- case "SplPayment":
- return {
- splPayment: {
- "0": c
- }
- }
-
- default:
- throw new Error("Invalid control")
- };
-}
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/groupPermissions.ts b/packages/libreplex-sdk/src/groupPermissions.ts
deleted file mode 100644
index a8f4b3f7..00000000
--- a/packages/libreplex-sdk/src/groupPermissions.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { Program } from "@coral-xyz/anchor"
-import { PublicKey, SystemProgram } from "@solana/web3.js"
-import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata";
-
-import { LIBREPLEX_METADATA_PROGRAM_ID } from "./constants";
-import { Connector } from "./createCollection";
-import { loadMetadataProgram } from "./programs";
-
-export enum UserPermission {
- Update,
- Delete,
- AddToGroup,
- }
-
- export function getCollectionWideUserPermissionsAddress(collection: PublicKey, user: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([Buffer.from("permissions"), user.toBuffer(), collection.toBuffer()], program)[0]
-
- }
-
- function convertPermission(p: UserPermission) {
- if (p === UserPermission.AddToGroup) {
- return {
- addToGroup: {}
- }
- }
-
- if (p === UserPermission.Delete) {
- return {
- addToGroup: {}
- }
- }
-
- if (p === UserPermission.Update) {
- return {
- addToGroup: {}
- }
- }
-
- throw new Error("Invalid permission enum")
- }
-
-
-export async function setUserPermissionsForGroup(
- {
- connector,
- collection,
- user,
- permissions,
- groupUpdateAuthority,
- }: {
- connector: Connector,
- collection: PublicKey,
- user: PublicKey,
- permissions: UserPermission[],
- groupUpdateAuthority: PublicKey,
- }
- ) {
- const permissionsAccountAddress = getCollectionWideUserPermissionsAddress(collection, user)
-
- const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider)
-
-
- const existingPermissionsInfo = await metadataProgram.provider.connection.getAccountInfo(permissionsAccountAddress)
-
- const anchorPermissions = permissions.map(convertPermission);
-
- if (!existingPermissionsInfo) {
- return metadataProgram.methods.delegateCollectionPermissions({
- permissions: anchorPermissions,
- }).accounts({
- collection,
- delegatedUser: user,
- systemProgram: SystemProgram.programId,
- updateAuthority: groupUpdateAuthority,
- userPermissions: permissionsAccountAddress
- })
- }
-
- return metadataProgram.methods.updatePermissions({
- permissions: anchorPermissions
- }).accounts({
- updateAuthority: groupUpdateAuthority,
- user: user,
- userPermissions: permissionsAccountAddress
- })
- }
-
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/index.ts b/packages/libreplex-sdk/src/index.ts
deleted file mode 100644
index 47a875b2..00000000
--- a/packages/libreplex-sdk/src/index.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-export {setupCollection} from "./createCollection"
-export * from "./constants"
-export * from "./pda"
-export {setUserPermissionsForGroup, UserPermission} from "./groupPermissions"
-
-export {setupCreator, setupCreatorWithCustomSalePhases} from "./setupCreator"
-export type {Phase} from "./setupCreator"
-
-export {mintFromCreatorController, mintFromCreatorControllerState} from "./mint"
-
-export type {CreatorControl, AllowListControl, CustomProgramControl,
- MintLimitControl, SolPaymentControl, SplPaymentControl as SplPayment} from "./creatorControls"
-
-export {anchorToControl, controlToAnchor} from "./creatorControls"
-
-export {updateCreator} from "./updateCreator"
-
-
-export type {UpdateCreatorInput} from "./updateCreator"
-
-export {mintSingle, setupLibreplexReadyMint} from "./mint"
-
-export {updateCollectionAuthority} from "./updateCollection"
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/mint.ts b/packages/libreplex-sdk/src/mint.ts
deleted file mode 100644
index c12ea3df..00000000
--- a/packages/libreplex-sdk/src/mint.ts
+++ /dev/null
@@ -1,800 +0,0 @@
-import {
- AccountMeta,
- Connection,
- Keypair,
- PublicKey,
- SYSVAR_SLOT_HASHES_PUBKEY,
- SystemProgram,
- Transaction,
- TransactionInstruction,
-} from '@solana/web3.js';
-import { LibreplexCreator } from '@libreplex/idls/lib/types/libreplex_creator';
-import { LibreplexMetadata } from '@libreplex/idls/lib/types/libreplex_metadata';
-import { LibreplexNft } from '@libreplex/idls/lib/types/libreplex_nft';
-import { LibreplexCreatorControls } from '@libreplex/idls/lib/types/libreplex_creator_controls';
-import {
- Program,
- AccountClient,
- IdlAccounts,
- IdlTypes,
- Provider,
-} from '@coral-xyz/anchor';
-import {
- LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID,
- LIBREPLEX_CREATOR_PROGRAM_ID,
- LIBREPLEX_METADATA_PROGRAM_ID,
- LIBREPLEX_NFT_PROGRAM_ID,
-} from './constants';
-import {
- MINT_SIZE,
- TOKEN_2022_PROGRAM_ID,
- createInitializeMint2Instruction,
- getMinimumBalanceForRentExemptMint,
- getAssociatedTokenAddressSync,
- createAssociatedTokenAccountInstruction,
- createMintToInstruction,
-} from '@solana/spl-token';
-import { struct, u8 } from '@solana/buffer-layout';
-import { publicKey } from '@solana/buffer-layout-utils';
-import { getMetadataAddress, getMintWrapperAddress } from './pda';
-import { getCollectionWideUserPermissionsAddress } from './groupPermissions';
-
-import { RoyaltyConfig } from './createCollection';
-import { loadMetadataProgram, loadNftProgram } from './programs';
-
-type CustomProgramAccountMeta = Omit & {
- key:
- | {
- keyType: 'PublicKey';
- value: PublicKey;
- }
- | {
- keyType: 'PDA';
- seeds: Buffer[];
- deriveFromMint: boolean;
- deriveFromBuyer: boolean;
- programIdToDeriveFrom: PublicKey;
- };
-};
-
-export type MintFromCreatorControllerInput = {
- creatorControllerProgram: Program;
- creatorProgram: Program;
- creatorController: PublicKey;
-
- mintKeyPair?: Keypair;
-
- // If there are multiple active sale phases, specify the one to mint in.
- phaseToMintIn?: string;
-
- merkleProofsForAllowLists?: {
- label: string;
- proof: Buffer[];
- }[];
-
- addTransferHookToMint?: {
- programId: PublicKey;
- authority: PublicKey;
- };
-};
-
-type MintFromCreatorControllerStateInput = {
- creator: PublicKey;
- targetPhase: IdlAccounts['creatorController']['phases'][0];
- minterNumbers: PublicKey | null;
- collection: PublicKey;
-} & Omit;
-
-export async function mintFromCreatorControllerState(
- input: MintFromCreatorControllerStateInput
-) {
- const {
- creatorControllerProgram,
- creatorController,
- merkleProofsForAllowLists,
- addTransferHookToMint,
- minterNumbers,
- targetPhase,
- collection,
- creator,
- } = input;
-
- let mintKeyPair = input.mintKeyPair || Keypair.generate();
-
- const connection = creatorControllerProgram.provider.connection;
- const me = creatorControllerProgram.provider.publicKey;
-
- if (!me) {
- throw new Error('Provider not setup. Perhaps your wallet is not connected');
- }
-
- const args: Buffer[] = [];
- const remainingAccounts: AccountMeta[] = [];
-
- const controls = targetPhase.controls;
-
- for (const control of controls) {
- if (control.payment) {
- remainingAccounts.push({
- isSigner: false,
- isWritable: true,
- pubkey: control.payment[0].recepient,
- });
- } else if (control.splPayment) {
- remainingAccounts.push({
- isSigner: false,
- isWritable: true,
- pubkey: control.splPayment[0].recepient,
- });
-
- remainingAccounts.push({
- isSigner: false,
- isWritable: true,
- pubkey: getAssociatedTokenAddressSync(
- control.splPayment[0].mint,
- me,
- undefined,
- control.splPayment[0].tokenProgram
- ),
- });
-
- remainingAccounts.push({
- isSigner: false,
- isWritable: false,
- pubkey: control.splPayment[0].tokenProgram,
- });
- } else if (control.mintLimit) {
- const seeds: Buffer[] = [Buffer.from('mint_limit')];
-
- if (control.mintLimit[0].scopedToBuyer) {
- seeds.push(me.toBuffer());
- }
-
- control.mintLimit[0].accountKey.forEach((keyElement) => {
- seeds.push(keyElement.toBuffer());
- });
-
- const mintLimitAccount = PublicKey.findProgramAddressSync(
- seeds,
- LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID
- )[0];
-
- remainingAccounts.push({
- isSigner: false,
- isWritable: true,
- pubkey: mintLimitAccount,
- });
- } else if (control.allowList) {
- if (!merkleProofsForAllowLists) {
- throw new Error(
- 'Must provide merkle proofs when your creator as an allowlist'
- );
- }
-
- const proofEntry = merkleProofsForAllowLists.find(
- (mp) => mp.label === control.allowList[0].label
- );
-
- if (!proofEntry) {
- throw new Error(
- `Proof entry not found for allowlist: ${control.allowList[0].label}`
- );
- }
-
- args.push(Buffer.concat(proofEntry.proof));
- } else if (control.customProgram) {
- const remainingAccountMetas: AccountMeta[] = [
- {
- isSigner: false,
- isWritable: false,
- pubkey: control.customProgram[0].programId,
- },
- ];
-
- for (const meta of control.customProgram[0].remainingAccountMetas) {
- const key: IdlTypes['CustomProgramAcountMetaKey'] =
- meta.key as any;
-
- if (key.pubkey) {
- remainingAccountMetas.push({
- ...meta,
- pubkey: key.pubkey[0],
- });
- } else if (key.derivedFromSeeds) {
- const programId = key.derivedFromSeeds[0].programId;
-
- const seeds: Buffer[] = [];
- for (const seed of key.derivedFromSeeds[0].seeds) {
- if (seed.bytes) {
- seeds.push(seed.bytes[0]);
- } else if (seed.mintPlaceHolder) {
- seeds.push(mintKeyPair.publicKey.toBuffer());
- } else if (seed.payerPlaceHolder || seed.receiverPlaceHolder) {
- seeds.push(me.toBuffer());
- } else {
- throw new Error('Invalid seed derivation');
- }
- }
-
- remainingAccountMetas.push({
- ...meta,
- pubkey: PublicKey.findProgramAddressSync(seeds, programId)[0],
- });
- } else {
- throw new Error('Invalid CustomProgramAcountMetaKey');
- }
- }
-
- remainingAccounts.push(...remainingAccountMetas);
- }
- }
-
- const metadata = getMetadataAddress(mintKeyPair.publicKey);
- const setupMintCtx = await setupLibreplexReadyMint(
- connection,
- me,
- me,
- me,
- me,
- 0,
- mintKeyPair,
- metadata,
- addTransferHookToMint
- );
-
- return {
- method: creatorControllerProgram.methods
- .mint({
- chosenPhase: targetPhase.label,
- args,
- })
- .accounts({
- attributeConfig: null,
- creator,
- creatorController,
- collection,
- libreplexCreatorProgram: LIBREPLEX_CREATOR_PROGRAM_ID,
- libreplexMetadataProgram: LIBREPLEX_METADATA_PROGRAM_ID,
- libreplexNftProgram: LIBREPLEX_NFT_PROGRAM_ID,
- mint: mintKeyPair.publicKey,
- metadata,
- mintAuthority: me,
- minterNumbers,
- mintWrapper: getMintWrapperAddress(mintKeyPair.publicKey),
- payer: me,
- receiver: me,
- receiverTokenAccount: getAssociatedTokenAddressSync(
- mintKeyPair.publicKey,
- me,
- undefined,
- TOKEN_2022_PROGRAM_ID
- ),
- recentSlothashes: SYSVAR_SLOT_HASHES_PUBKEY,
- systemProgram: SystemProgram.programId,
- tokenProgram: TOKEN_2022_PROGRAM_ID,
- collectionPermissions: getCollectionWideUserPermissionsAddress(
- collection,
- creator
- ),
- })
- .preInstructions([...setupMintCtx.transaction.instructions])
- .signers([mintKeyPair])
- .remainingAccounts(remainingAccounts),
-
- mint: mintKeyPair,
- };
-}
-
-export async function mintFromCreatorController(
- input: MintFromCreatorControllerInput
-) {
- const {
- creatorControllerProgram,
- creatorController,
- creatorProgram,
- phaseToMintIn,
- } = input;
-
- const controller =
- await creatorControllerProgram.account.creatorController.fetchNullable(
- creatorController
- );
-
- if (!controller) {
- throw new Error(
- `Creator controller at address: ${creatorController.toString()} not found`
- );
- }
-
- const creator = await creatorProgram.account.creator.fetchNullable(
- controller.creator
- );
-
- if (!creator) {
- throw new Error(
- `Creator at address ${controller.creator?.toString()} not found`
- );
- }
-
- const now = Date.now() / 1000;
-
- const availableSalePhases = controller.phases;
-
- const activePhases = availableSalePhases.filter(
- (ph) => now > ph.start && (ph.end === null || now < ph.end)
- );
-
- if (activePhases.length === 0) {
- throw new Error('No currently active phases to mint from');
- }
-
- let targetPhase = activePhases[0];
-
- if (activePhases.length > 1) {
- if (!phaseToMintIn) {
- throw new Error(
- 'Must provide a target phase to mint in when multiple are active'
- );
- }
-
- const maybeTargetPhase = activePhases.find(
- (ph) => ph.label === phaseToMintIn
- );
-
- if (!maybeTargetPhase) {
- throw new Error(
- `Specified phase to mint in ${phaseToMintIn} is not active`
- );
- }
-
- targetPhase = maybeTargetPhase;
- }
-
- return mintFromCreatorControllerState({
- ...input,
- targetPhase,
- creator: controller.creator,
- minterNumbers: creator.minterNumbers,
- collection: creator.collection,
- });
-}
-
-export type MintAssetUrl =
- | {
- type: 'jsonUrl';
- value: string;
- }
- | {
- type: 'imageUrl';
- value: string;
- }
- | {
- type: 'renderedOnChain';
- programId: PublicKey;
- description: string | null;
- };
-
-export type MetadataExtension = {
- // Metadata extension data. If you don't want it don't take it.
-
- licenseUrl?: string;
-
- /**
- * The list of keys that can add their signature to your metadata.
- */
- permittedSigners?: PublicKey[];
-
- /**
- * Only works when part of a group.
- * Attribute defintions exist on the group.
- * List of pointers to the on chain attributes stored in the group.
- */
- onChainAttributes?: number[];
-
- royalties?: RoyaltyConfig;
-};
-
-export type MetadataData = {
- name: string;
- symbol: string;
- assetUrl: MintAssetUrl;
-
- extension?: MetadataExtension;
-};
-
-export type MintSingleInput = {
- provider: Provider;
-
- mintData: MetadataData;
-
- mintToCollection?: {
- collection: PublicKey;
- checkValidGroup: boolean;
-
- /**
- * If you are not the update auth of the group.
- * But have been given permission to add metadatas to it.
- * Set this to true.
- *
- * Defaults to false.
- */
- groupDelegate?: boolean;
- };
-
- mintKp?: Keypair;
-
- receiver?: PublicKey;
-
- transferHook?: TransferHookConfig;
-
- metadataProgram?: Program;
- nftProgram?: Program;
-
- updateAuthority?: PublicKey;
-};
-
-export async function mintSingle(input: MintSingleInput) {
- const {
- provider,
- metadataProgram = await loadMetadataProgram(provider),
- nftProgram = await loadNftProgram(provider),
- mintToCollection,
- receiver = provider.publicKey,
- mintKp = Keypair.generate(),
- transferHook,
- updateAuthority = provider.publicKey,
- mintData,
- } = input;
-
- const me = provider.publicKey;
-
- if (!me) {
- throw new Error(
- 'Provider does have a wallet loaded into it. Are you sure your wallet is connected'
- );
- }
-
- if (mintToCollection) {
- if (mintToCollection.checkValidGroup) {
- const groupData = await metadataProgram.account.collection.fetchNullable(
- mintToCollection.collection
- );
-
- if (!groupData) {
- throw new Error('Group does not exist');
- }
-
- if (groupData.updateAuthority.toString() != me.toString()) {
- const groupWideAddress = getCollectionWideUserPermissionsAddress(
- mintToCollection.collection,
- me
- );
-
- const permissionsData =
- await metadataProgram.account.delegatePermissions.fetchNullable(
- groupWideAddress
- );
-
- const hasDelegatedPermission = !!permissionsData?.permissions.find(
- (perm) => !!perm.addToGroup
- );
-
- if (!permissionsData || !hasDelegatedPermission) {
- throw new Error(
- 'You do not have permission to add metadata to this group.'
- );
- }
-
- mintToCollection.groupDelegate = true;
- }
- }
- }
-
- const connection = provider.connection;
-
- const metadata = getMetadataAddress(mintKp.publicKey);
-
- const mintCtx = await setupLibreplexReadyMint(
- connection,
- me,
- me,
- receiver as PublicKey,
- me,
- 0,
- mintKp,
- metadata,
- transferHook
- );
-
- let anchorAssetUrl: IdlTypes['Asset'];
-
- const { assetUrl, name, symbol } = mintData;
-
- switch (assetUrl.type) {
- case 'jsonUrl':
- anchorAssetUrl = {
- json: {
- url: assetUrl.value,
- },
- };
- break;
- case 'imageUrl':
- anchorAssetUrl = {
- image: {
- url: assetUrl.value,
- description: null,
- },
- };
- break;
- case 'renderedOnChain':
- anchorAssetUrl = {
- chainRenderer: {
- programId: assetUrl.programId,
- },
- };
- break;
-
- default:
- throw new Error('Invalid asset type');
- }
-
- type ExtensionType = IdlTypes['MetadataExtension'];
- let extensions: ExtensionType[] = [];
-
- if (mintData.extension) {
- if (mintData.extension.onChainAttributes) {
- extensions.push({
- attributes: {
- attributes: Buffer.from(mintData.extension.onChainAttributes ?? []),
- },
- });
- }
- if (mintData.extension.royalties) {
- extensions.push({
- royalties: {
- royalties: mintData.extension.royalties || null,
- },
- });
- }
-
- if (mintData.extension.licenseUrl) {
- extensions.push({
- license: {
- license: {
- custom: {
- licenseUrl: mintData.extension.licenseUrl,
- },
- },
- },
- });
- }
-
- if (mintData.extension.permittedSigners) {
- extensions.push({
- signers: {
- signers: mintData.extension.permittedSigners || [],
- },
- });
- }
- }
- console.log('Creating metadata instructions');
- const createMetaData = metadataProgram.methods
- .createMetadata({
- asset: anchorAssetUrl,
- extensions,
- name,
- symbol,
- updateAuthority: updateAuthority as PublicKey,
- })
- .accounts({
- authority: me,
- metadata,
- payer: me,
- mint: mintKp.publicKey,
- invokedMigratorProgram: null,
- systemProgram: SystemProgram.programId,
- })
- .preInstructions(mintCtx.transaction.instructions)
- .signers([mintCtx.keypair]);
-
- console.log('Created metadata instruction');
-
- const postIxs: TransactionInstruction[] = [
- await nftProgram.methods
- .wrap()
- .accounts({
- authority: me,
- mint: mintKp.publicKey,
- payer: me,
- systemProgram: SystemProgram.programId,
- tokenProgram: TOKEN_2022_PROGRAM_ID,
- wrappedMint: getMintWrapperAddress(mintKp.publicKey),
- })
- .instruction(),
- ];
-
- if (mintToCollection) {
- const ix = await metadataProgram.methods
- .addMetadataToCollection()
- .accounts({
- delegatedCollectionWidePermissions: mintToCollection.groupDelegate
- ? getCollectionWideUserPermissionsAddress(
- mintToCollection.collection,
- me
- )
- : null,
- systemProgram: SystemProgram.programId,
- payer: me,
- metadata,
- metadataAuthority: me,
- collectionAuthority: me,
- delegatedMetadataSpecificPermissions: null,
- collection: mintToCollection.collection,
- })
- .instruction();
-
- postIxs.push(ix);
- }
-
- return {
- method: createMetaData.postInstructions(postIxs),
- mint: mintKp,
- };
-}
-
-const MetadataPointerMintSize = 234;
-const MintSizeForTranserHookAndPointer = 302;
-
-interface InitializeMetadataPointerIx {
- instruction: 39;
- metadataPointerInitIx: 0;
- authority: PublicKey;
- metadataAddress: PublicKey;
-}
-
-const initializeMetadataPointerInstructionData =
- struct([
- u8('instruction') as any,
- u8('metadataPointerInitIx'),
- publicKey('authority'),
- publicKey('metadataAddress'),
- ]);
-
-interface InitializeTransferHookInit {
- instruction: 36;
- transferHookInstruction: 0;
- authority: PublicKey;
- transferHookProgramId: PublicKey;
-}
-
-const initializeTransferHookInitInstructionData =
- struct([
- u8('instruction') as any,
- u8('transferHookInstruction'),
- publicKey('authority'),
- publicKey('transferHookProgramId'),
- ]);
-
-type TransferHookConfig = {
- programId: PublicKey;
- authority: PublicKey;
-};
-
-export async function setupLibreplexReadyMint(
- connection: Connection,
- payer: PublicKey,
- receiver: PublicKey,
- mintAuthority: PublicKey,
- freezeAuthority: PublicKey | null,
- decimals: number,
- mintKeypair = Keypair.generate(),
- metadata: PublicKey,
- transferHook?: TransferHookConfig,
- programId = TOKEN_2022_PROGRAM_ID
-) {
- const mintSize = transferHook
- ? MintSizeForTranserHookAndPointer
- : MetadataPointerMintSize;
- const lamports = await connection.getMinimumBalanceForRentExemption(mintSize);
-
- const initMetadataPointerExtensionIx = (() => {
- const initMetadataPointerIxSpan = Buffer.alloc(
- initializeMetadataPointerInstructionData.span
- );
-
- initializeMetadataPointerInstructionData.encode(
- {
- instruction: 39,
- authority: PublicKey.default,
- metadataPointerInitIx: 0,
- metadataAddress: metadata,
- },
- initMetadataPointerIxSpan
- );
-
- return new TransactionInstruction({
- keys: [
- {
- isSigner: false,
- isWritable: true,
- pubkey: mintKeypair.publicKey,
- },
- ],
- programId,
- data: initMetadataPointerIxSpan,
- });
- })();
-
- const preInitMintIxs: TransactionInstruction[] = [];
-
- if (transferHook) {
- const accounts = [
- { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true },
- ];
- const transferHookIxBuf = Buffer.alloc(
- initializeTransferHookInitInstructionData.span
- );
- initializeTransferHookInitInstructionData.encode(
- {
- authority: transferHook.authority,
- transferHookProgramId: transferHook.programId,
- instruction: 36,
- transferHookInstruction: 0,
- },
- transferHookIxBuf
- );
-
- preInitMintIxs.push(
- new TransactionInstruction({
- keys: accounts,
- programId: TOKEN_2022_PROGRAM_ID,
- data: transferHookIxBuf,
- })
- );
- }
-
- const assocTokenAccount = getAssociatedTokenAddressSync(
- mintKeypair.publicKey,
- receiver,
- undefined,
- TOKEN_2022_PROGRAM_ID
- );
- const transaction = new Transaction().add(
- SystemProgram.createAccount({
- fromPubkey: payer,
- newAccountPubkey: mintKeypair.publicKey,
- space: mintSize,
- lamports,
- programId,
- }),
- initMetadataPointerExtensionIx,
- ...preInitMintIxs,
- createInitializeMint2Instruction(
- mintKeypair.publicKey,
- decimals,
- mintAuthority,
- freezeAuthority,
- programId
- ),
- createAssociatedTokenAccountInstruction(
- payer,
- assocTokenAccount,
- receiver,
- mintKeypair.publicKey,
- TOKEN_2022_PROGRAM_ID
- ),
- createMintToInstruction(
- mintKeypair.publicKey,
- assocTokenAccount,
- mintAuthority,
- 1,
- undefined,
- TOKEN_2022_PROGRAM_ID
- )
- );
-
- return {
- transaction,
- keypair: mintKeypair,
- };
-}
diff --git a/packages/libreplex-sdk/src/pda.ts b/packages/libreplex-sdk/src/pda.ts
deleted file mode 100644
index 6a9ffa30..00000000
--- a/packages/libreplex-sdk/src/pda.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { PublicKey } from "@solana/web3.js";
-import { LIBREPLEX_METADATA_PROGRAM_ID, LIBREPLEX_CREATOR_PROGRAM_ID, LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID, LIBREPLEX_NFT_PROGRAM_ID } from "./constants";
-
-
-
-export function getCollectionAddress(collectionSeed: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([Buffer.from("collection"), collectionSeed.toBuffer()], LIBREPLEX_METADATA_PROGRAM_ID)[0]
-
- }
-
-export function getCreatorAddress(seed: PublicKey, program = LIBREPLEX_CREATOR_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([Buffer.from("creator"), seed.toBuffer()], program)[0]
-
-}
-
-export function getCreatorControllerAddress(seed: PublicKey, programId: PublicKey = LIBREPLEX_CREATOR_CONTROLS_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([seed.toBuffer()], programId)[0]
-}
-
-export function getMetadataAddress(mint: PublicKey, program = LIBREPLEX_METADATA_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([Buffer.from("metadata"),
- mint.toBuffer()], program)[0]
-}
-
-export function getMintWrapperAddress(mint: PublicKey, program = LIBREPLEX_NFT_PROGRAM_ID) {
- return PublicKey.findProgramAddressSync([mint.toBuffer()], program)[0]
-}
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/programs.ts b/packages/libreplex-sdk/src/programs.ts
deleted file mode 100644
index 45d56967..00000000
--- a/packages/libreplex-sdk/src/programs.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import {LibreplexCreator} from "@libreplex/idls/lib/types/libreplex_creator"
-import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata"
-import {LibreplexNft} from "@libreplex/idls/lib/types/libreplex_nft"
-import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls"
-import { Program, AccountClient , IdlAccounts, IdlTypes, Provider} from "@coral-xyz/anchor"
-import { LIBREPLEX_METADATA_PROGRAM_ID, LIBREPLEX_NFT_PROGRAM_ID } from "./constants"
-
-export async function loadMetadataProgram(provider: Provider) {
- return new Program((await import("@libreplex/idls/lib/cjs/libreplex_metadata")).IDL,
- LIBREPLEX_METADATA_PROGRAM_ID, provider)
-}
-
-export async function loadNftProgram(provider: Provider) {
- return new Program((await import("@libreplex/idls/lib/cjs/libreplex_nft")).IDL,
- LIBREPLEX_NFT_PROGRAM_ID, provider)
-}
diff --git a/packages/libreplex-sdk/src/setupCreator.ts b/packages/libreplex-sdk/src/setupCreator.ts
deleted file mode 100644
index 7499e3a0..00000000
--- a/packages/libreplex-sdk/src/setupCreator.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { BN, Program } from "@coral-xyz/anchor"
-import {LibreplexCreator} from "@libreplex/idls/lib/types/libreplex_creator"
-import {LibreplexMetadata} from "@libreplex/idls/lib/types/libreplex_metadata"
-import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls"
-
-import { Keypair, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js";
-import {getCreatorAddress, getCreatorControllerAddress} from "./pda"
-import {UserPermission, setUserPermissionsForGroup} from "./groupPermissions"
-import { CreatorControl, controlToAnchor } from "./creatorControls";
-import { LIBREPLEX_CREATOR_PROGRAM_ID } from "./constants";
-
-export type SetupCreatorData = {
- description: string,
- baseName: string,
- ordered: boolean,
- supply: number,
- symbol: string,
- baseUrl: {
- type: "json-prefix",
- url: string,
- } | {
- type: "chain-renderer",
- programId: PublicKey,
- description?: string,
- },
-}
-
-export type SetupCreatorInput = {
- mintAuthority: PublicKey,
- program: Program,
- metadataProgram: Program,
- collection: PublicKey,
- creatorData: SetupCreatorData,
-}
-
-
-
-export type Phase = {
- start: Date,
- end: Date | null,
- label: string,
- control: CreatorControl[]
-}
-
-export async function setupCreatorWithCustomSalePhases(
- input: SetupCreatorInput,
- creatorControllerProgram: Program,
- salePhases: Phase[],
- checkGroupIsValid = true) {
- const me = creatorControllerProgram.provider.publicKey
- const setupCreatorCtx = await setupCreator(input, checkGroupIsValid)
-
- const creatorControllerSeed = Keypair.generate()
-
- const anchorPhases = salePhases.map(p => {
- const anchorControls = p.control.map(controlToAnchor)
-
- return {
- start: new BN(Math.floor(p.start.getTime() / 1000)),
- end: p.end != null ? new BN(Math.floor(p.end.getTime() / 1000)) : null,
- label: p.label,
- controls: anchorControls,
- }
- });
-
- const creatorController = getCreatorControllerAddress(creatorControllerSeed.publicKey)
-
- const controllerCtx = await creatorControllerProgram.methods.initialize({
- seed: creatorControllerSeed.publicKey,
- phases: anchorPhases
- }).accounts({
- creator: setupCreatorCtx.creator,
- creatorController,
- libreplexCreatorProgram: LIBREPLEX_CREATOR_PROGRAM_ID,
- systemProgram: SystemProgram.programId,
- updateAuthority: me,
- payer: me,
- }).prepare()
-
- const method = setupCreatorCtx.method.postInstructions([controllerCtx.instruction])
-
- return {
- method,
- creatorController,
- creator: setupCreatorCtx.creator,
- minterNumbers: setupCreatorCtx.minterNumbers,
- }
-}
-
-
-export async function setupCreator(input: SetupCreatorInput, checkGroupIsValid = true) {
- const {program, collection, creatorData, mintAuthority, metadataProgram} = input;
- const {description, baseName, ordered, supply, symbol, baseUrl} = creatorData;
-
- const me = program.provider.publicKey
-
- if (checkGroupIsValid) {
- const groupAccount = await metadataProgram.account.collection.fetchNullable(collection)
-
- if (!groupAccount) {
- throw new Error("Provided group does not exist")
- }
-
-
- if (groupAccount.updateAuthority.toString() !== me?.toString()) {
- throw new Error(`You do not have authority over the provided group.
- ${groupAccount.updateAuthority.toString()} ${me?.toString()}`)
- }
- }
-
- const creatorSeed = Keypair.generate()
- const creator = getCreatorAddress(creatorSeed.publicKey);
-
- const preIx: TransactionInstruction[] = []
- const signers: Keypair[] = []
- let minterNumbers: PublicKey | null = null
-
- if (!ordered) {
- const minterNumbersKp = Keypair.generate()
- const minterNumbersSize = 8 + 32 + 4 * supply
- const rent = await program.provider.connection.getMinimumBalanceForRentExemption(minterNumbersSize, "confirmed")
-
- const creatorMinterNumbersIx = SystemProgram.createAccount({
- fromPubkey: program.provider.publicKey as PublicKey,
- lamports: rent,
- newAccountPubkey: minterNumbersKp.publicKey,
- programId: program.programId,
- space: minterNumbersSize,
- })
-
- minterNumbers = minterNumbersKp.publicKey
-
- preIx.push(creatorMinterNumbersIx)
- signers.push(minterNumbersKp)
- }
-
-
- let createCreatorMethod = await program.methods.createCreator({
- attributeMappings: null,
- collection,
- description,
- isOrdered: ordered,
- maxMints: supply,
- mintAuthority,
- name: baseName,
- seed: creatorSeed.publicKey,
- symbol: symbol,
- assetUrl: baseUrl.type === "json-prefix" ? {
- jsonPrefix: {
- url: baseUrl.url,
- }
- } : {
- chainRenderer: {
- programId: baseUrl.programId
- }
- }
- }).accounts({
- creator,
- minterNumbers,
- signer: program.provider.publicKey,
- systemProgram: SystemProgram.programId,
- }).preInstructions(preIx).signers(signers)
-
- const delegateToGroupMethod = await (await setUserPermissionsForGroup({
- collection,
- groupUpdateAuthority: me as PublicKey,
- user: creator,
- connector: {
- type: "program",
- metadataProgram,
- },
- permissions: [UserPermission.AddToGroup]
- })).prepare()
-
- createCreatorMethod = createCreatorMethod.postInstructions([delegateToGroupMethod.instruction])
-
-
-
- return {
- method: createCreatorMethod,
- creator,
- minterNumbers
- }
-}
\ No newline at end of file
diff --git a/packages/libreplex-sdk/src/updateCollection.ts b/packages/libreplex-sdk/src/updateCollection.ts
deleted file mode 100644
index 9c5011bd..00000000
--- a/packages/libreplex-sdk/src/updateCollection.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { PublicKey } from "@solana/web3.js";
-import { Connector } from "./createCollection";
-import { loadMetadataProgram } from "./programs";
-
-
-
-export async function updateCollectionAuthority(
- {
- connector,
- collection,
- new_authority
- }: {
- connector: Connector,
- collection: PublicKey,
- new_authority: PublicKey,
- }
- ) {
- const metadataProgram = connector.type === "program" ? connector.metadataProgram : await loadMetadataProgram(connector.provider)
-
- const me = metadataProgram.provider.publicKey;
-
- if (!me) {
- throw new Error("Provider not setup. Perhaps your wallet is not connected");
- }
-
- return metadataProgram.methods.updateCollectionAuthority(new_authority).accounts({
- collection,
- updateAuthority: me,
- })
- }
diff --git a/packages/libreplex-sdk/src/updateCreator.ts b/packages/libreplex-sdk/src/updateCreator.ts
deleted file mode 100644
index 0354cbd6..00000000
--- a/packages/libreplex-sdk/src/updateCreator.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Keypair, PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js";
-import { Phase } from "./setupCreator";
-import {controlToAnchor} from "./creatorControls"
-import {LibreplexCreatorControls} from "@libreplex/idls/lib/types/libreplex_creator_controls"
-import { Program, AccountClient , IdlAccounts, IdlTypes} from "@coral-xyz/anchor"
-
-export type UpdateCreatorInput = {
- program: Program,
- creatorController: PublicKey,
- phases: Phase[],
-}
-
-
-export async function updateCreator(input: UpdateCreatorInput) {
- const {program, phases, creatorController} = input;
-
- const me = program.provider.publicKey
-
- if (!me) {
- throw new Error("Missing provider. Are you sure your wallet is connected?")
- }
-
-
-
- const anchorPhases: IdlTypes["Phase"][] = [];
-
- for (const ph of phases) {
- const controls: IdlTypes["ControlType"][] = []
-
- for (const control of ph.control) {
- controls.push(controlToAnchor(control))
- }
-
- anchorPhases.push({
- controls,
- end: ph.end ? ph.end.getTime()/1000 : null,
- label: ph.label,
- start: ph.start.getTime()/1000,
- })
- }
-
- return program.methods.update({
- phases: anchorPhases,
- }).accounts({
- systemProgram: SystemProgram.programId,
- payer: me,
- updateAuthority: me,
- creatorController,
- });
-}
\ No newline at end of file
diff --git a/packages/libreplex-sdk/tsconfig.cjs.json b/packages/libreplex-sdk/tsconfig.cjs.json
deleted file mode 100644
index d1d9da77..00000000
--- a/packages/libreplex-sdk/tsconfig.cjs.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "include": ["src"],
- "extends": "./tsconfig.json",
- "compilerOptions": {
- "target": "ES2016",
- "module": "CommonJS",
- "outDir": "lib/cjs"
- }
- }
\ No newline at end of file
diff --git a/packages/libreplex-sdk/tsconfig.esm.json b/packages/libreplex-sdk/tsconfig.esm.json
deleted file mode 100644
index 0e27a423..00000000
--- a/packages/libreplex-sdk/tsconfig.esm.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "extends": "./tsconfig.json",
- "include": ["src"],
- "compilerOptions": {
- "outDir": "lib/esm",
- "declarationDir": "lib/types",
- "target": "ESNext",
- "module": "ESNext",
- "sourceMap": true,
- "declaration": true,
- "declarationMap": true,
- }
-}
\ No newline at end of file
diff --git a/packages/libreplex-sdk/tsconfig.json b/packages/libreplex-sdk/tsconfig.json
deleted file mode 100644
index 169a901e..00000000
--- a/packages/libreplex-sdk/tsconfig.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "include": [],
- "compilerOptions": {
- "lib": ["ES2021.String"],
- "target": "ESNext",
- "module": "ESNext",
- "moduleResolution": "Node",
- "esModuleInterop": true,
- "isolatedModules": true,
- "noImplicitAny": false,
- "noEmitOnError": true,
- "resolveJsonModule": true,
- "strict": true,
- "stripInternal": true,
- "skipLibCheck": true,
- "noEmit": false,
- "allowSyntheticDefaultImports": true,
- "composite": true
- },
- "references": [
- {
- "path": "./tsconfig.cjs.json"
- },
- {
- "path": "./tsconfig.esm.json"
- },
- ]
-}
diff --git a/programs/libreplex_editions/Cargo.toml b/programs/libreplex_editions/Cargo.toml
index 53ddcbe1..1f9fe037 100644
--- a/programs/libreplex_editions/Cargo.toml
+++ b/programs/libreplex_editions/Cargo.toml
@@ -19,7 +19,6 @@ default = []
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
-
[dependencies]
anchor-lang = {version = "~0.30", features = ["init-if-needed"]}
anchor-spl = {version = "~0.30"}
diff --git a/programs/libreplex_editions/src/errors.rs b/programs/libreplex_editions/src/errors.rs
index 20bafb21..eb204d10 100644
--- a/programs/libreplex_editions/src/errors.rs
+++ b/programs/libreplex_editions/src/errors.rs
@@ -8,7 +8,6 @@ pub enum EditionsError {
#[msg("Mint template too long")]
MintTemplateTooLong,
-
#[msg("Deployment template too long")]
DeploymentTemplateTooLong,
diff --git a/programs/libreplex_editions/src/instructions/add_to_hashlist.rs b/programs/libreplex_editions/src/instructions/add_to_hashlist.rs
index c3e2dd3d..3ca58b9e 100644
--- a/programs/libreplex_editions/src/instructions/add_to_hashlist.rs
+++ b/programs/libreplex_editions/src/instructions/add_to_hashlist.rs
@@ -4,8 +4,6 @@ use solana_program::program::invoke;
use solana_program::pubkey::Pubkey;
use solana_program::system_instruction;
-
-
pub fn add_to_hashlist<'a>(
new_number_of_mints: u32,
hashlist: &mut UncheckedAccount<'a>,
@@ -13,9 +11,8 @@ pub fn add_to_hashlist<'a>(
system_program: &Program<'a, System>,
mint: &Pubkey,
deployment: &Pubkey,
- order_number: u64) -> Result<()> {
-
- msg!("add_to_hashlist called");
+ order_number: u64
+) -> Result<()> {
let new_size = 8 + 32 + 4 + (new_number_of_mints) * (32 + 8);
let rent = Rent::get()?;
let new_minimum_balance = rent.minimum_balance(new_size as usize);
@@ -32,9 +29,9 @@ pub fn add_to_hashlist<'a>(
}
hashlist.realloc(new_size as usize, false)?;
let hashlist_account_info = hashlist.to_account_info();
-
+
let mut hashlist_data = hashlist_account_info.data.borrow_mut();
-
+
hashlist_data[40..44].copy_from_slice(&new_number_of_mints.to_le_bytes());
let mint_start_pos:usize = (44+(new_number_of_mints-1)*40) as usize;
hashlist_data[
@@ -43,13 +40,11 @@ pub fn add_to_hashlist<'a>(
hashlist_data[
mint_start_pos + 32..mint_start_pos + 40
].copy_from_slice(&order_number.to_le_bytes());
-
+
emit!(HashlistEvent {
mint: mint.key(),
deployment: deployment.key()
});
-
Ok(())
-
}
diff --git a/programs/libreplex_editions/src/instructions/initialise.rs b/programs/libreplex_editions/src/instructions/initialise.rs
index 1c026d8d..38327675 100644
--- a/programs/libreplex_editions/src/instructions/initialise.rs
+++ b/programs/libreplex_editions/src/instructions/initialise.rs
@@ -1,4 +1,4 @@
-use crate::{group_extension_program, utils::update_account_lamports_to_minimum_balance, EditionsDeployment, Hashlist, NAME_LIMIT, OFFCHAIN_URL_LIMIT, SYMBOL_LIMIT};
+use crate::{group_extension_program, utils::update_account_lamports_to_minimum_balance, EditionsDeployment, Hashlist, NAME_LIMIT, URI_LIMIT, SYMBOL_LIMIT};
use anchor_lang::prelude::*;
use libreplex_shared::{create_token_2022_and_metadata, MintAccounts2022, TokenGroupInput};
use solana_program::system_program;
@@ -7,22 +7,21 @@ use spl_token_metadata_interface::state::TokenMetadata;
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct InitialiseInput {
- pub max_number_of_tokens: u64, // this is the max *number* of tokens
pub symbol: String,
+ pub collection_name: String,
+ pub collection_uri: String,
+ pub max_number_of_tokens: u64, // this is the max *number* of tokens
+ pub creator_cosign_program_id: Option,
// add curlies if you want this to be created dynamically. For example
- // hippo #{} -> turns into hippo #0, hippo #1, etc
+ // ipfs://pippo/{} -> turns into ipfs://pippo/1, ipfs://pippo/2, etc
// without curlies the url is the same for all mints
- pub name: String,
+ pub item_base_uri: String,
// add curlies if you want this to be created dynamically. For example
- // ipfs://pippo/{} -> turns into ipfs://pippo/1, ipfs://pippo/2, etc
+ // hippo #{} -> turns into hippo #0, hippo #1, etc
// without curlies the url is the same for all mints
- pub offchain_url: String,
- pub creator_cosign_program_id: Option,
- pub item_base_uri: String,
- pub item_name: String,
+ pub item_base_name: String,
}
-
#[derive(Accounts)]
#[instruction(input: InitialiseInput)]
pub struct InitialiseCtx<'info> {
@@ -61,24 +60,22 @@ pub struct InitialiseCtx<'info> {
pub group_extension_program: AccountInfo<'info>,
}
-
pub fn initialise(ctx: Context, input: InitialiseInput) -> Result<()> {
- if input.offchain_url.len() > OFFCHAIN_URL_LIMIT {
- panic!("Offchain url too long");
- }
if input.symbol.len() > SYMBOL_LIMIT {
panic!("Symbol too long");
}
- if input.name.len() > NAME_LIMIT {
+ if input.collection_name.len() > NAME_LIMIT {
panic!("Name too long");
}
+ if input.collection_uri.len() > URI_LIMIT {
+ panic!("Offchain url too long");
+ }
let group_mint = &ctx.accounts.group_mint;
let group = &ctx.accounts.group;
-
- let url_is_template = match input.item_base_uri.matches("{}").count() {
+ let item_uri_is_template = match input.item_base_uri.matches("{}").count() {
0 => false,
1 => true,
_ => {
@@ -86,8 +83,7 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result
}
};
-
- let name_is_template = match input.item_name.matches("{}").count() {
+ let item_name_is_template = match input.item_base_name.matches("{}").count() {
0 => false,
1 => true,
_ => {
@@ -95,7 +91,6 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result
}
};
-
ctx.accounts.editions_deployment.set_inner(EditionsDeployment {
creator: ctx.accounts.creator.key(),
max_number_of_tokens: input.max_number_of_tokens,
@@ -107,14 +102,13 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result
_ => system_program::ID
},
symbol: input.symbol,
- name: input.item_name,
- url_is_template,
- name_is_template,
- offchain_url: input.item_base_uri,
+ item_base_name: input.item_base_name,
+ item_base_uri: input.item_base_uri,
+ item_name_is_template,
+ item_uri_is_template,
padding: [0; 98],
});
-
let editions_deployment = &ctx.accounts.editions_deployment;
let payer = &ctx.accounts.payer;
let group_mint = &ctx.accounts.group_mint;
@@ -130,7 +124,6 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result
&[ctx.bumps.editions_deployment],
];
- // msg!("Create token 2022 w/ metadata");
create_token_2022_and_metadata(
MintAccounts2022 {
authority: editions_deployment.to_account_info(),
@@ -141,9 +134,9 @@ pub fn initialise(ctx: Context, input: InitialiseInput) -> Result
},
0,
Some(TokenMetadata {
- name: input.name.clone(),
+ name: input.collection_name.clone(),
symbol: editions_deployment.symbol.clone(),
- uri: input.offchain_url.clone(),
+ uri: input.collection_uri.clone(),
update_authority,
mint: group_mint.key(),
additional_metadata: vec![], // Leave this empty for now,
diff --git a/programs/libreplex_editions/src/instructions/mint.rs b/programs/libreplex_editions/src/instructions/mint.rs
index 2fba7e67..04246bbc 100644
--- a/programs/libreplex_editions/src/instructions/mint.rs
+++ b/programs/libreplex_editions/src/instructions/mint.rs
@@ -85,7 +85,6 @@ pub struct MintCtx<'info> {
#[account()]
pub system_program: Program<'info, System>,
-
}
pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()> {
@@ -120,7 +119,6 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
return Err(EditionsError::MintedOut.into());
}
-
let update_authority =
OptionalNonZeroPubkey::try_from(Some(editions_deployment.key())).expect("Bad update auth");
@@ -130,16 +128,16 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
&[ctx.bumps.editions_deployment],
];
- let name = match editions_deployment.name_is_template {
- true => editions_deployment.name.format(&[editions_deployment.number_of_tokens_issued + 1]),
- false => editions_deployment.name.clone()
+ let item_name = match editions_deployment.item_name_is_template {
+ true => editions_deployment.item_base_name.format(&[editions_deployment.number_of_tokens_issued + 1]),
+ false => editions_deployment.item_base_name.clone()
};
- let url = match editions_deployment.url_is_template {
- true => editions_deployment.offchain_url.format(&[editions_deployment.number_of_tokens_issued + 1]),
- false => editions_deployment.offchain_url.clone()
+ let item_url = match editions_deployment.item_uri_is_template {
+ true => editions_deployment.item_base_uri.format(&[editions_deployment.number_of_tokens_issued + 1]),
+ false => editions_deployment.item_base_uri.clone()
};
- // msg!("Create token 2022 w/ metadata");
+
create_token_2022_and_metadata(
MintAccounts2022 {
authority: editions_deployment.to_account_info(),
@@ -150,9 +148,9 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
},
0,
Some(TokenMetadata {
- name,
+ name: item_name,
symbol: editions_deployment.symbol.clone(),
- uri: url,
+ uri: item_url,
update_authority,
mint: mint.key(),
additional_metadata: vec![],
@@ -167,7 +165,6 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
Some(group_extension_program.key()),
)?;
- // msg!("Minting 2022");
mint_non_fungible_2022_logic(
&mint.to_account_info(),
minter_token_account,
@@ -191,10 +188,8 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
)?;
// Retrieve metadata from the group mint
- msg!("Mint: get meta start");
let meta = get_mint_metadata(&mut group_mint.to_account_info())?;
let additional_meta = meta.additional_metadata;
- msg!("Mint: get meta end: meta len {}", additional_meta.len());
// Process each additional metadata key-value pair, excluding platform fee metadata
for additional_metadatum in additional_meta {
@@ -222,12 +217,11 @@ pub fn mint<'info>(ctx: Context<'_, '_, '_, 'info, MintCtx<'info>>) -> Result<()
}
// Transfer minimum rent to the mint account
- msg!("Mint: transfer minimum rent to mint account");
update_account_lamports_to_minimum_balance(
ctx.accounts.mint.to_account_info(),
ctx.accounts.payer.to_account_info(),
ctx.accounts.system_program.to_account_info(),
)?;
- msg!("Mint: done");
+
Ok(())
}
diff --git a/programs/libreplex_editions/src/lib.rs b/programs/libreplex_editions/src/lib.rs
index aff92787..6864fcdc 100644
--- a/programs/libreplex_editions/src/lib.rs
+++ b/programs/libreplex_editions/src/lib.rs
@@ -2,7 +2,7 @@ use anchor_lang::prelude::*;
pub mod instructions;
pub use instructions::*;
-declare_id!("587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq");
+declare_id!("GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD");
pub mod errors;
pub mod state;
diff --git a/programs/libreplex_editions/src/logic/add_to_hashlist.rs b/programs/libreplex_editions/src/logic/add_to_hashlist.rs
index 0b718f19..af337c87 100644
--- a/programs/libreplex_editions/src/logic/add_to_hashlist.rs
+++ b/programs/libreplex_editions/src/logic/add_to_hashlist.rs
@@ -4,17 +4,14 @@ use solana_program::program::invoke;
use solana_program::pubkey::Pubkey;
use solana_program::system_instruction;
-
-
pub fn add_to_hashlist<'a>(
new_number_of_mints: u32,
hashlist: &mut UncheckedAccount<'a>,
payer: &Signer<'a>,
system_program: &Program<'a, System>,
mint: &Pubkey,
- order_number: u64) -> Result<()> {
-
- msg!("add_to_hashlist called");
+ order_number: u64
+) -> Result<()> {
let new_size = 8 + 32 + 4 + (new_number_of_mints) * (32 + 8);
let rent = Rent::get()?;
let new_minimum_balance = rent.minimum_balance(new_size as usize);
@@ -31,9 +28,9 @@ pub fn add_to_hashlist<'a>(
}
hashlist.realloc(new_size as usize, false)?;
let hashlist_account_info = hashlist.to_account_info();
-
+
let mut hashlist_data = hashlist_account_info.data.borrow_mut();
-
+
hashlist_data[40..44].copy_from_slice(&new_number_of_mints.to_le_bytes());
let mint_start_pos:usize = (44+(new_number_of_mints-1)*40) as usize;
hashlist_data[
@@ -42,7 +39,6 @@ pub fn add_to_hashlist<'a>(
hashlist_data[
mint_start_pos + 32..mint_start_pos + 40
].copy_from_slice(&order_number.to_le_bytes());
-
- Ok(())
+ Ok(())
}
diff --git a/programs/libreplex_editions/src/state.rs b/programs/libreplex_editions/src/state.rs
index 49e07fbb..ed537ffd 100644
--- a/programs/libreplex_editions/src/state.rs
+++ b/programs/libreplex_editions/src/state.rs
@@ -1,9 +1,9 @@
use anchor_lang::prelude::*;
use solana_program::pubkey::Pubkey;
-pub const NAME_LIMIT: usize = 400;
pub const SYMBOL_LIMIT: usize = 100;
-pub const OFFCHAIN_URL_LIMIT: usize = 1200;
+pub const NAME_LIMIT: usize = 400;
+pub const URI_LIMIT: usize = 1200;
pub const META_LIST_ACCOUNT_SEED: &[u8] = b"extra-account-metas";
pub const APPROVE_ACCOUNT_SEED: &[u8] = b"approve-account";
pub const ROYALTY_BASIS_POINTS_FIELD: &str = "royalty_basis_points";
@@ -22,6 +22,7 @@ pub struct EditionsDeployment {
pub creator: Pubkey,
// set to 0 for unlimited
pub max_number_of_tokens: u64,
+
pub number_of_tokens_issued: u64,
// set to system account for no cosign
@@ -35,20 +36,18 @@ pub struct EditionsDeployment {
pub symbol: String,
#[max_len(NAME_LIMIT)]
- pub name: String,
+ pub item_base_name: String,
- #[max_len(OFFCHAIN_URL_LIMIT)]
- pub offchain_url: String, // pub padding: Vec
-
- pub name_is_template: bool,
+ #[max_len(URI_LIMIT)]
+ pub item_base_uri: String,
- pub url_is_template: bool,
+ pub item_name_is_template: bool,
+
+ pub item_uri_is_template: bool,
pub padding: [u8; 98]
-
}
-
// slightly more extended
#[account]
pub struct HashlistMarker {
diff --git a/programs/libreplex_editions_controls/Cargo.toml b/programs/libreplex_editions_controls/Cargo.toml
index d24fa948..eaf2c5fa 100644
--- a/programs/libreplex_editions_controls/Cargo.toml
+++ b/programs/libreplex_editions_controls/Cargo.toml
@@ -20,13 +20,10 @@ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
[dependencies]
anchor-lang = {version = "~0.30", features = ["init-if-needed"]}
-
anchor-spl = {version = "~0.30"}
-
-
libreplex_editions = {version="*", path = "../libreplex_editions", features =["cpi", "no-entrypoint"]}
-
+rarible-merkle-verify = { version="*", path = "../../libraries/rarible-merkle-verify" }
solana-program = {version = "1.17.13"}
arrayref = "0.3.7"
diff --git a/programs/libreplex_editions_controls/src/errors.rs b/programs/libreplex_editions_controls/src/errors.rs
index f616ae1e..6536f7a0 100644
--- a/programs/libreplex_editions_controls/src/errors.rs
+++ b/programs/libreplex_editions_controls/src/errors.rs
@@ -1,14 +1,13 @@
use anchor_lang::prelude::*;
#[error_code]
-pub enum EditionsError {
+pub enum EditionsControlsError {
#[msg("Ticker too long")]
TickerTooLong,
#[msg("Mint template too long")]
MintTemplateTooLong,
-
#[msg("Deployment template too long")]
DeploymentTemplateTooLong,
@@ -50,7 +49,44 @@ pub enum EditionsError {
#[msg("No phases have been added. Cannot mint.")]
NoPhasesAdded,
+
#[msg("Invalid phase index.")]
InvalidPhaseIndex,
+ #[msg("Private phase but no merkle proof provided")]
+ PrivatePhaseNoProof,
+
+ #[msg("Merkle root not set for allow list mint")]
+ MerkleRootNotSet,
+
+ #[msg("Merkle proof required for allow list mint")]
+ MerkleProofRequired,
+
+ #[msg("Allow list price and max claims are required for allow list mint")]
+ AllowListPriceAndMaxClaimsRequired,
+
+ #[msg("Invalid merkle proof")]
+ InvalidMerkleProof,
+
+ #[msg("This wallet has exceeded allow list max_claims in the current phase")]
+ ExceededAllowListMaxClaims,
+
+ #[msg("Phase not active")]
+ PhaseNotActive,
+
+ #[msg("Phase not yet started")]
+ PhaseNotStarted,
+
+ #[msg("Phase already finished")]
+ PhaseAlreadyFinished,
+
+ #[msg("Exceeded max mints for this phase")]
+ ExceededMaxMintsForPhase,
+
+ #[msg("Exceeded wallet max mints for this phase")]
+ ExceededWalletMaxMintsForPhase,
+
+ #[msg("Exceeded wallet max mints for the collection")]
+ ExceededWalletMaxMintsForCollection,
+
}
diff --git a/programs/libreplex_editions_controls/src/instructions/add_phase.rs b/programs/libreplex_editions_controls/src/instructions/add_phase.rs
index f9e355cd..54569e81 100644
--- a/programs/libreplex_editions_controls/src/instructions/add_phase.rs
+++ b/programs/libreplex_editions_controls/src/instructions/add_phase.rs
@@ -2,10 +2,8 @@ use anchor_lang::prelude::*;
use libreplex_editions::program::LibreplexEditions;
use libreplex_shared::wrapped_sol;
-
use crate::{EditionsControls, Phase};
-
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct InitialisePhaseInput {
pub price_amount: u64,
@@ -13,15 +11,14 @@ pub struct InitialisePhaseInput {
pub start_time: i64,
pub max_mints_per_wallet: u64,
pub max_mints_total: u64,
- pub end_time: i64 // set to i64::MAX if not supplied
+ pub end_time: i64,
+ pub is_private: bool,
+ pub merkle_root: Option<[u8; 32]>,
}
-
#[derive(Accounts)]
#[instruction(input: InitialisePhaseInput)]
pub struct AddPhaseCtx<'info> {
-
-
#[account(mut,
realloc = EditionsControls::get_size(editions_controls.phases.len()+1),
realloc::zero = false,
@@ -48,7 +45,6 @@ pub struct AddPhaseCtx<'info> {
}
pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Result<()> {
-
if !input.price_token.eq(&wrapped_sol::ID) {
panic!("Only native price currently supported")
}
@@ -61,12 +57,12 @@ pub fn add_phase(ctx: Context, input: InitialisePhaseInput) -> Resu
max_mints_per_wallet: input.max_mints_per_wallet,
active: true, // everything starts out as active -
end_time: input.end_time,
- padding: [0; 200],
max_mints_total: input.max_mints_total,
- current_mints: 0
+ current_mints: 0,
+ is_private: input.is_private,
+ merkle_root: input.merkle_root,
+ padding: [0; 200],
});
-
-
Ok(())
}
diff --git a/programs/libreplex_editions_controls/src/instructions/initialise.rs b/programs/libreplex_editions_controls/src/instructions/initialise.rs
index d1af9c18..949ea466 100644
--- a/programs/libreplex_editions_controls/src/instructions/initialise.rs
+++ b/programs/libreplex_editions_controls/src/instructions/initialise.rs
@@ -3,7 +3,7 @@ use libreplex_editions::{cpi::accounts::InitialiseCtx, group_extension_program,
use libreplex_editions::cpi::accounts::AddMetadata;
use libreplex_editions::cpi::accounts::AddRoyalties;
use crate::{EditionsControls, PlatformFeeRecipient, UpdatePlatformFeeArgs, DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN, DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN};
-use crate::errors::EditionsError;
+use crate::errors::EditionsControlsError;
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct InitialiseControlInput {
@@ -11,13 +11,13 @@ pub struct InitialiseControlInput {
pub treasury: Pubkey,
pub max_number_of_tokens: u64,
pub symbol: String,
- pub name: String,
- pub offchain_url: String,
+ pub collection_name: String,
+ pub collection_uri: String,
pub cosigner_program_id: Option,
pub royalties: UpdateRoyaltiesArgs,
pub extra_meta: Vec,
pub item_base_uri: String,
- pub item_name: String,
+ pub item_base_name: String,
pub platform_fee: UpdatePlatformFeeArgs
}
@@ -75,7 +75,6 @@ pub fn initialise_editions_controls(
) -> Result<()> {
let libreplex_editions_program = &ctx.accounts.libreplex_editions_program;
let editions_controls = &mut ctx.accounts.editions_controls;
-
let editions_deployment = &ctx.accounts.editions_deployment;
let hashlist = &ctx.accounts.hashlist;
let payer = &ctx.accounts.payer;
@@ -89,10 +88,10 @@ pub fn initialise_editions_controls(
let core_input = InitialiseInput {
max_number_of_tokens: input.max_number_of_tokens,
symbol: input.symbol,
- name: input.name,
- offchain_url: input.offchain_url,
+ collection_name: input.collection_name,
+ collection_uri: input.collection_uri,
creator_cosign_program_id: Some(crate::ID),
- item_name: input.item_name,
+ item_base_name: input.item_base_name,
item_base_uri: input.item_base_uri
};
@@ -118,13 +117,13 @@ pub fn initialise_editions_controls(
// Validate that platform_fee has up to 5 recipients
let provided_recipients = input.platform_fee.recipients.len();
if provided_recipients > 5 {
- return Err(EditionsError::TooManyRecipients.into());
+ return Err(EditionsControlsError::TooManyRecipients.into());
}
// Ensure that the sum of shares equals 100
let total_shares: u8 = input.platform_fee.recipients.iter().map(|r| r.share).sum();
if total_shares != 100 {
- return Err(EditionsError::InvalidFeeShares.into());
+ return Err(EditionsControlsError::InvalidFeeShares.into());
}
// Initialize an array of 5 PlatformFeeRecipient with default values
@@ -161,15 +160,15 @@ pub fn initialise_editions_controls(
editions_deployment: editions_deployment.key(),
creator: creator.key(),
max_mints_per_wallet: input.max_mints_per_wallet,
- padding: [0; 200],
cosigner_program_id: input.cosigner_program_id.unwrap_or(system_program::ID),
- phases: vec![],
treasury: input.treasury,
platform_fee_value: input.platform_fee.platform_fee_value,
is_fee_flat: input.platform_fee.is_fee_flat,
platform_fee_recipients: recipients_array.clone(),
platform_fee_primary_admin: DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN.parse().unwrap(),
platform_fee_secondary_admin: DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN.parse().unwrap(),
+ phases: vec![],
+ padding: [0; 200],
});
let editions_deployment_key = editions_deployment.key();
diff --git a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs
index 6562319d..bbebf44e 100644
--- a/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs
+++ b/programs/libreplex_editions_controls/src/instructions/mint_with_controls.rs
@@ -1,16 +1,28 @@
-use anchor_lang::prelude::*;
-use anchor_lang::system_program;
+use anchor_lang::{prelude::*, system_program};
use anchor_spl::{
associated_token::AssociatedToken,
token_2022::{self, ID as TOKEN_2022_ID},
};
-use libreplex_editions::{cpi::accounts::MintCtx, program::LibreplexEditions, EditionsDeployment};
-use crate::{check_phase_constraints, errors::EditionsError, EditionsControls, MinterStats};
-use libreplex_editions::group_extension_program;
+use libreplex_editions::{
+ group_extension_program,
+ program::LibreplexEditions,
+ EditionsDeployment,
+ cpi::accounts::MintCtx
+};
+use crate::{
+ EditionsControls,
+ MinterStats,
+ errors::EditionsControlsError,
+ check_phase_constraints,
+ check_allow_list_constraints
+};
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct MintInput {
pub phase_index: u32,
+ pub merkle_proof: Option>,
+ pub allow_list_price: Option,
+ pub allow_list_max_claims: Option,
}
#[derive(Accounts)]
@@ -26,11 +38,11 @@ pub struct MintWithControlsCtx<'info> {
)]
pub editions_controls: Box>,
- /// CHECK: Checked via CPI
+ // CHECK: Checked via CPI
#[account(mut)]
pub hashlist: UncheckedAccount<'info>,
- /// CHECK: Checked via CPI
+ // CHECK: Checked via CPI
#[account(mut)]
pub hashlist_marker: UncheckedAccount<'info>,
@@ -45,7 +57,7 @@ pub struct MintWithControlsCtx<'info> {
)]
pub signer: Signer<'info>,
- /// CHECK: Anybody can sign, anybody can receive the inscription
+ // CHECK: Anybody can sign, anybody can receive the inscription
#[account(mut)]
pub minter: UncheckedAccount<'info>,
@@ -137,8 +149,8 @@ pub fn mint_with_controls(
let minter_stats_phase = &mut ctx.accounts.minter_stats_phase;
let minter = &ctx.accounts.minter;
- // Phase validation and price retrieval
- let price_amount = validate_phase(editions_controls, mint_input.phase_index)?;
+ // Phase validation
+ validate_phase(editions_controls, mint_input.phase_index)?;
// Check phase constraints
check_phase_constraints(
@@ -146,10 +158,32 @@ pub fn mint_with_controls(
minter_stats,
minter_stats_phase,
editions_controls,
- );
+ )?;
- // Update minter statistics
- update_minter_stats(
+ // Get the default/standard price amount for the phase
+ let mut price_amount = editions_controls.phases[mint_input.phase_index as usize].price_amount;
+
+ // Check if it's a normal mint or an allow list mint based on the presence of a merkle proof
+ if mint_input.merkle_proof.is_some() {
+ check_allow_list_constraints(
+ &editions_controls.phases[mint_input.phase_index as usize],
+ &minter.key(),
+ minter_stats_phase,
+ mint_input.merkle_proof,
+ mint_input.allow_list_price,
+ mint_input.allow_list_max_claims,
+ )?;
+ // Override the price amount with the allow list price
+ price_amount = mint_input.allow_list_price.unwrap_or(0);
+ } else {
+ // if the phase is private, and the merkle proof was not provided, throw error
+ if editions_controls.phases[mint_input.phase_index as usize].is_private {
+ return Err(EditionsControlsError::PrivatePhaseNoProof.into());
+ }
+ }
+
+ // Update minter and phase states
+ update_minter_and_phase_stats(
minter_stats,
minter_stats_phase,
&minter.key(),
@@ -177,20 +211,19 @@ pub fn mint_with_controls(
fn validate_phase(
editions_controls: &EditionsControls,
phase_index: u32,
-) -> Result {
+) -> Result<()> {
if phase_index >= editions_controls.phases.len() as u32 {
if editions_controls.phases.is_empty() {
- return Err(EditionsError::NoPhasesAdded.into());
+ return Err(EditionsControlsError::NoPhasesAdded.into());
} else {
- return Err(EditionsError::InvalidPhaseIndex.into());
+ return Err(EditionsControlsError::InvalidPhaseIndex.into());
}
}
- let price_amount = editions_controls.phases[phase_index as usize].price_amount;
- Ok(price_amount)
+ Ok(())
}
-fn update_minter_stats(
+fn update_minter_and_phase_stats(
minter_stats: &mut MinterStats,
minter_stats_phase: &mut MinterStats,
minter_key: &Pubkey,
@@ -220,7 +253,7 @@ fn process_platform_fees(
// Ensure that the sum of shares equals 100
let total_shares: u8 = recipients.iter().map(|r| r.share).sum();
if total_shares != 100 {
- return Err(EditionsError::InvalidFeeShares.into());
+ return Err(EditionsControlsError::InvalidFeeShares.into());
}
let total_fee: u64;
@@ -233,12 +266,12 @@ fn process_platform_fees(
// Calculate fee as (price_amount * platform_fee_value) / 10,000 (assuming basis points)
total_fee = price_amount
.checked_mul(editions_controls.platform_fee_value as u64)
- .ok_or(EditionsError::FeeCalculationError)?
+ .ok_or(EditionsControlsError::FeeCalculationError)?
.checked_div(10_000)
- .ok_or(EditionsError::FeeCalculationError)?;
+ .ok_or(EditionsControlsError::FeeCalculationError)?;
remaining_amount = price_amount.checked_sub(total_fee)
- .ok_or(EditionsError::FeeCalculationError)?;
+ .ok_or(EditionsControlsError::FeeCalculationError)?;
}
// Distribute fees to recipients
@@ -249,17 +282,16 @@ fn process_platform_fees(
let recipient_account = &ctx.accounts.platform_fee_recipient_1;
- msg!("check recipients {}: {} vs {}", i, recipient_account.key(), recipient_struct.address.key());
// Ensure that the account matches the expected recipient
if recipient_account.key() != recipient_struct.address.key() {
- return Err(EditionsError::RecipientMismatch.into());
+ return Err(EditionsControlsError::RecipientMismatch.into());
}
let recipient_fee = total_fee
.checked_mul(recipient_struct.share as u64)
- .ok_or(EditionsError::FeeCalculationError)?
+ .ok_or(EditionsControlsError::FeeCalculationError)?
.checked_div(100)
- .ok_or(EditionsError::FeeCalculationError)?;
+ .ok_or(EditionsControlsError::FeeCalculationError)?;
// Transfer platform fee to recipient
system_program::transfer(
diff --git a/programs/libreplex_editions_controls/src/instructions/mod.rs b/programs/libreplex_editions_controls/src/instructions/mod.rs
index 818ff67b..cd4b141f 100644
--- a/programs/libreplex_editions_controls/src/instructions/mod.rs
+++ b/programs/libreplex_editions_controls/src/instructions/mod.rs
@@ -1,8 +1,3 @@
-
-/*
- initialises a new launch. does not create any
- on-chain accounts, mints, token accounts etc
-*/
pub mod initialise;
pub use initialise::*;
diff --git a/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs b/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs
index b7f0dc5d..8ea925f3 100644
--- a/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs
+++ b/programs/libreplex_editions_controls/src/instructions/update_platform_fee.rs
@@ -32,7 +32,6 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu
let platform_fee_value = platform_fee_input.platform_fee_value;
let is_fee_flat = platform_fee_input.is_fee_flat;
- msg!("libreplex_editions::cpi::modify_platform_ffe start");
let editions_controls = &mut ctx.accounts.editions_controls;
// Initialize an array of 5 PlatformFeeRecipient with default values
@@ -59,7 +58,6 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu
},
];
- msg!("libreplex_editions_controls:: update editions_controls");
// Populate the array with provided recipients
for (i, recipient) in platform_fee_input.recipients.iter().enumerate() {
recipients_array[i] = recipient.clone();
@@ -68,7 +66,5 @@ pub fn update_platform_fee(ctx: Context, platform_fee_inpu
editions_controls.is_fee_flat = is_fee_flat;
editions_controls.platform_fee_recipients = recipients_array;
- msg!("libreplex_editions::cpi::modify_platform_fee done");
-
Ok(())
}
diff --git a/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs b/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs
index a5e98852..ad6a8be4 100644
--- a/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs
+++ b/programs/libreplex_editions_controls/src/instructions/update_platform_fee_secondary_admin.rs
@@ -1,17 +1,13 @@
use anchor_lang::prelude::*;
use libreplex_editions::program::LibreplexEditions;
use libreplex_shared::wrapped_sol;
-
-
use crate::{EditionsControls, Phase};
-
#[derive(AnchorDeserialize, AnchorSerialize, Clone)]
pub struct UpdatePlatformFeeSecondaryAdminInput {
pub new_admin: Pubkey,
}
-
#[derive(Accounts)]
#[instruction(input: UpdatePlatformFeeSecondaryAdminInput)]
pub struct UpdatePlatformFeeSecondaryAdminCtx<'info> {
diff --git a/programs/libreplex_editions_controls/src/instructions/update_royalties.rs b/programs/libreplex_editions_controls/src/instructions/update_royalties.rs
index 3793e8a9..3916431f 100644
--- a/programs/libreplex_editions_controls/src/instructions/update_royalties.rs
+++ b/programs/libreplex_editions_controls/src/instructions/update_royalties.rs
@@ -58,7 +58,7 @@ pub fn update_royalties(ctx: Context, royalties_input: Updat
editions_deployment_key.as_ref(),
&[ctx.bumps.editions_controls],
];
- msg!("libreplex_editions::cpi::modify_royalties start");
+
libreplex_editions::cpi::modify_royalties(
CpiContext::new_with_signer(
libreplex_editions_program.to_account_info(),
@@ -72,6 +72,6 @@ pub fn update_royalties(ctx: Context, royalties_input: Updat
},
&[seeds]
), royalties_input)?;
- msg!("libreplex_editions::cpi::modify_royalties done");
+
Ok(())
}
diff --git a/programs/libreplex_editions_controls/src/lib.rs b/programs/libreplex_editions_controls/src/lib.rs
index 584e083e..c8dd0375 100644
--- a/programs/libreplex_editions_controls/src/lib.rs
+++ b/programs/libreplex_editions_controls/src/lib.rs
@@ -5,7 +5,7 @@ pub use logic::*;
pub mod instructions;
pub use instructions::*;
-declare_id!("5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs");
+declare_id!("CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN");
pub mod errors;
pub mod state;
diff --git a/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs
new file mode 100644
index 00000000..280a5eb5
--- /dev/null
+++ b/programs/libreplex_editions_controls/src/logic/check_allow_list_constraints.rs
@@ -0,0 +1,57 @@
+use anchor_lang::{
+ accounts::account::Account,
+ prelude::*,
+ solana_program::hash::hashv,
+};
+use rarible_merkle_verify::verify;
+use crate::{
+ MinterStats, Phase,
+ errors::{EditionsControlsError},
+};
+
+/// We need to discern between leaf and intermediate nodes to prevent trivial second
+/// pre-image attacks.
+/// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack
+const LEAF_PREFIX: &[u8] = &[0];
+
+pub fn check_allow_list_constraints(
+ phase: &Phase,
+ minter: &Pubkey,
+ minter_stats_phase: &mut Account,
+ merkle_proof: Option>,
+ allow_list_price: Option,
+ allow_list_max_claims: Option,
+) -> Result<()> {
+ if let Some(merkle_root) = phase.merkle_root {
+ if let Some(proof) = merkle_proof {
+ if let (Some(phase_list_price), Some(phase_max_claims)) = (allow_list_price, allow_list_max_claims) {
+ /// 1. check constraints
+ /// dev: notice that if phase_max_claims is 0, this constraint is disabled
+ if phase_max_claims > 0 && minter_stats_phase.mint_count >= phase_max_claims {
+ return Err(EditionsControlsError::ExceededAllowListMaxClaims.into());
+ }
+
+ /// 2. construct leaf
+ let leaf = hashv(&[
+ &minter.to_bytes(),
+ &phase_list_price.to_le_bytes(),
+ &phase_max_claims.to_le_bytes(),
+ ]);
+ let node = hashv(&[LEAF_PREFIX, &leaf.to_bytes()]);
+
+ /// 3. verify proof against merkle root
+ if !verify(proof, merkle_root, node.to_bytes()) {
+ return Err(EditionsControlsError::InvalidMerkleProof.into());
+ }
+ } else {
+ return Err(EditionsControlsError::AllowListPriceAndMaxClaimsRequired.into());
+ }
+ } else {
+ return Err(EditionsControlsError::MerkleProofRequired.into());
+ }
+ } else {
+ return Err(EditionsControlsError::MerkleRootNotSet.into());
+ }
+
+ Ok(())
+}
diff --git a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs
index 64ef4069..9d208f53 100644
--- a/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs
+++ b/programs/libreplex_editions_controls/src/logic/check_phase_constraints.rs
@@ -1,43 +1,50 @@
-use anchor_lang::accounts::account::Account;
-
-use anchor_lang::prelude::*;
-
-use crate::{EditionsControls, MinterStats, Phase};
+use anchor_lang::{
+ accounts::account::Account,
+ prelude::*,
+};
+use crate::{
+ EditionsControls,MinterStats,Phase,
+ errors::EditionsControlsError,
+};
pub fn check_phase_constraints(
phase: &Phase,
minter_stats: &mut Account,
minter_stats_phase: &mut Account,
editions_controls: &Account,
-) {
- // check
+) -> Result<()> {
let clock = Clock::get().unwrap();
let current_time = clock.unix_timestamp;
if !phase.active {
- panic!("Phase not active")
+ return Err(EditionsControlsError::PhaseNotActive.into());
}
- msg!("{} {}", phase.start_time, current_time);
if phase.start_time > current_time {
- panic!("Phase not yet started")
+ return Err(EditionsControlsError::PhaseNotStarted.into());
}
if phase.end_time <= current_time {
- panic!("Phase already finished")
- }
-
- if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet {
- panic!("This wallet has exceeded max mints in the current phase")
+ return Err(EditionsControlsError::PhaseAlreadyFinished.into());
}
+ /// Checks if the total mints for the phase has been exceeded (phase sold out)
+ /// @dev dev: notice that if max_mints_total is 0, this constraint is disabled
if phase.max_mints_total > 0 && phase.current_mints >= phase.max_mints_total {
- panic!("Total mints exceeded in this phase")
+ return Err(EditionsControlsError::ExceededMaxMintsForPhase.into());
}
+ /// Checks if the user has exceeded the max mints for the deployment (across all phases!)
+ /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled
if editions_controls.max_mints_per_wallet > 0 && minter_stats.mint_count >= editions_controls.max_mints_per_wallet {
- panic!("This wallet has exceeded max mints for the deployment")
+ return Err(EditionsControlsError::ExceededWalletMaxMintsForCollection.into());
+ }
+
+ /// Checks if the user has exceeded the max mints for the current phase
+ /// dev: notice that if max_mints_per_wallet is 0, this constraint is disabled
+ if phase.max_mints_per_wallet > 0 && minter_stats_phase.mint_count >= phase.max_mints_per_wallet {
+ return Err(EditionsControlsError::ExceededWalletMaxMintsForPhase.into());
}
-
+ Ok(())
}
diff --git a/programs/libreplex_editions_controls/src/logic/mod.rs b/programs/libreplex_editions_controls/src/logic/mod.rs
index 3ee46df1..6b143b6c 100644
--- a/programs/libreplex_editions_controls/src/logic/mod.rs
+++ b/programs/libreplex_editions_controls/src/logic/mod.rs
@@ -1,3 +1,5 @@
-
pub mod check_phase_constraints;
pub use check_phase_constraints::*;
+
+pub mod check_allow_list_constraints;
+pub use check_allow_list_constraints::*;
diff --git a/programs/libreplex_editions_controls/src/state.rs b/programs/libreplex_editions_controls/src/state.rs
index 5fd11fbb..58f99365 100644
--- a/programs/libreplex_editions_controls/src/state.rs
+++ b/programs/libreplex_editions_controls/src/state.rs
@@ -10,15 +10,16 @@ pub struct PlatformFeeRecipient {
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct UpdatePlatformFeeArgs {
pub platform_fee_value: u64, // Always required
- pub recipients: Vec,
+ pub recipients: Vec,
pub is_fee_flat: bool, // Flag to indicate if the fee is flat
}
impl PlatformFeeRecipient {
- pub const SIZE: usize = 8 + 32 + 1; // Pubkey + u8
+ pub const SIZE: usize = 8 // discriminator
+ + 32 // address
+ + 1; // share
}
-// max length: 8 + 8 + 8 + 1 + 8 = 33
#[derive(Clone, AnchorDeserialize, AnchorSerialize)]
pub struct Phase {
pub price_amount: u64,
@@ -26,22 +27,27 @@ pub struct Phase {
pub start_time: i64, // set to any date before now for instant activate
pub active: bool,
pub max_mints_per_wallet: u64, // set to 0 for unlimited
- pub max_mints_total: u64, // set to 0 for unlimited (applied across all the phases)
+ pub max_mints_total: u64, // set to 0 for unlimited
pub end_time: i64, // set to i64::MAX for unlimited
pub current_mints: u64,
+ pub is_private: bool,
+ pub merkle_root: Option<[u8; 32]>,
pub padding: [u8; 200]
}
impl Phase {
- pub const SIZE: usize = 8
- + 32
- + 8
- + 1
- + 8
- + 8
- + 8
- + 8
- + 200;
+ pub const SIZE: usize = 8 // discriminator
+ + 8 // price_amount
+ + 32 // price_token
+ + 8 // start_time
+ + 1 // active
+ + 8 // max_mints_per_wallet
+ + 8 // max_mints_total
+ + 8 // end_time
+ + 8 // current_mints
+ + 1 // is_private
+ + 32 + 1 // merkle_root
+ + 200; // padding
}
pub const DEFAULT_PLATFORM_FEE_PRIMARY_ADMIN: &str = "674s1Sap3KVnr8WGrY5KGQ69oTYjjgr1disKJo6GpTYw";
@@ -50,12 +56,15 @@ pub const DEFAULT_PLATFORM_FEE_SECONDARY_ADMIN: &str = "9qGioKcyuh1mwXS8MbmMgdtQ
#[account]
pub struct MinterStats {
pub wallet: Pubkey,
- pub mint_count: u64, // set to any date before now for instant activate
+ pub mint_count: u64,
pub padding: [u8; 50]
}
impl MinterStats {
- pub const SIZE: usize = 8 + 32 + 8 + 8 + 50;
+ pub const SIZE: usize = 8 // discriminator
+ + 32 // wallet
+ + 8 // mint_count
+ + 50; // padding
}
#[account]
@@ -70,8 +79,8 @@ pub struct EditionsControls {
pub platform_fee_value: u64, // Fee amount or basis points
pub is_fee_flat: bool, // True for flat fee, false for percentage-based fee
pub platform_fee_recipients: [PlatformFeeRecipient; 5], // Fixed-length array of 5 recipients and their shares
+ pub phases: Vec, // Vec of phases
pub padding: [u8; 200], // in case we need some more stuff in the future
- pub phases: Vec,
}
impl EditionsControls {
@@ -86,8 +95,9 @@ impl EditionsControls {
+ 8 // platform_fee_value
+ 1 // is_fee_flat
+ (PlatformFeeRecipient::SIZE * 5) // platform_fee_recipients (5 * 33 = 165)
- + 200 // padding
- + 4; // Vec length for phases
+ + 4 // Vec length for phases
+ + 200; // padding
+
pub fn get_size(number_of_phases: usize) -> usize {
EditionsControls::INITIAL_SIZE + Phase::SIZE * number_of_phases
}
diff --git a/target/types/libreplex_default_renderer.ts b/target/types/libreplex_default_renderer.ts
deleted file mode 100644
index 312568ba..00000000
--- a/target/types/libreplex_default_renderer.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Program IDL in camelCase format in order to be used in JS/TS.
- *
- * Note that this is only a type helper and is not the actual IDL. The original
- * IDL can be found at `target/idl/libreplex_default_renderer.json`.
- */
-export type LibreplexDefaultRenderer = {
- "address": "EfC2horkE4hCaCruMvoDDWxUhcCgVaufHeG3Hkj69eR8",
- "metadata": {
- "name": "libreplexDefaultRenderer",
- "version": "0.1.2",
- "spec": "0.1.0",
- "description": "Created with Anchor"
- },
- "instructions": [
- {
- "name": "canonical",
- "discriminator": [
- 233,
- 11,
- 68,
- 244,
- 108,
- 63,
- 142,
- 79
- ],
- "accounts": [
- {
- "name": "metadata"
- },
- {
- "name": "mint"
- },
- {
- "name": "group"
- },
- {
- "name": "renderState",
- "pda": {
- "seeds": [
- {
- "kind": "account",
- "path": "mint"
- }
- ]
- }
- },
- {
- "name": "outputAccount"
- }
- ],
- "args": [
- {
- "name": "renderInput",
- "type": {
- "defined": {
- "name": "renderInput"
- }
- }
- }
- ],
- "returns": "bytes"
- }
- ],
- "types": [
- {
- "name": "renderInput",
- "type": {
- "kind": "struct",
- "fields": []
- }
- }
- ]
-};
diff --git a/target/types/libreplex_editions.ts b/target/types/libreplex_editions.ts
index 0b51055c..c40adcad 100644
--- a/target/types/libreplex_editions.ts
+++ b/target/types/libreplex_editions.ts
@@ -820,19 +820,19 @@ export type LibreplexEditions = {
"type": "string"
},
{
- "name": "name",
+ "name": "itemBaseName",
"type": "string"
},
{
- "name": "offchainUrl",
+ "name": "itemBaseUri",
"type": "string"
},
{
- "name": "nameIsTemplate",
+ "name": "itemNameIsTemplate",
"type": "bool"
},
{
- "name": "urlIsTemplate",
+ "name": "itemUriIsTemplate",
"type": "bool"
},
{
@@ -890,22 +890,22 @@ export type LibreplexEditions = {
"type": {
"kind": "struct",
"fields": [
- {
- "name": "maxNumberOfTokens",
- "type": "u64"
- },
{
"name": "symbol",
"type": "string"
},
{
- "name": "name",
+ "name": "collectionName",
"type": "string"
},
{
- "name": "offchainUrl",
+ "name": "collectionUri",
"type": "string"
},
+ {
+ "name": "maxNumberOfTokens",
+ "type": "u64"
+ },
{
"name": "creatorCosignProgramId",
"type": {
@@ -917,7 +917,7 @@ export type LibreplexEditions = {
"type": "string"
},
{
- "name": "itemName",
+ "name": "itemBaseName",
"type": "string"
}
]
diff --git a/target/types/libreplex_editions_controls.ts b/target/types/libreplex_editions_controls.ts
index f2519fa6..758cdd30 100644
--- a/target/types/libreplex_editions_controls.ts
+++ b/target/types/libreplex_editions_controls.ts
@@ -5,7 +5,7 @@
* IDL can be found at `target/idl/libreplex_editions_controls.json`.
*/
export type LibreplexEditionsControls = {
- "address": "5hEa5j38yNJRM9vQA44Q6gXVj4Db8y3mWxkDtQeofKKs",
+ "address": "CCWfNBFcrjbKSjKes9DqgiQYsnmCFgCVKx21mmV2xWgN",
"metadata": {
"name": "libreplexEditionsControls",
"version": "0.2.1",
@@ -51,7 +51,7 @@ export type LibreplexEditionsControls = {
},
{
"name": "libreplexEditionsProgram",
- "address": "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
+ "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"
}
],
"args": [
@@ -152,7 +152,7 @@ export type LibreplexEditionsControls = {
},
{
"name": "libreplexEditionsProgram",
- "address": "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
+ "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"
}
],
"args": [
@@ -365,7 +365,7 @@ export type LibreplexEditionsControls = {
},
{
"name": "libreplexEditionsProgram",
- "address": "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
+ "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"
}
],
"args": [
@@ -595,7 +595,7 @@ export type LibreplexEditionsControls = {
},
{
"name": "libreplexEditionsProgram",
- "address": "587DoLBH2H39i5bToWBc6zRgbD2iJZtc4Kb8nYsskYTq"
+ "address": "GHVmd43GebD2SSedqG1AAh76izf5CPAhz6KotkZyRzUD"
}
],
"args": [
@@ -736,6 +736,66 @@ export type LibreplexEditionsControls = {
"code": 6016,
"name": "invalidPhaseIndex",
"msg": "Invalid phase index."
+ },
+ {
+ "code": 6017,
+ "name": "privatePhaseNoProof",
+ "msg": "Private phase but no merkle proof provided"
+ },
+ {
+ "code": 6018,
+ "name": "merkleRootNotSet",
+ "msg": "Merkle root not set for allow list mint"
+ },
+ {
+ "code": 6019,
+ "name": "merkleProofRequired",
+ "msg": "Merkle proof required for allow list mint"
+ },
+ {
+ "code": 6020,
+ "name": "allowListPriceAndMaxClaimsRequired",
+ "msg": "Allow list price and max claims are required for allow list mint"
+ },
+ {
+ "code": 6021,
+ "name": "invalidMerkleProof",
+ "msg": "Invalid merkle proof"
+ },
+ {
+ "code": 6022,
+ "name": "exceededAllowListMaxClaims",
+ "msg": "This wallet has exceeded allow list max_claims in the current phase"
+ },
+ {
+ "code": 6023,
+ "name": "phaseNotActive",
+ "msg": "Phase not active"
+ },
+ {
+ "code": 6024,
+ "name": "phaseNotStarted",
+ "msg": "Phase not yet started"
+ },
+ {
+ "code": 6025,
+ "name": "phaseAlreadyFinished",
+ "msg": "Phase already finished"
+ },
+ {
+ "code": 6026,
+ "name": "exceededMaxMintsForPhase",
+ "msg": "Exceeded max mints for this phase"
+ },
+ {
+ "code": 6027,
+ "name": "exceededWalletMaxMintsForPhase",
+ "msg": "Exceeded wallet max mints for this phase"
+ },
+ {
+ "code": 6028,
+ "name": "exceededWalletMaxMintsForCollection",
+ "msg": "Exceeded wallet max mints for the collection"
}
],
"types": [
@@ -825,15 +885,6 @@ export type LibreplexEditionsControls = {
]
}
},
- {
- "name": "padding",
- "type": {
- "array": [
- "u8",
- 200
- ]
- }
- },
{
"name": "phases",
"type": {
@@ -843,6 +894,15 @@ export type LibreplexEditionsControls = {
}
}
}
+ },
+ {
+ "name": "padding",
+ "type": {
+ "array": [
+ "u8",
+ 200
+ ]
+ }
}
]
}
@@ -881,19 +941,19 @@ export type LibreplexEditionsControls = {
"type": "string"
},
{
- "name": "name",
+ "name": "itemBaseName",
"type": "string"
},
{
- "name": "offchainUrl",
+ "name": "itemBaseUri",
"type": "string"
},
{
- "name": "nameIsTemplate",
+ "name": "itemNameIsTemplate",
"type": "bool"
},
{
- "name": "urlIsTemplate",
+ "name": "itemUriIsTemplate",
"type": "bool"
},
{
@@ -930,11 +990,11 @@ export type LibreplexEditionsControls = {
"type": "string"
},
{
- "name": "name",
+ "name": "collectionName",
"type": "string"
},
{
- "name": "offchainUrl",
+ "name": "collectionUri",
"type": "string"
},
{
@@ -966,7 +1026,7 @@ export type LibreplexEditionsControls = {
"type": "string"
},
{
- "name": "itemName",
+ "name": "itemBaseName",
"type": "string"
},
{
@@ -1008,6 +1068,21 @@ export type LibreplexEditionsControls = {
{
"name": "endTime",
"type": "i64"
+ },
+ {
+ "name": "isPrivate",
+ "type": "bool"
+ },
+ {
+ "name": "merkleRoot",
+ "type": {
+ "option": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
}
]
}
@@ -1020,6 +1095,31 @@ export type LibreplexEditionsControls = {
{
"name": "phaseIndex",
"type": "u32"
+ },
+ {
+ "name": "merkleProof",
+ "type": {
+ "option": {
+ "vec": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ }
+ },
+ {
+ "name": "allowListPrice",
+ "type": {
+ "option": "u64"
+ }
+ },
+ {
+ "name": "allowListMaxClaims",
+ "type": {
+ "option": "u64"
+ }
}
]
}
@@ -1086,6 +1186,21 @@ export type LibreplexEditionsControls = {
"name": "currentMints",
"type": "u64"
},
+ {
+ "name": "isPrivate",
+ "type": "bool"
+ },
+ {
+ "name": "merkleRoot",
+ "type": {
+ "option": {
+ "array": [
+ "u8",
+ 32
+ ]
+ }
+ }
+ },
{
"name": "padding",
"type": {
diff --git a/target/types/libreplex_monoswap.ts b/target/types/libreplex_monoswap.ts
deleted file mode 100644
index 28b3c05e..00000000
--- a/target/types/libreplex_monoswap.ts
+++ /dev/null
@@ -1,467 +0,0 @@
-/**
- * Program IDL in camelCase format in order to be used in JS/TS.
- *
- * Note that this is only a type helper and is not the actual IDL. The original
- * IDL can be found at `target/idl/libreplex_monoswap.json`.
- */
-export type LibreplexMonoswap = {
- "address": "8Ktjj9tddWm4QM1ywrxgNRyxkNHnhBCwr9Tq3iGbPDxc",
- "metadata": {
- "name": "libreplexMonoswap",
- "version": "0.0.0",
- "spec": "0.1.0",
- "description": "Created with Anchor",
- "repository": "https://github.com/LibrePlex/metadata"
- },
- "instructions": [
- {
- "name": "createMonoswap",
- "discriminator": [
- 98,
- 245,
- 160,
- 178,
- 252,
- 5,
- 159,
- 225
- ],
- "accounts": [
- {
- "name": "swapMarker",
- "writable": true,
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 109,
- 97,
- 114,
- 107,
- 101,
- 114
- ]
- },
- {
- "kind": "account",
- "path": "namespace"
- },
- {
- "kind": "account",
- "path": "mintOutgoing"
- },
- {
- "kind": "account",
- "path": "mintIncoming"
- }
- ]
- }
- },
- {
- "name": "mintIncoming",
- "writable": true
- },
- {
- "name": "mintOutgoing"
- },
- {
- "name": "mintOutgoingTokenAccountSource",
- "writable": true
- },
- {
- "name": "escrowHolder",
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 101,
- 115,
- 99,
- 114,
- 111,
- 119
- ]
- },
- {
- "kind": "account",
- "path": "namespace"
- },
- {
- "kind": "account",
- "path": "mintIncoming"
- }
- ]
- }
- },
- {
- "name": "mintOutgoingTokenAccountEscrow",
- "writable": true
- },
- {
- "name": "payer",
- "writable": true,
- "signer": true
- },
- {
- "name": "mintOutgoingOwner",
- "writable": true,
- "signer": true
- },
- {
- "name": "namespace",
- "signer": true
- },
- {
- "name": "tokenProgram"
- },
- {
- "name": "associatedTokenProgram",
- "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
- },
- {
- "name": "systemProgram",
- "address": "11111111111111111111111111111111"
- }
- ],
- "args": [
- {
- "name": "input",
- "type": {
- "defined": {
- "name": "createMonoSwapInput"
- }
- }
- }
- ]
- },
- {
- "name": "swap",
- "discriminator": [
- 248,
- 198,
- 158,
- 145,
- 225,
- 117,
- 135,
- 200
- ],
- "accounts": [
- {
- "name": "swapMarker",
- "writable": true,
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 109,
- 97,
- 114,
- 107,
- 101,
- 114
- ]
- },
- {
- "kind": "account",
- "path": "swap_marker.namespace",
- "account": "swapMarker"
- },
- {
- "kind": "account",
- "path": "mintOutgoing"
- },
- {
- "kind": "account",
- "path": "mintIncoming"
- }
- ]
- }
- },
- {
- "name": "swapMarkerReverse",
- "docs": [
- "swapping always creates a symmetrical swap marker that enables a swap back"
- ],
- "writable": true,
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 109,
- 97,
- 114,
- 107,
- 101,
- 114
- ]
- },
- {
- "kind": "account",
- "path": "swap_marker.namespace",
- "account": "swapMarker"
- },
- {
- "kind": "account",
- "path": "mintIncoming"
- },
- {
- "kind": "account",
- "path": "mintOutgoing"
- }
- ]
- }
- },
- {
- "name": "mintIncoming"
- },
- {
- "name": "mintOutgoing"
- },
- {
- "name": "mintIncomingTokenAccountSource",
- "writable": true
- },
- {
- "name": "escrowHolder",
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 101,
- 115,
- 99,
- 114,
- 111,
- 119
- ]
- },
- {
- "kind": "account",
- "path": "swap_marker.namespace",
- "account": "swapMarker"
- },
- {
- "kind": "account",
- "path": "mintIncoming"
- }
- ]
- }
- },
- {
- "name": "escrowHolderReverse",
- "pda": {
- "seeds": [
- {
- "kind": "const",
- "value": [
- 115,
- 119,
- 97,
- 112,
- 95,
- 101,
- 115,
- 99,
- 114,
- 111,
- 119
- ]
- },
- {
- "kind": "account",
- "path": "swap_marker.namespace",
- "account": "swapMarker"
- },
- {
- "kind": "account",
- "path": "mintOutgoing"
- }
- ]
- }
- },
- {
- "name": "mintIncomingTokenAccountTarget",
- "writable": true
- },
- {
- "name": "mintOutgoingTokenAccountSource",
- "writable": true
- },
- {
- "name": "mintOutgoingTokenAccountTarget",
- "writable": true
- },
- {
- "name": "payer",
- "writable": true,
- "signer": true
- },
- {
- "name": "tokenProgram",
- "address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
- },
- {
- "name": "tokenProgram2022",
- "address": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
- },
- {
- "name": "associatedTokenProgram",
- "address": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
- },
- {
- "name": "systemProgram",
- "address": "11111111111111111111111111111111"
- }
- ],
- "args": []
- }
- ],
- "accounts": [
- {
- "name": "swapMarker",
- "discriminator": [
- 186,
- 7,
- 231,
- 231,
- 117,
- 67,
- 107,
- 191
- ]
- }
- ],
- "errors": [
- {
- "code": 6000,
- "name": "badMint",
- "msg": "Metadata has a bad mint"
- },
- {
- "code": 6001,
- "name": "cannotInscribeFungible",
- "msg": "Cannot inscribe a fungible asset"
- },
- {
- "code": 6002,
- "name": "badAuthority",
- "msg": "Bad authority"
- },
- {
- "code": 6003,
- "name": "badAuthorityForHolderInscription",
- "msg": "Bad authority for holder inscription"
- },
- {
- "code": 6004,
- "name": "badAuthorityForUpdateAuthInscription",
- "msg": "Bad authority for update auth inscription"
- },
- {
- "code": 6005,
- "name": "multiSigThresholdMustBeOne",
- "msg": "Multi Signature threshold must be one to create / edit inscriptions"
- },
- {
- "code": 6006,
- "name": "notSquadsMember",
- "msg": "Not squads member"
- },
- {
- "code": 6007,
- "name": "inscription2KeyMismatch",
- "msg": "Inscription V2 key mismatch"
- },
- {
- "code": 6008,
- "name": "inscriptionV3KeyMismatch",
- "msg": "Inscription V3 key mismatch"
- },
- {
- "code": 6009,
- "name": "dataHashMismatch",
- "msg": "Metadata data missmatch"
- }
- ],
- "types": [
- {
- "name": "createMonoSwapInput",
- "type": {
- "kind": "struct",
- "fields": [
- {
- "name": "mintOutgoingAmount",
- "type": "u64"
- },
- {
- "name": "mintIncomingAmount",
- "type": "u64"
- }
- ]
- }
- },
- {
- "name": "swapMarker",
- "type": {
- "kind": "struct",
- "fields": [
- {
- "name": "namespace",
- "type": "pubkey"
- },
- {
- "name": "mintIncoming",
- "type": "pubkey"
- },
- {
- "name": "mintOutgoing",
- "type": "pubkey"
- },
- {
- "name": "mintIncomingAmount",
- "type": "u64"
- },
- {
- "name": "mintOutgoingAmount",
- "type": "u64"
- },
- {
- "name": "used",
- "type": "bool"
- }
- ]
- }
- }
- ]
-};
diff --git a/test/data/merkle_tree.json b/test/data/merkle_tree.json
new file mode 100644
index 00000000..2121a02e
--- /dev/null
+++ b/test/data/merkle_tree.json
@@ -0,0 +1,41 @@
+{
+ "merkle_root": [
+ 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204,
+ 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245
+ ],
+ "max_num_nodes": 2,
+ "max_total_claim": 6,
+ "tree_nodes": [
+ {
+ "claimant": [
+ 104, 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114,
+ 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221,
+ 120, 54
+ ],
+ "claim_price": 500000,
+ "max_claims": 3,
+ "proof": [
+ [
+ 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140,
+ 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25,
+ 232, 172, 235, 168
+ ]
+ ]
+ },
+ {
+ "claimant": [
+ 186, 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80,
+ 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6
+ ],
+ "claim_price": 500000,
+ "max_claims": 3,
+ "proof": [
+ [
+ 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215,
+ 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17,
+ 125, 63, 188
+ ]
+ ]
+ }
+ ]
+}
diff --git a/test/data/minter1.json b/test/data/minter1.json
new file mode 100644
index 00000000..018778a6
--- /dev/null
+++ b/test/data/minter1.json
@@ -0,0 +1,6 @@
+[
+ 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108,
+ 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104,
+ 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73,
+ 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54
+]
diff --git a/test/data/minter2.json b/test/data/minter2.json
new file mode 100644
index 00000000..6abe9d9b
--- /dev/null
+++ b/test/data/minter2.json
@@ -0,0 +1,6 @@
+[
+ 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46,
+ 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186, 39,
+ 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220,
+ 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6
+]
diff --git a/test/tests/editions_controls.test.ts b/test/tests/editions_controls.test.ts
new file mode 100644
index 00000000..903fc022
--- /dev/null
+++ b/test/tests/editions_controls.test.ts
@@ -0,0 +1,1428 @@
+import * as anchor from '@coral-xyz/anchor';
+import { Program } from '@coral-xyz/anchor';
+import { PublicKey, Keypair, SystemProgram, ComputeBudgetProgram, LAMPORTS_PER_SOL } from '@solana/web3.js';
+import { ASSOCIATED_TOKEN_PROGRAM_ID, getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from '@solana/spl-token';
+import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls';
+import { LibreplexEditions } from '../../target/types/libreplex_editions';
+import { expect } from 'chai';
+import { describe, it } from 'mocha';
+import { estimateTransactionFee, getCluster } from '../utils/utils';
+import {
+ getEditions,
+ getEditionsControls,
+ getMinterStats,
+ getTokenMetadata,
+ logEditions,
+ logEditionsControls,
+ logMinterStats,
+ logMinterStatsPhase,
+ logTokenMetadata,
+} from '../utils/getters';
+import { Transaction } from '@solana/web3.js';
+// devnote: try to make tests don't rely on hard addresses but on dynamic runtime ids.
+import { TOKEN_GROUP_EXTENSION_PROGRAM_ID } from '../../constants';
+import { getEditionsPda, getEditionsControlsPda, getHashlistPda, getHashlistMarkerPda, getMinterStatsPda, getMinterStatsPhasePda } from '../utils/pdas';
+import { CollectionConfig, AllowListConfig, PhaseConfig } from '../utils/types';
+
+const VERBOSE_LOGGING = false;
+
+describe('Editions Controls Test Suite', () => {
+ const provider = anchor.AnchorProvider.env();
+ anchor.setProvider(provider);
+
+ // Modify compute units
+ const modifiedComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
+ units: 800000,
+ });
+
+ let editionsControlsProgram: Program;
+ let editionsProgram: Program;
+
+ let editionsPda: PublicKey;
+ let editionsControlsPda: PublicKey;
+ let hashlistPda: PublicKey;
+
+ let payer: Keypair;
+ let creator1: Keypair;
+ let creator2: Keypair;
+ let treasury: Keypair;
+ let platformFeeAdmin: Keypair;
+ let groupMint: Keypair;
+ let group: Keypair;
+
+ let minter0: Keypair; // in allowlist
+ let minter1: Keypair; // in allowlist
+ let minter2: Keypair; // not in allowlist
+
+ let collectionConfig: CollectionConfig;
+ let allowListConfig: AllowListConfig;
+
+ let phase0Config: PhaseConfig;
+ let phase1Config: PhaseConfig;
+ let phase2Config: PhaseConfig;
+ let phase3Config: PhaseConfig;
+ let phase4Config: PhaseConfig;
+ let phase5Config: PhaseConfig;
+
+ // Generate dynamic keypairs and collection config
+ before(async () => {
+ if (VERBOSE_LOGGING) {
+ const cluster = await getCluster(provider.connection);
+ console.log('Cluster:', cluster);
+ }
+
+ editionsControlsProgram = anchor.workspace.LibreplexEditionsControls as Program;
+ editionsProgram = anchor.workspace.LibreplexEditions as Program;
+
+ payer = (provider.wallet as anchor.Wallet).payer;
+ creator1 = Keypair.generate();
+ creator2 = Keypair.generate();
+ treasury = Keypair.generate();
+ platformFeeAdmin = Keypair.generate();
+ groupMint = Keypair.generate();
+ group = Keypair.generate();
+
+ collectionConfig = {
+ maxNumberOfTokens: new anchor.BN(20),
+ symbol: 'COOLX55',
+ collectionName: 'Collection name with meta, platform fee and royalties',
+ collectionUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/0',
+ treasury: treasury.publicKey,
+ maxMintsPerWallet: new anchor.BN(100),
+ royalties: {
+ royaltyBasisPoints: new anchor.BN(1000),
+ creators: [
+ {
+ address: creator1.publicKey,
+ share: 50,
+ },
+ {
+ address: creator2.publicKey,
+ share: 50,
+ },
+ ],
+ },
+ platformFee: {
+ platformFeeValue: new anchor.BN(50000),
+ recipients: [
+ {
+ address: platformFeeAdmin.publicKey,
+ share: 100,
+ },
+ ],
+ isFeeFlat: true,
+ },
+ extraMeta: [
+ { field: 'field1', value: 'value1' },
+ { field: 'field2', value: 'value2' },
+ { field: 'field3', value: 'value3' },
+ { field: 'field4', value: 'value4' },
+ ],
+ itemBaseUri: 'ipfs://QmbsXNSkPUtYNmKfYw1mUSVuz9QU8nhu7YvzM1aAQsv6xw/{}',
+ itemBaseName: 'Item T8 V4 #{}',
+ cosignerProgramId: null,
+ };
+ if (VERBOSE_LOGGING) {
+ console.log('Collection config: ', collectionConfig);
+ }
+
+ editionsPda = getEditionsPda(collectionConfig.symbol, editionsProgram.programId);
+ editionsControlsPda = getEditionsControlsPda(editionsPda, editionsControlsProgram.programId);
+ hashlistPda = getHashlistPda(editionsPda, editionsProgram.programId);
+ });
+
+ // Generate allowlist variables
+ before(async () => {
+ minter0 = Keypair.fromSecretKey(
+ new Uint8Array([
+ 110, 76, 59, 154, 201, 225, 246, 121, 152, 90, 45, 211, 52, 244, 216, 108, 118, 248, 113, 239, 61, 248, 207, 122, 98, 26, 184, 92, 51, 97, 52, 218, 104,
+ 164, 83, 51, 23, 177, 193, 29, 252, 241, 86, 132, 173, 155, 114, 131, 130, 73, 27, 101, 233, 95, 12, 45, 107, 255, 120, 26, 121, 221, 120, 54,
+ ])
+ );
+ minter1 = Keypair.fromSecretKey(
+ new Uint8Array([
+ 16, 27, 49, 140, 228, 142, 201, 93, 199, 209, 62, 136, 151, 212, 238, 114, 46, 204, 155, 132, 26, 227, 44, 245, 239, 29, 195, 63, 77, 162, 28, 220, 186,
+ 39, 133, 92, 39, 241, 42, 161, 180, 15, 92, 18, 15, 101, 248, 80, 238, 254, 220, 231, 1, 14, 231, 145, 170, 49, 163, 111, 239, 112, 135, 6,
+ ])
+ );
+ minter2 = Keypair.generate();
+ allowListConfig = {
+ merkleRoot: Buffer.from([
+ 125, 184, 194, 116, 52, 36, 65, 219, 171, 135, 154, 27, 188, 122, 207, 204, 111, 70, 66, 115, 161, 228, 44, 84, 67, 97, 29, 70, 253, 69, 11, 245,
+ ]),
+ list: [
+ {
+ address: minter0.publicKey,
+ price: new anchor.BN(500000), // 0.005 SOL
+ max_claims: new anchor.BN(3),
+ proof: [
+ Buffer.from([
+ 64, 131, 242, 169, 206, 112, 155, 119, 81, 214, 17, 137, 174, 140, 208, 220, 141, 177, 213, 131, 127, 104, 181, 15, 121, 228, 87, 25, 232, 172,
+ 235, 168,
+ ]),
+ ],
+ },
+ {
+ address: minter1.publicKey,
+ price: new anchor.BN(500000), // 0.005 SOL
+ max_claims: new anchor.BN(3),
+ proof: [
+ Buffer.from([
+ 86, 37, 15, 136, 192, 159, 125, 244, 163, 213, 251, 242, 217, 215, 159, 249, 93, 166, 82, 38, 187, 58, 199, 64, 161, 50, 122, 122, 17, 125, 63,
+ 188,
+ ]),
+ ],
+ },
+ ],
+ };
+ });
+
+ // Perform needed airdrops to minter0, treasury and platformFeeRecipient
+ before(async () => {
+ // Airdrop SOL to minter0
+ const minter0AirdropSignature = await provider.connection.requestAirdrop(minter0.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(minter0AirdropSignature);
+
+ // Airdrop SOL to minter1
+ const minter1AirdropSignature = await provider.connection.requestAirdrop(minter1.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(minter1AirdropSignature);
+
+ // Airdrop SOL to minter2
+ const minter2AirdropSignature = await provider.connection.requestAirdrop(minter2.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(minter2AirdropSignature);
+
+ // Airdrop SOL to treasury
+ const treasuryAirdropSignature = await provider.connection.requestAirdrop(collectionConfig.treasury, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(treasuryAirdropSignature);
+
+ // Airdrop SOL to platformFeeRecipient
+ const platformFeeRecipientAirdropSignature = await provider.connection.requestAirdrop(platformFeeAdmin.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(platformFeeRecipientAirdropSignature);
+ });
+
+ describe('Deploying', () => {
+ it('Should deploy a collection', async () => {
+ // Modify compute units for the transaction
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
+ units: 800000,
+ });
+
+ try {
+ const initialiseIx = await editionsControlsProgram.methods
+ .initialiseEditionsControls({
+ maxMintsPerWallet: collectionConfig.maxMintsPerWallet,
+ treasury: collectionConfig.treasury,
+ maxNumberOfTokens: collectionConfig.maxNumberOfTokens,
+ symbol: collectionConfig.symbol,
+ collectionName: collectionConfig.collectionName,
+ collectionUri: collectionConfig.collectionUri,
+ cosignerProgramId: collectionConfig.cosignerProgramId,
+ royalties: collectionConfig.royalties,
+ platformFee: collectionConfig.platformFee,
+ extraMeta: collectionConfig.extraMeta,
+ itemBaseUri: collectionConfig.itemBaseUri,
+ itemBaseName: collectionConfig.itemBaseName,
+ })
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ editionsDeployment: editionsPda,
+ hashlist: hashlistPda,
+ payer: payer.publicKey,
+ creator: payer.publicKey,
+ groupMint: groupMint.publicKey,
+ group: group.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ groupExtensionProgram: new PublicKey('5hx15GaPPqsYA61v6QpcGPpo125v7rfvEfZQ4dJErG5V'),
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifyComputeUnits).add(initialiseIx);
+
+ await provider.sendAndConfirm(transaction, [groupMint, group, payer]);
+
+ // Fetch updated state
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const metadata = await getTokenMetadata(provider.connection, groupMint.publicKey);
+ if (VERBOSE_LOGGING) {
+ logEditions(editionsDecoded);
+ logEditionsControls(editionsControlsDecoded);
+ logTokenMetadata(metadata);
+ }
+
+ // Verify Editions deployment
+ expect(editionsDecoded.data.symbol).to.equal(collectionConfig.symbol);
+ expect(editionsDecoded.data.creator.toBase58()).to.equal(editionsControlsPda.toBase58());
+ expect(editionsDecoded.data.maxNumberOfTokens.toString()).to.equal(collectionConfig.maxNumberOfTokens.toString());
+ expect(editionsDecoded.data.itemBaseName).to.equal(collectionConfig.itemBaseName);
+ expect(editionsDecoded.data.itemBaseUri).to.equal(collectionConfig.itemBaseUri);
+
+ // Verify EditionsControls deployment
+ expect(editionsControlsDecoded.data.editionsDeployment.toBase58()).to.equal(editionsPda.toBase58());
+ expect(editionsControlsDecoded.data.creator.toBase58()).to.equal(payer.publicKey.toBase58());
+ expect(editionsControlsDecoded.data.treasury.toBase58()).to.equal(collectionConfig.treasury.toBase58());
+ expect(Number(editionsControlsDecoded.data.maxMintsPerWallet)).to.equal(Number(collectionConfig.maxMintsPerWallet));
+ expect(editionsControlsDecoded.data.phases.length).to.equal(0);
+
+ // Verify metadata
+ expect(metadata.name).to.equal(collectionConfig.collectionName);
+ expect(metadata.uri).to.equal(collectionConfig.collectionUri);
+ expect(metadata.mint.toBase58()).to.equal(groupMint.publicKey.toBase58());
+ // Verify that every key in extraMeta is present in metadata.additionalMetadata
+ collectionConfig.extraMeta.forEach((meta) => {
+ expect(metadata.additionalMetadata).to.have.property(meta.field);
+ expect(metadata.additionalMetadata[meta.field]).to.equal(meta.value);
+ });
+
+ // Add more assertions as needed
+ } catch (error) {
+ console.error('Error in initialiseEditionsControls:', error);
+ throw error;
+ }
+ });
+ });
+
+ describe('Adding phases', () => {
+ it('Should add a private phase with allowlist. [Phase Index 0]: Open: Not Available, Allowlist: 0.05 SOL', async () => {
+ phase0Config = {
+ maxMintsPerWallet: new anchor.BN(100),
+ maxMintsTotal: new anchor.BN(1000),
+ priceAmount: new anchor.BN(0), // Not openly buyable, only available to allowlist
+ startTime: new anchor.BN(new Date().getTime() / 1000),
+ endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: true,
+ merkleRoot: allowListConfig.merkleRoot,
+ };
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase0Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(1);
+ });
+
+ it('Should add a public phase with allowlist. [Phase Index 1]: Open: O.1 SOL, Allowlist: 0.05 SOL', async () => {
+ phase1Config = {
+ maxMintsPerWallet: new anchor.BN(5),
+ maxMintsTotal: new anchor.BN(10),
+ priceAmount: new anchor.BN(1000000), // 0.1 SOL
+ startTime: new anchor.BN(new Date().getTime() / 1000),
+ endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: false,
+ merkleRoot: allowListConfig.merkleRoot,
+ };
+
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase1Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ // get state
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(2);
+ });
+
+ it('Should add a public phase without allowlist. [Phase Index 2]: Open: 0.05 SOL, Allowlist: Not Available', async () => {
+ phase2Config = {
+ maxMintsPerWallet: new anchor.BN(100),
+ maxMintsTotal: new anchor.BN(1000),
+ priceAmount: new anchor.BN(500000), // 0.05 SOL
+ startTime: new anchor.BN(new Date().getTime() / 1000),
+ endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: false,
+ merkleRoot: null,
+ };
+
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase2Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ // get state
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(3);
+ });
+
+ it('Should add a public phase without max supply, up to collection max. [Phase Index 3]', async () => {
+ phase3Config = {
+ maxMintsPerWallet: new anchor.BN(100),
+ maxMintsTotal: new anchor.BN(0), // unlimited supply
+ priceAmount: new anchor.BN(500000), // 0.05 SOL
+ startTime: new anchor.BN(new Date().getTime() / 1000),
+ endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: false,
+ merkleRoot: null,
+ };
+
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase3Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ // get state
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(4);
+ });
+
+ it('Should add a phase that starts in the future. [Phase Index 4]', async () => {
+ phase4Config = {
+ maxMintsPerWallet: new anchor.BN(100),
+ maxMintsTotal: new anchor.BN(1000),
+ priceAmount: new anchor.BN(500000), // 0.05 SOL
+ startTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24), // 1 day from now
+ endTime: new anchor.BN(new Date().getTime() / 1000 + 60 * 60 * 24 * 7), // 1 week from now
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: false,
+ merkleRoot: null,
+ };
+
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase4Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ // get state
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(5);
+ });
+
+ it('Should add a phase that has already ended. [Phase Index 5]', async () => {
+ phase5Config = {
+ maxMintsPerWallet: new anchor.BN(100),
+ maxMintsTotal: new anchor.BN(1000),
+ priceAmount: new anchor.BN(500000), // 0.05 SOL
+ startTime: new anchor.BN(new Date().getTime() / 1000 - 60 * 60 * 24 * 7), // 1 week ago
+ endTime: new anchor.BN(new Date().getTime() / 1000 - 60 * 60 * 24), // 1 day associatedTokenProgram
+ priceToken: new PublicKey('So11111111111111111111111111111111111111112'),
+ isPrivate: false,
+ merkleRoot: null,
+ };
+
+ const phaseIx = await editionsControlsProgram.methods
+ .addPhase(phase5Config)
+ .accountsStrict({
+ editionsControls: editionsControlsPda,
+ creator: payer.publicKey,
+ payer: payer.publicKey,
+ systemProgram: SystemProgram.programId,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .signers([])
+ .instruction();
+
+ const transaction = new Transaction().add(phaseIx);
+ await provider.sendAndConfirm(transaction, [payer]);
+
+ // get state
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ if (VERBOSE_LOGGING) {
+ logEditionsControls(editionsControlsDecoded);
+ }
+
+ // verify state
+ expect(editionsControlsDecoded.data.phases.length).to.equal(6);
+ });
+ });
+
+ describe('Minting', () => {
+ describe('Minting on a private allowlist-only phase [Phase Index 0]', () => {
+ it('Should be able to mint with valid allowlist proof', async () => {
+ const mintConfig = {
+ phaseIndex: 0,
+ merkleProof: allowListConfig.list[0].proof,
+ allowListPrice: allowListConfig.list[0].price,
+ allowListMaxClaims: allowListConfig.list[0].max_claims,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter0.publicKey,
+ signer: minter0.publicKey,
+ minter: minter0.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+
+ const minter0BalanceBefore = await provider.connection.getBalance(minter0.publicKey);
+ const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ // Estimate transaction fee
+ const estimatedFee = await estimateTransactionFee(provider.connection, transaction, [minter0, mint, member]);
+ if (VERBOSE_LOGGING) {
+ console.log(`Estimated transaction fee: ${estimatedFee} lamports (${estimatedFee / LAMPORTS_PER_SOL} SOL)`);
+ }
+
+ try {
+ await provider.sendAndConfirm(transaction, [minter0, mint, member]);
+ } catch (error) {
+ console.error('Error in mintWithControls:', error);
+ throw error;
+ }
+
+ // Verify state after minting
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ // Pull new balances
+ const minter0BalanceAfter = await provider.connection.getBalance(minter0.publicKey);
+ const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ // Verify that the token was minted
+ expect(editionsDecoded.data.numberOfTokensIssued.toString()).to.equal('1');
+ expect(editionsControlsDecoded.data.phases[0].currentMints.toString()).to.equal('1');
+ expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1');
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1');
+
+ // When the fee is flat, is paid by the minter.
+ const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue;
+ const expectedTreasuryIncome = mintConfig.allowListPrice;
+
+ if (VERBOSE_LOGGING) {
+ console.log('balances before', {
+ minter0: minter0BalanceBefore / LAMPORTS_PER_SOL,
+ treasury: treasuryBalanceBefore / LAMPORTS_PER_SOL,
+ platformFeeRecipient: platformFeeRecipientBalanceBefore / LAMPORTS_PER_SOL,
+ });
+
+ console.log('balances after', {
+ minter0: minter0BalanceAfter / LAMPORTS_PER_SOL,
+ treasury: treasuryBalanceAfter / LAMPORTS_PER_SOL,
+ platformFeeRecipient: platformFeeRecipientBalanceAfter / LAMPORTS_PER_SOL,
+ });
+ }
+
+ // Verify that the treasury received the correct amount of SOL (0.05 SOL from allowlist price)
+ expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber());
+ // Verify that the platform fee recipient received the correct amount of royalties (0.05 SOL from collectionConfig platform fee)
+ expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber());
+ });
+
+ it('Should not be able to mint without allowlist proof (private phase)', async () => {
+ const mintConfig = {
+ phaseIndex: 0,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter0.publicKey,
+ signer: minter0.publicKey,
+ minter: minter0.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter0, mint, member]);
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Private phase but no merkle proof provided');
+ }
+ });
+
+ it('Should fail to mint when exceeding allowlist max claims', async () => {
+ const mintConfig = {
+ phaseIndex: 0,
+ merkleProof: allowListConfig.list[0].proof,
+ allowListPrice: allowListConfig.list[0].price,
+ allowListMaxClaims: allowListConfig.list[0].max_claims,
+ };
+
+ // mint twice, then the third mint should fail because the max claims for the allowlist is 3
+ const mintWithControls = async () => {
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter0.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter0.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter0.publicKey,
+ signer: minter0.publicKey,
+ minter: minter0.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter0, mint, member]);
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ for (let i = 0; i < 2; i++) {
+ try {
+ await mintWithControls();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ try {
+ await mintWithControls();
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('This wallet has exceeded allow list max_claims in the current phase.');
+ }
+
+ // expect the user to have minted three total items on the phase
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter0.publicKey, 0, editionsControlsProgram.programId);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ expect(editionsControlsDecoded.data.phases[0].currentMints.toString()).to.equal('3');
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('3');
+ });
+ });
+
+ describe('Minting on a public phase with optional allowlist [Phase Index 1]', () => {
+ it('Should be able to mint with allowlist proof at discounted price', async () => {
+ const mintConfig = {
+ phaseIndex: 1,
+ merkleProof: allowListConfig.list[1].proof,
+ allowListPrice: allowListConfig.list[1].price,
+ allowListMaxClaims: allowListConfig.list[1].max_claims,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ // pull before balances
+ const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter1.publicKey,
+ signer: minter1.publicKey,
+ minter: minter1.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter1, mint, member]);
+ } catch (error) {
+ console.error('Error in mintWithControls:', error);
+ throw error;
+ }
+
+ // Verify state after minting
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ // Verify mint
+ expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('1');
+ expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1'); // user already minted on phase 0
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1');
+
+ // Verify protocol fees & treasury income
+ const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue;
+ const expectedTreasuryIncome = mintConfig.allowListPrice;
+
+ const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber());
+ expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber());
+ });
+
+ it('Should be able to mint without allowlist proof at full price', async () => {
+ const mintConfig = {
+ phaseIndex: 1,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ // pull before balances
+ const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter1.publicKey,
+ signer: minter1.publicKey,
+ minter: minter1.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter1, mint, member]);
+ } catch (error) {
+ console.error('Error in mintWithControls:', error);
+ throw error;
+ }
+
+ // Verify state after minting
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ expect(editionsControlsDecoded.data.phases[1].currentMints.toString()).to.equal('2');
+ expect(minterStatsDecoded.data.mintCount.toString()).to.equal('2');
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('2');
+
+ // Verify protocol fees & treasury income
+ const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue;
+ const expectedTreasuryIncome = phase1Config.priceAmount;
+
+ const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber());
+ expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber());
+ });
+
+ // Max wallet mints on Phase 1 is 5
+ it('Should fail to mint when exceeding phase max per wallet', async () => {
+ const mintConfig = {
+ phaseIndex: 1,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const mintWithControls = async () => {
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter1.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter1.publicKey, 1, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter1.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter1.publicKey,
+ signer: minter1.publicKey,
+ minter: minter1.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter1, mint, member]);
+ } catch (error) {
+ throw error;
+ }
+ };
+
+ for (let i = 0; i < 3; i++) {
+ try {
+ await mintWithControls();
+ } catch (error) {
+ throw error;
+ }
+ }
+
+ // 5'th mint should fail
+ try {
+ await mintWithControls();
+ throw new Error('Mint should fail');
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Exceeded wallet max mints for this phase.');
+ }
+ });
+
+ // Max total mints on Phase 1 is 10
+ it('Should fail to mint when exceeding phase max mints', async () => {
+ // phase mint count is 5, perform 5 mints more with random wallets that should succeed, then the 11th mint should fail
+ const mintWithControls = async (minter: Keypair, mint: Keypair, member: Keypair) => {
+ const mintConfig = {
+ phaseIndex: 1,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 1, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter.publicKey,
+ signer: minter.publicKey,
+ minter: minter.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter, mint, member]);
+ } catch (error) {
+ throw error;
+ }
+ };
+ if (VERBOSE_LOGGING) {
+ console.log('Performing 5 mints with random wallets, takes a while..');
+ }
+ const mintPromises = [];
+ for (let i = 0; i < 5; i++) {
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ mintPromises.push(mintWithControls(minter, mint, member));
+ }
+
+ try {
+ await Promise.all(mintPromises);
+ } catch (error) {
+ console.error('Error in parallel minting:', error);
+ throw error;
+ }
+
+ // 11st mint should fail
+ try {
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ await mintWithControls(minter, mint, member);
+ throw new Error('Mint should fail');
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Exceeded max mints for this phase.');
+ }
+ });
+
+ describe('Minting on a public phase without allowlist [Phase Index 2]', () => {
+ it('Should be able to mint without any allowlist data (open mint)', async () => {
+ const mintConfig = {
+ phaseIndex: 2,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter2.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter2.publicKey, 2, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter2.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ // pull before balances
+ const treasuryBalanceBefore = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceBefore = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter2.publicKey,
+ signer: minter2.publicKey,
+ minter: minter2.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter2, mint, member]);
+ } catch (error) {
+ console.error('Error in mintWithControls:', error);
+ throw error;
+ }
+
+ // Verify state after minting
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1');
+ expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1');
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1');
+
+ // Verify protocol fees & treasury income
+ const expectedPlatformFee = collectionConfig.platformFee.platformFeeValue;
+ const expectedTreasuryIncome = phase3Config.priceAmount;
+
+ const treasuryBalanceAfter = await provider.connection.getBalance(treasury.publicKey);
+ const platformFeeRecipientBalanceAfter = await provider.connection.getBalance(platformFeeAdmin.publicKey);
+
+ expect(treasuryBalanceAfter - treasuryBalanceBefore).to.equal(expectedTreasuryIncome.toNumber());
+ expect(platformFeeRecipientBalanceAfter - platformFeeRecipientBalanceBefore).to.equal(expectedPlatformFee.toNumber());
+ });
+
+ it('Should not be able to mint with allowlist (phase does not have allowlist)', async () => {
+ const mintConfig = {
+ phaseIndex: 2,
+ merkleProof: allowListConfig.list[0].proof,
+ allowListPrice: allowListConfig.list[0].price,
+ allowListMaxClaims: allowListConfig.list[0].max_claims,
+ };
+
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter2.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter2.publicKey, 2, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter2.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter2.publicKey,
+ signer: minter2.publicKey,
+ minter: minter2.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter2, mint, member]);
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Merkle root not set for allow list mint');
+ }
+ // get state
+ const editionsDecoded = await getEditions(provider.connection, editionsPda, editionsProgram);
+ const editionsControlsDecoded = await getEditionsControls(provider.connection, editionsControlsPda, editionsControlsProgram);
+ const minterStatsDecoded = await getMinterStats(provider.connection, minterStatsPda, editionsControlsProgram);
+ const minterStatsPhaseDecoded = await getMinterStats(provider.connection, minterStatsPhasePda, editionsControlsProgram);
+
+ expect(editionsControlsDecoded.data.phases[2].currentMints.toString()).to.equal('1');
+ expect(minterStatsDecoded.data.mintCount.toString()).to.equal('1');
+ expect(minterStatsPhaseDecoded.data.mintCount.toString()).to.equal('1');
+
+ // log final state
+ if (VERBOSE_LOGGING) {
+ logEditions(editionsDecoded);
+ logEditionsControls(editionsControlsDecoded);
+ logMinterStats(minterStatsDecoded);
+ logMinterStatsPhase(minterStatsPhaseDecoded);
+ }
+ });
+ });
+
+ describe('Minting on a public phase without allowlist and unlimited supply up to collection max [Phase Index 3]', () => {
+ // should fail to mint if it exceeds the max mints for the entire collection, max mints for the collection is 20.
+ it('Should fail to mint when exceeding collection max mints', async () => {
+ if (VERBOSE_LOGGING) {
+ const decodedEditions = await getEditions(provider.connection, editionsPda, editionsProgram);
+ logEditions(decodedEditions);
+ }
+
+ // current mint count is 14, perform 6 mints with random wallets, then the 21st mint should fail
+ const mintWithControls = async (minter: Keypair, mint: Keypair, member: Keypair) => {
+ const mintConfig = {
+ phaseIndex: 3,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 3, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter.publicKey,
+ signer: minter.publicKey,
+ minter: minter.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter, mint, member]);
+ } catch (error) {
+ throw error;
+ }
+ };
+ if (VERBOSE_LOGGING) {
+ console.log('Performing 6 mints with random wallets, takes a while..');
+ }
+ const mintPromises = [];
+ for (let i = 0; i < 6; i++) {
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ mintPromises.push(mintWithControls(minter, mint, member));
+ }
+
+ try {
+ await Promise.all(mintPromises);
+ } catch (error) {
+ console.error('Error in parallel minting:', error);
+ throw error;
+ }
+ if (VERBOSE_LOGGING) {
+ console.log('Performing 21th mint with random wallet..');
+ }
+ try {
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ await mintWithControls(minter, mint, member);
+ throw new Error('Mint should fail');
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Minted out.');
+ }
+ });
+ });
+
+ describe('Attempting to mint on invalid phases', () => {
+ it('Should fail to mint on a phase that has not started yet', async () => {
+ const mintConfig = {
+ phaseIndex: 4,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 4, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter.publicKey,
+ signer: minter.publicKey,
+ minter: minter.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter, mint, member]);
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Phase not yet started');
+ }
+ });
+
+ it('Should fail to mint on a phase that has already ended', async () => {
+ const mintConfig = {
+ phaseIndex: 5,
+ merkleProof: null,
+ allowListPrice: null,
+ allowListMaxClaims: null,
+ };
+
+ const minter = Keypair.generate();
+ const mint = Keypair.generate();
+ const member = Keypair.generate();
+
+ // airdrop minter
+ const airdropTx = await provider.connection.requestAirdrop(minter.publicKey, 1 * LAMPORTS_PER_SOL);
+ await provider.connection.confirmTransaction(airdropTx, 'confirmed');
+
+ const hashlistMarkerPda = getHashlistMarkerPda(editionsPda, mint.publicKey, editionsProgram.programId);
+ const minterStatsPda = getMinterStatsPda(editionsPda, minter.publicKey, editionsControlsProgram.programId);
+ const minterStatsPhasePda = getMinterStatsPhasePda(editionsPda, minter.publicKey, 5, editionsControlsProgram.programId);
+ const associatedTokenAddressSync = getAssociatedTokenAddressSync(mint.publicKey, minter.publicKey, false, TOKEN_2022_PROGRAM_ID);
+
+ const mintIx = await editionsControlsProgram.methods
+ .mintWithControls(mintConfig)
+ .accountsStrict({
+ editionsDeployment: editionsPda,
+ editionsControls: editionsControlsPda,
+ hashlist: hashlistPda,
+ hashlistMarker: hashlistMarkerPda,
+ payer: minter.publicKey,
+ signer: minter.publicKey,
+ minter: minter.publicKey,
+ minterStats: minterStatsPda,
+ minterStatsPhase: minterStatsPhasePda,
+ mint: mint.publicKey,
+ member: member.publicKey,
+ group: group.publicKey,
+ groupMint: groupMint.publicKey,
+ platformFeeRecipient1: platformFeeAdmin.publicKey,
+ tokenAccount: associatedTokenAddressSync,
+ treasury: treasury.publicKey,
+ tokenProgram: TOKEN_2022_PROGRAM_ID,
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
+ groupExtensionProgram: TOKEN_GROUP_EXTENSION_PROGRAM_ID,
+ systemProgram: SystemProgram.programId,
+ libreplexEditionsProgram: editionsProgram.programId,
+ })
+ .instruction();
+
+ const transaction = new Transaction().add(modifiedComputeUnits).add(mintIx);
+ try {
+ await provider.sendAndConfirm(transaction, [minter, mint, member]);
+ } catch (error) {
+ const errorString = JSON.stringify(error);
+ expect(errorString).to.include('Phase already finished');
+ }
+ });
+ });
+ });
+ });
+});
diff --git a/test/utils/getters.ts b/test/utils/getters.ts
new file mode 100644
index 00000000..96f73f63
--- /dev/null
+++ b/test/utils/getters.ts
@@ -0,0 +1,228 @@
+import { BorshCoder, Program } from '@coral-xyz/anchor';
+import { Connection, PublicKey } from '@solana/web3.js';
+import { LibreplexEditionsControls } from '../../target/types/libreplex_editions_controls';
+import { LibreplexEditions } from '../../target/types/libreplex_editions';
+import { IdlAccounts } from '@coral-xyz/anchor';
+import { getTokenMetadata as getSplTokenMetadata } from '@solana/spl-token';
+
+export type EditionsDeployment =
+ IdlAccounts['editionsDeployment'];
+
+export type EditionsControls =
+ IdlAccounts['editionsControls'];
+
+export type MinterStats = IdlAccounts['minterStats'];
+
+export const decodeEditions =
+ (program: Program) =>
+ (buffer: Buffer | undefined, pubkey: PublicKey) => {
+ const coder = new BorshCoder(program.idl);
+ const data = buffer
+ ? coder.accounts.decode('editionsDeployment', buffer)
+ : null;
+
+ return {
+ data,
+ pubkey,
+ };
+ };
+
+export const getEditions = async (
+ connection: Connection,
+ editionsPda: PublicKey,
+ editionsProgram: Program
+) => {
+ const editionsAccountInfo = await connection.getAccountInfo(editionsPda);
+ if (!editionsAccountInfo) {
+ throw new Error('Editions account not found');
+ }
+ const editionsDecoded = decodeEditions(editionsProgram)(
+ editionsAccountInfo.data,
+ editionsPda
+ );
+ return editionsDecoded;
+};
+
+export const logEditions = (editionsDecoded: {
+ data: EditionsDeployment;
+ pubkey: PublicKey;
+}) => {
+ console.log({
+ Editions: {
+ symbol: editionsDecoded.data.symbol,
+ creator: editionsDecoded.data.creator.toBase58(),
+ groupMint: editionsDecoded.data.groupMint.toBase58(),
+ maxNumberOfTokens: editionsDecoded.data.maxNumberOfTokens.toString(),
+ cosignerProgramId: editionsDecoded.data.cosignerProgramId
+ ? editionsDecoded.data.cosignerProgramId.toBase58()
+ : null,
+ tokensMinted: editionsDecoded.data.numberOfTokensIssued.toString(),
+ itemBaseName: editionsDecoded.data.itemBaseName,
+ itemBaseUri: editionsDecoded.data.itemBaseUri,
+ itemNameIsTemplate: editionsDecoded.data.itemNameIsTemplate,
+ itemUriIsTemplate: editionsDecoded.data.itemUriIsTemplate,
+ },
+ });
+};
+
+export const decodeEditionsControls =
+ (program: Program) =>
+ (buffer: Buffer | undefined, pubkey: PublicKey) => {
+ const coder = new BorshCoder(program.idl);
+ const data = buffer
+ ? coder.accounts.decode('editionsControls', buffer)
+ : null;
+
+ return {
+ data,
+ pubkey,
+ };
+ };
+
+export const getEditionsControls = async (
+ connection: Connection,
+ editionsControlsPda: PublicKey,
+ editionsControlsProgram: Program
+) => {
+ const editionsControlsAccountInfo = await connection.getAccountInfo(
+ editionsControlsPda
+ );
+ if (!editionsControlsAccountInfo) {
+ throw new Error(
+ 'EditionsControls account not found. The collection was not initialized with controls.'
+ );
+ }
+ const editionsControlsDecoded = decodeEditionsControls(
+ editionsControlsProgram
+ )(editionsControlsAccountInfo.data, editionsControlsPda);
+ return editionsControlsDecoded;
+};
+
+export const logEditionsControls = (editionsControlsDecoded: {
+ data: EditionsControls;
+ pubkey: PublicKey;
+}) => {
+ console.log({
+ EditionsControls: {
+ address: editionsControlsDecoded.pubkey.toBase58(),
+ coreDeployment:
+ editionsControlsDecoded.data.editionsDeployment.toBase58(),
+ creator: editionsControlsDecoded.data.creator.toBase58(),
+ treasury: editionsControlsDecoded.data.treasury.toBase58(),
+ maxMintsPerWallet: Number(editionsControlsDecoded.data.maxMintsPerWallet),
+ },
+ phases: editionsControlsDecoded.data.phases.map((item, idx) => ({
+ phaseIndex: idx,
+ currentMints: Number(item.currentMints),
+ maxMintsPerWallet: Number(item.maxMintsPerWallet),
+ maxMintsTotal: Number(item.maxMintsTotal),
+ startTime: Number(item.startTime),
+ endTime: Number(item.endTime),
+ priceAmount: Number(item.priceAmount),
+ priceToken: item.priceToken ? item.priceToken.toBase58() : null,
+ isPrivate: item.isPrivate,
+ merkleRoot: item.merkleRoot ? JSON.stringify(item.merkleRoot) : null,
+ })),
+ });
+};
+
+export const parseMetadata = (
+ rawMetadata: (readonly [string, string])[]
+): Record => {
+ const metadata: Record = {};
+ for (const [key, value] of rawMetadata) {
+ metadata[key] = value;
+ }
+ return metadata;
+};
+
+export const getTokenMetadata = async (
+ connection: Connection,
+ mint: PublicKey
+) => {
+ const rawMetadata = await getSplTokenMetadata(connection, mint);
+ const additionalMetadata = rawMetadata.additionalMetadata;
+ const parsedMetadata = parseMetadata(additionalMetadata);
+ return {
+ ...rawMetadata,
+ additionalMetadata: parsedMetadata,
+ };
+};
+
+export const logTokenMetadata = (metadata: {
+ name: string;
+ symbol: string;
+ uri: string;
+ updateAuthority?: PublicKey;
+ mint: PublicKey;
+ additionalMetadata: Record;
+}) => {
+ console.log({
+ TokenMetadata: {
+ name: metadata.name,
+ symbol: metadata.symbol,
+ uri: metadata.uri,
+ updateAuthority: metadata.updateAuthority.toBase58(),
+ mint: metadata.mint.toBase58(),
+ additionalMetadata: metadata.additionalMetadata,
+ },
+ });
+};
+
+export const decodeMinterStats =
+ (program: Program) =>
+ (buffer: Buffer | undefined, pubkey: PublicKey) => {
+ const coder = new BorshCoder(program.idl);
+ const data = buffer
+ ? coder.accounts.decode('minterStats', buffer)
+ : null;
+
+ return {
+ data,
+ pubkey,
+ };
+ };
+
+export const getMinterStats = async (
+ connection: Connection,
+ minterStatsPda: PublicKey,
+ editionsControlsProgram: Program
+) => {
+ const minterStatsAccountInfo = await connection.getAccountInfo(
+ minterStatsPda
+ );
+ if (!minterStatsAccountInfo) {
+ throw new Error('MinterStats account not found');
+ }
+ const minterStatsDecoded = decodeMinterStats(editionsControlsProgram)(
+ minterStatsAccountInfo.data,
+ minterStatsPda
+ );
+ return minterStatsDecoded;
+};
+
+export const logMinterStats = (minterStatsDecoded: {
+ data: MinterStats;
+ pubkey: PublicKey;
+}) => {
+ console.log({
+ MinterStats: {
+ address: minterStatsDecoded.pubkey.toBase58(),
+ wallet: minterStatsDecoded.data.wallet.toBase58(),
+ mintCount: minterStatsDecoded.data.mintCount.toString(),
+ },
+ });
+};
+
+export const logMinterStatsPhase = (minterStatsDecoded: {
+ data: MinterStats;
+ pubkey: PublicKey;
+}) => {
+ console.log({
+ MinterStatsPhase: {
+ address: minterStatsDecoded.pubkey.toBase58(),
+ wallet: minterStatsDecoded.data.wallet.toBase58(),
+ mintCount: minterStatsDecoded.data.mintCount.toString(),
+ },
+ });
+};
diff --git a/test/utils/pdas.ts b/test/utils/pdas.ts
new file mode 100644
index 00000000..0af25bdc
--- /dev/null
+++ b/test/utils/pdas.ts
@@ -0,0 +1,75 @@
+import { PublicKey } from '@solana/web3.js';
+import { toBufferLE } from 'bigint-buffer';
+
+export const getEditionsPda = (
+ symbol: string,
+ editionsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [Buffer.from('editions_deployment'), Buffer.from(symbol)],
+ editionsProgramId
+ )[0];
+};
+
+export const getEditionsControlsPda = (
+ editionsDeployment: PublicKey,
+ editionsControlsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [Buffer.from('editions_controls'), editionsDeployment.toBuffer()],
+ editionsControlsProgramId
+ )[0];
+};
+
+export const getHashlistPda = (
+ deployment: PublicKey,
+ editionsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [Buffer.from('hashlist'), deployment.toBuffer()],
+ editionsProgramId
+ )[0];
+};
+
+export const getHashlistMarkerPda = (
+ editionsDeployment: PublicKey,
+ mint: PublicKey,
+ editionsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [
+ Buffer.from('hashlist_marker'),
+ editionsDeployment.toBuffer(),
+ mint.toBuffer(),
+ ],
+ editionsProgramId
+ )[0];
+};
+
+export const getMinterStatsPda = (
+ deployment: PublicKey,
+ minter: PublicKey,
+ editionsControlsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [Buffer.from('minter_stats'), deployment.toBuffer(), minter.toBuffer()],
+ editionsControlsProgramId
+ )[0];
+};
+
+export const getMinterStatsPhasePda = (
+ deployment: PublicKey,
+ minter: PublicKey,
+ phaseIndex: number,
+ editionsControlsProgramId: PublicKey
+) => {
+ return PublicKey.findProgramAddressSync(
+ [
+ Buffer.from('minter_stats_phase'),
+ deployment.toBuffer(),
+ minter.toBuffer(),
+ toBufferLE(BigInt(phaseIndex), 4),
+ ],
+ editionsControlsProgramId
+ )[0];
+};
diff --git a/test/utils/types.ts b/test/utils/types.ts
new file mode 100644
index 00000000..4b0c2d8f
--- /dev/null
+++ b/test/utils/types.ts
@@ -0,0 +1,45 @@
+import * as anchor from '@coral-xyz/anchor';
+import { PublicKey } from '@solana/web3.js';
+
+export interface CollectionConfig {
+ symbol: string;
+ maxMintsPerWallet: anchor.BN;
+ maxNumberOfTokens: anchor.BN;
+ collectionName: string;
+ collectionUri: string;
+ royalties: {
+ royaltyBasisPoints: anchor.BN;
+ creators: { address: PublicKey; share: number }[];
+ };
+ platformFee: {
+ platformFeeValue: anchor.BN;
+ recipients: { address: PublicKey; share: number }[];
+ isFeeFlat: boolean;
+ };
+ extraMeta: { field: string; value: string }[];
+ itemBaseUri: string;
+ itemBaseName: string;
+ treasury: PublicKey;
+ cosignerProgramId: PublicKey | null;
+}
+
+export interface AllowListConfig {
+ merkleRoot: Buffer;
+ list: {
+ address: PublicKey;
+ price: anchor.BN;
+ max_claims: anchor.BN;
+ proof: Buffer[];
+ }[];
+}
+
+export interface PhaseConfig {
+ maxMintsPerWallet: anchor.BN;
+ maxMintsTotal: anchor.BN;
+ priceAmount: anchor.BN;
+ startTime: anchor.BN;
+ endTime: anchor.BN;
+ priceToken: PublicKey;
+ isPrivate: boolean;
+ merkleRoot: Buffer | null;
+}
diff --git a/test/utils/utils.ts b/test/utils/utils.ts
new file mode 100644
index 00000000..b34f0570
--- /dev/null
+++ b/test/utils/utils.ts
@@ -0,0 +1,35 @@
+import { Connection, Keypair, Transaction } from '@solana/web3.js';
+
+export async function getCluster(connection: Connection): Promise {
+ // Get the genesis hash
+ const genesisHash = await connection.getGenesisHash();
+
+ // Compare the genesis hash with known cluster genesis hashes
+ switch (genesisHash) {
+ case '5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d':
+ return 'mainnet-beta';
+ case 'EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG':
+ return 'testnet';
+ case '4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY':
+ return 'devnet';
+ default:
+ // If it doesn't match any known cluster, it's likely localhost
+ return 'localhost';
+ }
+}
+
+
+export async function estimateTransactionFee(connection: Connection, transaction: Transaction, signers: Keypair[]): Promise {
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
+ transaction.recentBlockhash = blockhash;
+ transaction.sign(...signers);
+
+ const message = transaction.compileMessage();
+ const fees = await connection.getFeeForMessage(message);
+
+ if (fees === null) {
+ throw new Error('Failed to estimate fee');
+ }
+
+ return fees.value;
+}
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
index a58c9f14..713f4570 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,18 +1,16 @@
{
"compilerOptions": {
- "types": ["mocha", "chai"],
- "typeRoots": ["./node_modules/@types"],
- "lib": ["es2015"],
- "module": "commonjs",
- "target": "es6",
- "esModuleInterop": true
- },
- "include": [],
- "exclude": ["node_modules"],
- "references": [
- {
- "path": "./packages/libreplex-idls"
- },
-
- ],
+ "types": ["mocha", "chai"],
+ "typeRoots": ["./node_modules/@types"],
+ "lib": ["es2015"],
+ "module": "commonjs",
+ "target": "es6",
+ "esModuleInterop": true,
+ "baseUrl": ".",
+ "paths": {
+ "*": ["node_modules/*"]
+ }
+ },
+ "include": ["test/**/*"],
+ "exclude": ["node_modules"]
}
diff --git a/yarn.lock b/yarn.lock
index 2f896775..1b4c1879 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -510,13 +510,164 @@
dependencies:
buffer "~6.0.3"
-"@solana/spl-token@0.3.8", "@solana/spl-token@^0.3.8":
- version "0.3.8"
- resolved "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.3.8.tgz"
- integrity sha512-ogwGDcunP9Lkj+9CODOWMiVJEdRtqHAtX2rWF62KxnnSWtMZtV9rDhTrZFshiyJmxDnRL/1nKE1yJHg4jjs3gg==
+"@solana/codecs-core@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-preview.4.tgz#770826105f2f884110a21662573e7a2014654324"
+ integrity sha512-A0VVuDDA5kNKZUinOqHxJQK32aKTucaVbvn31YenGzHX1gPqq+SOnFwgaEY6pq4XEopSmaK16w938ZQS8IvCnw==
+ dependencies:
+ "@solana/errors" "2.0.0-preview.4"
+
+"@solana/codecs-core@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-core/-/codecs-core-2.0.0-rc.1.tgz#1a2d76b9c7b9e7b7aeb3bd78be81c2ba21e3ce22"
+ integrity sha512-bauxqMfSs8EHD0JKESaNmNuNvkvHSuN3bbWAF5RjOfDu2PugxHrvRebmYauvSumZ3cTfQ4HJJX6PG5rN852qyQ==
+ dependencies:
+ "@solana/errors" "2.0.0-rc.1"
+
+"@solana/codecs-data-structures@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-preview.4.tgz#f8a2470982a9792334737ea64000ccbdff287247"
+ integrity sha512-nt2k2eTeyzlI/ccutPcG36M/J8NAYfxBPI9h/nQjgJ+M+IgOKi31JV8StDDlG/1XvY0zyqugV3I0r3KAbZRJpA==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-preview.4"
+ "@solana/codecs-numbers" "2.0.0-preview.4"
+ "@solana/errors" "2.0.0-preview.4"
+
+"@solana/codecs-data-structures@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-data-structures/-/codecs-data-structures-2.0.0-rc.1.tgz#d47b2363d99fb3d643f5677c97d64a812982b888"
+ integrity sha512-rinCv0RrAVJ9rE/rmaibWJQxMwC5lSaORSZuwjopSUE6T0nb/MVg6Z1siNCXhh/HFTOg0l8bNvZHgBcN/yvXog==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-rc.1"
+ "@solana/codecs-numbers" "2.0.0-rc.1"
+ "@solana/errors" "2.0.0-rc.1"
+
+"@solana/codecs-numbers@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-preview.4.tgz#6a53b456bb7866f252d8c032c81a92651e150f66"
+ integrity sha512-Q061rLtMadsO7uxpguT+Z7G4UHnjQ6moVIxAQxR58nLxDPCC7MB1Pk106/Z7NDhDLHTcd18uO6DZ7ajHZEn2XQ==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-preview.4"
+ "@solana/errors" "2.0.0-preview.4"
+
+"@solana/codecs-numbers@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-numbers/-/codecs-numbers-2.0.0-rc.1.tgz#f34978ddf7ea4016af3aaed5f7577c1d9869a614"
+ integrity sha512-J5i5mOkvukXn8E3Z7sGIPxsThRCgSdgTWJDQeZvucQ9PT6Y3HiVXJ0pcWiOWAoQ3RX8e/f4I3IC+wE6pZiJzDQ==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-rc.1"
+ "@solana/errors" "2.0.0-rc.1"
+
+"@solana/codecs-strings@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-preview.4.tgz#4d06bb722a55a5d04598d362021bfab4bd446760"
+ integrity sha512-YDbsQePRWm+xnrfS64losSGRg8Wb76cjK1K6qfR8LPmdwIC3787x9uW5/E4icl/k+9nwgbIRXZ65lpF+ucZUnw==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-preview.4"
+ "@solana/codecs-numbers" "2.0.0-preview.4"
+ "@solana/errors" "2.0.0-preview.4"
+
+"@solana/codecs-strings@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/codecs-strings/-/codecs-strings-2.0.0-rc.1.tgz#e1d9167075b8c5b0b60849f8add69c0f24307018"
+ integrity sha512-9/wPhw8TbGRTt6mHC4Zz1RqOnuPTqq1Nb4EyuvpZ39GW6O2t2Q7Q0XxiB3+BdoEjwA2XgPw6e2iRfvYgqty44g==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-rc.1"
+ "@solana/codecs-numbers" "2.0.0-rc.1"
+ "@solana/errors" "2.0.0-rc.1"
+
+"@solana/codecs@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-preview.4.tgz#a1923cc78a6f64ebe656c7ec6335eb6b70405b22"
+ integrity sha512-gLMupqI4i+G4uPi2SGF/Tc1aXcviZF2ybC81x7Q/fARamNSgNOCUUoSCg9nWu1Gid6+UhA7LH80sWI8XjKaRog==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-preview.4"
+ "@solana/codecs-data-structures" "2.0.0-preview.4"
+ "@solana/codecs-numbers" "2.0.0-preview.4"
+ "@solana/codecs-strings" "2.0.0-preview.4"
+ "@solana/options" "2.0.0-preview.4"
+
+"@solana/codecs@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/codecs/-/codecs-2.0.0-rc.1.tgz#146dc5db58bd3c28e04b4c805e6096c2d2a0a875"
+ integrity sha512-qxoR7VybNJixV51L0G1RD2boZTcxmwUWnKCaJJExQ5qNKwbpSyDdWfFJfM5JhGyKe9DnPVOZB+JHWXnpbZBqrQ==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-rc.1"
+ "@solana/codecs-data-structures" "2.0.0-rc.1"
+ "@solana/codecs-numbers" "2.0.0-rc.1"
+ "@solana/codecs-strings" "2.0.0-rc.1"
+ "@solana/options" "2.0.0-rc.1"
+
+"@solana/errors@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-preview.4.tgz#056ba76b6dd900dafa70117311bec3aef0f5250b"
+ integrity sha512-kadtlbRv2LCWr8A9V22On15Us7Nn8BvqNaOB4hXsTB3O0fU40D1ru2l+cReqLcRPij4znqlRzW9Xi0m6J5DIhA==
+ dependencies:
+ chalk "^5.3.0"
+ commander "^12.1.0"
+
+"@solana/errors@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/errors/-/errors-2.0.0-rc.1.tgz#3882120886eab98a37a595b85f81558861b29d62"
+ integrity sha512-ejNvQ2oJ7+bcFAYWj225lyRkHnixuAeb7RQCixm+5mH4n1IA4Qya/9Bmfy5RAAHQzxK43clu3kZmL5eF9VGtYQ==
+ dependencies:
+ chalk "^5.3.0"
+ commander "^12.1.0"
+
+"@solana/options@2.0.0-preview.4":
+ version "2.0.0-preview.4"
+ resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-preview.4.tgz#212d35d1da87c7efb13de4d3569ad9eb070f013d"
+ integrity sha512-tv2O/Frxql/wSe3jbzi5nVicIWIus/BftH+5ZR+r9r3FO0/htEllZS5Q9XdbmSboHu+St87584JXeDx3xm4jaA==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-preview.4"
+ "@solana/codecs-data-structures" "2.0.0-preview.4"
+ "@solana/codecs-numbers" "2.0.0-preview.4"
+ "@solana/codecs-strings" "2.0.0-preview.4"
+ "@solana/errors" "2.0.0-preview.4"
+
+"@solana/options@2.0.0-rc.1":
+ version "2.0.0-rc.1"
+ resolved "https://registry.yarnpkg.com/@solana/options/-/options-2.0.0-rc.1.tgz#06924ba316dc85791fc46726a51403144a85fc4d"
+ integrity sha512-mLUcR9mZ3qfHlmMnREdIFPf9dpMc/Bl66tLSOOWxw4ml5xMT2ohFn7WGqoKcu/UHkT9CrC6+amEdqCNvUqI7AA==
+ dependencies:
+ "@solana/codecs-core" "2.0.0-rc.1"
+ "@solana/codecs-data-structures" "2.0.0-rc.1"
+ "@solana/codecs-numbers" "2.0.0-rc.1"
+ "@solana/codecs-strings" "2.0.0-rc.1"
+ "@solana/errors" "2.0.0-rc.1"
+
+"@solana/spl-token-group@^0.0.5":
+ version "0.0.5"
+ resolved "https://registry.yarnpkg.com/@solana/spl-token-group/-/spl-token-group-0.0.5.tgz#f955dcca782031c85e862b2b46878d1bb02db6c2"
+ integrity sha512-CLJnWEcdoUBpQJfx9WEbX3h6nTdNiUzswfFdkABUik7HVwSNA98u5AYvBVK2H93d9PGMOHAak2lHW9xr+zAJGQ==
+ dependencies:
+ "@solana/codecs" "2.0.0-preview.4"
+ "@solana/spl-type-length-value" "0.1.0"
+
+"@solana/spl-token-metadata@^0.1.3", "@solana/spl-token-metadata@^0.1.5":
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/@solana/spl-token-metadata/-/spl-token-metadata-0.1.5.tgz#91616470d6862ec6b762e6cfcf882b8a8a24b1e8"
+ integrity sha512-DSBlo7vjuLe/xvNn75OKKndDBkFxlqjLdWlq6rf40StnrhRn7TDntHGLZpry1cf3uzQFShqeLROGNPAJwvkPnA==
+ dependencies:
+ "@solana/codecs" "2.0.0-rc.1"
+ "@solana/spl-type-length-value" "0.1.0"
+
+"@solana/spl-token@^0.4.8":
+ version "0.4.8"
+ resolved "https://registry.yarnpkg.com/@solana/spl-token/-/spl-token-0.4.8.tgz#a84e4131af957fa9fbd2727e5fc45dfbf9083586"
+ integrity sha512-RO0JD9vPRi4LsAbMUdNbDJ5/cv2z11MGhtAvFeRzT4+hAGE/FUzRi0tkkWtuCfSIU3twC6CtmAihRp/+XXjWsA==
dependencies:
"@solana/buffer-layout" "^4.0.0"
"@solana/buffer-layout-utils" "^0.2.0"
+ "@solana/spl-token-group" "^0.0.5"
+ "@solana/spl-token-metadata" "^0.1.3"
+ buffer "^6.0.3"
+
+"@solana/spl-type-length-value@0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@solana/spl-type-length-value/-/spl-type-length-value-0.1.0.tgz#b5930cf6c6d8f50c7ff2a70463728a4637a2f26b"
+ integrity sha512-JBMGB0oR4lPttOZ5XiUGyvylwLQjt1CPJa6qQ5oM+MBCndfjz2TKKkw0eATlLLcYmq1jBVsNlJ2cD6ns2GR7lA==
+ dependencies:
buffer "^6.0.3"
"@solana/web3.js@^1.32.0", "@solana/web3.js@^1.68.0", "@solana/web3.js@^1.73.0":
@@ -1059,6 +1210,11 @@ chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
+chalk@^5.3.0:
+ version "5.3.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385"
+ integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz"
@@ -1202,6 +1358,11 @@ combined-stream@^1.0.8:
dependencies:
delayed-stream "~1.0.0"
+commander@^12.1.0:
+ version "12.1.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
+ integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
+
commander@^2.20.3:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
@@ -1934,18 +2095,6 @@ glob@^10.2.2:
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry "^1.10.1"
-glob@^7.0.0:
- version "7.2.3"
- resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
- integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
- dependencies:
- fs.realpath "^1.0.0"
- inflight "^1.0.4"
- inherits "2"
- minimatch "^3.1.1"
- once "^1.3.0"
- path-is-absolute "^1.0.0"
-
glob@^7.1.3, glob@^7.1.4:
version "7.1.7"
resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz"
@@ -2221,11 +2370,6 @@ inquirer@^8.2.4:
through "^2.3.6"
wrap-ansi "^6.0.1"
-interpret@^1.0.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
- integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
-
ip@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz"
@@ -2257,13 +2401,6 @@ is-core-module@^2.11.0, is-core-module@^2.5.0, is-core-module@^2.8.1:
dependencies:
has "^1.0.3"
-is-core-module@^2.13.0:
- version "2.13.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
- integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
- dependencies:
- has "^1.0.3"
-
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
@@ -2897,7 +3034,7 @@ minimatch@4.2.1:
dependencies:
brace-expansion "^1.1.7"
-minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
+minimatch@^3.0.4, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
@@ -2934,7 +3071,7 @@ minimist-options@4.1.0:
is-plain-obj "^1.1.0"
kind-of "^6.0.3"
-minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
+minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.8"
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
@@ -3824,13 +3961,6 @@ readdirp@~3.6.0:
dependencies:
picomatch "^2.2.1"
-rechoir@^0.6.2:
- version "0.6.2"
- resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
- integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==
- dependencies:
- resolve "^1.1.6"
-
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz"
@@ -3866,15 +3996,6 @@ resolve-from@^4.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
-resolve@^1.1.6:
- version "1.22.4"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
- integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
- dependencies:
- is-core-module "^2.13.0"
- path-parse "^1.0.7"
- supports-preserve-symlinks-flag "^1.0.0"
-
resolve@^1.10.0:
version "1.22.2"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz"
@@ -4011,23 +4132,6 @@ shebang-regex@^3.0.0:
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-shelljs@^0.8.5:
- version "0.8.5"
- resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
- integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
- dependencies:
- glob "^7.0.0"
- interpret "^1.0.0"
- rechoir "^0.6.2"
-
-shx@^0.3.4:
- version "0.3.4"
- resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02"
- integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==
- dependencies:
- minimist "^1.2.3"
- shelljs "^0.8.5"
-
signal-exit@3.0.7, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
@@ -4163,7 +4267,16 @@ ssri@^9.0.1:
dependencies:
minipass "^3.1.1"
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -4195,7 +4308,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -4598,7 +4718,7 @@ workerpool@6.2.0:
resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz"
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -4616,6 +4736,15 @@ wrap-ansi@^6.0.1:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"