diff --git a/.github/.ghaignore b/.github/.ghaignore index 1397efbc9..79b188e39 100644 --- a/.github/.ghaignore +++ b/.github/.ghaignore @@ -9,7 +9,7 @@ tools/shank-and-solita/native tokens/pda-mint-authority/native tokens/nft-minter/native tokens/transfer-tokens/native -tokens/spl-token-minter/native +tokens/token-minter/native tokens/create-token/native tokens/token-swap/anchor @@ -68,6 +68,6 @@ basics/transfer-sol/steel tokens/escrow/steel tokens/pda-mint-authority/steel -tokens/spl-token-minter/steel +tokens/token-minter/steel tokens/token-swap/steel tokens/transfer-tokens/steel \ No newline at end of file diff --git a/README.md b/README.md index cba68d679..3a15fef95 100644 --- a/README.md +++ b/README.md @@ -153,11 +153,11 @@ Create an NFT collection, mint NFTs, and verify NFTs as part of a collection usi [⚓ Anchor](./tokens/nft-operations/anchor) [💫 Quasar](./tokens/nft-operations/quasar) -### SPL Token Minter +### Token Minter -Mint tokens from inside your own program using the Token program. +Mint tokens from inside your own program using the Classic Token Program. -[⚓ Anchor](./tokens/spl-token-minter/anchor) [💫 Quasar](./tokens/spl-token-minter/quasar) [🦀 Native](./tokens/spl-token-minter/native) +[⚓ Anchor](./tokens/token-minter/anchor) [💫 Quasar](./tokens/token-minter/quasar) [🦀 Native](./tokens/token-minter/native) ### Transfer Tokens diff --git a/basics/close-account/anchor/README.md b/basics/close-account/anchor/README.md index 4da8bdb2c..030e494aa 100644 --- a/basics/close-account/anchor/README.md +++ b/basics/close-account/anchor/README.md @@ -1,30 +1,40 @@ -# Destroy an Account +# Close Account -1. A `PDA` is created using the [create_user.rs](programs/destroy-an-account/src/instructions/create_user.rs) instruction. +Two instruction handlers: `create_user` initializes a PDA `UserState` account, and `close_user` closes it and returns the rent to the user. + +1. `create_user` initializes the PDA with Anchor's `init` constraint: ```rust #[account( init, - seeds = [User::PREFIX.as_bytes(), user.key().as_ref()], payer = user, - space = User::SIZE, + space = UserState::DISCRIMINATOR.len() + UserState::INIT_SPACE, + seeds = [b"USER", user.key().as_ref()], bump, )] - pub user_account: Box>, + pub user_account: Account<'info, UserState>, ``` -2. The account is closed in [destroy_user.rs](programs/destroy-an-account/src/instructions/destroy_user.rs), using Anchor's `close` helper on the account info: + See [`programs/close-account/src/instructions/create_user.rs`](programs/close-account/src/instructions/create_user.rs). + +2. `close_user` closes the account using Anchor's `close` constraint, which returns lamports to the given account: ```rust - user_account.close(user.to_account_info())?; + #[account( + mut, + seeds = [b"USER", user.key().as_ref()], + bump = user_account.bump, + close = user, // close account and return lamports to user + )] + pub user_account: Account<'info, UserState>, ``` -3. The test [destroy-an-account.ts](tests/destroy-an-account.ts) verifies that the account is null both before creation and after closing, via `fetchNullable`: + See [`programs/close-account/src/instructions/close_user.rs`](programs/close-account/src/instructions/close_user.rs). - ```typescript - const userAccountBefore = await program.account.user.fetchNullable(userAccountAddress, "processed"); - assert.equal(userAccountBefore, null); - // ... - const userAccountAfter = await program.account.user.fetchNullable(userAccountAddress, "processed"); - assert.notEqual(userAccountAfter, null); - ``` +## Tests + +Tests live in [`programs/close-account/tests/test_close_account.rs`](programs/close-account/tests/test_close_account.rs) and run against litesvm. `Anchor.toml`'s `scripts.test` is `cargo test`, so `anchor test` builds the program and runs the Rust tests: + +```bash +anchor test +``` diff --git a/basics/cross-program-invocation/README.md b/basics/cross-program-invocation/README.md index a11c91245..2c05d4166 100644 --- a/basics/cross-program-invocation/README.md +++ b/basics/cross-program-invocation/README.md @@ -26,14 +26,22 @@ In the `lever` crate's `Cargo.toml`: no-entrypoint = [] ``` -Then, in the `hand` crate, import `lever` with that feature enabled: +In this example each crate also defines a `cpi` feature that depends on `no-entrypoint`, so callers can pick the more descriptive name: + +```toml +[features] +no-entrypoint = [] +cpi = ["no-entrypoint"] +``` + +Then, in the `hand` crate, import `lever` with the `cpi` feature enabled: ```toml [dependencies] -lever = { path = "../lever", features = ["no-entrypoint"] } +cross-program-invocatio-native-lever = { path = "../lever", features = ["cpi"] } ``` -In the `lever` crate, gate the `entrypoint!` macro on the feature being absent: +In the `lever` crate, gate the `entrypoint!` macro on the `no-entrypoint` feature being absent: ```rust #[cfg(not(feature = "no-entrypoint"))] diff --git a/compression/cnft-burn/anchor/README.md b/compression/cnft-burn/anchor/README.md index fb4da3b9e..ff90a49ac 100644 --- a/compression/cnft-burn/anchor/README.md +++ b/compression/cnft-burn/anchor/README.md @@ -4,19 +4,16 @@ An Anchor program that burns compressed NFTs (cNFTs) in your collection. The pro ## Components -- `programs/` — the Anchor program. -- `tests/` — tests for the program. +- `programs/cnft-burn/` — the Anchor program. +- `migrations/` — deployment script. -## Deployment +There is no `tests/` directory in this example today. The program is intended to be deployed and exercised against a real cluster. -The program is deployed on devnet at `FbeHkUEevbhKmdk5FE5orcTaJkCYn5drwZoZXaxQXXNn`. To deploy your own copy, change the program ID in `lib.rs` and `Anchor.toml`. +## Deployment -## How to run +The program ID declared in [`programs/cnft-burn/src/lib.rs`](programs/cnft-burn/src/lib.rs) is `C6qxH8n6mZxrrbtMtYWYSp8JR8vkQ55X1o4EBg7twnMv`. Whether this address is currently deployed on any cluster is not tracked in this repo — verify with `solana program show ` against the cluster you care about. -1. Configure the RPC endpoint in `cnft-burn.ts`. -2. `anchor build` from the example root. -3. `anchor deploy` to deploy to your chosen cluster. -4. `pnpm test` to run the tests. +To deploy your own copy, change the program ID in `lib.rs` and `Anchor.toml`, then run `anchor build && anchor deploy`. ## Acknowledgements diff --git a/compression/cnft-vault/anchor/README.md b/compression/cnft-vault/anchor/README.md index 894e9f9ab..47451153a 100644 --- a/compression/cnft-vault/anchor/README.md +++ b/compression/cnft-vault/anchor/README.md @@ -13,13 +13,15 @@ Use this as a reference for working with cNFTs in your own programs. ## Components -- `programs/` — the Anchor program. -- `tests/` — TypeScript client-side tests. -- `tests/scripts/` — standalone scripts you can run individually. `withdrawWithLookup.ts` demonstrates using the program with Address Lookup Tables. +- `programs/cnft-vault/` — the Anchor program. + +There is no `tests/` directory in this example today. The program is intended to be deployed and exercised against a real cluster. ## Deployment -Deployed on devnet at `CNftyK7T8udPwYRzZUMWzbh79rKrz9a5GwV2wv7iEHpk`. To deploy your own, change the program ID in `lib.rs` and `Anchor.toml`. +The program ID declared in [`programs/cnft-vault/src/lib.rs`](programs/cnft-vault/src/lib.rs) is `Fd4iwpPWaCU8BNwGQGtvvrcvG4Tfizq3RgLm8YLBJX6D`. Whether this address is currently deployed on any cluster is not tracked in this repo — verify with `solana program show ` against the cluster you care about. + +To deploy your own copy, change the program ID in `lib.rs` and `Anchor.toml`, then run `anchor build && anchor deploy`. ## Limitations diff --git a/compression/cutils/anchor/README.md b/compression/cutils/anchor/README.md index 373206718..d46460fa0 100644 --- a/compression/cutils/anchor/README.md +++ b/compression/cutils/anchor/README.md @@ -11,34 +11,20 @@ Use this as a reference for working with cNFTs in your own programs. ## Components -- `programs/` — the Anchor program. The setup uses a `validate`/`actuate` pattern via Anchor's `access_control` macro; this pairs well with the cNFT verification logic. -- `tests/` — TypeScript tests. - - `setup.ts` — run first if you don't already have a collection with a merkle tree. - - `tests.ts` — individual minting and verification tests. +- `programs/cutils/` — the Anchor program. The setup uses a `validate`/`actuate` pattern via Anchor's `access_control` macro; this pairs well with the cNFT verification logic. + +There is no `tests/` directory in this example today. The program is intended to be deployed and exercised against a real cluster. ## Deployment -Deployed on devnet at `burZc1SfqbrAP35XG63YZZ82C9Zd22QUwhCXoEUZWNF`. To deploy your own, change the program ID in `lib.rs` and `Anchor.toml`. +The program ID declared in [`programs/cutils/src/lib.rs`](programs/cutils/src/lib.rs) is `BuFyrgRYzg2nPhqYrxZ7d9uYUs4VXtxH71U8EcoAfTQZ`. Whether this address is currently deployed on any cluster is not tracked in this repo — verify with `solana program show ` against the cluster you care about. + +To deploy your own copy, change the program ID in `lib.rs` and `Anchor.toml`, then run `anchor build && anchor deploy`. ## Limitations Reference implementation only. -**This example pins Anchor 0.26.0** because of mpl-bubblegum dependency constraints at the time of writing. - -## How to run - -1. Configure the RPC endpoint in `utils/readAPI.ts`. -2. `cd` to the example root. -3. `pnpm install`. -4. (Optional) `npx tsx tests/setup.ts` to create an NFT collection and its merkle tree. -5. Comment out the tests you don't want to run in `tests/tests.ts`. -6. If minting, set your NFT URI. -7. If verifying, set the asset ID (cNFT mint address) you want to verify. -8. Run `anchor test --skip-build --skip-deploy --skip-local-validator`. -9. View your cNFTs on devnet via the Solflare wallet. -10. You may also want to change the wallet path in `Anchor.toml`. - ## Acknowledgements - [@nickfrosty](https://twitter.com/nickfrosty) for the sample code and [live demo](https://youtu.be/LxhTxS9DexU). diff --git a/tokens/create-token/README.md b/tokens/create-token/README.md index 058d25e59..ed85237f6 100644 --- a/tokens/create-token/README.md +++ b/tokens/create-token/README.md @@ -1,12 +1,12 @@ -# Create an SPL Token +# Create a Token -Create an SPL Token on Solana with metadata such as a symbol and an icon. +Create a token on Solana with metadata such as a symbol and an icon. -All tokens on Solana — including NFTs — are SPL Tokens. They follow the SPL Token standard (similar in spirit to ERC-20). +All fungible assets and NFTs on Solana are tokens. They follow the Classic Token Program standard (similar in spirit to ERC-20), or the newer Token Extensions standard. ```text -Default SPL Tokens : 9 decimals -NFTs : 0 decimals +Typical fungible tokens : 9 decimals +NFTs : 0 decimals ``` ## How decimals work @@ -19,7 +19,7 @@ For a token JOE with 9 decimals: ## Mint and metadata -An SPL Token is represented onchain by a **Mint Account**: +A token is represented onchain by a **Mint Account**: ```typescript { @@ -41,9 +41,11 @@ Metadata about a mint — name, symbol, image URI — lives in a separate **Meta } ``` -> Metaplex is the de facto standard for SPL Token metadata on Solana. The [Metaplex Token Metadata Program](https://docs.metaplex.com/) is what creates these metadata accounts. +> Metaplex is the de facto standard for token metadata on Solana with the Classic Token Program. The [Metaplex Token Metadata Program](https://docs.metaplex.com/) creates these metadata accounts. +> +> Tokens using the Token Extensions metadata extension store metadata directly on the mint and don't need a separate Metaplex account. -## Steps to create an SPL Token +## Steps to create a token 1. Create an account for the mint. 2. Initialize that account as a Mint Account. diff --git a/tokens/create-token/quasar/src/lib.rs b/tokens/create-token/quasar/src/lib.rs index 6932eca26..223aa4d6b 100644 --- a/tokens/create-token/quasar/src/lib.rs +++ b/tokens/create-token/quasar/src/lib.rs @@ -11,7 +11,7 @@ declare_id!("22222222222222222222222222222222222222222222"); /// Creates a token mint and mints initial tokens to the creator's token account. /// /// The Anchor version uses Metaplex for onchain metadata. Quasar's metadata -/// crate is demonstrated in the `nft-minter` and `spl-token-minter` examples; +/// crate is demonstrated in the `nft-minter` and `token-minter` examples; /// this example focuses on the core SPL Token operations: creating a mint and /// minting tokens. #[program] diff --git a/tokens/nft-minter/README.md b/tokens/nft-minter/README.md index 8ccaa1135..9582cf121 100644 --- a/tokens/nft-minter/README.md +++ b/tokens/nft-minter/README.md @@ -1,8 +1,8 @@ # NFT Minter -Minting NFTs is the same as [minting any SPL Token on Solana](../spl-token-minter/), with one extra step at the end. +Minting NFTs is the same as [minting any token on Solana](../token-minter/), with one extra step at the end. -When you mint SPL Tokens, you can in most cases continue to mint more tokens later, growing the supply. An NFT is supposed to have a supply of **one**. So we need to make sure no more can ever be minted. +When you mint tokens, you can in most cases continue to mint more later, growing the supply. An NFT is supposed to have a supply of **one**, so no more can ever be minted. The way to do that is to remove the mint authority from the mint: diff --git a/tokens/nft-operations/anchor/README.md b/tokens/nft-operations/anchor/README.md index 70978d39a..199c74414 100644 --- a/tokens/nft-operations/anchor/README.md +++ b/tokens/nft-operations/anchor/README.md @@ -91,103 +91,86 @@ When an account *is* already initialized (as in the verify-collection flow below ### Implementation for `CreateCollection` +Each instruction handler is a free function (`pub fn handler(accounts: &mut X, bumps: &XBumps)`) called from the `#[program]` module in `lib.rs`. The account-validation struct lives in the same file as the handler. + ```rust -impl<'info> CreateCollection<'info> { - pub fn create_collection(&mut self, bumps: &CreateCollectionBumps) -> Result<()> { - - let metadata = &self.metadata.to_account_info(); - let master_edition = &self.master_edition.to_account_info(); - let mint = &self.mint.to_account_info(); - let authority = &self.mint_authority.to_account_info(); - let payer = &self.user.to_account_info(); - let system_program = &self.system_program.to_account_info(); - let spl_token_program = &self.token_program.to_account_info(); - let spl_metadata_program = &self.token_metadata_program.to_account_info(); - - let seeds = &[ - &b"authority"[..], - &[bumps.mint_authority] - ]; - let signer_seeds = &[&seeds[..]]; - - let cpi_program = self.token_program.to_account_info(); - let cpi_accounts = MintTo { - mint: self.mint.to_account_info(), - to: self.destination.to_account_info(), - authority: self.mint_authority.to_account_info(), - }; - let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); - mint_to(cpi_ctx, 1)?; - msg!("Collection NFT minted!"); - - let creator = vec![ - Creator { - address: self.mint_authority.key().clone(), - verified: true, - share: 100, - }, - ]; - - let metadata_account = CreateMetadataAccountV3Cpi::new( - spl_metadata_program, - CreateMetadataAccountV3CpiAccounts { - metadata, - mint, - mint_authority: authority, - payer, - update_authority: (authority, true), - system_program, - rent: None, - }, - CreateMetadataAccountV3InstructionArgs { - data: DataV2 { - name: "DummyCollection".to_owned(), - symbol: "DC".to_owned(), - uri: "".to_owned(), - seller_fee_basis_points: 0, - creators: Some(creator), - collection: None, - uses: None, - }, - is_mutable: true, - collection_details: Some( - CollectionDetails::V1 { - size: 0 - } - ) - } - ); - metadata_account.invoke_signed(signer_seeds)?; - msg!("Metadata Account created!"); - - let master_edition_account = CreateMasterEditionV3Cpi::new( - spl_metadata_program, - CreateMasterEditionV3CpiAccounts { - edition: master_edition, - update_authority: authority, - mint_authority: authority, - mint, - payer, - metadata, - token_program: spl_token_program, - system_program, - rent: None, +pub fn handler(accounts: &mut CreateCollection, bumps: &CreateCollectionBumps) -> Result<()> { + + let metadata = &accounts.metadata.to_account_info(); + let master_edition = &accounts.master_edition.to_account_info(); + let mint = &accounts.mint.to_account_info(); + let authority = &accounts.mint_authority.to_account_info(); + let payer = &accounts.user.to_account_info(); + let system_program = &accounts.system_program.to_account_info(); + let spl_token_program = &accounts.token_program.to_account_info(); + let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + + let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; + let signer_seeds = &[&seeds[..]]; + + let cpi_accounts = MintTo { + mint: accounts.mint.to_account_info(), + to: accounts.destination.to_account_info(), + authority: accounts.mint_authority.to_account_info(), + }; + let cpi_ctx = CpiContext::new_with_signer(accounts.token_program.key(), cpi_accounts, signer_seeds); + mint_to(cpi_ctx, 1)?; + msg!("Collection NFT minted!"); + + let creator = vec![Creator { + address: accounts.mint_authority.key().clone(), + verified: true, + share: 100, + }]; + + let metadata_account = CreateMetadataAccountV3Cpi::new( + spl_metadata_program, + CreateMetadataAccountV3CpiAccounts { + metadata, mint, mint_authority: authority, payer, + update_authority: (authority, true), + system_program, + rent: None, + }, + CreateMetadataAccountV3InstructionArgs { + data: DataV2 { + name: "DummyCollection".to_owned(), + symbol: "DC".to_owned(), + uri: "".to_owned(), + seller_fee_basis_points: 0, + creators: Some(creator), + collection: None, + uses: None, }, - CreateMasterEditionV3InstructionArgs { - max_supply: Some(0), - } - ); - master_edition_account.invoke_signed(signer_seeds)?; - msg!("Master Edition Account created"); - - Ok(()) - } + is_mutable: true, + collection_details: Some(CollectionDetails::V1 { size: 0 }), + }, + ); + metadata_account.invoke_signed(signer_seeds)?; + msg!("Metadata Account created!"); + + let master_edition_account = CreateMasterEditionV3Cpi::new( + spl_metadata_program, + CreateMasterEditionV3CpiAccounts { + edition: master_edition, + update_authority: authority, + mint_authority: authority, + mint, payer, metadata, + token_program: spl_token_program, + system_program, + rent: None, + }, + CreateMasterEditionV3InstructionArgs { max_supply: Some(0) }, + ); + master_edition_account.invoke_signed(signer_seeds)?; + msg!("Master Edition Account created"); + + Ok(()) } ``` Three steps: -1. Mint one token to the destination token account via a CPI to the Token Program. +1. Mint one token to the destination token account via a CPI to the Classic Token Program. 2. Create a metadata account for the mint via a CPI to the Token Metadata program. The mint authority signs the CPI, so we use `invoke_signed` with the authority PDA's seeds. 3. Create a master edition account for the mint via a CPI to the Token Metadata program. This enforces the NFT-specific constraints and transfers both the mint authority and freeze authority to the Master Edition PDA. Again, the mint authority signs. @@ -256,100 +239,84 @@ That's where the `collection` account comes from — it provides the address tha ### Implementation for `MintNFT` ```rust -impl<'info> MintNFT<'info> { - pub fn mint_nft(&mut self, bumps: &MintNFTBumps) -> Result<()> { - - let metadata = &self.metadata.to_account_info(); - let master_edition = &self.master_edition.to_account_info(); - let mint = &self.mint.to_account_info(); - let authority = &self.mint_authority.to_account_info(); - let payer = &self.owner.to_account_info(); - let system_program = &self.system_program.to_account_info(); - let spl_token_program = &self.token_program.to_account_info(); - let spl_metadata_program = &self.token_metadata_program.to_account_info(); - - let seeds = &[ - &b"authority"[..], - &[bumps.mint_authority] - ]; - let signer_seeds = &[&seeds[..]]; - - let cpi_program = self.token_program.to_account_info(); - let cpi_accounts = MintTo { - mint: self.mint.to_account_info(), - to: self.destination.to_account_info(), - authority: self.mint_authority.to_account_info(), - }; - let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); - mint_to(cpi_ctx, 1)?; - msg!("Collection NFT minted!"); - - let creator = vec![ - Creator { - address: self.mint_authority.key(), - verified: true, - share: 100, +pub fn handler(accounts: &mut MintNFT, bumps: &MintNFTBumps) -> Result<()> { + + let metadata = &accounts.metadata.to_account_info(); + let master_edition = &accounts.master_edition.to_account_info(); + let mint = &accounts.mint.to_account_info(); + let authority = &accounts.mint_authority.to_account_info(); + let payer = &accounts.owner.to_account_info(); + let system_program = &accounts.system_program.to_account_info(); + let spl_token_program = &accounts.token_program.to_account_info(); + let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + + let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; + let signer_seeds = &[&seeds[..]]; + + let cpi_accounts = MintTo { + mint: accounts.mint.to_account_info(), + to: accounts.destination.to_account_info(), + authority: accounts.mint_authority.to_account_info(), + }; + let cpi_ctx = CpiContext::new_with_signer(accounts.token_program.key(), cpi_accounts, signer_seeds); + mint_to(cpi_ctx, 1)?; + msg!("Collection NFT minted!"); + + let creator = vec![Creator { + address: accounts.mint_authority.key(), + verified: true, + share: 100, + }]; + + let metadata_account = CreateMetadataAccountV3Cpi::new( + spl_metadata_program, + CreateMetadataAccountV3CpiAccounts { + metadata, mint, mint_authority: authority, payer, + update_authority: (authority, true), + system_program, + rent: None, + }, + CreateMetadataAccountV3InstructionArgs { + data: DataV2 { + name: "Mint Test".to_string(), + symbol: "YAY".to_string(), + uri: "".to_string(), + seller_fee_basis_points: 0, + creators: Some(creator), + collection: Some(Collection { + verified: false, + key: accounts.collection_mint.key(), + }), + uses: None, }, - ]; - - let metadata_account = CreateMetadataAccountV3Cpi::new( - spl_metadata_program, - CreateMetadataAccountV3CpiAccounts { - metadata, - mint, - mint_authority: authority, - payer, - update_authority: (authority, true), - system_program, - rent: None, - }, - CreateMetadataAccountV3InstructionArgs { - data: DataV2 { - name: "Mint Test".to_string(), - symbol: "YAY".to_string(), - uri: "".to_string(), - seller_fee_basis_points: 0, - creators: Some(creator), - collection: Some(Collection { - verified: false, - key: self.collection_mint.key(), - }), - uses: None - }, - is_mutable: true, - collection_details: None, - } - ); - metadata_account.invoke_signed(signer_seeds)?; - - let master_edition_account = CreateMasterEditionV3Cpi::new( - spl_metadata_program, - CreateMasterEditionV3CpiAccounts { - edition: master_edition, - update_authority: authority, - mint_authority: authority, - mint, - payer, - metadata, - token_program: spl_token_program, - system_program, - rent: None, - }, - CreateMasterEditionV3InstructionArgs { - max_supply: Some(0), - } - ); - master_edition_account.invoke_signed(signer_seeds)?; - - Ok(()) - - } + is_mutable: true, + collection_details: None, + }, + ); + metadata_account.invoke_signed(signer_seeds)?; + + let master_edition_account = CreateMasterEditionV3Cpi::new( + spl_metadata_program, + CreateMasterEditionV3CpiAccounts { + edition: master_edition, + update_authority: authority, + mint_authority: authority, + mint, payer, metadata, + token_program: spl_token_program, + system_program, + rent: None, + }, + CreateMasterEditionV3InstructionArgs { max_supply: Some(0) }, + ); + master_edition_account.invoke_signed(signer_seeds)?; + + Ok(()) } ``` Because a collection NFT is just a regular NFT with special metadata, the implementation mirrors `CreateCollection`. The same three steps: -1. Mint one token to the destination via a Token Program CPI. +1. Mint one token to the destination via a Classic Token Program CPI. 2. Create a metadata account via a Token Metadata CPI (signed with the PDA seeds). 3. Create a master edition account via a Token Metadata CPI (signed with the PDA seeds). @@ -420,7 +387,7 @@ pub struct VerifyCollectionMint<'info> { pub collection_metadata: Account<'info, MetadataAccount>, pub collection_master_edition: Account<'info, MasterEditionAccount>, pub system_program: Program<'info, System>, - #[account(address = INSTRUCTIONS_ID)] + #[account(address = INSTRUCTIONS_SYSVAR_ID)] /// CHECK: Sysvar instruction account that is being checked with an address constraint pub sysvar_instruction: UncheckedAccount<'info>, pub token_metadata_program: Program<'info, Metadata>, @@ -445,45 +412,41 @@ Only the NFT and collection NFT metadata accounts need to be mutable — both ar ### Implementation for `VerifyCollectionMint` ```rust -impl<'info> VerifyCollectionMint<'info> { - pub fn verify_collection(&mut self, bumps: &VerifyCollectionMintBumps) -> Result<()> { - let metadata = &self.metadata.to_account_info(); - let authority = &self.mint_authority.to_account_info(); - let collection_mint = &self.collection_mint.to_account_info(); - let collection_metadata = &self.collection_metadata.to_account_info(); - let collection_master_edition = &self.collection_master_edition.to_account_info(); - let system_program = &self.system_program.to_account_info(); - let sysvar_instructions = &self.sysvar_instruction.to_account_info(); - let spl_metadata_program = &self.token_metadata_program.to_account_info(); - - let seeds = &[ - &b"authority"[..], - &[bumps.mint_authority] - ]; - let signer_seeds = &[&seeds[..]]; - - let verify_collection = VerifyCollectionV1Cpi::new( - spl_metadata_program, - VerifyCollectionV1CpiAccounts { - authority, - delegate_record: None, - metadata, - collection_mint, - collection_metadata: Some(collection_metadata), - collection_master_edition: Some(collection_master_edition), - system_program, - sysvar_instructions, - } - ); - verify_collection.invoke_signed(signer_seeds)?; - - msg!("Collection Verified!"); - - Ok(()) - } +pub fn handler(accounts: &mut VerifyCollectionMint, bumps: &VerifyCollectionMintBumps) -> Result<()> { + let metadata = &accounts.metadata.to_account_info(); + let authority = &accounts.mint_authority.to_account_info(); + let collection_mint = &accounts.collection_mint.to_account_info(); + let collection_metadata = &accounts.collection_metadata.to_account_info(); + let collection_master_edition = &accounts.collection_master_edition.to_account_info(); + let system_program = &accounts.system_program.to_account_info(); + let sysvar_instructions = &accounts.sysvar_instruction.to_account_info(); + let spl_metadata_program = &accounts.token_metadata_program.to_account_info(); + + let seeds = &[&b"authority"[..], &[bumps.mint_authority]]; + let signer_seeds = &[&seeds[..]]; + + let verify_collection = VerifyCollectionV1Cpi::new( + spl_metadata_program, + VerifyCollectionV1CpiAccounts { + authority, + delegate_record: None, + metadata, + collection_mint, + collection_metadata: Some(collection_metadata), + collection_master_edition: Some(collection_master_edition), + system_program, + sysvar_instructions, + }, + ); + verify_collection.invoke_signed(signer_seeds)?; + + msg!("Collection Verified!"); + Ok(()) } ``` +> `INSTRUCTIONS_SYSVAR_ID` is the well-known sysvar address `Sysvar1nstructions1111111111111111111111111`, defined directly in [`verify_collection.rs`](programs/mint-nft/src/instructions/verify_collection.rs) because `sysvar::instructions::ID` moved in Anchor 1.0. + `verify_collection` performs a CPI to the Token Metadata program with the right accounts. The collection NFT's mint authority signs the CPI, and the NFT is verified as part of the collection. Use this as a starting point for your own collections, NFTs, and verification flows. diff --git a/tokens/token-extensions/default-account-state/native/README.md b/tokens/token-extensions/default-account-state/native/README.md index 32555eeb6..705ceaa52 100644 --- a/tokens/token-extensions/default-account-state/native/README.md +++ b/tokens/token-extensions/default-account-state/native/README.md @@ -1,4 +1,4 @@ -# Token-2022 with Default Account State +# Token Extensions — Default Account State This extension sets a default state for all token accounts of a given mint. diff --git a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/README.md b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/README.md index be73fc0c1..ac1221e2c 100644 --- a/tokens/token-extensions/nft-meta-data-pointer/anchor-example/README.md +++ b/tokens/token-extensions/nft-meta-data-pointer/anchor-example/README.md @@ -1,6 +1,6 @@ -# Token Extension Metadata-Pointer NFT +# Token Extensions Metadata-Pointer NFT -An Anchor program that mints an NFT using the Token-2022 metadata-pointer extension. The mint itself stores its own metadata via the metadata extension, so no separate Metaplex metadata account is needed. +An Anchor program that mints an NFT using the Token Extensions metadata-pointer extension. The mint itself stores its own metadata via the metadata extension, so no separate Metaplex metadata account is needed. This is particularly useful for games — you get arbitrary key/value metadata stored onchain that you can use to record character state. In this example, the player's level and collected wood are stored on the NFT. @@ -39,7 +39,7 @@ Creating an NFT this way: 7. Mint one token to the ATA. 8. Remove the mint authority — irreversible, makes it an NFT. -See `programs/extension-nft/src/instructions/mint_nft.rs` for the Rust implementation. +See `programs/extension_nft/src/instructions/mint_nft.rs` for the Rust implementation. ## Energy system (example onchain game) @@ -47,7 +47,7 @@ The program includes a simple energy system: a player initializes a `PlayerData` ```rust const TIME_TO_REFILL_ENERGY: i64 = 60; // seconds per energy point -const MAX_ENERGY: u64 = 10; +const MAX_ENERGY: u64 = 100; ``` The JS client subscribes to the player account via WebSocket and runs the same energy calculation locally to show a countdown timer. @@ -55,11 +55,12 @@ The JS client subscribes to the player account via WebSocket and runs the same e ## Project structure ```text -anchor/programs/extension-nft/src/ +anchor/programs/extension_nft/src/ ├── instructions/ │ ├── chop_tree.rs │ ├── init_player.rs -│ └── update_energy.rs +│ ├── mint_nft.rs +│ └── mod.rs ├── state/ │ ├── game_data.rs │ ├── mod.rs @@ -69,6 +70,8 @@ anchor/programs/extension-nft/src/ └── lib.rs ``` +`PlayerData::update_energy` (in `state/player_data.rs`) is where the lazy refill is computed; there is no separate `update_energy.rs` instruction handler. + ## Session keys The example uses [Gum session keys](https://github.com/magicblock-labs/session-keys) to auto-approve transactions: a local keypair is topped up with a small amount of SOL and is allowed to sign specific program instructions for a limited window (currently 23h). When it expires, the SOL is returned and a new session can be created. diff --git a/tokens/token-extensions/transfer-hook/allow-block-list-token/README.md b/tokens/token-extensions/transfer-hook/allow-block-list-token/README.md index d17dc5520..f58862726 100644 --- a/tokens/token-extensions/transfer-hook/allow-block-list-token/README.md +++ b/tokens/token-extensions/transfer-hook/allow-block-list-token/README.md @@ -1,6 +1,6 @@ # Allow/Block-List Token -A Token-2022 example that gates transfers through an allow/block list managed by a separate authority. The list is consumed by a transfer hook. +A Token Extensions example that gates transfers through an allow/block list managed by a separate authority. The list is consumed by a transfer hook. One list authority can manage lists for many mints — useful when an issuer wants a third-party-managed list or wants to share a single list across a set of assets. diff --git a/tokens/token-extensions/transfer-hook/block-list/readme.md b/tokens/token-extensions/transfer-hook/block-list/readme.md index 926b40195..32d952447 100644 --- a/tokens/token-extensions/transfer-hook/block-list/readme.md +++ b/tokens/token-extensions/transfer-hook/block-list/readme.md @@ -1,146 +1,143 @@ -# Block List +# Block List -This is a Block List program that implements the Token2022 Transfer-hook execute instruction. -It allows a centralized authority to defined a block list - a collection of wallets that are blocked. -Token issuers (transfer-hook extension authorities), can then setup this program as the hook to be used and choose an operation mode (either filter source wallet, or both source and destination). +A block-list program that implements the Token Extensions transfer-hook `execute` instruction. -## Operation Mode +A central authority maintains a block list — a collection of blocked wallets. Token issuers (transfer-hook extension authorities) can wire this program in as their hook and choose an operation mode: filter the source wallet only, or both source and destination. -The Block list has different operation modes depending whether the block list is empty or not and the issuer choice. These modes are achieved by building a different `extra-account-metas` account for the token mint (see `setup_extra_metas` bellow). When the list gets the first blocked wallet, the issuer needs to re-set the `extra-account-metas`. -The modes are the following: -- Empty extra metas - default behaviour when config account counter is 0 -- Check Source - default behaviour when config account counter is above 0 -- Check both source and destination - optional behaviour when config account counter is above 0 +## Operation modes -## Accounts +The mode depends on whether the block list is empty, plus the issuer's choice. Each mode corresponds to a different `extra-account-metas` account built for the mint (see `setup_extra_metas` below). When the list goes from empty to non-empty, the issuer must call `setup_extra_metas` again. -### Config -- Defines the block list authority. -- Tracks the number of blocked wallets. +- **Empty extra metas** — default when the config counter is 0. +- **Check source** — default when the config counter is > 0. +- **Check both source and destination** — optional behavior when the counter is > 0. + +## Accounts -### WalletBlock -- Defines a wallet as blocked +### `Config` -## Instructions +- Defines the block-list authority. +- Tracks the number of blocked wallets. -### init +### `WalletBlock` -Initializes the global `Config` account with a given authority to control the block list. +- Marks a single wallet as blocked. -### block_wallet +## Instruction handlers -Adds a given wallet address to the blocked wallets. This creates a `WalletBlock` reccord account. +The program dispatches on a one-byte discriminator in `process_instruction` ([`program/src/lib.rs`](pinocchio/program/src/lib.rs)): -### unblock_wallet +### `Init` -Removes a given wallet address from the blocked wallets. This removes a `WalletBlock` reccord account. +Initializes the global `Config` account with an authority. -### setup_extra_metas +### `BlockWallet` -Sets up the `extra-account-metas` account dependency for the Transfer-Hook extension. Receives an optional bool value to switch operation modes when the blocked wallet counter is non zero. -Note: once wallets are added to the block list, the issuer needs to call this method again to setup one of the blocking modes. +Adds a wallet to the block list, creating a `WalletBlock` record. -### tx_hook +### `UnblockWallet` -The hook that is executed during token transfers. +Removes a wallet from the block list, closing its `WalletBlock` record. -## Repo contents +### `SetupExtraMetas` -### Smart Contract +Sets up the `extra-account-metas` account that the transfer-hook extension depends on. Takes an optional bool to switch operation modes when the counter is non-zero. -A pinocchio based Block List smart contract under the [pinocchio/program](pinocchio/program/) folder. +Once wallets are added to the block list, the issuer must call this again to pick one of the blocking modes. -### SDKs +### `TxHook` -Codama generated rust and ts [SDKs](pinocchio/sdk/). +The hook invoked during token transfers. -### CLI +## Repository layout -A rust CLI to interact with the contract. +- **Program:** a Pinocchio-based block list under [`pinocchio/program/`](pinocchio/program/). +- **SDKs:** Codama-generated Rust and TypeScript SDKs under [`pinocchio/sdk/`](pinocchio/sdk/). +- **CLI:** a Rust CLI to interact with the program, under [`pinocchio/cli/`](pinocchio/cli/). ## Building -All commands below should be run from the [pinocchio](pinocchio/) directory. +All commands below should be run from the [`pinocchio/`](pinocchio/) directory. -First install dependencies: -``` +Install dependencies: + +```bash cd pinocchio pnpm install ``` -To build the smart contract: -``` +Build the program: + +```bash cd program cargo build-sbf ``` -To deploy the smart contract: -``` +Deploy it: + +```bash solana program deploy --program-id target/deploy/block_list.so ``` -To generate the SDKs: -``` +Generate the SDKs: + +```bash pnpm run generate-sdks ``` -To build the CLI: -``` +Build the CLI: + +```bash cd cli cargo build ``` ## Setup -### Block List +### Block list -Initialize the block list and defined the authority: -``` +Initialize the list and set the authority: + +```bash target/debug/block-list-cli init ``` -Add a wallet to the block list: -``` +Add a wallet: + +```bash target/debug/block-list-cli block-wallet ``` -Remove a wallet from the block list: -``` +Remove a wallet: + +```bash target/debug/block-list-cli unblock-wallet ``` +### Token mint -### Token Mint +The `spl-token` CLI references the Token Extensions program as `--program-2022`. Create a new mint with the hook wired up: -Initialize a new token mint: -``` +```bash spl-token create-token --program-2022 --transfer-hook BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf ``` Initialize the extra account metas: -``` + +```bash target/debug/block-list-cli setup-extra-metas ``` -Change the extra account metas to filter both source and destination token account wallets: -``` +Switch to checking both source and destination wallets: + +```bash target/debug/block-list-cli setup-extra-metas --check-both-wallets ``` ## Devnet deployment -Smart contract was deployed to devnet at address `BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf`. - -Test transfer with empty block list [here](https://explorer.solana.com/tx/2EnQD5mFZvrR3EAyFamCfxJDS3yAtZQxNVhFtK46PanCgbX6rpvgcQ961ZAs8H3auawJZPaVZMpAxoj3qZK55mHT?cluster=devnet&customUrl=http%3A%2F%2Flocalhost%3A8899). - -Test transfer with non empty block list only checking source TA [here](https://explorer.solana.com/tx/4pmx31Lx5mXS7FWUtRjAxdRiwKZKCwJv3Du2qGhbLpQUenBuRxRUbrCaGGVjLjeDtpt4AXHzoNex1ppBsmKWSS7r?cluster=devnet&customUrl=http%3A%2F%2Flocalhost%3A8899). - -Test transfer with non empty block list checking both source and destination TAs [here](https://explorer.solana.com/tx/Q5Bk6GjGQ9TJtwS5zjDKp7GiFZK6efmGNCcxjqcmzf1YoZZJVE3rQkkSgSBNo7tst4hjUX6SJMsmEGXQ2NAdBjF?cluster=devnet&customUrl=http%3A%2F%2Flocalhost%3A8899). - -Simulated transaction that fails due to destination TA owner being blocked [here](https://explorer.solana.com/tx/inspector?cluster=devnet&signatures=%255B%25221111111111111111111111111111111111111111111111111111111111111111%2522%255D&message=AQAHCgqDBmqk%252FDMT5D9rK85EOwBVSTyxwkSJNDGhjodJl5A8fkyFjtMOw8TOzjiallL3mM8ylDy3Dmf4kPO6zjRCB5meTp%252FmYh4SPAIwzTHZRyKqrqiz%252FskDcCP4xKa5KaJaNQKmMSi6syOX%252BagX8jS6oj8o9glIci7jjFsFtVKThVTSAwZGb%252BUhFzL%252F7K26csOb57yM5bvF9xJrLEObOkAAAAC1QoHXoRYodtouw5cKbwI1AuPk%252BVWEpzwvoAzgkyTWD7vvmloKSuwS0IrUHLk7n0Yfp3DOKmgbjiyFpaYfufnS5xfqCyGJ%252BEpC8iKMH9T%252FdgnUADYw6SCHmevlcTztM6TwOn%252FMbMOP4VGXJKhkykzArfWQd9JuJlU%252B0GDnERJVAQbd9uHudY%252FeGEJdvORszdq2GvxNg7kNJ%252F69%252BSjYoYv8sm6yFK1CM9Gp2RvGj6wbHdQmQ4vCDR59WzHPZ5aOHbIDBAAJA9i4BQAAAAAABAAFAkANAwAJCQEIAgAABQcDBgoMAMqaOwAAAAAJ) (press simulate to see logs). - -Simulated transaction that fails due to source TA owner being blocked [here](https://explorer.solana.com/tx/inspector?cluster=devnet&signatures=%255B%25221111111111111111111111111111111111111111111111111111111111111111%2522%255D&message=AQAHCrod5ZzEG06%252BJzr8OnDqiGNK2oQt0Rghykcx3Sw51mE4cZQ%252BDFc%252BtWThZi0XGFuhfdEKDoUp3bkLE8gIYc3DR2N%252BTIWO0w7DxM7OOJqWUveYzzKUPLcOZ%252FiQ87rONEIHmQKmMSi6syOX%252BagX8jS6oj8o9glIci7jjFsFtVKThVTSAwZGb%252BUhFzL%252F7K26csOb57yM5bvF9xJrLEObOkAAAAC1QoHXoRYodtouw5cKbwI1AuPk%252BVWEpzwvoAzgkyTWD7vvmloKSuwS0IrUHLk7n0Yfp3DOKmgbjiyFpaYfufnS8Dp%252FzGzDj%252BFRlySoZMpMwK31kHfSbiZVPtBg5xESVQH3LKeXpXVZHuJ4gl0YZu2j5%252FXT6SUfgp2Znq1tIs7tSwbd9uHudY%252FeGEJdvORszdq2GvxNg7kNJ%252F69%252BSjYoYv8tp02GkX6M1fpsk76QI9ZgGPx%252BxaMNWlOk82JXeuOngcDBAAJA9i4BQAAAAAABAAFAkANAwAJCQEHAgAACAUDBgoMAMqaOwAAAAAJ) (press simulate to see logs). +The `declare_id!` in [`pinocchio/program/src/lib.rs`](pinocchio/program/src/lib.rs) is `BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf`. Whether this address is currently deployed on devnet is not tracked in this repo — verify with `solana program show BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf --url devnet`. -## DISCLAIMER +## Disclaimer -THIS CODE IS NOT AUDITED NOR REVIEWED. USE AT YOUR OWN DISCRETION. \ No newline at end of file +This code has not been audited or reviewed. Use at your own discretion. diff --git a/tokens/token-extensions/transfer-hook/pblock-list/README.md b/tokens/token-extensions/transfer-hook/pblock-list/README.md deleted file mode 100644 index 6f10a553a..000000000 --- a/tokens/token-extensions/transfer-hook/pblock-list/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Block List - -A block-list program that implements the Token-2022 transfer-hook `execute` instruction. - -A central authority maintains a block list — a collection of blocked wallets. Token issuers (transfer-hook extension authorities) can wire this program in as their hook and choose an operation mode: filter the source wallet only, or both source and destination. - -## Operation modes - -The mode depends on whether the block list is empty, plus the issuer's choice. Each mode corresponds to a different `extra-account-metas` account built for the mint (see `setup_extra_metas` below). When the list goes from empty to non-empty, the issuer must call `setup_extra_metas` again. - -- **Empty extra metas** — default when the config counter is 0. -- **Check source** — default when the config counter is > 0. -- **Check both source and destination** — optional behavior when the counter is > 0. - -## Accounts - -### `Config` - -- Defines the block-list authority. -- Tracks the number of blocked wallets. - -### `WalletBlock` - -- Marks a single wallet as blocked. - -## Instruction handlers - -### `init` - -Initializes the global `Config` account with an authority. - -### `block_wallet` - -Adds a wallet to the block list, creating a `WalletBlock` record. - -### `unblock_wallet` - -Removes a wallet from the block list, closing its `WalletBlock` record. - -### `setup_extra_metas` - -Sets up the `extra-account-metas` account that the transfer-hook extension depends on. Takes an optional bool to switch operation modes when the counter is non-zero. - -Once wallets are added to the block list, the issuer must call this again to pick one of the blocking modes. - -### `tx_hook` - -The hook invoked during token transfers. - -## Repository layout - -- **Program:** a Pinocchio-based block list under [`pinocchio/program/`](pinocchio/program/). -- **SDKs:** Codama-generated Rust and TypeScript SDKs under [`pinocchio/sdk/`](pinocchio/sdk/). -- **CLI:** a Rust CLI to interact with the program. - -## Building - -All commands below should be run from the [`pinocchio/`](pinocchio/) directory. - -Install dependencies: - -```bash -cd pinocchio -pnpm install -``` - -Build the program: - -```bash -cd program -cargo build-sbf -``` - -Deploy it: - -```bash -solana program deploy --program-id target/deploy/block_list.so -``` - -Generate the SDKs: - -```bash -pnpm run generate-sdks -``` - -Build the CLI: - -```bash -cd cli -cargo build -``` - -## Setup - -### Block list - -Initialize the list and set the authority: - -```bash -target/debug/block-list-cli init -``` - -Add a wallet: - -```bash -target/debug/block-list-cli block-wallet -``` - -Remove a wallet: - -```bash -target/debug/block-list-cli unblock-wallet -``` - -### Token mint - -Create a new mint with the hook wired up: - -```bash -spl-token create-token --program-2022 --transfer-hook BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf -``` - -Initialize the extra account metas: - -```bash -target/debug/block-list-cli setup-extra-metas -``` - -Switch to checking both source and destination wallets: - -```bash -target/debug/block-list-cli setup-extra-metas --check-both-wallets -``` - -## Devnet deployment - -The program is deployed to devnet at `BLoCKLSG2qMQ9YxEyrrKKAQzthvW4Lu8Eyv74axF6mf`. - -Example transactions: - -- [Empty block list — transfer succeeds](https://explorer.solana.com/tx/2EnQD5mFZvrR3EAyFamCfxJDS3yAtZQxNVhFtK46PanCgbX6rpvgcQ961ZAs8H3auawJZPaVZMpAxoj3qZK55mHT?cluster=devnet) -- [Block list checking source only — transfer succeeds](https://explorer.solana.com/tx/4pmx31Lx5mXS7FWUtRjAxdRiwKZKCwJv3Du2qGhbLpQUenBuRxRUbrCaGGVjLjeDtpt4AXHzoNex1ppBsmKWSS7r?cluster=devnet) -- [Block list checking both — transfer succeeds](https://explorer.solana.com/tx/Q5Bk6GjGQ9TJtwS5zjDKp7GiFZK6efmGNCcxjqcmzf1YoZZJVE3rQkkSgSBNo7tst4hjUX6SJMsmEGXQ2NAdBjF?cluster=devnet) - -## Disclaimer - -This code has not been audited or reviewed. Use at your own discretion. diff --git a/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md b/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md index 4efef0980..9d76cf18a 100644 --- a/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md +++ b/tokens/token-extensions/transfer-hook/whitelist/anchor/README.md @@ -1,5 +1,5 @@ # Transfer Hook — Whitelist (Anchor) -A simple whitelist enforced by a Token-2022 transfer hook. +A whitelist enforced by a Token Extensions transfer hook. The whitelist is stored inline on a single account. -This approach doesn't scale to large whitelists: it eventually runs out of account space. A better approach for larger lists is to store entries in external PDAs (one PDA per whitelisted wallet) — see the [`pblock-list`](../../pblock-list/) example for that pattern. +This approach doesn't scale: the whitelist eventually runs out of account space. For larger lists, store entries in external PDAs (one PDA per whitelisted wallet) — see the [`block-list`](../../block-list/) example for that pattern. diff --git a/tokens/token-fundraiser/anchor/README.md b/tokens/token-fundraiser/anchor/README.md index f0ef8a420..a17df4069 100644 --- a/tokens/token-fundraiser/anchor/README.md +++ b/tokens/token-fundraiser/anchor/README.md @@ -1,10 +1,10 @@ # Token Fundraiser -Create a fundraiser for SPL Tokens. A user creates a fundraiser account, specifies the mint they want to collect, the target amount, and a duration. Other users contribute. If the target is reached, the maker can claim the funds; if it isn't reached within the duration, contributors can refund. +Create a fundraiser that collects tokens. A user creates a fundraiser account, specifies the mint they want to receive, the target amount, and a duration. Other users contribute. If the target is reached, the maker can claim the funds; if it isn't reached within the duration, contributors can refund. ## Architecture -A fundraising account consists of: +The fundraiser state account: ```rust #[account] @@ -15,32 +15,61 @@ pub struct Fundraiser { pub amount_to_raise: u64, pub current_amount: u64, pub time_started: i64, - pub duration: u8, + pub duration: u16, pub bump: u8, } ``` -### In this state account, we will store: +Fields: -- maker: the person who is starting the fundraising +- `maker` — the person starting the fundraiser. +- `mint_to_raise` — the mint the maker wants to receive. +- `amount_to_raise` — the target amount. +- `current_amount` — total amount currently contributed. +- `time_started` — when the fundraiser was created. +- `duration` — fundraising window in days. +- `bump` — canonical bump for the Fundraiser PDA. -- mint_to_raise: the mint that the maker wants to receive +The `InitSpace` derive macro implements the `Space` trait, which calculates the size of the account (not counting the Anchor discriminator). -- amount_to_raise: the target amount that the maker is trying to raise +A per-contributor record: -- current_amount: the total amount currently donated +```rust +#[account] +#[derive(InitSpace)] +pub struct Contributor { + pub amount: u64, + pub bump: u8, +} +``` -- time_started: the time when the account was created +- `amount` — total amount contributed by this contributor. +- `bump` — canonical bump for the Contributor PDA. -- duration: the timeframe to collect all the contributions (in days) +The Contributor PDA uses `init_if_needed`, which only runs the init branch on first call. The handler stores `bumps.contributor_account` into `bump` on first init (when `bump == 0`); see [`instructions/contribute.rs`](programs/fundraiser/src/instructions/contribute.rs). -- bump: since our Fundraiser account will be a PDA (Program Derived Address), we will store the bump of the account +### Constants -The `InitSpace` derive macro implements the `Space` trait, which calculates the size of the account (not counting the Anchor discriminator). +From [`constants.rs`](programs/fundraiser/src/constants.rs): + +```rust +pub const MIN_AMOUNT_TO_RAISE: u64 = 3; +pub const SECONDS_TO_DAYS: i64 = 86400; +pub const MAX_CONTRIBUTION_PERCENTAGE: u64 = 10; +pub const PERCENTAGE_SCALER: u64 = 100; +``` + +`MAX_CONTRIBUTION_PERCENTAGE / PERCENTAGE_SCALER` = 10%, the per-contributor cap. + +### Code layout -### Creating a Fundraiser +Each instruction handler is a free function (`pub fn handle_(accounts: &mut , ...)`) called from the `#[program]` module in `lib.rs`. Account-validation structs sit in the same file as the handler. -Users create Fundraiser accounts via this context: +## Instruction handlers + +### `initialize` + +[`programs/fundraiser/src/instructions/initialize.rs`](programs/fundraiser/src/instructions/initialize.rs). ```rust #[derive(Accounts)] @@ -71,360 +100,49 @@ pub struct Initialize<'info> { Account breakdown: -- `maker`: the person starting the fundraiser. Signs the transaction; mutable so we can deduct lamports from it. -- `mint_to_raise`: the mint the maker wants to receive. -- `fundraiser`: the state account being initialized. The Fundraiser PDA is derived from `b"fundraiser"` and the maker's public key; Anchor calculates the canonical bump and stores it in the struct. -- `vault`: the ATA that receives contributions, derived from `mint_to_raise` and the Fundraiser account. -- `system_program`: initializes new accounts. -- `token_program`, `associated_token_program`: create new ATAs. +- `maker` — the person starting the fundraiser. Signs; mutable so we can deduct lamports. +- `mint_to_raise` — the mint the maker wants to receive. +- `fundraiser` — the state account. Derived from `b"fundraiser"` and the maker's public key; Anchor calculates the canonical bump and stores it in the struct. +- `vault` — the ATA that receives contributions, owned by the Fundraiser PDA. +- `system_program`, `token_program`, `associated_token_program` — needed to initialize the new accounts. -### Implementation for `Initialize` +The handler requires `amount >= MIN_AMOUNT_TO_RAISE.pow(mint.decimals)` and initializes the Fundraiser state. -```rust -impl<'info> Initialize<'info> { - pub fn initialize(&mut self, amount: u64, duration: u8, bumps: &InitializeBumps) -> Result<()> { - - // Check if the amount to raise meets the minimum amount required - require!( - amount > MIN_AMOUNT_TO_RAISE.pow(self.mint_to_raise.decimals as u32), - FundraiserError::InvalidAmount - ); - - // Initialize the fundraiser account - self.fundraiser.set_inner(Fundraiser { - maker: self.maker.key(), - mint_to_raise: self.mint_to_raise.key(), - amount_to_raise: amount, - current_amount: 0, - time_started: Clock::get()?.unix_timestamp, - duration, - bump: bumps.fundraiser - }); - - Ok(()) - } -} -``` +### `contribute` -Set the data on the Fundraiser account if the target amount meets the minimum. +[`programs/fundraiser/src/instructions/contribute.rs`](programs/fundraiser/src/instructions/contribute.rs). -### Contributing +Account-validation struct: see source. The handler performs four `require!` checks in order: -A contribution account consists of: +1. `amount >= 1_u64.pow(mint.decimals)` — minimum contribution (this is `1`, since `1.pow(n) == 1`; effectively contributions just need to be non-zero). +2. `amount <= amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE / PERCENTAGE_SCALER` — per-call cap of 10% of the target. +3. `fundraiser.duration <= (current_time - time_started) / SECONDS_TO_DAYS` — see the [duration semantics note](#duration-check-semantics) below. +4. Cumulative contributor cap: this contributor's running total (existing + new) must not exceed 10% of the target. -```rust -#[account] -#[derive(InitSpace)] -pub struct Contributor { - pub amount: u64, -} -```rust +If all four checks pass, tokens are transferred from `contributor_ata` to `vault` via a CPI to the Classic Token Program, and both `Fundraiser.current_amount` and `Contributor.amount` are updated. -Stores the total amount contributed by a specific contributor. +### `check_contributions` -#[derive(Accounts)] -pub struct Contribute<'info> { - #[account(mut)] - pub contributor: Signer<'info>, - pub mint_to_raise: Account<'info, Mint>, - #[account( - mut, - has_one = mint_to_raise, - seeds = [b"fundraiser".as_ref(), fundraiser.maker.as_ref()], - bump = fundraiser.bump, - )] - pub fundraiser: Account<'info, Fundraiser>, - #[account( - init_if_needed, - payer = contributor, - seeds = [b"contributor", fundraiser.key().as_ref(), contributor.key().as_ref()], - bump, - space = Contributor::DISCRIMINATOR.len() + Contributor::INIT_SPACE, - )] - pub contributor_account: Account<'info, Contributor>, - #[account( - mut, - associated_token::mint = mint_to_raise, - associated_token::authority = contributor - )] - pub contributor_ata: Account<'info, TokenAccount>, - #[account( - mut, - associated_token::mint = fundraiser.mint_to_raise, - associated_token::authority = fundraiser - )] - pub vault: Account<'info, TokenAccount>, - pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, -} -``` +[`programs/fundraiser/src/instructions/checker.rs`](programs/fundraiser/src/instructions/checker.rs). -Account breakdown: +Lets the maker claim the funds. Requires `vault.amount >= amount_to_raise`. The CPI uses `new_with_signer` with the Fundraiser PDA's seeds because the vault is owned by the PDA. The Fundraiser account is closed (via the `close = maker` constraint) and its rent is refunded to the maker. -- `contributor`: the contributor. -- `mint_to_raise`: the mint being collected. -- `fundraiser`: an initialized Fundraiser account; constraints check the mint, seeds, and bump. -- `contributor_account`: initialized if needed; tracks the contributor's running total. -- `contributor_ata`: the ATA tokens are transferred *from*. Mint and authority are checked; mutable. -- `vault`: the ATA tokens are transferred *to*. Mint and authority are checked; mutable. -- `token_program`: used for token transfers. +### `refund` -### Implementation for `Contribute` +[`programs/fundraiser/src/instructions/refund.rs`](programs/fundraiser/src/instructions/refund.rs). -```rust -impl<'info> Contribute<'info> { - pub fn contribute(&mut self, amount: u64) -> Result<()> { - - // Check if the amount to contribute meets the minimum amount required - require!( - amount > 1_u8.pow(self.mint_to_raise.decimals as u32) as u64, - FundraiserError::ContributionTooSmall - ); - - // Check if the amount to contribute is less than the maximum allowed contribution - require!( - amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER, - FundraiserError::ContributionTooBig - ); - - // Check if the maximum contributions per contributor have been reached - require!( - (self.contributor_account.amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER) - && (self.contributor_account.amount + amount <= (self.fundraiser.amount_to_raise * MAX_CONTRIBUTION_PERCENTAGE) / PERCENTAGE_SCALER), - FundraiserError::MaximumContributionsReached - ); - - // Check if the fundraising duration has been reached - let current_time = Clock::get()?.unix_timestamp; - require!( - self.fundraiser.duration <= ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u8, - crate::FundraiserError::FundraisingEnded - ); - - // Transfer the funds to the vault - // CPI to the token program to transfer the funds - let cpi_program = self.token_program.to_account_info(); - - // Transfer the funds from the contributor to the vault - let cpi_accounts = Transfer { - from: self.contributor_ata.to_account_info(), - to: self.vault.to_account_info(), - authority: self.contributor.to_account_info(), - }; - - // Crete a CPI context - let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); - - // Transfer the funds from the contributor to the vault - transfer(cpi_ctx, amount)?; - - // Update the fundraiser and contributor accounts with the new amounts - self.fundraiser.current_amount += amount; - - self.contributor_account.amount += amount; - - Ok(()) - } -} -``` -Checks performed: +Lets a contributor reclaim their contribution if the target wasn't met. Two checks: -- Contribution is at least one token. -- Contribution is at most 10% of the target. -- Total contribution from this contributor doesn't exceed 10% of the target. -- Fundraising duration has not elapsed. +1. `fundraiser.duration >= (current_time - time_started) / SECONDS_TO_DAYS` — see the [duration semantics note](#duration-check-semantics) below. +2. `vault.amount < amount_to_raise` — target not met. -A CPI to the token program transfers tokens from the contributor's ATA to the vault. The contributor signs (they own the source ATA). Finally, state accounts are updated. +Then the vault's tokens are transferred back to the contributor's ATA (CPI with PDA signer seeds) and the Contributor account is closed (via `close = contributor`), refunding its rent to the contributor. -### Claiming +## Duration check semantics -```rust -#[derive(Accounts)] -pub struct CheckContributions<'info> { - #[account(mut)] - pub maker: Signer<'info>, - pub mint_to_raise: Account<'info, Mint>, - #[account( - mut, - seeds = [b"fundraiser".as_ref(), maker.key().as_ref()], - bump = fundraiser.bump, - close = maker, - )] - pub fundraiser: Account<'info, Fundraiser>, - #[account( - mut, - associated_token::mint = mint_to_raise, - associated_token::authority = fundraiser, - )] - pub vault: Account<'info, TokenAccount>, - #[account( - init_if_needed, - payer = maker, - associated_token::mint = mint_to_raise, - associated_token::authority = maker, - )] - pub maker_ata: Account<'info, TokenAccount>, - pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, - pub associated_token_program: Program<'info, AssociatedToken>, -} -``` - -Account breakdown: - -- `maker`: the fundraiser owner. Mutable; pays initialization fees and receives rent back when the Fundraiser account closes. -- `mint_to_raise`: the mint being collected. -- `fundraiser`: the initialized Fundraiser account. -- `vault`: the ATA tokens are transferred *from*. -- `maker_ata`: the ATA tokens are transferred *to*. Initialized if needed (the maker pays). -- `system_program`, `associated_token_program`: needed to initialize the maker's ATA if necessary. -- `token_program`: used for the transfer. - -### Implementation for `CheckContributions` - -```rust -impl<'info> CheckContributions<'info> { - pub fn check_contributions(&self) -> Result<()> { - - // Check if the target amount has been met - require!( - self.vault.amount >= self.fundraiser.amount_to_raise, - FundraiserError::TargetNotMet - ); - - // Transfer the funds to the maker - // CPI to the token program to transfer the funds - let cpi_program = self.token_program.to_account_info(); - - // Transfer the funds from the vault to the maker - let cpi_accounts = Transfer { - from: self.vault.to_account_info(), - to: self.maker_ata.to_account_info(), - authority: self.fundraiser.to_account_info(), - }; - - // Signer seeds to sign the CPI on behalf of the fundraiser account - let signer_seeds: [&[&[u8]]; 1] = [&[ - b"fundraiser".as_ref(), - self.maker.to_account_info().key.as_ref(), - &[self.fundraiser.bump], - ]]; - - // CPI context with signer since the fundraiser account is a PDA - let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, &signer_seeds); - - // Transfer the funds from the vault to the maker - transfer(cpi_ctx, self.vault.amount)?; - - Ok(()) - } -} -``` - -Check the vault holds at least the target amount; if so, CPI into the token program to transfer the vault's balance to the maker's ATA. The vault is owned by the Fundraiser PDA, so the CPI uses `new_with_signer` with the PDA seeds. - -Finally, the Fundraiser account is closed (via the `close` constraint) and its rent is refunded to the maker. - -### Refunding - -```rust -#[derive(Accounts)] -pub struct Refund<'info> { - #[account(mut)] - pub contributor: Signer<'info>, - pub maker: SystemAccount<'info>, - pub mint_to_raise: Account<'info, Mint>, - #[account( - mut, - has_one = mint_to_raise, - seeds = [b"fundraiser", maker.key().as_ref()], - bump = fundraiser.bump, - )] - pub fundraiser: Account<'info, Fundraiser>, - #[account( - mut, - seeds = [b"contributor", fundraiser.key().as_ref(), contributor.key().as_ref()], - bump, - close = contributor, - )] - pub contributor_account: Account<'info, Contributor>, - #[account( - mut, - associated_token::mint = mint_to_raise, - associated_token::authority = contributor - )] - pub contributor_ata: Account<'info, TokenAccount>, - #[account( - mut, - associated_token::mint = mint_to_raise, - associated_token::authority = fundraiser - )] - pub vault: Account<'info, TokenAccount>, - pub token_program: Program<'info, Token>, - pub system_program: Program<'info, System>, -} -``` +The `contribute` and `refund` handlers compare `fundraiser.duration` (a `u16` in *days*) against elapsed days since `time_started`. The two checks use opposite comparison operators, which is worth reading carefully: -Account breakdown: - -- `contributor`: the contributor being refunded. -- `maker`: the fundraiser owner. -- `mint_to_raise`: the mint being collected. -- `fundraiser`: the Fundraiser account. -- `contributor_account`: the Contributor account. -- `contributor_ata`: the ATA the refund goes *to*. -- `vault`: the ATA the refund comes *from*. -- `token_program`: used for the transfer. - -### Implementation for `Refund` - -```rust -impl<'info> Refund<'info> { - pub fn refund(&mut self) -> Result<()> { - - // Check if the fundraising duration has been reached - let current_time = Clock::get()?.unix_timestamp; - - require!( - self.fundraiser.duration <= ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u8, - crate::FundraiserError::FundraiserNotEnded - ); - - require!( - self.vault.amount < self.fundraiser.amount_to_raise, - crate::FundraiserError::TargetMet - ); - - // Transfer the funds back to the contributor - // CPI to the token program to transfer the funds - let cpi_program = self.token_program.to_account_info(); - - // Transfer the funds from the vault to the contributor - let cpi_accounts = Transfer { - from: self.vault.to_account_info(), - to: self.contributor_ata.to_account_info(), - authority: self.fundraiser.to_account_info(), - }; - - // Signer seeds to sign the CPI on behalf of the fundraiser account - let signer_seeds: [&[&[u8]]; 1] = [&[ - b"fundraiser".as_ref(), - self.maker.to_account_info().key.as_ref(), - &[self.fundraiser.bump], - ]]; - - // CPI context with signer since the fundraiser account is a PDA - let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, &signer_seeds); - - // Transfer the funds from the vault to the contributor - transfer(cpi_ctx, self.contributor_account.amount)?; - - // Update the fundraiser state by reducing the amount contributed - self.fundraiser.current_amount -= self.contributor_account.amount; - - Ok(()) - } -} -``` +- `contribute`: `require!(duration <= elapsed_days, FundraiserEnded)` — fails (with `FundraiserEnded`) when `elapsed_days < duration`. +- `refund`: `require!(duration >= elapsed_days, FundraiserNotEnded)` — fails (with `FundraiserNotEnded`) when `elapsed_days > duration`. -Verify the fundraising duration has elapsed and the target was not met, then transfer the contributor's tokens from the vault back to their ATA. +> ⚠️ Both comparisons look inverted relative to their error names. If you adapt this code, audit the duration logic carefully before relying on it. diff --git a/tokens/spl-token-minter/README.md b/tokens/token-minter/README.md similarity index 51% rename from tokens/spl-token-minter/README.md rename to tokens/token-minter/README.md index e50914a81..36902a3c3 100644 --- a/tokens/spl-token-minter/README.md +++ b/tokens/token-minter/README.md @@ -1,8 +1,8 @@ -# SPL Token Minter +# Token Minter -Minting SPL Tokens is conceptually straightforward. The only subtle part is understanding how Solana tracks per-user token balances. +Minting tokens is conceptually straightforward. The subtle part is understanding how Solana tracks per-user token balances. -Every account on Solana tracks its own balance of SOL. It can't possibly also track its own balance of every SPL Token on the network. Instead, balances for SPL Tokens are held in separate accounts that are specific to a given mint and a given owner. These are called **Associated Token Accounts (ATAs)**. +Every account on Solana tracks its own balance of SOL. It can't possibly also track its own balance of every token on the network. Instead, token balances are held in separate accounts that are specific to a given mint and a given owner. These are called **Associated Token Accounts (ATAs)**. To know what someone's balance of token JOE is, you would: diff --git a/tokens/spl-token-minter/anchor/Anchor.toml b/tokens/token-minter/anchor/Anchor.toml similarity index 88% rename from tokens/spl-token-minter/anchor/Anchor.toml rename to tokens/token-minter/anchor/Anchor.toml index c50931e8c..ff3b0862b 100644 --- a/tokens/spl-token-minter/anchor/Anchor.toml +++ b/tokens/token-minter/anchor/Anchor.toml @@ -6,7 +6,7 @@ resolution = true skip-lint = false [programs.localnet] -spl_token_minter = "3of89Z9jwek9zrFgpCWc9jZvQvitpVMxpZNsrAD2vQUD" +token_minter = "3of89Z9jwek9zrFgpCWc9jZvQvitpVMxpZNsrAD2vQUD" # [registry] section removed — no longer used in Anchor 1.0 diff --git a/tokens/spl-token-minter/anchor/Cargo.toml b/tokens/token-minter/anchor/Cargo.toml similarity index 100% rename from tokens/spl-token-minter/anchor/Cargo.toml rename to tokens/token-minter/anchor/Cargo.toml diff --git a/tokens/spl-token-minter/anchor/prepare.mjs b/tokens/token-minter/anchor/prepare.mjs similarity index 100% rename from tokens/spl-token-minter/anchor/prepare.mjs rename to tokens/token-minter/anchor/prepare.mjs diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml b/tokens/token-minter/anchor/programs/token-minter/Cargo.toml similarity index 93% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml rename to tokens/token-minter/anchor/programs/token-minter/Cargo.toml index 9d602f735..e19cdfaaa 100644 --- a/tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml +++ b/tokens/token-minter/anchor/programs/token-minter/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "spl-token-minter" +name = "token-minter" version = "0.1.0" description = "Created with Anchor" edition = "2021" [lib] crate-type = ["cdylib", "lib"] -name = "spl_token_minter" +name = "token_minter" [features] default = [] diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/Xargo.toml b/tokens/token-minter/anchor/programs/token-minter/Xargo.toml similarity index 100% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/Xargo.toml rename to tokens/token-minter/anchor/programs/token-minter/Xargo.toml diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/create.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs similarity index 100% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/create.rs rename to tokens/token-minter/anchor/programs/token-minter/src/instructions/create.rs diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs similarity index 100% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs rename to tokens/token-minter/anchor/programs/token-minter/src/instructions/mint.rs diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mod.rs b/tokens/token-minter/anchor/programs/token-minter/src/instructions/mod.rs similarity index 100% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mod.rs rename to tokens/token-minter/anchor/programs/token-minter/src/instructions/mod.rs diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/src/lib.rs b/tokens/token-minter/anchor/programs/token-minter/src/lib.rs similarity index 95% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/src/lib.rs rename to tokens/token-minter/anchor/programs/token-minter/src/lib.rs index e747ceecf..f7415398a 100644 --- a/tokens/spl-token-minter/anchor/programs/spl-token-minter/src/lib.rs +++ b/tokens/token-minter/anchor/programs/token-minter/src/lib.rs @@ -6,7 +6,7 @@ use instructions::*; declare_id!("3of89Z9jwek9zrFgpCWc9jZvQvitpVMxpZNsrAD2vQUD"); #[program] -pub mod spl_token_minter { +pub mod token_minter { use super::*; pub fn create_token( diff --git a/tokens/spl-token-minter/anchor/programs/spl-token-minter/tests/test_spl_token_minter.rs b/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs similarity index 91% rename from tokens/spl-token-minter/anchor/programs/spl-token-minter/tests/test_spl_token_minter.rs rename to tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs index a51678762..b77480205 100644 --- a/tokens/spl-token-minter/anchor/programs/spl-token-minter/tests/test_spl_token_minter.rs +++ b/tokens/token-minter/anchor/programs/token-minter/tests/test_token_minter.rs @@ -57,10 +57,10 @@ fn derive_ata(wallet: &Pubkey, mint: &Pubkey) -> Pubkey { } fn setup() -> (LiteSVM, Pubkey, Keypair) { - let program_id = spl_token_minter::id(); + let program_id = token_minter::id(); let mut svm = LiteSVM::new(); - let program_bytes = include_bytes!("../../../target/deploy/spl_token_minter.so"); + let program_bytes = include_bytes!("../../../target/deploy/token_minter.so"); svm.add_program(program_id, program_bytes).unwrap(); let metadata_bytes = include_bytes!("../../../tests/fixtures/mpl_token_metadata.so"); @@ -79,13 +79,13 @@ fn test_create_token() { let create_ix = Instruction::new_with_bytes( program_id, - &spl_token_minter::instruction::CreateToken { + &token_minter::instruction::CreateToken { token_name: "Solana Gold".to_string(), token_symbol: "GOLDSOL".to_string(), token_uri: "https://example.com/token.json".to_string(), } .data(), - spl_token_minter::accounts::CreateToken { + token_minter::accounts::CreateToken { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), metadata_account, @@ -126,13 +126,13 @@ fn test_create_and_mint_tokens() { // 1. Create token let create_ix = Instruction::new_with_bytes( program_id, - &spl_token_minter::instruction::CreateToken { + &token_minter::instruction::CreateToken { token_name: "Solana Gold".to_string(), token_symbol: "GOLDSOL".to_string(), token_uri: "https://example.com/token.json".to_string(), } .data(), - spl_token_minter::accounts::CreateToken { + token_minter::accounts::CreateToken { payer: payer.pubkey(), mint_account: mint_keypair.pubkey(), metadata_account, @@ -157,8 +157,8 @@ fn test_create_and_mint_tokens() { let mint_ix = Instruction::new_with_bytes( program_id, - &spl_token_minter::instruction::MintToken { amount: 100 }.data(), - spl_token_minter::accounts::MintToken { + &token_minter::instruction::MintToken { amount: 100 }.data(), + token_minter::accounts::MintToken { mint_authority: payer.pubkey(), recipient: payer.pubkey(), mint_account: mint_keypair.pubkey(), diff --git a/tokens/spl-token-minter/anchor/tests/fixtures/mpl_token_metadata.so b/tokens/token-minter/anchor/tests/fixtures/mpl_token_metadata.so similarity index 100% rename from tokens/spl-token-minter/anchor/tests/fixtures/mpl_token_metadata.so rename to tokens/token-minter/anchor/tests/fixtures/mpl_token_metadata.so diff --git a/tokens/spl-token-minter/native/cicd.sh b/tokens/token-minter/native/cicd.sh similarity index 100% rename from tokens/spl-token-minter/native/cicd.sh rename to tokens/token-minter/native/cicd.sh diff --git a/tokens/spl-token-minter/native/package.json b/tokens/token-minter/native/package.json similarity index 100% rename from tokens/spl-token-minter/native/package.json rename to tokens/token-minter/native/package.json diff --git a/tokens/spl-token-minter/native/pnpm-lock.yaml b/tokens/token-minter/native/pnpm-lock.yaml similarity index 100% rename from tokens/spl-token-minter/native/pnpm-lock.yaml rename to tokens/token-minter/native/pnpm-lock.yaml diff --git a/tokens/spl-token-minter/native/program/Cargo.toml b/tokens/token-minter/native/program/Cargo.toml similarity index 90% rename from tokens/spl-token-minter/native/program/Cargo.toml rename to tokens/token-minter/native/program/Cargo.toml index ceea4431d..9f655a657 100644 --- a/tokens/spl-token-minter/native/program/Cargo.toml +++ b/tokens/token-minter/native/program/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "spl-token-minter-native-program" +name = "token-minter-native-program" version = "0.1.0" edition = "2021" diff --git a/tokens/spl-token-minter/native/program/src/instructions/create.rs b/tokens/token-minter/native/program/src/instructions/create.rs similarity index 100% rename from tokens/spl-token-minter/native/program/src/instructions/create.rs rename to tokens/token-minter/native/program/src/instructions/create.rs diff --git a/tokens/spl-token-minter/native/program/src/instructions/mint.rs b/tokens/token-minter/native/program/src/instructions/mint.rs similarity index 100% rename from tokens/spl-token-minter/native/program/src/instructions/mint.rs rename to tokens/token-minter/native/program/src/instructions/mint.rs diff --git a/tokens/spl-token-minter/native/program/src/instructions/mod.rs b/tokens/token-minter/native/program/src/instructions/mod.rs similarity index 100% rename from tokens/spl-token-minter/native/program/src/instructions/mod.rs rename to tokens/token-minter/native/program/src/instructions/mod.rs diff --git a/tokens/spl-token-minter/native/program/src/lib.rs b/tokens/token-minter/native/program/src/lib.rs similarity index 100% rename from tokens/spl-token-minter/native/program/src/lib.rs rename to tokens/token-minter/native/program/src/lib.rs diff --git a/tokens/spl-token-minter/native/program/src/processor.rs b/tokens/token-minter/native/program/src/processor.rs similarity index 67% rename from tokens/spl-token-minter/native/program/src/processor.rs rename to tokens/token-minter/native/program/src/processor.rs index 90ee4eb87..54eca0f5b 100644 --- a/tokens/spl-token-minter/native/program/src/processor.rs +++ b/tokens/token-minter/native/program/src/processor.rs @@ -9,7 +9,7 @@ use crate::instructions::{ }; #[derive(BorshSerialize, BorshDeserialize, Debug)] -enum SplMinterIntstruction { +enum MinterInstruction { Create(CreateTokenArgs), Mint(MintToArgs), } @@ -19,10 +19,10 @@ pub fn process_instruction( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let instruction = SplMinterIntstruction::try_from_slice(instruction_data)?; + let instruction = MinterInstruction::try_from_slice(instruction_data)?; match instruction { - SplMinterIntstruction::Create(args) => create_token(accounts, args), - SplMinterIntstruction::Mint(args) => mint_to(accounts, args), + MinterInstruction::Create(args) => create_token(accounts, args), + MinterInstruction::Mint(args) => mint_to(accounts, args), } } diff --git a/tokens/spl-token-minter/native/tests/instructions.ts b/tokens/token-minter/native/tests/instructions.ts similarity index 92% rename from tokens/spl-token-minter/native/tests/instructions.ts rename to tokens/token-minter/native/tests/instructions.ts index 70df73542..50fdb29a3 100644 --- a/tokens/spl-token-minter/native/tests/instructions.ts +++ b/tokens/token-minter/native/tests/instructions.ts @@ -1,6 +1,6 @@ import * as borsh from "borsh"; -export enum SplMinterInstruction { +export enum MinterInstruction { Create = 0, Mint = 1, } diff --git a/tokens/spl-token-minter/native/tests/test.ts b/tokens/token-minter/native/tests/test.ts similarity index 93% rename from tokens/spl-token-minter/native/tests/test.ts rename to tokens/token-minter/native/tests/test.ts index e15166765..e92e3a878 100644 --- a/tokens/spl-token-minter/native/tests/test.ts +++ b/tokens/token-minter/native/tests/test.ts @@ -12,13 +12,13 @@ import { TransactionInstruction, } from "@solana/web3.js"; import { BN } from "bn.js"; -import { borshSerialize, CreateTokenArgsSchema, MintToArgsSchema, SplMinterInstruction } from "./instructions"; +import { borshSerialize, CreateTokenArgsSchema, MinterInstruction, MintToArgsSchema } from "./instructions"; function createKeypairFromFile(path: string): Keypair { return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(require("node:fs").readFileSync(path, "utf-8")))); } -describe("SPL Token Minter", async () => { +describe("Token Minter", async () => { // const connection = new Connection(`http://localhost:8899`, 'confirmed'); const connection = new Connection("https://api.devnet.solana.com/", "confirmed"); const payer = createKeypairFromFile(`${require("node:os").homedir()}/.config/solana/id.json`); @@ -26,14 +26,14 @@ describe("SPL Token Minter", async () => { const mintKeypair: Keypair = Keypair.generate(); - it("Create an SPL Token!", async () => { + it("Create a token", async () => { const metadataAddress = PublicKey.findProgramAddressSync( [Buffer.from("metadata"), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mintKeypair.publicKey.toBuffer()], TOKEN_METADATA_PROGRAM_ID, )[0]; const instructionData = borshSerialize(CreateTokenArgsSchema, { - instruction: SplMinterInstruction.Create, + instruction: MinterInstruction.Create, token_title: "Solana Gold", token_symbol: "GOLDSOL", token_uri: @@ -70,7 +70,7 @@ describe("SPL Token Minter", async () => { const associatedTokenAccountAddress = await getAssociatedTokenAddress(mintKeypair.publicKey, payer.publicKey); const instructionData = borshSerialize(MintToArgsSchema, { - instruction: SplMinterInstruction.Mint, + instruction: MinterInstruction.Mint, quantity: new BN(150), }); diff --git a/tokens/spl-token-minter/native/tsconfig.json b/tokens/token-minter/native/tsconfig.json similarity index 100% rename from tokens/spl-token-minter/native/tsconfig.json rename to tokens/token-minter/native/tsconfig.json diff --git a/tokens/spl-token-minter/quasar/Cargo.toml b/tokens/token-minter/quasar/Cargo.toml similarity index 98% rename from tokens/spl-token-minter/quasar/Cargo.toml rename to tokens/token-minter/quasar/Cargo.toml index 992c4d88d..a1443e717 100644 --- a/tokens/spl-token-minter/quasar/Cargo.toml +++ b/tokens/token-minter/quasar/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "quasar-spl-token-minter" +name = "quasar-token-minter" version = "0.1.0" edition = "2021" diff --git a/tokens/spl-token-minter/quasar/Quasar.toml b/tokens/token-minter/quasar/Quasar.toml similarity index 87% rename from tokens/spl-token-minter/quasar/Quasar.toml rename to tokens/token-minter/quasar/Quasar.toml index 508a29c2a..17745e4bb 100644 --- a/tokens/spl-token-minter/quasar/Quasar.toml +++ b/tokens/token-minter/quasar/Quasar.toml @@ -1,5 +1,5 @@ [project] -name = "quasar_spl_token_minter" +name = "quasar_token_minter" [toolchain] type = "solana" diff --git a/tokens/spl-token-minter/quasar/src/instructions/create.rs b/tokens/token-minter/quasar/src/instructions/create.rs similarity index 100% rename from tokens/spl-token-minter/quasar/src/instructions/create.rs rename to tokens/token-minter/quasar/src/instructions/create.rs diff --git a/tokens/spl-token-minter/quasar/src/instructions/mint.rs b/tokens/token-minter/quasar/src/instructions/mint.rs similarity index 100% rename from tokens/spl-token-minter/quasar/src/instructions/mint.rs rename to tokens/token-minter/quasar/src/instructions/mint.rs diff --git a/tokens/spl-token-minter/quasar/src/instructions/mod.rs b/tokens/token-minter/quasar/src/instructions/mod.rs similarity index 100% rename from tokens/spl-token-minter/quasar/src/instructions/mod.rs rename to tokens/token-minter/quasar/src/instructions/mod.rs diff --git a/tokens/spl-token-minter/quasar/src/lib.rs b/tokens/token-minter/quasar/src/lib.rs similarity index 94% rename from tokens/spl-token-minter/quasar/src/lib.rs rename to tokens/token-minter/quasar/src/lib.rs index 05e40d8b9..6a3b061f4 100644 --- a/tokens/spl-token-minter/quasar/src/lib.rs +++ b/tokens/token-minter/quasar/src/lib.rs @@ -9,13 +9,13 @@ mod tests; declare_id!("22222222222222222222222222222222222222222222"); -/// SPL token minter with Metaplex metadata. +/// Token minter with Metaplex metadata. /// /// Two instructions: /// - `create_token` — creates a mint and associated Metaplex metadata account /// - `mint_token` — mints tokens to a recipient's associated token account #[program] -mod quasar_spl_token_minter { +mod quasar_token_minter { use super::*; // String capacities follow Metaplex Token Metadata limits: diff --git a/tokens/spl-token-minter/quasar/src/tests.rs b/tokens/token-minter/quasar/src/tests.rs similarity index 97% rename from tokens/spl-token-minter/quasar/src/tests.rs rename to tokens/token-minter/quasar/src/tests.rs index 7a8869c67..14433ac3a 100644 --- a/tokens/spl-token-minter/quasar/src/tests.rs +++ b/tokens/token-minter/quasar/src/tests.rs @@ -7,7 +7,7 @@ use { }; fn setup() -> QuasarSvm { - let elf = std::fs::read("target/deploy/quasar_spl_token_minter.so").unwrap(); + let elf = std::fs::read("target/deploy/quasar_token_minter.so").unwrap(); QuasarSvm::new() .with_program(&crate::ID, &elf) .with_token_program() diff --git a/tokens/token-swap/README.md b/tokens/token-swap/README.md index 11d094a12..434fe237b 100644 --- a/tokens/token-swap/README.md +++ b/tokens/token-swap/README.md @@ -27,7 +27,7 @@ Implementation choices: - **Shared parameters.** A single AMM account stores the shared trading-fee config and admin. Each pool then has its own account. - **Unique pools.** Each pool is a PDA seeded from the AMM, `mint_a`, and `mint_b` (in that order, with `mint_a < mint_b`). -- **LP accounting via SPL Token.** The LP positions are tracked as SPL Tokens (the `mint_liquidity` mint), so they're composable with any wallet or downstream protocol. +- **LP accounting via tokens.** LP positions are tracked as tokens (the `mint_liquidity` mint), so they're composable with any wallet or downstream protocol. ## Onchain-design principles applied here diff --git a/tokens/transfer-tokens/README.md b/tokens/transfer-tokens/README.md index f8674f5a0..b1a9f9b6b 100644 --- a/tokens/transfer-tokens/README.md +++ b/tokens/transfer-tokens/README.md @@ -1,7 +1,7 @@ # Transfer Tokens -Like minting, SPL Token transfers happen between Associated Token Accounts. +Like minting, token transfers happen between Associated Token Accounts. -Use the `transfer()` instruction provided by the SPL Token Program to transfer any SPL Token, given the appropriate permissions. +Use the Classic Token Program's `transfer` instruction handler to move tokens, given the appropriate permissions. -See [SPL Token Minter](../spl-token-minter) and [NFT Minter](../nft-minter) for more on Associated Token Accounts. +See [Token Minter](../token-minter) and [NFT Minter](../nft-minter) for more on Associated Token Accounts.