jpcaulfi vor 2 Jahren
Ursprung
Commit
7fa5bc61c4
31 geänderte Dateien mit 1040 neuen und 13 gelöschten Zeilen
  1. 12 1
      tokens/create-token/README.md
  2. 2 2
      tokens/create-token/native/package.json
  3. 1 0
      tokens/nft-minter/README.md
  4. 14 0
      tokens/nft-minter/anchor/Anchor.toml
  5. 13 0
      tokens/nft-minter/anchor/Cargo.toml
  6. 15 0
      tokens/nft-minter/anchor/package.json
  7. 22 0
      tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml
  8. 2 0
      tokens/nft-minter/anchor/programs/nft-minter/Xargo.toml
  9. 76 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/create.rs
  10. 127 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mint.rs
  11. 5 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mod.rs
  12. 38 0
      tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs
  13. 101 0
      tokens/nft-minter/anchor/tests/test.ts
  14. 10 0
      tokens/nft-minter/anchor/tsconfig.json
  15. 8 0
      tokens/nft-minter/native/cicd.sh
  16. 22 0
      tokens/nft-minter/native/package.json
  17. 15 0
      tokens/nft-minter/native/program/Cargo.toml
  18. 126 0
      tokens/nft-minter/native/program/src/instructions/create.rs
  19. 146 0
      tokens/nft-minter/native/program/src/instructions/mint.rs
  20. 5 0
      tokens/nft-minter/native/program/src/instructions/mod.rs
  21. 22 0
      tokens/nft-minter/native/program/src/lib.rs
  22. 41 0
      tokens/nft-minter/native/program/src/processor.rs
  23. 50 0
      tokens/nft-minter/native/tests/instructions.ts
  24. 146 0
      tokens/nft-minter/native/tests/test.ts
  25. 10 0
      tokens/nft-minter/native/tsconfig.json
  26. 1 1
      tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml
  27. 1 1
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs
  28. 2 2
      tokens/spl-token-minter/native/package.json
  29. 2 1
      tokens/spl-token-minter/native/program/src/instructions/create.rs
  30. 2 2
      tokens/spl-token-minter/native/program/src/instructions/mint.rs
  31. 3 3
      tokens/spl-token-minter/native/tests/test.ts

+ 12 - 1
tokens/create-token/README.md

@@ -1 +1,12 @@
-# Create an SPL Token
+# Create an SPL Token
+
+This example demonstrates how to create an SPL Token on Solana with some metadata such as a token symbol and icon.
+
+### :key: Keys:
+
+- SPL Tokens by default have **9 decimals**, and **NFTs have 0 decimals**. "Decimals" here means the number of decimal; ie. a token with 3 decimals will be tracked in increments of 0.001.   
+- You can use [Metaplex's Token Metadata Program](https://docs.metaplex.com/) to create metadata for your token.
+- Steps:
+    1. Create an account for the Mint.
+    2. Initialize that account as a Mint Account.
+    3. Create a metadata account associated with that Mint Account.

+ 2 - 2
tokens/create-token/native/package.json

@@ -4,8 +4,8 @@
   },
   "dependencies": {
     "@metaplex-foundation/mpl-token-metadata": "^2.5.2",
-    "@solana/spl-token": "^0.2.0",
-    "@solana/web3.js": "^1.47.3",
+    "@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"

+ 1 - 0
tokens/nft-minter/README.md

@@ -0,0 +1 @@
+# NFT Minter

+ 14 - 0
tokens/nft-minter/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+nft_minter = "A6itasS5iqANkC9yrzP1HJPBnJxj9tC8G5TmJzQGogGG"
+
+[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/nft-minter/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/nft-minter/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/nft-minter/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/nft-minter/anchor/programs/nft-minter/Xargo.toml

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

+ 76 - 0
tokens/nft-minter/anchor/programs/nft-minter/src/instructions/create.rs

@@ -0,0 +1,76 @@
+use {
+    anchor_lang::{
+        prelude::*,
+        solana_program::program::invoke,
+    },
+    anchor_spl::token,
+    mpl_token_metadata::instruction as mpl_instruction,
+};
+
+
+pub fn create_token(
+    ctx: Context<CreateToken>, 
+    nft_title: String, 
+    nft_symbol: String, 
+    nft_uri: String,
+) -> Result<()> {
+
+    msg!("Creating metadata account...");
+    msg!("Metadata account address: {}", &ctx.accounts.metadata_account.key());
+    invoke(
+        &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(),
+        ],
+    )?;
+
+    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>,
+    pub mint_authority: SystemAccount<'info>,
+    #[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>,
+}

+ 127 - 0
tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mint.rs

@@ -0,0 +1,127 @@
+use {
+    anchor_lang::{
+        prelude::*,
+        solana_program::program::invoke,
+    },
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+    mpl_token_metadata::instruction as mpl_instruction,
+    // spl_token::instruction::AuthorityType,
+};
+
+
+pub fn mint_to(
+    ctx: Context<MintTo>, 
+) -> Result<()> {
+
+    // 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(
+            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(),
+            },
+        ),
+        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(
+        &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(),
+        ]
+    )?;
+
+    // If we don't use Metaplex Editions, we must disable minting manually
+    // -------------------------------------------------------------------
+    //
+    // msg!("Disabling future minting of this NFT...");   
+    // token::set_authority(
+    //     CpiContext::new(
+    //         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(),
+    //         },
+    //     ),
+    //     AuthorityType::MintTokens,
+    //     None,
+    // )?;
+    // token::set_authority(
+    //     CpiContext::new(
+    //         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(),
+    //         },
+    //     ),
+    //     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(),
+    )]
+    pub mint_account: Account<'info, token::Mint>,
+    pub mint_authority: SystemAccount<'info>,
+    #[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>,
+}

+ 5 - 0
tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mod.rs

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

+ 38 - 0
tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs

@@ -0,0 +1,38 @@
+use anchor_lang::prelude::*;
+
+pub mod instructions;
+
+use instructions::*;
+
+
+declare_id!("A6itasS5iqANkC9yrzP1HJPBnJxj9tC8G5TmJzQGogGG");
+
+
+#[program]
+pub mod nft_minter {
+    use super::*;
+
+    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, 
+        )
+    }
+}

+ 101 - 0
tokens/nft-minter/anchor/tests/test.ts

@@ -0,0 +1,101 @@
+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 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("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: payer.publicKey,
+        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();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+
+  it("Mint some tokens 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: payer.publicKey,
+        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({skipPreflight: true});
+
+      console.log("Success!");
+      console.log(`   ATA Address: ${associatedTokenAccountAddress}`);
+      console.log(`   Tx Signature: ${sx}`);
+  });
+});

+ 10 - 0
tokens/nft-minter/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/nft-minter/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/nft-minter/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/nft-minter/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"]

+ 126 - 0
tokens/nft-minter/native/program/src/instructions/create.rs

@@ -0,0 +1,126 @@
+use {
+    borsh::{ 
+        BorshDeserialize, 
+        BorshSerialize, 
+    },
+    solana_program::{
+        account_info::{next_account_info, AccountInfo}, 
+        entrypoint::ProgramResult, 
+        msg, 
+        program::invoke,
+        program_pack::Pack,
+        rent::Rent,
+        system_instruction,
+        sysvar::Sysvar,
+    },
+    spl_token::{
+        instruction as token_instruction,
+        state::Mint,
+    },
+    mpl_token_metadata::{
+        instruction as mpl_instruction,
+    },
+};
+
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+pub struct CreateTokenArgs {
+    pub nft_title: String,
+    pub nft_symbol: String,
+    pub nft_uri: String,
+}
+
+
+pub fn create_token(
+    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)?;
+
+    // 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(
+        &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(),
+        ]
+    )?;
+
+    msg!("Token mint created successfully.");
+
+    Ok(())
+}

+ 146 - 0
tokens/nft-minter/native/program/src/instructions/mint.rs

@@ -0,0 +1,146 @@
+use {
+    solana_program::{
+        account_info::{next_account_info, AccountInfo},
+        entrypoint::ProgramResult, 
+        msg, 
+        program::invoke,
+    },
+    spl_token::{
+        instruction as token_instruction,
+    },
+    spl_associated_token_account::{
+        instruction as associated_token_account_instruction,
+    },
+    mpl_token_metadata::{
+        instruction as mpl_instruction,
+    },
+};
+
+
+pub fn mint_to(
+    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)?;
+
+    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(
+        &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(),
+        ],
+    )?;
+
+    // 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(
+        &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(),
+        ]
+    )?;
+
+    // If we don't use Metaplex Editions, we must disable minting manually
+    //
+    // -------------------------------------------------------------------
+    // msg!("Disabling future minting of this NFT...");
+    // invoke(
+    //     &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(
+    //     &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(),
+    //     ],
+    // )?;
+
+    msg!("NFT minted successfully.");
+
+    Ok(())
+}
+

+ 5 - 0
tokens/nft-minter/native/program/src/instructions/mod.rs

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

+ 22 - 0
tokens/nft-minter/native/program/src/lib.rs

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

+ 41 - 0
tokens/nft-minter/native/program/src/processor.rs

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

+ 50 - 0
tokens/nft-minter/native/tests/instructions.ts

@@ -0,0 +1,50 @@
+import * as borsh from "borsh";
+
+
+class Assignable {
+    constructor(properties) {
+        Object.keys(properties).map((key) => {
+            return (this[key] = properties[key]);
+        });
+    };
+};
+
+export enum NftMinterInstruction {
+    Create,
+    Mint,
+}
+
+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'],
+            ]
+        }
+    ]
+]);

+ 146 - 0
tokens/nft-minter/native/tests/test.ts

@@ -0,0 +1,146 @@
+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,
+    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 mintKeypair: Keypair = Keypair.generate();
+
+    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: payer.publicKey, 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]
+        );
+
+        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: payer.publicKey, 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],
+            {skipPreflight: true}
+        );
+
+        console.log("Success!");
+        console.log(`   ATA Address: ${associatedTokenAccountAddress}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+});

+ 10 - 0
tokens/nft-minter/native/tsconfig.json

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

+ 1 - 1
tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml

@@ -16,6 +16,6 @@ cpi = ["no-entrypoint"]
 default = []
 
 [dependencies]
-anchor-lang = "0.25.0"
+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" ] }

+ 1 - 1
tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs

@@ -43,7 +43,7 @@ pub struct MintTo<'info> {
     pub mint_account: Account<'info, token::Mint>,
     pub mint_authority: SystemAccount<'info>,
     #[account(
-        init,
+        init_if_needed,
         payer = payer,
         associated_token::mint = mint_account,
         associated_token::authority = payer,

+ 2 - 2
tokens/spl-token-minter/native/package.json

@@ -4,8 +4,8 @@
   },
   "dependencies": {
     "@metaplex-foundation/mpl-token-metadata": "^2.5.2",
-    "@solana/spl-token": "^0.2.0",
-    "@solana/web3.js": "^1.47.3",
+    "@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"

+ 2 - 1
tokens/spl-token-minter/native/program/src/instructions/create.rs

@@ -92,7 +92,7 @@ pub fn create_token(
     msg!("Creating metadata account...");
     msg!("Metadata account address: {}", metadata_account.key);
     invoke(
-        &mpl_instruction::create_metadata_accounts_v2(
+        &mpl_instruction::create_metadata_accounts_v3(
             *token_metadata_program.key,
             *metadata_account.key,
             *mint_account.key,
@@ -108,6 +108,7 @@ pub fn create_token(
             false,
             None,
             None,
+            None,
         ),
         &[
             metadata_account.clone(),

+ 2 - 2
tokens/spl-token-minter/native/program/src/instructions/mint.rs

@@ -35,8 +35,7 @@ pub fn mint_to(
     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 system_program = next_account_info(accounts_iter)?;
     let token_program = next_account_info(accounts_iter)?;
     let associated_token_program = next_account_info(accounts_iter)?;
 
@@ -52,6 +51,7 @@ pub fn mint_to(
                 mint_account.clone(),
                 associated_token_account.clone(),
                 payer.clone(),
+                system_program.clone(),
                 token_program.clone(),
                 associated_token_program.clone(),
             ]

+ 3 - 3
tokens/spl-token-minter/native/tests/test.ts

@@ -103,8 +103,7 @@ describe("SPL Token Minter", async () => {
                 { pubkey: payer.publicKey, 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: SystemProgram.programId, isSigner: false, isWritable: true },                  // System program
                 { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },               // Token program
                 { pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },    // Token metadata program
             ],
@@ -115,7 +114,8 @@ describe("SPL Token Minter", async () => {
         const sx = await sendAndConfirmTransaction(
             connection, 
             new Transaction().add(ix),
-            [payer]
+            [payer],
+            {skipPreflight: true}
         );
 
         console.log("Success!");