jpcaulfi 2 лет назад
Родитель
Сommit
54f2543089
28 измененных файлов с 1209 добавлено и 20 удалено
  1. 7 20
      tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs
  2. 27 0
      tokens/pda-mint-authority/README.md
  3. 14 0
      tokens/pda-mint-authority/anchor/Anchor.toml
  4. 13 0
      tokens/pda-mint-authority/anchor/Cargo.toml
  5. 15 0
      tokens/pda-mint-authority/anchor/package.json
  6. 22 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/Cargo.toml
  7. 2 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/Xargo.toml
  8. 82 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/create.rs
  9. 25 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/init.rs
  10. 138 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/mint.rs
  11. 7 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/mod.rs
  12. 30 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/lib.rs
  13. 11 0
      tokens/pda-mint-authority/anchor/programs/nft-minter/src/state/mod.rs
  14. 121 0
      tokens/pda-mint-authority/anchor/tests/test.ts
  15. 10 0
      tokens/pda-mint-authority/anchor/tsconfig.json
  16. 8 0
      tokens/pda-mint-authority/native/cicd.sh
  17. 22 0
      tokens/pda-mint-authority/native/package.json
  18. 15 0
      tokens/pda-mint-authority/native/program/Cargo.toml
  19. 124 0
      tokens/pda-mint-authority/native/program/src/instructions/create.rs
  20. 48 0
      tokens/pda-mint-authority/native/program/src/instructions/init.rs
  21. 145 0
      tokens/pda-mint-authority/native/program/src/instructions/mint.rs
  22. 7 0
      tokens/pda-mint-authority/native/program/src/instructions/mod.rs
  23. 17 0
      tokens/pda-mint-authority/native/program/src/lib.rs
  24. 31 0
      tokens/pda-mint-authority/native/program/src/processor.rs
  25. 11 0
      tokens/pda-mint-authority/native/program/src/state/mod.rs
  26. 67 0
      tokens/pda-mint-authority/native/tests/instructions.ts
  27. 180 0
      tokens/pda-mint-authority/native/tests/test.ts
  28. 10 0
      tokens/pda-mint-authority/native/tsconfig.json

+ 7 - 20
tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs

@@ -4,35 +4,22 @@ pub mod instructions;
 
 use instructions::*;
 
-
-declare_id!("A6itasS5iqANkC9yrzP1HJPBnJxj9tC8G5TmJzQGogGG");
-
+declare_id!("JBBC7GafzY2CGkfnNEF8vCiwF3qSY31MYm15Q5iaEqXN");
 
 #[program]
 pub mod nft_minter {
     use super::*;
 
     pub fn create_token(
-        ctx: Context<CreateToken>, 
-        nft_title: String, 
-        nft_symbol: String, 
+        ctx: Context<CreateToken>,
+        nft_title: String,
+        nft_symbol: String,
         nft_uri: String,
     ) -> Result<()> {
-
-        create::create_token(
-            ctx, 
-            nft_title, 
-            nft_symbol, 
-            nft_uri,
-        )
+        create::create_token(ctx, nft_title, nft_symbol, nft_uri)
     }
 
-    pub fn mint_to(
-        ctx: Context<MintTo>, 
-    ) -> Result<()> {
-
-        mint::mint_to(
-            ctx, 
-        )
+    pub fn mint_to(ctx: Context<MintTo>) -> Result<()> {
+        mint::mint_to(ctx)
     }
 }

+ 27 - 0
tokens/pda-mint-authority/README.md

@@ -0,0 +1,27 @@
+# NFT Minter
+
+Minting NFTs is exactly the same as [minting any SPL Token on Solana](../spl-token-minter/), except for immediately after the actual minting has occurred.   
+   
+What does this mean? Well, when you mint SPL Tokens, you can attach a fixed supply, but in most cases you can continue to mint new tokens at will - increasing the supply of that token.   
+   
+With an NFT, you're supposed to only have **one**.   
+   
+So, how do we ensure only one single token can be minted for any NFT?
+
+---
+
+We have to disable minting by changing the Mint Authority on the Mint.   
+   
+> The Mint Authority is the account that is permitted to mint new tokens into supply.
+   
+If we remove this authority - effectively setting it to `null` - we can disable minting of new tokens for this Mint.   
+   
+> By design, **this is irreversible**.   
+   
+---
+
+Although we can set this authority to `null` manually, we can also make use of Metaplex again, this time to mark our NFT as Limited Edition.   
+   
+When we use an Edition - such as a Master Edition - for our NFT, we get some extra metadata associated with our NFT and we also get our Mint Authority deactivated by delegating this authority to the Master Edition account.   
+   
+This will effectively disable future minting, but make sure you understand the ramifications of having the Master Edition account be the Mint Authority - rather than setting it permanently to `null`.

+ 14 - 0
tokens/pda-mint-authority/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+nft_minter = "DU92SqPvfiDVicA4ah3E9wP9Ci7ASog1VmJhZrYU3Hyk"
+
+[registry]
+url = "https://anchor.projectserum.com"
+
+[provider]
+cluster = "devnet"
+wallet = "~/.config/solana/id.json"
+
+[scripts]
+test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"

+ 13 - 0
tokens/pda-mint-authority/anchor/Cargo.toml

@@ -0,0 +1,13 @@
+[workspace]
+members = [
+    "programs/*"
+]
+
+[profile.release]
+overflow-checks = true
+lto = "fat"
+codegen-units = 1
+[profile.release.build-override]
+opt-level = 3
+incremental = false
+codegen-units = 1

+ 15 - 0
tokens/pda-mint-authority/anchor/package.json

@@ -0,0 +1,15 @@
+{
+    "dependencies": {
+        "@metaplex-foundation/mpl-token-metadata": "^2.5.2",
+        "@project-serum/anchor": "^0.24.2"
+    },
+    "devDependencies": {
+        "@types/bn.js": "^5.1.0",
+        "@types/chai": "^4.3.0",
+        "@types/mocha": "^9.0.0",
+        "chai": "^4.3.4",
+        "mocha": "^9.0.3",
+        "ts-mocha": "^10.0.0",
+        "typescript": "^4.3.5"
+    }
+}

+ 22 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "nft-minter"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "nft_minter"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = { version="0.25.0", features=["init-if-needed"] }
+anchor-spl = "0.25.0"
+mpl-token-metadata = { version="1.2.5", features = [ "no-entrypoint" ] }
+spl-token = "3.3.0"

+ 2 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/Xargo.toml

@@ -0,0 +1,2 @@
+[target.bpfel-unknown-unknown.dependencies.std]
+features = []

+ 82 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/create.rs

@@ -0,0 +1,82 @@
+use {
+    anchor_lang::{prelude::*, solana_program::program::invoke_signed},
+    anchor_spl::token,
+    mpl_token_metadata::instruction as mpl_instruction,
+};
+
+use crate::state::MintAuthorityPda;
+
+pub fn create_token(
+    ctx: Context<CreateToken>,
+    nft_title: String,
+    nft_symbol: String,
+    nft_uri: String,
+) -> Result<()> {
+    let bump = ctx.accounts.mint_authority.bump;
+    msg!("Creating metadata account...");
+    msg!(
+        "Metadata account address: {}",
+        &ctx.accounts.metadata_account.key()
+    );
+    invoke_signed(
+        &mpl_instruction::create_metadata_accounts_v3(
+            ctx.accounts.token_metadata_program.key(), // Program ID (the Token Metadata Program)
+            ctx.accounts.metadata_account.key(),       // Metadata account
+            ctx.accounts.mint_account.key(),           // Mint account
+            ctx.accounts.mint_authority.key(),         // Mint authority
+            ctx.accounts.payer.key(),                  // Payer
+            ctx.accounts.mint_authority.key(),         // Update authority
+            nft_title,                                 // Name
+            nft_symbol,                                // Symbol
+            nft_uri,                                   // URI
+            None,                                      // Creators
+            0,                                         // Seller fee basis points
+            true,                                      // Update authority is signer
+            false,                                     // Is mutable
+            None,                                      // Collection
+            None,                                      // Uses
+            None,                                      // Collection Details
+        ),
+        &[
+            ctx.accounts.metadata_account.to_account_info(),
+            ctx.accounts.mint_account.to_account_info(),
+            ctx.accounts.mint_authority.to_account_info(),
+            ctx.accounts.payer.to_account_info(),
+            ctx.accounts.mint_authority.to_account_info(),
+            ctx.accounts.rent.to_account_info(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    msg!("NFT created successfully.");
+
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct CreateToken<'info> {
+    /// CHECK: We're about to create this with Metaplex
+    #[account(mut)]
+    pub metadata_account: UncheckedAccount<'info>,
+    #[account(
+        init,
+        payer = payer,
+        mint::decimals = 0,
+        mint::authority = mint_authority.key(),
+        mint::freeze_authority = mint_authority.key(),
+    )]
+    pub mint_account: Account<'info, token::Mint>,
+    #[account(
+        mut,
+        seeds = [ MintAuthorityPda::SEED_PREFIX.as_bytes() ],
+        bump = mint_authority.bump,
+    )]
+    pub mint_authority: Account<'info, MintAuthorityPda>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: Program<'info, System>,
+    pub token_program: Program<'info, token::Token>,
+    /// CHECK: Metaplex will check this
+    pub token_metadata_program: UncheckedAccount<'info>,
+}

+ 25 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/init.rs

@@ -0,0 +1,25 @@
+use anchor_lang::prelude::*;
+
+use crate::state::MintAuthorityPda;
+
+pub fn init(ctx: Context<Init>) -> Result<()> {
+    ctx.accounts.mint_authority.set_inner(MintAuthorityPda {
+        bump: *ctx.bumps.get("mint_authority").expect("Bump not found."),
+    });
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct Init<'info> {
+    #[account(
+        init,
+        payer = payer,
+        space = MintAuthorityPda::SIZE,
+        seeds = [ MintAuthorityPda::SEED_PREFIX.as_bytes() ],
+        bump
+    )]
+    pub mint_authority: Account<'info, MintAuthorityPda>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub system_program: Program<'info, System>,
+}

+ 138 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/mint.rs

@@ -0,0 +1,138 @@
+use {
+    anchor_lang::{prelude::*, solana_program::program::invoke_signed},
+    anchor_spl::{associated_token, token},
+    mpl_token_metadata::instruction as mpl_instruction,
+    // spl_token::instruction::AuthorityType,
+};
+
+use crate::state::MintAuthorityPda;
+
+pub fn mint_to(ctx: Context<MintTo>) -> Result<()> {
+    let bump = ctx.accounts.mint_authority.bump;
+    // Mint the NFT to the user's wallet
+    //
+    msg!("Minting NFT to associated token account...");
+    msg!(
+        "Mint: {}",
+        &ctx.accounts.mint_account.to_account_info().key()
+    );
+    msg!(
+        "Token Address: {}",
+        &ctx.accounts.associated_token_account.key()
+    );
+    token::mint_to(
+        CpiContext::new_with_signer(
+            ctx.accounts.token_program.to_account_info(),
+            token::MintTo {
+                mint: ctx.accounts.mint_account.to_account_info(),
+                to: ctx.accounts.associated_token_account.to_account_info(),
+                authority: ctx.accounts.mint_authority.to_account_info(),
+            },
+            &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+        ),
+        1,
+    )?;
+
+    // We can make this a Limited Edition NFT through Metaplex,
+    //      which will disable minting by setting the Mint & Freeze Authorities to the
+    //      Edition Account.
+    //
+    msg!("Creating edition account...");
+    msg!(
+        "Edition account address: {}",
+        ctx.accounts.edition_account.key()
+    );
+    invoke_signed(
+        &mpl_instruction::create_master_edition_v3(
+            ctx.accounts.token_metadata_program.key(), // Program ID
+            ctx.accounts.edition_account.key(),        // Edition
+            ctx.accounts.mint_account.key(),           // Mint
+            ctx.accounts.mint_authority.key(),         // Update Authority
+            ctx.accounts.mint_authority.key(),         // Mint Authority
+            ctx.accounts.metadata_account.key(),       // Metadata
+            ctx.accounts.payer.key(),                  // Payer
+            Some(1),                                   // Max Supply
+        ),
+        &[
+            ctx.accounts.edition_account.to_account_info(),
+            ctx.accounts.metadata_account.to_account_info(),
+            ctx.accounts.mint_account.to_account_info(),
+            ctx.accounts.mint_authority.to_account_info(),
+            ctx.accounts.payer.to_account_info(),
+            ctx.accounts.token_metadata_program.to_account_info(),
+            ctx.accounts.rent.to_account_info(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    // If we don't use Metaplex Editions, we must disable minting manually
+    // -------------------------------------------------------------------
+    //
+    // msg!("Disabling future minting of this NFT...");
+    // token::set_authority(
+    //     CpiContext::new_with_signer(
+    //         ctx.accounts.token_program.to_account_info(),
+    //         token::SetAuthority {
+    //             current_authority: ctx.accounts.payer.to_account_info(),
+    //             account_or_mint: ctx.accounts.mint_account.to_account_info(),
+    //         },
+    //         &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    //     ),
+    //     AuthorityType::MintTokens,
+    //     None,
+    // )?;
+    // token::set_authority(
+    //     CpiContext::new_with_signer(
+    //         ctx.accounts.token_program.to_account_info(),
+    //         token::SetAuthority {
+    //             current_authority: ctx.accounts.payer.to_account_info(),
+    //             account_or_mint: ctx.accounts.mint_account.to_account_info(),
+    //         },
+    //         &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    //     ),
+    //     AuthorityType::FreezeAccount,
+    //     None,
+    // )?;
+
+    msg!("NFT minted successfully.");
+
+    Ok(())
+}
+
+#[derive(Accounts)]
+pub struct MintTo<'info> {
+    /// CHECK: Metaplex will check this
+    #[account(mut)]
+    pub edition_account: UncheckedAccount<'info>,
+    /// CHECK: Metaplex will check this
+    #[account(mut)]
+    pub metadata_account: UncheckedAccount<'info>,
+    #[account(
+        mut,
+        mint::decimals = 0,
+        mint::authority = mint_authority.key(),
+        mint::freeze_authority = mint_authority.key(),
+    )]
+    pub mint_account: Account<'info, token::Mint>,
+    #[account(
+        mut,
+        seeds = [ MintAuthorityPda::SEED_PREFIX.as_bytes() ],
+        bump = mint_authority.bump,
+    )]
+    pub mint_authority: Account<'info, MintAuthorityPda>,
+    #[account(
+        init_if_needed,
+        payer = payer,
+        associated_token::mint = mint_account,
+        associated_token::authority = payer,
+    )]
+    pub associated_token_account: Account<'info, token::TokenAccount>,
+    #[account(mut)]
+    pub payer: Signer<'info>,
+    pub rent: Sysvar<'info, Rent>,
+    pub system_program: Program<'info, System>,
+    pub token_program: Program<'info, token::Token>,
+    pub associated_token_program: Program<'info, associated_token::AssociatedToken>,
+    /// CHECK: Metaplex will check this
+    pub token_metadata_program: UncheckedAccount<'info>,
+}

+ 7 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/instructions/mod.rs

@@ -0,0 +1,7 @@
+pub mod create;
+pub mod init;
+pub mod mint;
+
+pub use create::*;
+pub use init::*;
+pub use mint::*;

+ 30 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/lib.rs

@@ -0,0 +1,30 @@
+use anchor_lang::prelude::*;
+
+pub mod instructions;
+pub mod state;
+
+use instructions::*;
+
+declare_id!("DU92SqPvfiDVicA4ah3E9wP9Ci7ASog1VmJhZrYU3Hyk");
+
+#[program]
+pub mod nft_minter {
+    use super::*;
+
+    pub fn init(ctx: Context<Init>) -> Result<()> {
+        init::init(ctx)
+    }
+
+    pub fn create_token(
+        ctx: Context<CreateToken>,
+        nft_title: String,
+        nft_symbol: String,
+        nft_uri: String,
+    ) -> Result<()> {
+        create::create_token(ctx, nft_title, nft_symbol, nft_uri)
+    }
+
+    pub fn mint_to(ctx: Context<MintTo>) -> Result<()> {
+        mint::mint_to(ctx)
+    }
+}

+ 11 - 0
tokens/pda-mint-authority/anchor/programs/nft-minter/src/state/mod.rs

@@ -0,0 +1,11 @@
+use anchor_lang::prelude::*;
+
+#[account]
+pub struct MintAuthorityPda {
+    pub bump: u8,
+}
+
+impl MintAuthorityPda {
+    pub const SEED_PREFIX: &'static str = "mint_authority";
+    pub const SIZE: usize = 8 + 8;
+}

+ 121 - 0
tokens/pda-mint-authority/anchor/tests/test.ts

@@ -0,0 +1,121 @@
+import { 
+  PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID
+} from '@metaplex-foundation/mpl-token-metadata';
+import * as anchor from "@project-serum/anchor";
+import { ASSOCIATED_PROGRAM_ID } from '@project-serum/anchor/dist/cjs/utils/token';
+import { NftMinter } from "../target/types/nft_minter";
+
+
+describe("NFT Minter", () => {
+  
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = anchor.workspace.NftMinter as anchor.Program<NftMinter>;
+
+  const mintAuthorityPublicKey = anchor.web3.PublicKey.findProgramAddressSync(
+    [Buffer.from("mint_authority")],
+    program.programId,
+  )[0];
+
+  const nftTitle = "Homer NFT";
+  const nftSymbol = "HOMR";
+  const nftUri = "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json";
+
+  const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+
+  it("Init Mint Authority PDA", async () => {
+
+    const sx = await program.methods.init()
+      .accounts({
+        mintAuthority: mintAuthorityPublicKey,
+        payer: payer.publicKey,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Authority: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature  : ${sx}`);
+  });
+  
+  it("Create an NFT!", async () => {
+
+    const metadataAddress = (anchor.web3.PublicKey.findProgramAddressSync(
+        [
+          Buffer.from("metadata"),
+          TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+          mintKeypair.publicKey.toBuffer(),
+        ],
+        TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const sx = await program.methods.createToken(
+      nftTitle, nftSymbol, nftUri
+    )
+      .accounts({
+        metadataAccount: metadataAddress,
+        mintAccount: mintKeypair.publicKey,
+        mintAuthority: mintAuthorityPublicKey,
+        payer: payer.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        systemProgram: anchor.web3.SystemProgram.programId,
+        tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
+        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      })
+      .signers([mintKeypair, payer.payer])
+      .rpc({skipPreflight: true});
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+
+  it("Mint the NFT to your wallet!", async () => {
+
+    const metadataAddress = (anchor.web3.PublicKey.findProgramAddressSync(
+        [
+          Buffer.from("metadata"),
+          TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+          mintKeypair.publicKey.toBuffer(),
+        ],
+        TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const editionAddress = (anchor.web3.PublicKey.findProgramAddressSync(
+        [
+          Buffer.from("metadata"),
+          TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+          mintKeypair.publicKey.toBuffer(),
+          Buffer.from("edition"),
+        ],
+        TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const associatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: mintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+
+    const sx = await program.methods.mintTo()
+      .accounts({
+        associatedTokenAccount: associatedTokenAccountAddress,
+        editionAccount: editionAddress,
+        metadataAccount: metadataAddress,
+        mintAccount: mintKeypair.publicKey,
+        mintAuthority: mintAuthorityPublicKey,
+        payer: payer.publicKey,
+        rent: anchor.web3.SYSVAR_RENT_PUBKEY,
+        systemProgram: anchor.web3.SystemProgram.programId,
+        tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
+        associatedTokenProgram: ASSOCIATED_PROGRAM_ID,
+        tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+      console.log("Success!");
+      console.log(`   ATA Address: ${associatedTokenAccountAddress}`);
+      console.log(`   Tx Signature: ${sx}`);
+  });
+});

+ 10 - 0
tokens/pda-mint-authority/anchor/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true
+  }
+}

+ 8 - 0
tokens/pda-mint-authority/native/cicd.sh

@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# This script is for quick building & deploying of the program.
+# It also serves as a reference for the commands used for building & deploying Solana programs.
+# Run this bad boy with "bash cicd.sh" or "./cicd.sh"
+
+cargo build-bpf --manifest-path=./program/Cargo.toml
+solana program deploy ./program/target/deploy/program.so

+ 22 - 0
tokens/pda-mint-authority/native/package.json

@@ -0,0 +1,22 @@
+{
+  "scripts": {
+    "test": "yarn run ts-mocha -p ./tsconfig.json -t 1000000 ./tests/test.ts"
+  },
+  "dependencies": {
+    "@metaplex-foundation/mpl-token-metadata": "^2.5.2",
+    "@solana/spl-token": "^0.3.7",
+    "@solana/web3.js": "^1.73.0",
+    "borsh": "^0.7.0",
+    "buffer": "^6.0.3",
+    "fs": "^0.0.1-security"
+  },
+  "devDependencies": {
+    "@types/bn.js": "^5.1.0",
+    "@types/chai": "^4.3.1",
+    "@types/mocha": "^9.1.1",
+    "chai": "^4.3.4",
+    "mocha": "^9.0.3",
+    "ts-mocha": "^10.0.0",
+    "typescript": "^4.3.5"
+  }
+}

+ 15 - 0
tokens/pda-mint-authority/native/program/Cargo.toml

@@ -0,0 +1,15 @@
+[package]
+name = "program"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+borsh = "0.9.3"
+borsh-derive = "0.9.1"
+solana-program = "1.10.26"
+spl-token = { version="3.3.0", features = [ "no-entrypoint" ] }
+spl-associated-token-account = { version="1.0.5", features = [ "no-entrypoint" ] }
+mpl-token-metadata = { version="1.2.5", features = [ "no-entrypoint" ] }
+
+[lib]
+crate-type = ["cdylib", "lib"]

+ 124 - 0
tokens/pda-mint-authority/native/program/src/instructions/create.rs

@@ -0,0 +1,124 @@
+use {
+    borsh::{BorshDeserialize, BorshSerialize},
+    mpl_token_metadata::instruction as mpl_instruction,
+    solana_program::{
+        account_info::{next_account_info, AccountInfo},
+        entrypoint::ProgramResult,
+        msg,
+        program::{invoke, invoke_signed},
+        program_pack::Pack,
+        pubkey::Pubkey,
+        rent::Rent,
+        system_instruction,
+        sysvar::Sysvar,
+    },
+    spl_token::{instruction as token_instruction, state::Mint},
+};
+
+use crate::state::MintAuthorityPda;
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+pub struct CreateTokenArgs {
+    pub nft_title: String,
+    pub nft_symbol: String,
+    pub nft_uri: String,
+}
+
+pub fn create_token(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    args: CreateTokenArgs,
+) -> ProgramResult {
+    let accounts_iter = &mut accounts.iter();
+
+    let mint_account = next_account_info(accounts_iter)?;
+    let mint_authority = next_account_info(accounts_iter)?;
+    let metadata_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let rent = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+    let token_program = next_account_info(accounts_iter)?;
+    let token_metadata_program = next_account_info(accounts_iter)?;
+
+    let (mint_authority_pda, bump) =
+        Pubkey::find_program_address(&[MintAuthorityPda::SEED_PREFIX.as_bytes()], program_id);
+    assert!(&mint_authority_pda.eq(mint_authority.key));
+
+    // First create the account for the Mint
+    //
+    msg!("Creating mint account...");
+    msg!("Mint: {}", mint_account.key);
+    invoke(
+        &system_instruction::create_account(
+            &payer.key,
+            &mint_account.key,
+            (Rent::get()?).minimum_balance(Mint::LEN),
+            Mint::LEN as u64,
+            &token_program.key,
+        ),
+        &[
+            mint_account.clone(),
+            payer.clone(),
+            system_program.clone(),
+            token_program.clone(),
+        ],
+    )?;
+
+    // Now initialize that account as a Mint (standard Mint)
+    //
+    msg!("Initializing mint account...");
+    msg!("Mint: {}", mint_account.key);
+    invoke(
+        &token_instruction::initialize_mint(
+            &token_program.key,
+            &mint_account.key,
+            &mint_authority.key,
+            Some(&mint_authority.key),
+            0, // 0 Decimals for the NFT standard
+        )?,
+        &[
+            mint_account.clone(),
+            mint_authority.clone(),
+            token_program.clone(),
+            rent.clone(),
+        ],
+    )?;
+
+    // Now create the account for that Mint's metadata
+    //
+    msg!("Creating metadata account...");
+    msg!("Metadata account address: {}", metadata_account.key);
+    invoke_signed(
+        &mpl_instruction::create_metadata_accounts_v3(
+            *token_metadata_program.key,
+            *metadata_account.key,
+            *mint_account.key,
+            *mint_authority.key,
+            *payer.key,
+            *mint_authority.key,
+            args.nft_title,
+            args.nft_symbol,
+            args.nft_uri,
+            None,
+            0,
+            true,
+            false,
+            None,
+            None,
+            None,
+        ),
+        &[
+            metadata_account.clone(),
+            mint_account.clone(),
+            mint_authority.clone(),
+            payer.clone(),
+            token_metadata_program.clone(),
+            rent.clone(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    msg!("Token mint created successfully.");
+
+    Ok(())
+}

+ 48 - 0
tokens/pda-mint-authority/native/program/src/instructions/init.rs

@@ -0,0 +1,48 @@
+use borsh::BorshSerialize;
+use solana_program::{
+    account_info::{next_account_info, AccountInfo},
+    entrypoint::ProgramResult,
+    msg,
+    program::invoke_signed,
+    pubkey::Pubkey,
+    rent::Rent,
+    system_instruction,
+    sysvar::Sysvar,
+};
+
+use crate::state::MintAuthorityPda;
+
+pub fn init(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+    let accounts_iter = &mut accounts.iter();
+
+    let mint_authority = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let system_program = next_account_info(accounts_iter)?;
+
+    let (mint_authority_pda, bump) =
+        Pubkey::find_program_address(&[MintAuthorityPda::SEED_PREFIX.as_bytes()], program_id);
+    assert!(&mint_authority_pda.eq(mint_authority.key));
+
+    msg!("Creating mint authority PDA...");
+    msg!("Mint Authority: {}", &mint_authority.key);
+    invoke_signed(
+        &system_instruction::create_account(
+            &payer.key,
+            &mint_authority.key,
+            (Rent::get()?).minimum_balance(MintAuthorityPda::SIZE),
+            MintAuthorityPda::SIZE as u64,
+            program_id,
+        ),
+        &[
+            mint_authority.clone(),
+            payer.clone(),
+            system_program.clone(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    let data = MintAuthorityPda { bump };
+    data.serialize(&mut &mut mint_authority.data.borrow_mut()[..])?;
+
+    Ok(())
+}

+ 145 - 0
tokens/pda-mint-authority/native/program/src/instructions/mint.rs

@@ -0,0 +1,145 @@
+use {
+    mpl_token_metadata::instruction as mpl_instruction,
+    solana_program::{
+        account_info::{next_account_info, AccountInfo},
+        entrypoint::ProgramResult,
+        msg,
+        program::{invoke, invoke_signed},
+        pubkey::Pubkey,
+    },
+    spl_associated_token_account::instruction as associated_token_account_instruction,
+    spl_token::instruction as token_instruction,
+};
+
+use crate::state::MintAuthorityPda;
+
+pub fn mint_to(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
+    let accounts_iter = &mut accounts.iter();
+
+    let mint_account = next_account_info(accounts_iter)?;
+    let metadata_account = next_account_info(accounts_iter)?;
+    let edition_account = next_account_info(accounts_iter)?;
+    let mint_authority = next_account_info(accounts_iter)?;
+    let associated_token_account = next_account_info(accounts_iter)?;
+    let payer = next_account_info(accounts_iter)?;
+    let rent = next_account_info(accounts_iter)?;
+    let _system_program = next_account_info(accounts_iter)?;
+    let token_program = next_account_info(accounts_iter)?;
+    let associated_token_program = next_account_info(accounts_iter)?;
+    let token_metadata_program = next_account_info(accounts_iter)?;
+
+    let (mint_authority_pda, bump) =
+        Pubkey::find_program_address(&[MintAuthorityPda::SEED_PREFIX.as_bytes()], program_id);
+    assert!(&mint_authority_pda.eq(mint_authority.key));
+
+    if associated_token_account.lamports() == 0 {
+        msg!("Creating associated token account...");
+        invoke(
+            &associated_token_account_instruction::create_associated_token_account(
+                &payer.key,
+                &payer.key,
+                &mint_account.key,
+            ),
+            &[
+                mint_account.clone(),
+                associated_token_account.clone(),
+                payer.clone(),
+                token_program.clone(),
+                associated_token_program.clone(),
+            ],
+        )?;
+    } else {
+        msg!("Associated token account exists.");
+    }
+    msg!("Associated Token Address: {}", associated_token_account.key);
+
+    // Mint the NFT to the user's wallet
+    //
+    msg!("Minting NFT to associated token account...");
+    invoke_signed(
+        &token_instruction::mint_to(
+            &token_program.key,
+            &mint_account.key,
+            &associated_token_account.key,
+            &mint_authority.key,
+            &[&mint_authority.key],
+            1,
+        )?,
+        &[
+            mint_account.clone(),
+            mint_authority.clone(),
+            associated_token_account.clone(),
+            token_program.clone(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    // We can make this a Limited Edition NFT through Metaplex,
+    //      which will disable minting by setting the Mint & Freeze Authorities to the
+    //      Edition Account.
+    //
+    msg!("Creating edition account...");
+    msg!("Edition account address: {}", edition_account.key);
+    invoke_signed(
+        &mpl_instruction::create_master_edition_v3(
+            *token_metadata_program.key, // Program ID
+            *edition_account.key,        // Edition
+            *mint_account.key,           // Mint
+            *mint_authority.key,         // Update Authority
+            *mint_authority.key,         // Mint Authority
+            *metadata_account.key,       // Metadata
+            *payer.key,                  // Payer
+            Some(1),                     // Max Supply
+        ),
+        &[
+            edition_account.clone(),
+            metadata_account.clone(),
+            mint_account.clone(),
+            mint_authority.clone(),
+            payer.clone(),
+            token_metadata_program.clone(),
+            rent.clone(),
+        ],
+        &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    )?;
+
+    // If we don't use Metaplex Editions, we must disable minting manually
+    //
+    // -------------------------------------------------------------------
+    // msg!("Disabling future minting of this NFT...");
+    // invoke_signed(
+    //     &token_instruction::set_authority(
+    //         &token_program.key,
+    //         &mint_account.key,
+    //         None,
+    //         token_instruction::AuthorityType::MintTokens,
+    //         &mint_authority.key,
+    //         &[&mint_authority.key],
+    //     )?,
+    //     &[
+    //         mint_account.clone(),
+    //         mint_authority.clone(),
+    //         token_program.clone(),
+    //     ],
+    // )?;
+    // invoke_signed(
+    //     &token_instruction::set_authority(
+    //         &token_program.key,
+    //         &mint_account.key,
+    //         None,
+    //         token_instruction::AuthorityType::FreezeAccount,
+    //         &mint_authority.key,
+    //         &[&mint_authority.key],
+    //     )?,
+    //     &[
+    //         mint_account.clone(),
+    //         mint_authority.clone(),
+    //         token_program.clone(),
+    //     ],
+    //     &[&[MintAuthorityPda::SEED_PREFIX.as_bytes(), &[bump]]],
+    // )?;
+
+    msg!("NFT minted successfully.");
+
+    Ok(())
+}

+ 7 - 0
tokens/pda-mint-authority/native/program/src/instructions/mod.rs

@@ -0,0 +1,7 @@
+pub mod create;
+pub mod init;
+pub mod mint;
+
+pub use create::*;
+pub use init::*;
+pub use mint::*;

+ 17 - 0
tokens/pda-mint-authority/native/program/src/lib.rs

@@ -0,0 +1,17 @@
+use solana_program::{
+    account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, pubkey::Pubkey,
+};
+
+pub mod instructions;
+pub mod processor;
+pub mod state;
+
+entrypoint!(process_instruction);
+
+fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+    processor::process_instruction(program_id, accounts, instruction_data)
+}

+ 31 - 0
tokens/pda-mint-authority/native/program/src/processor.rs

@@ -0,0 +1,31 @@
+use {
+    borsh::{BorshDeserialize, BorshSerialize},
+    solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey},
+};
+
+use crate::instructions::{
+    create::{create_token, CreateTokenArgs},
+    init::init,
+    mint::mint_to,
+};
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+enum MyInstruction {
+    Init,
+    Create(CreateTokenArgs),
+    Mint,
+}
+
+pub fn process_instruction(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+    let instruction = MyInstruction::try_from_slice(instruction_data)?;
+
+    match instruction {
+        MyInstruction::Init => init(program_id, accounts),
+        MyInstruction::Create(args) => create_token(program_id, accounts, args),
+        MyInstruction::Mint => mint_to(program_id, accounts),
+    }
+}

+ 11 - 0
tokens/pda-mint-authority/native/program/src/state/mod.rs

@@ -0,0 +1,11 @@
+use borsh::{BorshDeserialize, BorshSerialize};
+
+#[derive(BorshDeserialize, BorshSerialize)]
+pub struct MintAuthorityPda {
+    pub bump: u8,
+}
+
+impl MintAuthorityPda {
+    pub const SEED_PREFIX: &'static str = "mint_authority";
+    pub const SIZE: usize = 8 + 8;
+}

+ 67 - 0
tokens/pda-mint-authority/native/tests/instructions.ts

@@ -0,0 +1,67 @@
+import * as borsh from "borsh";
+
+
+class Assignable {
+    constructor(properties) {
+        Object.keys(properties).map((key) => {
+            return (this[key] = properties[key]);
+        });
+    };
+};
+
+export enum NftMinterInstruction {
+    Init,
+    Create,
+    Mint,
+}
+
+export class InitArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(InitArgsSchema, this));
+    }
+}
+const InitArgsSchema = new Map([
+    [
+        InitArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+            ]
+        }
+    ]
+]);
+
+export class CreateTokenArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(CreateTokenArgsSchema, this));
+    }
+};
+const CreateTokenArgsSchema = new Map([
+    [
+        CreateTokenArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+                ['nft_title', 'string'],
+                ['nft_symbol', 'string'],
+                ['nft_uri', 'string'],
+            ]
+        }
+    ]
+]);
+
+export class MintToArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(MintToArgsSchema, this));
+    }
+};
+const MintToArgsSchema = new Map([
+    [
+        MintToArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+            ]
+        }
+    ]
+]);

+ 180 - 0
tokens/pda-mint-authority/native/tests/test.ts

@@ -0,0 +1,180 @@
+import { 
+    PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID
+} from '@metaplex-foundation/mpl-token-metadata';
+import {
+    Connection,
+    Keypair,
+    PublicKey,
+    SystemProgram,
+    SYSVAR_RENT_PUBKEY,
+    TransactionInstruction,
+    Transaction,
+    sendAndConfirmTransaction,
+} from '@solana/web3.js';
+import {
+    ASSOCIATED_TOKEN_PROGRAM_ID,
+    getAssociatedTokenAddress,
+    TOKEN_PROGRAM_ID,
+} from '@solana/spl-token';
+import { Buffer } from "buffer";
+import { 
+    CreateTokenArgs,
+    InitArgs,
+    MintToArgs,
+    NftMinterInstruction,
+} from './instructions';
+
+
+function createKeypairFromFile(path: string): Keypair {
+    return Keypair.fromSecretKey(
+        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
+    )
+};
+
+
+describe("NFT Minter", async () => {
+
+    // const connection = new Connection(`http://localhost:8899`, 'confirmed');
+    const connection = new Connection(`https://api.devnet.solana.com/`, 'confirmed');
+    const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
+    const program = createKeypairFromFile('./program/target/deploy/program-keypair.json');
+
+    const mintAuthorityPublicKey = PublicKey.findProgramAddressSync(
+        [Buffer.from("mint_authority")],
+        program.publicKey,
+    )[0];
+
+    const mintKeypair: Keypair = Keypair.generate();
+
+    it("Init Mint Authority PDA", async () => {
+
+        const instructionData = new InitArgs({
+            instruction: NftMinterInstruction.Init,
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: mintAuthorityPublicKey, isSigner: false, isWritable: true },
+                { pubkey: payer.publicKey, isSigner: false, isWritable: true },
+                { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
+            ],
+            programId: program.publicKey,
+            data: instructionData.toBuffer(),
+        });
+
+        const sx = await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer],
+            { skipPreflight: true }
+        );
+
+        console.log("Success!");
+        console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+    });
+
+    it("Create an NFT!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        const instructionData = new CreateTokenArgs({
+            instruction: NftMinterInstruction.Create,
+            nft_title: "Homer NFT",
+            nft_symbol: "HOMR",
+            nft_uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json",
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: mintKeypair.publicKey, isSigner: true, isWritable: true },            // Mint account
+                { pubkey: mintAuthorityPublicKey, isSigner: false, isWritable: true },                 // Mint authority account
+                { pubkey: metadataAddress, isSigner: false, isWritable: true },                 // Metadata account
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Payer
+                { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },             // Rent account
+                { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },        // System program
+                { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },               // Token program
+                { pubkey: TOKEN_METADATA_PROGRAM_ID, isSigner: false, isWritable: false },      // Token metadata program
+            ],
+            programId: program.publicKey,
+            data: instructionData.toBuffer(),
+        });
+
+        const sx = await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer, mintKeypair],
+            { skipPreflight: true }
+        );
+
+        console.log("Success!");
+        console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+    });
+
+    it("Mint the NFT to your wallet!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+
+        const editionAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+              Buffer.from("edition"),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+
+        const associatedTokenAccountAddress = await getAssociatedTokenAddress(
+            mintKeypair.publicKey,
+            payer.publicKey,
+        );
+        
+        const instructionData = new MintToArgs({
+            instruction: NftMinterInstruction.Mint,
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true },           // Mint account
+                { pubkey: metadataAddress, isSigner: false, isWritable: true },                 // Metadata account
+                { pubkey: editionAddress, isSigner: false, isWritable: true },                  // Edition account
+                { pubkey: mintAuthorityPublicKey, isSigner: false, isWritable: true },                 // Mint authority account
+                { pubkey: associatedTokenAccountAddress, isSigner: false, isWritable: true },   // ATA
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Payer
+                { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },             // Rent account
+                { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },        // System program
+                { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },               // Token program
+                { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },    // Associated token program
+                { pubkey: TOKEN_METADATA_PROGRAM_ID, isSigner: false, isWritable: false },      // Token metadata program
+            ],
+            programId: program.publicKey,
+            data: instructionData.toBuffer(),
+        });
+
+        const sx = await sendAndConfirmTransaction(
+            connection, 
+            new Transaction().add(ix),
+            [payer],
+        );
+
+        console.log("Success!");
+        console.log(`   ATA Address: ${associatedTokenAccountAddress}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+});

+ 10 - 0
tokens/pda-mint-authority/native/tsconfig.json

@@ -0,0 +1,10 @@
+{
+  "compilerOptions": {
+    "types": ["mocha", "chai"],
+    "typeRoots": ["./node_modules/@types"],
+    "lib": ["es2015"],
+    "module": "commonjs",
+    "target": "es6",
+    "esModuleInterop": true,
+  }
+}