Browse Source

Merge pull request #19 from solana-developers/new-examples/tokens

New examples/tokens
Joe Caulfield 2 years ago
parent
commit
a21077e3f1
100 changed files with 3210 additions and 962 deletions
  1. 6 0
      tokens/.assets/nft.json
  2. 6 0
      tokens/.assets/spl-token.json
  3. 0 28
      tokens/README.md
  4. 50 0
      tokens/create-token/README.md
  5. 14 0
      tokens/create-token/anchor/Anchor.toml
  6. 0 0
      tokens/create-token/anchor/Cargo.toml
  7. 1 0
      tokens/create-token/anchor/package.json
  8. 2 2
      tokens/create-token/anchor/programs/create-token/Cargo.toml
  9. 0 0
      tokens/create-token/anchor/programs/create-token/Xargo.toml
  10. 94 0
      tokens/create-token/anchor/programs/create-token/src/lib.rs
  11. 90 0
      tokens/create-token/anchor/tests/test.ts
  12. 0 0
      tokens/create-token/anchor/tsconfig.json
  13. 2 2
      tokens/create-token/native/cicd.sh
  14. 3 2
      tokens/create-token/native/package.json
  15. 0 0
      tokens/create-token/native/program/Cargo.toml
  16. 43 58
      tokens/create-token/native/program/src/lib.rs
  17. 159 0
      tokens/create-token/native/tests/test.ts
  18. 0 0
      tokens/create-token/native/tsconfig.json
  19. 3 0
      tokens/freeze-tokens/README.md
  20. 3 0
      tokens/metaplex-expanded/README.md
  21. 0 11
      tokens/mint-2/README.md
  22. 0 9
      tokens/mint-2/anchor/programs/mint-2/src/instructions/mod.rs
  23. 0 68
      tokens/mint-2/anchor/programs/mint-2/src/lib.rs
  24. 0 175
      tokens/mint-2/anchor/tests/test.ts
  25. 0 6
      tokens/mint-2/anchor/tests/token_metadata.json
  26. 0 83
      tokens/mint-2/native/program/src/instructions/mint_to_wallet.rs
  27. 0 3
      tokens/mint-2/native/program/src/instructions/mod.rs
  28. 0 81
      tokens/mint-2/native/program/src/instructions/transfer_to_wallet.rs
  29. 0 53
      tokens/mint-2/native/program/src/processor.rs
  30. 0 21
      tokens/mint-2/native/program/src/state/mint_state.rs
  31. 0 1
      tokens/mint-2/native/program/src/state/mod.rs
  32. 0 240
      tokens/mint-2/native/tests/test.ts
  33. 0 6
      tokens/mint-2/native/tests/token_metadata.json
  34. 27 0
      tokens/nft-minter/README.md
  35. 1 1
      tokens/nft-minter/anchor/Anchor.toml
  36. 13 0
      tokens/nft-minter/anchor/Cargo.toml
  37. 15 0
      tokens/nft-minter/anchor/package.json
  38. 22 0
      tokens/nft-minter/anchor/programs/nft-minter/Cargo.toml
  39. 2 0
      tokens/nft-minter/anchor/programs/nft-minter/Xargo.toml
  40. 76 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/create.rs
  41. 128 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mint.rs
  42. 5 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mod.rs
  43. 38 0
      tokens/nft-minter/anchor/programs/nft-minter/src/lib.rs
  44. 101 0
      tokens/nft-minter/anchor/tests/test.ts
  45. 10 0
      tokens/nft-minter/anchor/tsconfig.json
  46. 8 0
      tokens/nft-minter/native/cicd.sh
  47. 22 0
      tokens/nft-minter/native/package.json
  48. 15 0
      tokens/nft-minter/native/program/Cargo.toml
  49. 126 0
      tokens/nft-minter/native/program/src/instructions/create.rs
  50. 146 0
      tokens/nft-minter/native/program/src/instructions/mint.rs
  51. 5 0
      tokens/nft-minter/native/program/src/instructions/mod.rs
  52. 0 1
      tokens/nft-minter/native/program/src/lib.rs
  53. 41 0
      tokens/nft-minter/native/program/src/processor.rs
  54. 50 0
      tokens/nft-minter/native/tests/instructions.ts
  55. 145 0
      tokens/nft-minter/native/tests/test.ts
  56. 10 0
      tokens/nft-minter/native/tsconfig.json
  57. 23 0
      tokens/spl-token-minter/README.md
  58. 14 0
      tokens/spl-token-minter/anchor/Anchor.toml
  59. 13 0
      tokens/spl-token-minter/anchor/Cargo.toml
  60. 15 0
      tokens/spl-token-minter/anchor/package.json
  61. 21 0
      tokens/spl-token-minter/anchor/programs/spl-token-minter/Cargo.toml
  62. 2 0
      tokens/spl-token-minter/anchor/programs/spl-token-minter/Xargo.toml
  63. 15 35
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/create.rs
  64. 11 27
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs
  65. 5 0
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mod.rs
  66. 40 0
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/lib.rs
  67. 81 0
      tokens/spl-token-minter/anchor/tests/test.ts
  68. 10 0
      tokens/spl-token-minter/anchor/tsconfig.json
  69. 8 0
      tokens/spl-token-minter/native/cicd.sh
  70. 22 0
      tokens/spl-token-minter/native/package.json
  71. 15 0
      tokens/spl-token-minter/native/program/Cargo.toml
  72. 126 0
      tokens/spl-token-minter/native/program/src/instructions/create.rs
  73. 86 0
      tokens/spl-token-minter/native/program/src/instructions/mint.rs
  74. 5 0
      tokens/spl-token-minter/native/program/src/instructions/mod.rs
  75. 22 0
      tokens/spl-token-minter/native/program/src/lib.rs
  76. 44 0
      tokens/spl-token-minter/native/program/src/processor.rs
  77. 51 0
      tokens/spl-token-minter/native/tests/instructions.ts
  78. 124 0
      tokens/spl-token-minter/native/tests/test.ts
  79. 10 0
      tokens/spl-token-minter/native/tsconfig.json
  80. 3 0
      tokens/token-2022/README.md
  81. 3 0
      tokens/token-lending/README.md
  82. 7 0
      tokens/transfer-tokens/README.md
  83. 14 0
      tokens/transfer-tokens/anchor/Anchor.toml
  84. 13 0
      tokens/transfer-tokens/anchor/Cargo.toml
  85. 15 0
      tokens/transfer-tokens/anchor/package.json
  86. 21 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml
  87. 2 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/Xargo.toml
  88. 83 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs
  89. 128 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_nft.rs
  90. 12 30
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_spl.rs
  91. 9 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mod.rs
  92. 20 19
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs
  93. 62 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs
  94. 245 0
      tokens/transfer-tokens/anchor/tests/test.ts
  95. 10 0
      tokens/transfer-tokens/anchor/tsconfig.json
  96. 8 0
      tokens/transfer-tokens/native/cicd.sh
  97. 22 0
      tokens/transfer-tokens/native/package.json
  98. 15 0
      tokens/transfer-tokens/native/program/Cargo.toml
  99. 127 0
      tokens/transfer-tokens/native/program/src/instructions/create.rs
  100. 146 0
      tokens/transfer-tokens/native/program/src/instructions/mint_nft.rs

+ 6 - 0
tokens/.assets/nft.json

@@ -0,0 +1,6 @@
+{
+    "name": "Homer NFT",
+    "symbol": "HOMR",
+    "description": "An NFT of Homer Simpson",
+    "image": "https://static.onecms.io/wp-content/uploads/sites/6/2018/08/simp_homersingle08_f_hires2-2000.jpg"
+}

+ 6 - 0
tokens/.assets/spl-token.json

@@ -0,0 +1,6 @@
+{
+    "name": "Solana Gold",
+    "symbol": "GOLDSOL",
+    "description": "A gold Solana SPL token :)",
+    "image": "https://w7.pngwing.com/pngs/153/594/png-transparent-solana-coin-sign-icon-shiny-golden-symmetric-geometrical-design.png"
+}

+ 0 - 28
tokens/README.md

@@ -1,28 +0,0 @@
-### :warning: All token examples are on devnet!
-
-`https://api.devnet.solana.com/`
-
-### About Tokens
-
-Tokens on Solana are - like everything else on Solana - accounts! They:
-- are owned by the Token Program
-- can only be changed by the Token Program
-- have an associated Mint Authority - the only account that can mint new tokens (by calling the Token program)
-
-How they work:   
-- :apple: `Mint Account` - stores information about the token.
-- :handbag: `Associated Token Account` - stores a specific balance of the Mint Account (this is essentially a counter).
-
-> You can read all about tokens in [Solana's official SPL Token documentation](https://spl.solana.com/token).
-
-### This Folder
-
-All examples in this folder demonstrate the following:
-- How to create a new token mint.
-- How to mint some amount of this token to a wallet.
-- How to transfer this token to a wallet.   
-
-Each example differs in a few key aspects:
-- `mint-1` - The **Mint** and the **Mint Authority** are generated keypairs.
-- `mint-2` - The **Mint** is a generated keypair. The **Mint Authority** is a Program Derived Address (PDA).
-- `mint-3` - The **Mint** and the **Mint Authority** are Program Derived Addresses (PDAs).

+ 50 - 0
tokens/create-token/README.md

@@ -0,0 +1,50 @@
+# 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.
+
+---
+All tokens - including Non-Fungible Tokens (NFTs) are SPL Tokens on Solana.   
+   
+They follow the SPL Token standard (similar to ERC-20).   
+   
+```text
+Default SPL Tokens  :   9 decimals
+NFTs                :   0 decimals
+```
+### How Decimals Work
+```text
+Consider token JOE with 9 decimals:
+
+    1 JOE = quantity * 10 ^ (-1 * decimals) = 1 * 10 ^ (-1 * 9) = 0.000000001
+```
+### Mint & Metadata
+SPL Tokens on Solana are referred to as a Mint.   
+   
+A Mint is defined by a specific type of account on Solana that describes information about a token:
+```TypeScript
+{
+    isInitialized,
+    supply,             // The current supply of this token mint on Solana
+    decimals,           // The number of decimals this mint breaks down to
+    mintAuthority,      // The account who can authorize minting of new tokens
+    freezeAuthority,    // The account who can authorize freezing of tokens
+}
+```
+Any metadata about this Mint - such as a nickname, symbol, or image - is stored in a **separate** account called a Metadata Account:
+```TypeScript
+{
+    title,
+    symbol,
+    uri,                // The URI to the hosted image
+}
+```
+
+
+> Project Metaplex is the standard for SPL Token metadata on Solana   
+> You can use [Metaplex's Token Metadata Program](https://docs.metaplex.com/) to create metadata for your token.
+
+
+### Steps to Create an SPL Token
+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.

+ 14 - 0
tokens/create-token/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+create_token = "5yRmjtx87UJMJF4NEeqjpmgAu7MBJZACW6ksiCYqQxVh"
+
+[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"

+ 0 - 0
tokens/mint-2/anchor/Cargo.toml → tokens/create-token/anchor/Cargo.toml


+ 1 - 0
tokens/mint-2/anchor/package.json → tokens/create-token/anchor/package.json

@@ -1,5 +1,6 @@
 {
     "dependencies": {
+        "@metaplex-foundation/mpl-token-metadata": "^2.5.2",
         "@project-serum/anchor": "^0.24.2"
     },
     "devDependencies": {

+ 2 - 2
tokens/mint-2/anchor/programs/mint-2/Cargo.toml → tokens/create-token/anchor/programs/create-token/Cargo.toml

@@ -1,12 +1,12 @@
 [package]
-name = "mint-2"
+name = "create-token"
 version = "0.1.0"
 description = "Created with Anchor"
 edition = "2021"
 
 [lib]
 crate-type = ["cdylib", "lib"]
-name = "mint_2"
+name = "create_token"
 
 [features]
 no-entrypoint = []

+ 0 - 0
tokens/mint-2/anchor/programs/mint-2/Xargo.toml → tokens/create-token/anchor/programs/create-token/Xargo.toml


+ 94 - 0
tokens/create-token/anchor/programs/create-token/src/lib.rs

@@ -0,0 +1,94 @@
+use {
+    anchor_lang::{
+        prelude::*,
+        solana_program::program::invoke,
+    },
+    anchor_spl::token,
+    mpl_token_metadata::instruction as mpl_instruction,
+};
+
+
+declare_id!("5yRmjtx87UJMJF4NEeqjpmgAu7MBJZACW6ksiCYqQxVh");
+
+
+#[program]
+pub mod create_token {
+    use super::*;
+
+    pub fn create_token_mint(
+        ctx: Context<CreateTokenMint>, 
+        token_title: String, 
+        token_symbol: String, 
+        token_uri: String,
+        _token_decimals: u8,
+    ) -> 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
+                token_title,                                    // Name
+                token_symbol,                                   // Symbol
+                token_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!("Token mint created successfully.");
+    
+        Ok(())
+    }
+}
+
+
+// The macros within the Account Context will create our
+//      Mint account and initialize it as a Mint
+//      We just have to do the metadata
+//
+#[derive(Accounts)]
+#[instruction(
+    token_title: String, 
+    token_symbol: String, 
+    token_uri: String,
+    token_decimals: u8,
+)]
+pub struct CreateTokenMint<'info> {
+    /// CHECK: We're about to create this with Metaplex
+    #[account(mut)]
+    pub metadata_account: UncheckedAccount<'info>,
+    #[account(
+        init,
+        payer = payer,
+        mint::decimals = token_decimals,
+        mint::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>,
+}

+ 90 - 0
tokens/create-token/anchor/tests/test.ts

@@ -0,0 +1,90 @@
+import { 
+  PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID
+} from '@metaplex-foundation/mpl-token-metadata';
+import * as anchor from "@project-serum/anchor";
+import { CreateToken } from "../target/types/create_token";
+
+
+describe("Create Tokens", () => {
+  
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = anchor.workspace.CreateToken as anchor.Program<CreateToken>;
+
+  const tokenTitle = "Solana Gold";
+  const tokenSymbol = "GOLDSOL";
+  const tokenUri = "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json";
+
+  it("Create an SPL Token!", async () => {
+
+    const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+
+    const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
+      [
+        Buffer.from("metadata"),
+        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+        mintKeypair.publicKey.toBuffer(),
+      ],
+      TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    // SPL Token default = 9 decimals
+    //
+    const sx = await program.methods.createTokenMint(
+      tokenTitle, tokenSymbol, tokenUri, 9
+    )
+      .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("Create an NFT!", async () => {
+    
+    const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+
+    const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
+      [
+        Buffer.from("metadata"),
+        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+        mintKeypair.publicKey.toBuffer(),
+      ],
+      TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    // NFT default = 0 decimals
+    //
+    const sx = await program.methods.createTokenMint(
+      tokenTitle, tokenSymbol, tokenUri, 0
+    )
+      .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}`);
+  });
+});

+ 0 - 0
tokens/mint-2/anchor/tsconfig.json → tokens/create-token/anchor/tsconfig.json


+ 2 - 2
tokens/mint-2/native/cicd.sh → tokens/create-token/native/cicd.sh

@@ -4,5 +4,5 @@
 # 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 --bpf-out-dir=./program/target/so
-solana program deploy ./program/target/so/program.so
+cargo build-bpf --manifest-path=./program/Cargo.toml
+solana program deploy ./program/target/deploy/program.so

+ 3 - 2
tokens/mint-2/native/package.json → tokens/create-token/native/package.json

@@ -3,8 +3,9 @@
     "test": "yarn run ts-mocha -p ./tsconfig.json -t 1000000 ./tests/test.ts"
   },
   "dependencies": {
-    "@solana/spl-token": "^0.2.0",
-    "@solana/web3.js": "^1.47.3",
+    "@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"

+ 0 - 0
tokens/mint-2/native/program/Cargo.toml → tokens/create-token/native/program/Cargo.toml


+ 43 - 58
tokens/mint-2/native/program/src/instructions/create_token_mint.rs → tokens/create-token/native/program/src/lib.rs

@@ -1,9 +1,14 @@
 use {
+    borsh::{ 
+        BorshSerialize, 
+        BorshDeserialize 
+    },
     solana_program::{
         account_info::{next_account_info, AccountInfo}, 
+        entrypoint,
         entrypoint::ProgramResult, 
         msg, 
-        program::invoke_signed,
+        program::invoke,
         program_pack::Pack,
         pubkey::Pubkey,
         rent::Rent,
@@ -20,15 +25,26 @@ use {
 };
 
 
-pub fn create_token_mint(
-    program_id: &Pubkey,
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+pub struct CreateTokenArgs {
+    pub token_title: String,
+    pub token_symbol: String,
+    pub token_uri: String,
+    pub token_decimals: u8,
+}
+
+
+entrypoint!(process_instruction);
+
+
+fn process_instruction(
+    _program_id: &Pubkey,
     accounts: &[AccountInfo],
-    token_title: String,
-    token_symbol: String,
-    token_uri: String,
-    mint_authority_pda_bump: u8,
+    instruction_data: &[u8],
 ) -> ProgramResult {
 
+    let args = CreateTokenArgs::try_from_slice(instruction_data)?;
+
     let accounts_iter = &mut accounts.iter();
 
     let mint_account = next_account_info(accounts_iter)?;
@@ -40,31 +56,11 @@ pub fn create_token_mint(
     let token_program = next_account_info(accounts_iter)?;
     let token_metadata_program = next_account_info(accounts_iter)?;
 
-    msg!("Creating mint authority...");
-    msg!("Mint Authority: {}", mint_authority.key);
-    invoke_signed(
-        &system_instruction::create_account(
-            &payer.key,
-            &mint_authority.key,
-            (Rent::get()?).minimum_balance(8) as u64,
-            8,
-            &program_id,
-        ),
-        &[
-            payer.clone(),
-            mint_authority.clone(),
-            system_program.clone(),
-        ],
-        &[&[
-            b"mint_authority_", 
-            mint_account.key.as_ref(),
-            &[mint_authority_pda_bump],
-        ]]
-    )?;
-    
+    // First create the account for the Mint
+    //
     msg!("Creating mint account...");
     msg!("Mint: {}", mint_account.key);
-    invoke_signed(
+    invoke(
         &system_instruction::create_account(
             &payer.key,
             &mint_account.key,
@@ -75,57 +71,53 @@ pub fn create_token_mint(
         &[
             mint_account.clone(),
             payer.clone(),
+            system_program.clone(),
             token_program.clone(),
-        ],
-        &[&[
-            b"mint_authority_", 
-            mint_account.key.as_ref(),
-            &[mint_authority_pda_bump],
-        ]]
+        ]
     )?;
 
+    // Now initialize that account as a Mint (standard Mint)
+    //
     msg!("Initializing mint account...");
     msg!("Mint: {}", mint_account.key);
-    invoke_signed(
+    invoke(
         &token_instruction::initialize_mint(
             &token_program.key,
             &mint_account.key,
             &mint_authority.key,
             Some(&mint_authority.key),
-            9,
+            args.token_decimals,
         )?,
         &[
             mint_account.clone(),
             mint_authority.clone(),
             token_program.clone(),
             rent.clone(),
-        ],
-        &[&[
-            b"mint_authority_", 
-            mint_account.key.as_ref(),
-            &[mint_authority_pda_bump],
-        ]]
+        ]
     )?;
 
+    // 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_v2(
+    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,
-            token_title,
-            token_symbol,
-            token_uri,
+            args.token_title,
+            args.token_symbol,
+            args.token_uri,
             None,
             0,
             true,
             false,
             None,
             None,
+            None,
         ),
         &[
             metadata_account.clone(),
@@ -134,17 +126,10 @@ pub fn create_token_mint(
             payer.clone(),
             token_metadata_program.clone(),
             rent.clone(),
-        ],
-        &[&[
-            b"mint_authority_", 
-            mint_account.key.as_ref(),
-            &[mint_authority_pda_bump],
-        ]]
+        ]
     )?;
 
     msg!("Token mint created successfully.");
 
     Ok(())
-}
-
-pub struct MintAuthority {}
+}

+ 159 - 0
tokens/create-token/native/tests/test.ts

@@ -0,0 +1,159 @@
+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 {
+    TOKEN_PROGRAM_ID,
+} from '@solana/spl-token';
+import * as borsh from "borsh";
+import { Buffer } from "buffer";
+
+
+function createKeypairFromFile(path: string): Keypair {
+    return Keypair.fromSecretKey(
+        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
+    )
+};
+
+
+class Assignable {
+    constructor(properties) {
+        Object.keys(properties).map((key) => {
+            return (this[key] = properties[key]);
+        });
+    };
+};
+
+class CreateTokenArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(CreateTokenArgsSchema, this));
+    }
+};
+const CreateTokenArgsSchema = new Map([
+    [
+        CreateTokenArgs, {
+            kind: 'struct',
+            fields: [
+                ['token_title', 'string'],
+                ['token_symbol', 'string'],
+                ['token_uri', 'string'],
+                ['token_decimals', 'u8'],
+            ]
+        }
+    ]
+]);
+
+
+describe("Create Tokens!", 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');
+
+    it("Create an SPL Token!", async () => {
+
+        const mintKeypair: Keypair = Keypair.generate();
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        // SPL Token default = 9 decimals
+        //
+        const instructionData = new CreateTokenArgs({
+            token_title: "Solana Gold",
+            token_symbol: "GOLDSOL",
+            token_uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json",
+            token_decimals: 9,
+        });
+
+        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("Create an NFT!", async () => {
+
+        const mintKeypair: Keypair = Keypair.generate();
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        // NFT default = 0 decimals
+        //
+        const instructionData = new CreateTokenArgs({
+            token_title: "Homer NFT",
+            token_symbol: "HOMR",
+            token_uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json",
+            token_decimals: 9,
+        });
+
+        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}`);
+    });
+  });
+  

+ 0 - 0
tokens/mint-2/native/tsconfig.json → tokens/create-token/native/tsconfig.json


+ 3 - 0
tokens/freeze-tokens/README.md

@@ -0,0 +1,3 @@
+# Freeze Tokens
+
+> Coming soon!

+ 3 - 0
tokens/metaplex-expanded/README.md

@@ -0,0 +1,3 @@
+# Metaplex Metadata Expanded
+
+> Coming soon!

+ 0 - 11
tokens/mint-2/README.md

@@ -1,11 +0,0 @@
-# Minting an SPL Token to a Wallet
-
-This example demonstrates how to mint an SPl Token to a Solana users's wallet.
-
-### :key: Keys:
-
-- The person requesting the mint must have an **associated token account** for that mint. We create this token account in the program!
-- Steps:
-    1. Create an associated token account for the Mint.
-    2. Initialize that associated token account.
-    3. Mint some amount of the Mint to the new token account.

+ 0 - 9
tokens/mint-2/anchor/programs/mint-2/src/instructions/mod.rs

@@ -1,9 +0,0 @@
-pub mod create_token_mint;
-pub mod mint_to_your_wallet;
-pub mod mint_to_another_wallet;
-pub mod transfer_to_another_wallet;
-
-pub use create_token_mint::*;
-pub use mint_to_your_wallet::*;
-pub use mint_to_another_wallet::*;
-pub use transfer_to_another_wallet::*;

+ 0 - 68
tokens/mint-2/anchor/programs/mint-2/src/lib.rs

@@ -1,68 +0,0 @@
-use anchor_lang::prelude::*;
-
-pub mod instructions;
-
-use instructions::*;
-
-
-declare_id!("8vbaY8zv9r3AgeLjyAr7LEprJwLN5Jjus97crJBD2AV2");
-
-
-#[program]
-pub mod mint_2 {
-    use super::*;
-
-    pub fn create_token_mint(
-        ctx: Context<CreateTokenMint>, 
-        metadata_title: String, 
-        metadata_symbol: String, 
-        metadata_uri: String,
-        mint_authority_pda_bump: u8,
-    ) -> Result<()> {
-
-        create_token_mint::create_token_mint(
-            ctx, 
-            metadata_title, 
-            metadata_symbol, 
-            metadata_uri,
-            mint_authority_pda_bump,
-        )
-    }
-
-    pub fn mint_to_your_wallet(
-        ctx: Context<MintToYourWallet>, 
-        amount: u64,
-        mint_authority_pda_bump: u8,
-    ) -> Result<()> {
-
-        mint_to_your_wallet::mint_to_your_wallet(
-            ctx, 
-            amount,
-            mint_authority_pda_bump,
-        )
-    }
-
-    pub fn mint_to_another_wallet(
-        ctx: Context<MintToAnotherWallet>, 
-        amount: u64,
-        mint_authority_pda_bump: u8,
-    ) -> Result<()> {
-
-        mint_to_another_wallet::mint_to_another_wallet(
-            ctx, 
-            amount,
-            mint_authority_pda_bump,
-        )
-    }
-
-    pub fn transfer_to_another_wallet(
-        ctx: Context<TransferToAnotherWallet>, 
-        amount: u64,
-    ) -> Result<()> {
-
-        transfer_to_another_wallet::transfer_to_another_wallet(
-            ctx, 
-            amount,
-        )
-    }
-}

+ 0 - 175
tokens/mint-2/anchor/tests/test.ts

@@ -1,175 +0,0 @@
-import * as anchor from "@project-serum/anchor";
-import { Mint2 } from "../target/types/mint_2";
-
-
-const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(
-  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
-);
-
-
-describe("mint-token", () => {
-  
-  const provider = anchor.AnchorProvider.env();
-  anchor.setProvider(provider);
-  const payer = provider.wallet as anchor.Wallet;
-  const program = anchor.workspace.Mint2 as anchor.Program<Mint2>;
-
-  const testTokenTitle = "Solana Gold";
-  const testTokenSymbol = "GOLDSOL";
-  const testTokenUri = "https://raw.githubusercontent.com/solana-developers/program-examples/main/tokens/mint-2/anchor/tests/token_metadata.json";
-
-  const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
-  console.log(`New token: ${mintKeypair.publicKey}`);
-
-  it("Create the mint", async () => {
-
-    const [mintAuthorityPda, mintAuthorityPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
-      [
-        Buffer.from("mint_authority_"),
-        mintKeypair.publicKey.toBuffer(),
-      ],
-      program.programId,
-    );
-
-    const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
-      [
-        Buffer.from("metadata"),
-        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
-        mintKeypair.publicKey.toBuffer(),
-      ],
-      TOKEN_METADATA_PROGRAM_ID
-    ))[0];
-
-    await program.methods.createTokenMint(
-      testTokenTitle, testTokenSymbol, testTokenUri, mintAuthorityPdaBump
-    )
-    .accounts({
-      metadataAccount: metadataAddress,
-      mintAccount: mintKeypair.publicKey,
-      mintAuthority: mintAuthorityPda,
-      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();
-  });
-
-  it("Mint to your wallet!", async () => {
-
-    const [mintAuthorityPda, mintAuthorityPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
-      [
-        Buffer.from("mint_authority_"),
-        mintKeypair.publicKey.toBuffer(),
-      ],
-      program.programId,
-    );
-
-    const amountToMint = 1;
-
-    const tokenAddress = await anchor.utils.token.associatedAddress({
-        mint: mintKeypair.publicKey,
-        owner: payer.publicKey
-    });
-    console.log(`Token Address: ${tokenAddress}`);
-
-    await program.methods.mintToYourWallet(
-      new anchor.BN(amountToMint), mintAuthorityPdaBump
-    )
-    .accounts({
-      mintAccount: mintKeypair.publicKey,
-      mintAuthority: mintAuthorityPda,
-      tokenAccount: tokenAddress,
-      payer: payer.publicKey,
-      rent: anchor.web3.SYSVAR_RENT_PUBKEY,
-      systemProgram: anchor.web3.SystemProgram.programId,
-      tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
-      associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
-    })
-    .signers([payer.payer])
-    .rpc();
-  });
-
-  it("Mint to another person's wallet (airdrop)!", async () => {
-
-    const recipientKeypair = anchor.web3.Keypair.generate();
-    await provider.connection.confirmTransaction(
-      await provider.connection.requestAirdrop(recipientKeypair.publicKey, 1 * anchor.web3.LAMPORTS_PER_SOL)
-    );
-    console.log(`Recipient pubkey: ${recipientKeypair.publicKey}`);
-
-    const [mintAuthorityPda, mintAuthorityPdaBump] = await anchor.web3.PublicKey.findProgramAddress(
-      [
-        Buffer.from("mint_authority_"),
-        mintKeypair.publicKey.toBuffer(),
-      ],
-      program.programId,
-    );
-
-    const amountToMint = 1;
-
-    const tokenAddress = await anchor.utils.token.associatedAddress({
-        mint: mintKeypair.publicKey,
-        owner: recipientKeypair.publicKey
-    });
-    console.log(`Token Address: ${tokenAddress}`);
-
-    await program.methods.mintToAnotherWallet(
-      new anchor.BN(amountToMint), mintAuthorityPdaBump
-    )
-    .accounts({
-      mintAccount: mintKeypair.publicKey,
-      mintAuthority: mintAuthorityPda,
-      recipient: recipientKeypair.publicKey,
-      tokenAccount: tokenAddress,
-      payer: payer.publicKey,
-      rent: anchor.web3.SYSVAR_RENT_PUBKEY,
-      systemProgram: anchor.web3.SystemProgram.programId,
-      tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
-      associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
-    })
-    .signers([payer.payer])
-    .rpc();
-  });
-
-  it("Transfer to another person's wallet!", async () => {
-
-    const recipientWallet = anchor.web3.Keypair.generate();
-    await provider.connection.confirmTransaction(
-        await provider.connection.requestAirdrop(recipientWallet.publicKey, 2 * anchor.web3.LAMPORTS_PER_SOL)
-    );
-    console.log(`Recipient Pubkey: ${recipientWallet.publicKey}`);
-
-    const amountToTransfer = 1;
-
-    const ownerTokenAddress = await anchor.utils.token.associatedAddress({
-        mint: mintKeypair.publicKey,
-        owner: payer.publicKey
-    });
-    console.log(`Owner Token Address: ${ownerTokenAddress}`);
-    const recipientTokenAddress = await anchor.utils.token.associatedAddress({
-        mint: mintKeypair.publicKey,
-        owner: recipientWallet.publicKey
-    });
-    console.log(`Recipient Token Address: ${recipientTokenAddress}`);
-
-    await program.methods.transferToAnotherWallet(
-      new anchor.BN(amountToTransfer)
-    )
-    .accounts({
-      mintAccount: mintKeypair.publicKey,
-      ownerTokenAccount: ownerTokenAddress,
-      recipientTokenAccount: recipientTokenAddress,
-      owner: payer.publicKey,
-      recipient: recipientWallet.publicKey,
-      rent: anchor.web3.SYSVAR_RENT_PUBKEY,
-      systemProgram: anchor.web3.SystemProgram.programId,
-      tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
-      associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
-    })
-    .signers([payer.payer])
-    .rpc();
-  });
-});

+ 0 - 6
tokens/mint-2/anchor/tests/token_metadata.json

@@ -1,6 +0,0 @@
-{
-    "name": "Solana Gold",
-    "symbol": "GOLDSOL",
-    "description": "A gold Solana SPL token :)",
-    "image": "https://images.all-free-download.com/images/graphiclarge/solana_coin_sign_icon_shiny_golden_symmetric_geometrical_design_6919941.jpg"
-}

+ 0 - 83
tokens/mint-2/native/program/src/instructions/mint_to_wallet.rs

@@ -1,83 +0,0 @@
-use {
-    solana_program::{
-        account_info::{next_account_info, AccountInfo},
-        entrypoint::ProgramResult, 
-        msg, 
-        program::{invoke, invoke_signed},
-        pubkey::Pubkey,
-    },
-    spl_token::{
-        instruction as token_instruction,
-    },
-    spl_associated_token_account::{
-        instruction as token_account_instruction,
-    },
-};
-
-
-pub fn mint_to_wallet(
-    _program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    amount: u64,
-    mint_authority_pda_bump: u8,
-) -> ProgramResult {
-
-    let accounts_iter = &mut accounts.iter();
-
-    let mint_account = next_account_info(accounts_iter)?;
-    let mint_authority = next_account_info(accounts_iter)?;
-    let 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)?;
-
-    msg!("Creating token account...");
-    msg!("Token Address: {}", token_account.key);    
-    invoke(
-        &token_account_instruction::create_associated_token_account(
-            &payer.key,
-            &payer.key,
-            &mint_account.key,
-        ),
-        &[
-            mint_account.clone(),
-            token_account.clone(),
-            payer.clone(),
-            token_program.clone(),
-            associated_token_program.clone(),
-        ]
-    )?;
-
-    msg!("Minting {} tokens to token account...", amount);
-    msg!("Mint: {}", mint_account.key);   
-    msg!("Token Address: {}", token_account.key);
-    invoke_signed(
-        &token_instruction::mint_to(
-            &token_program.key,
-            &mint_account.key,
-            &token_account.key,
-            &mint_authority.key,
-            &[&mint_authority.key],
-            amount,
-        )?,
-        &[
-            mint_account.clone(),
-            mint_authority.clone(),
-            token_account.clone(),
-            token_program.clone(),
-            rent.clone(),
-        ],
-        &[&[
-            b"mint_authority_", 
-            mint_account.key.as_ref(),
-            &[mint_authority_pda_bump],
-        ]]
-    )?;
-
-    msg!("Tokens minted to wallet successfully.");
-
-    Ok(())
-}
-

+ 0 - 3
tokens/mint-2/native/program/src/instructions/mod.rs

@@ -1,3 +0,0 @@
-pub mod create_token_mint;
-pub mod mint_to_wallet;
-pub mod transfer_to_wallet;

+ 0 - 81
tokens/mint-2/native/program/src/instructions/transfer_to_wallet.rs

@@ -1,81 +0,0 @@
-use {
-    solana_program::{
-        account_info::{next_account_info, AccountInfo},
-        entrypoint::ProgramResult, 
-        msg, 
-        program::invoke,
-        pubkey::Pubkey,
-    },
-    spl_token::{
-        instruction as token_instruction,
-    },
-    spl_associated_token_account::{
-        instruction as token_account_instruction,
-    },
-};
-
-
-pub fn transfer_to_wallet(
-    _program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    amount: u64,
-) -> ProgramResult {
-
-    let accounts_iter = &mut accounts.iter();
-
-    let mint_account = next_account_info(accounts_iter)?;
-    let owner_token_account = next_account_info(accounts_iter)?;
-    let recipient_token_account = next_account_info(accounts_iter)?;
-    let owner = next_account_info(accounts_iter)?;
-    let recipient = 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)?;
-
-    msg!("Creating token account for recipient...");
-    msg!("Recipient Token Address: {}", recipient_token_account.key); 
-    let create_recipient_token_account_ix = &token_account_instruction::create_associated_token_account(
-        &recipient.key,
-        &recipient.key,
-        &mint_account.key,
-    );
-    let create_recipient_token_account_accts = &[
-        mint_account.clone(),
-        recipient_token_account.clone(),
-        recipient.clone(),
-        token_program.clone(),
-        associated_token_program.clone(),
-    ];
-    match invoke(create_recipient_token_account_ix, create_recipient_token_account_accts) {
-        Ok(_) => msg!("Recipient token account created successfully."),
-        Err(_) => msg!("Recipient token account exists! Using..."),
-    }
-
-    msg!("Minting {} tokens to token account...", amount);
-    msg!("Mint: {}", mint_account.key);   
-    msg!("Owner Token Address: {}", owner_token_account.key);
-    msg!("Recipient Token Address: {}", recipient_token_account.key);
-    invoke(
-        &token_instruction::transfer(
-            &token_program.key,
-            &owner_token_account.key,
-            &recipient_token_account.key,
-            &owner.key,
-            &[&owner.key, &recipient.key],
-            amount,
-        )?,
-        &[
-            mint_account.clone(),
-            owner_token_account.clone(),
-            recipient_token_account.clone(),
-            owner.clone(),
-            recipient.clone(),
-            token_program.clone(),
-        ]
-    )?;
-
-    msg!("Tokens transferred to wallet successfully.");
-
-    Ok(())
-}
-

+ 0 - 53
tokens/mint-2/native/program/src/processor.rs

@@ -1,53 +0,0 @@
-use {
-    borsh::BorshDeserialize,
-    solana_program::{
-        account_info::AccountInfo, 
-        entrypoint::ProgramResult, 
-        program_error::ProgramError, 
-        pubkey::Pubkey,
-    },
-};
-
-use crate::instructions::{ create_token_mint, mint_to_wallet, transfer_to_wallet };
-use crate::state::mint_state::{ TokenMetadata, MintTokensTo, TransferTokensTo };
-
-
-pub fn process_instruction(
-    program_id: &Pubkey,
-    accounts: &[AccountInfo],
-    instruction_data: &[u8],
-) -> ProgramResult {
-
-    match TokenMetadata::try_from_slice(instruction_data) {
-        Ok(token_metadata_instruction) => return create_token_mint::create_token_mint(
-            program_id,
-            accounts,
-            token_metadata_instruction.title,
-            token_metadata_instruction.symbol,
-            token_metadata_instruction.uri,
-            token_metadata_instruction.mint_authority_pda_bump,
-        ),
-        Err(_) => {},
-    };
-
-    match MintTokensTo::try_from_slice(instruction_data) {
-        Ok(mint_to_wallet_instruction) => return mint_to_wallet::mint_to_wallet(
-            program_id,
-            accounts,
-            mint_to_wallet_instruction.amount,
-            mint_to_wallet_instruction.mint_authority_pda_bump,
-        ),
-        Err(_) => {},
-    };
-
-    match TransferTokensTo::try_from_slice(instruction_data) {
-        Ok(transfer_to_wallet_instruction) => return transfer_to_wallet::transfer_to_wallet(
-            program_id,
-            accounts,
-            transfer_to_wallet_instruction.amount,
-        ),
-        Err(_) => {},
-    };
-
-    Err(ProgramError::InvalidInstructionData)
-}

+ 0 - 21
tokens/mint-2/native/program/src/state/mint_state.rs

@@ -1,21 +0,0 @@
-use borsh::{ BorshSerialize, BorshDeserialize };
-
-
-#[derive(BorshSerialize, BorshDeserialize, Debug)]
-pub struct TokenMetadata {
-    pub title: String,
-    pub symbol: String,
-    pub uri: String,
-    pub mint_authority_pda_bump: u8,
-}
-
-#[derive(BorshSerialize, BorshDeserialize, Debug)]
-pub struct MintTokensTo {
-    pub amount: u64,
-    pub mint_authority_pda_bump: u8,
-}
-
-#[derive(BorshSerialize, BorshDeserialize, Debug)]
-pub struct TransferTokensTo {
-    pub amount: u64,
-}

+ 0 - 1
tokens/mint-2/native/program/src/state/mod.rs

@@ -1 +0,0 @@
-pub mod mint_state;

+ 0 - 240
tokens/mint-2/native/tests/test.ts

@@ -1,240 +0,0 @@
-import {
-    Connection,
-    Keypair,
-    PublicKey,
-    SystemProgram,
-    SYSVAR_RENT_PUBKEY,
-    TransactionInstruction,
-    Transaction,
-    sendAndConfirmTransaction,
-    LAMPORTS_PER_SOL,
-} from '@solana/web3.js';
-import {
-    ASSOCIATED_TOKEN_PROGRAM_ID,
-    getAssociatedTokenAddress,
-    TOKEN_PROGRAM_ID,
-} from '@solana/spl-token';
-import * as borsh from "borsh";
-import { Buffer } from "buffer";
-
-
-const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
-    "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
-);
-
-
-function createKeypairFromFile(path: string): Keypair {
-    return Keypair.fromSecretKey(
-        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
-    )
-};
-
-
-class Assignable {
-    constructor(properties) {
-        Object.keys(properties).map((key) => {
-            return (this[key] = properties[key]);
-        });
-    };
-};
-
-class TokenMetadata extends Assignable {
-    toBuffer() {
-        return Buffer.from(borsh.serialize(TokenMetadataSchema, this));
-    }
-};
-const TokenMetadataSchema = new Map([
-    [
-        TokenMetadata, {
-            kind: 'struct',
-            fields: [
-                ['title', 'string'],
-                ['symbol', 'string'],
-                ['uri', 'string'],
-                ['mint_authority_pda_bump', 'u8'],
-            ]
-        }
-    ]
-]);
-
-class MintTokensTo extends Assignable {
-    toBuffer() {
-        return Buffer.from(borsh.serialize(MintTokensToSchema, this));
-    }
-};
-const MintTokensToSchema = new Map([
-    [
-        MintTokensTo, {
-            kind: 'struct',
-            fields: [
-                ['amount', 'u64'],
-                ['mint_authority_pda_bump', 'u8'],
-            ]
-        }
-    ]
-]);
-
-class TransferTokensTo extends Assignable {
-    toBuffer() {
-        return Buffer.from(borsh.serialize(TransferTokensToSchema, this));
-    }
-};
-const TransferTokensToSchema = new Map([
-    [
-        TransferTokensTo, {
-            kind: 'struct',
-            fields: [
-                ['amount', 'u64'],
-            ]
-        }
-    ]
-]);
-
-
-describe("mint-token", async () => {
-
-    const connection = new Connection(`http://api.devnet.solana.com/`, 'confirmed');
-    const payer = createKeypairFromFile(require('os').homedir() + '/.config/solana/id.json');
-    const program = createKeypairFromFile('./program/target/so/program-keypair.json');
-
-    const mintKeypair: Keypair = Keypair.generate();
-    console.log(`New token: ${mintKeypair.publicKey}`);
-
-    it("Mint!", async () => {
-
-        const [mintAuthorityPda, mintAuthorityPdaBump] = await PublicKey.findProgramAddress(
-            [
-              Buffer.from("mint_authority_"),
-              mintKeypair.publicKey.toBuffer(),
-            ],
-            program.publicKey,
-        );
-
-        const metadataAddress = (await PublicKey.findProgramAddress(
-            [
-              Buffer.from("metadata"),
-              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
-              mintKeypair.publicKey.toBuffer(),
-            ],
-            TOKEN_METADATA_PROGRAM_ID
-        ))[0];
-        
-        const metadataInstructionData = new TokenMetadata({
-            title: "Solana Gold",
-            symbol: "GOLDSOL",
-            uri: "https://raw.githubusercontent.com/solana-developers/program-examples/main/tokens/mint-2/native/tests/token_metadata.json",
-            mint_authority_pda_bump: mintAuthorityPdaBump
-        });
-
-        let ix = new TransactionInstruction({
-            keys: [
-                { pubkey: mintKeypair.publicKey, isSigner: true, isWritable: true },            // Mint account
-                { pubkey: mintAuthorityPda, 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: metadataInstructionData.toBuffer(),
-        });
-
-        await sendAndConfirmTransaction(
-            connection, 
-            new Transaction().add(ix),
-            [payer, mintKeypair]
-        );
-    });
-
-
-    it("Mint to a wallet!", async () => {
-
-        const [mintAuthorityPda, mintAuthorityPdaBump] = await PublicKey.findProgramAddress(
-            [
-              Buffer.from("mint_authority_"),
-              mintKeypair.publicKey.toBuffer(),
-            ],
-            program.publicKey,
-        );
-
-        const tokenAddress = await getAssociatedTokenAddress(
-            mintKeypair.publicKey,
-            payer.publicKey
-        );
-        console.log(`Token Address: ${tokenAddress}`);
-
-        const mintToInstructionData = new MintTokensTo({
-            amount: 1,
-            mint_authority_pda_bump: mintAuthorityPdaBump,
-        });
-
-        let ix = new TransactionInstruction({
-            keys: [
-                { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true },           // Mint account
-                { pubkey: mintAuthorityPda, isSigner: false, isWritable: false },               // Mint authority account
-                { pubkey: tokenAddress, isSigner: false, isWritable: true },                    // Token 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: ASSOCIATED_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },    // Associated token program
-            ],
-            programId: program.publicKey,
-            data: mintToInstructionData.toBuffer(),
-        });
-
-        await sendAndConfirmTransaction(
-            connection, 
-            new Transaction().add(ix),
-            [payer]
-        );
-    });
-
-    it("Transfer to a wallet!", async () => {
-
-        const recipientWallet = Keypair.generate();
-        await connection.confirmTransaction(
-            await connection.requestAirdrop(recipientWallet.publicKey, 2 * LAMPORTS_PER_SOL)
-        );
-        console.log(`Recipient Pubkey: ${recipientWallet.publicKey}`);
-
-        const ownerTokenAddress = await getAssociatedTokenAddress(
-            mintKeypair.publicKey,
-            payer.publicKey
-        );
-        console.log(`Owner Token Address: ${ownerTokenAddress}`);
-        const recipientTokenAddress = await getAssociatedTokenAddress(
-            mintKeypair.publicKey,
-            recipientWallet.publicKey
-        );
-        console.log(`Recipient Token Address: ${recipientTokenAddress}`);
-
-        const transferToInstructionData = new TransferTokensTo({
-            amount: 1,
-        });
-
-        let ix = new TransactionInstruction({
-            keys: [
-                { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true },           // Mint account
-                { pubkey: ownerTokenAddress, isSigner: false, isWritable: true },               // Owner Token account
-                { pubkey: recipientTokenAddress, isSigner: false, isWritable: true },           // Recipient Token account
-                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Owner
-                { pubkey: recipientWallet.publicKey, isSigner: true, isWritable: true },        // Recipient
-                { 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
-            ],
-            programId: program.publicKey,
-            data: transferToInstructionData.toBuffer(),
-        });
-
-        await sendAndConfirmTransaction(
-            connection, 
-            new Transaction().add(ix),
-            [payer, recipientWallet]
-        );
-    });
-  });
-  

+ 0 - 6
tokens/mint-2/native/tests/token_metadata.json

@@ -1,6 +0,0 @@
-{
-    "name": "Solana Gold",
-    "symbol": "GOLDSOL",
-    "description": "A gold Solana SPL token :)",
-    "image": "https://images.all-free-download.com/images/graphiclarge/solana_coin_sign_icon_shiny_golden_symmetric_geometrical_design_6919941.jpg"
-}

+ 27 - 0
tokens/nft-minter/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`.

+ 1 - 1
tokens/mint-2/anchor/Anchor.toml → tokens/nft-minter/anchor/Anchor.toml

@@ -1,7 +1,7 @@
 [features]
 seeds = false
 [programs.devnet]
-mint_2 = "8vbaY8zv9r3AgeLjyAr7LEprJwLN5Jjus97crJBD2AV2"
+nft_minter = "A6itasS5iqANkC9yrzP1HJPBnJxj9tC8G5TmJzQGogGG"
 
 [registry]
 url = "https://anchor.projectserum.com"

+ 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>,
+}

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

@@ -0,0 +1,128 @@
+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(),
+        mint::freeze_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 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: 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();
+
+      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::*;

+ 0 - 1
tokens/mint-2/native/program/src/lib.rs → tokens/nft-minter/native/program/src/lib.rs

@@ -7,7 +7,6 @@ use solana_program::{
 
 pub mod processor;
 pub mod instructions;
-pub mod state;
 
 
 entrypoint!(process_instruction);

+ 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'],
+            ]
+        }
+    ]
+]);

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

@@ -0,0 +1,145 @@
+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],
+        );
+
+        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,
+  }
+}

+ 23 - 0
tokens/spl-token-minter/README.md

@@ -0,0 +1,23 @@
+# SPL Token Minter
+
+Minting SPL Tokens is a conceptually straightforward process.   
+   
+The only tricky part is understanding how Solana tracks users' balance of SPL Tokens.   
+   
+---
+
+After all, we know every account on Solana by default tracks that account's balance of SOL (the native token), but how could every account on Solana possibly track it's own balance of *any possible* SPL Token on the Solana network?   
+   
+TL/DR it's impossible. Instead, we have to use separate accounts that are specifically configured per SPL Token. These are called **Associated Token Accounts**.   
+   
+---
+For example, if I create the JOE token, and I want to know what someone's balance of JOE is, I would need to do the following:
+```text
+1. Create the JOE token
+2. Create an Associated Token Account for this user's wallet to track his/her balance of JOE
+3. Mint or transfer JOE token to their JOE Associated Token Account
+```
+
+---
+
+Thus, you can think of Associated Token Accounts as simple counters, which point to a Mint and a Wallet. They simply say "here's the balance of this particular Mint for this particular person's Wallet".

+ 14 - 0
tokens/spl-token-minter/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+spl_token_minter = "5BStrknWEiQWHmDGdDBaG7j9qegfN2Nq83M3hXajjgXY"
+
+[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/spl-token-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/spl-token-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"
+    }
+}

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

@@ -0,0 +1,21 @@
+[package]
+name = "spl-token-minter"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "spl_token_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" ] }

+ 2 - 0
tokens/spl-token-minter/anchor/programs/spl-token-minter/Xargo.toml

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

+ 15 - 35
tokens/mint-2/anchor/programs/mint-2/src/instructions/create_token_mint.rs → tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/create.rs

@@ -1,40 +1,40 @@
 use {
     anchor_lang::{
         prelude::*,
-        solana_program::program::invoke_signed,
+        solana_program::program::invoke,
     },
     anchor_spl::token,
     mpl_token_metadata::instruction as mpl_instruction,
 };
 
 
-pub fn create_token_mint(
-    ctx: Context<CreateTokenMint>, 
-    metadata_title: String, 
-    metadata_symbol: String, 
-    metadata_uri: String,
-    mint_authority_pda_bump: u8,
+pub fn create_token(
+    ctx: Context<CreateToken>, 
+    token_title: String, 
+    token_symbol: String, 
+    token_uri: String,
 ) -> Result<()> {
 
     msg!("Creating metadata account...");
     msg!("Metadata account address: {}", &ctx.accounts.metadata_account.key());
-    invoke_signed(
-        &mpl_instruction::create_metadata_accounts_v2(
+    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
-            metadata_title,                                 // Name
-            metadata_symbol,                                // Symbol
-            metadata_uri,                                   // URI
+            token_title,                                    // Name
+            token_symbol,                                   // Symbol
+            token_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(),
@@ -44,13 +44,6 @@ pub fn create_token_mint(
             ctx.accounts.mint_authority.to_account_info(),
             ctx.accounts.rent.to_account_info(),
         ],
-        &[
-            &[
-                b"mint_authority_", 
-                ctx.accounts.mint_account.key().as_ref(),
-                &[mint_authority_pda_bump],
-            ]
-        ]
     )?;
 
     msg!("Token mint created successfully.");
@@ -60,7 +53,7 @@ pub fn create_token_mint(
 
 
 #[derive(Accounts)]
-pub struct CreateTokenMint<'info> {
+pub struct CreateToken<'info> {
     /// CHECK: We're about to create this with Metaplex
     #[account(mut)]
     pub metadata_account: UncheckedAccount<'info>,
@@ -71,17 +64,7 @@ pub struct CreateTokenMint<'info> {
         mint::authority = mint_authority.key(),
     )]
     pub mint_account: Account<'info, token::Mint>,
-    #[account(
-        init, 
-        payer = payer,
-        space = 8 + 32,
-        seeds = [
-            b"mint_authority_", 
-            mint_account.key().as_ref(),
-        ],
-        bump
-    )]
-    pub mint_authority: Account<'info, MintAuthorityPda>,
+    pub mint_authority: SystemAccount<'info>,
     #[account(mut)]
     pub payer: Signer<'info>,
     pub rent: Sysvar<'info, Rent>,
@@ -89,7 +72,4 @@ pub struct CreateTokenMint<'info> {
     pub token_program: Program<'info, token::Token>,
     /// CHECK: Metaplex will check this
     pub token_metadata_program: UncheckedAccount<'info>,
-}
-
-#[account]
-pub struct MintAuthorityPda {}
+}

+ 11 - 27
tokens/mint-2/anchor/programs/mint-2/src/instructions/mint_to_your_wallet.rs → tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs

@@ -5,33 +5,26 @@ use {
         associated_token,
     },
 };
-use crate::create_token_mint::MintAuthorityPda;
 
 
-pub fn mint_to_your_wallet(
-    ctx: Context<MintToYourWallet>, 
-    amount: u64,
-    mint_authority_pda_bump: u8,
+pub fn mint_to(
+    ctx: Context<MintTo>, 
+    quantity: u64,
 ) -> Result<()> {
 
     msg!("Minting token to token account...");
     msg!("Mint: {}", &ctx.accounts.mint_account.to_account_info().key());   
-    msg!("Token Address: {}", &ctx.accounts.token_account.key());     
+    msg!("Token Address: {}", &ctx.accounts.associated_token_account.key());     
     token::mint_to(
-        CpiContext::new_with_signer(
+        CpiContext::new(
             ctx.accounts.token_program.to_account_info(),
             token::MintTo {
                 mint: ctx.accounts.mint_account.to_account_info(),
-                to: ctx.accounts.token_account.to_account_info(),
+                to: ctx.accounts.associated_token_account.to_account_info(),
                 authority: ctx.accounts.mint_authority.to_account_info(),
             },
-            &[&[
-                b"mint_authority_", 
-                ctx.accounts.mint_account.key().as_ref(),
-                &[mint_authority_pda_bump],
-            ]]
         ),
-        amount,
+        quantity,
     )?;
 
     msg!("Token minted to wallet successfully.");
@@ -41,30 +34,21 @@ pub fn mint_to_your_wallet(
 
 
 #[derive(Accounts)]
-#[instruction(amount: u64, mint_authority_pda_bump: u8)]
-pub struct MintToYourWallet<'info> {
+pub struct MintTo<'info> {
     #[account(
         mut,
         mint::decimals = 9,
         mint::authority = mint_authority.key(),
     )]
     pub mint_account: Account<'info, token::Mint>,
+    pub mint_authority: SystemAccount<'info>,
     #[account(
-        mut, 
-        seeds = [
-            b"mint_authority_", 
-            mint_account.key().as_ref()
-        ],
-        bump = mint_authority_pda_bump
-    )]
-    pub mint_authority: Account<'info, MintAuthorityPda>,
-    #[account(
-        init,
+        init_if_needed,
         payer = payer,
         associated_token::mint = mint_account,
         associated_token::authority = payer,
     )]
-    pub token_account: Account<'info, token::TokenAccount>,
+    pub associated_token_account: Account<'info, token::TokenAccount>,
     #[account(mut)]
     pub payer: Signer<'info>,
     pub rent: Sysvar<'info, Rent>,

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

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

+ 40 - 0
tokens/spl-token-minter/anchor/programs/spl-token-minter/src/lib.rs

@@ -0,0 +1,40 @@
+use anchor_lang::prelude::*;
+
+pub mod instructions;
+
+use instructions::*;
+
+
+declare_id!("5BStrknWEiQWHmDGdDBaG7j9qegfN2Nq83M3hXajjgXY");
+
+
+#[program]
+pub mod spl_token_minter {
+    use super::*;
+
+    pub fn create_token(
+        ctx: Context<CreateToken>, 
+        token_title: String, 
+        token_symbol: String, 
+        token_uri: String,
+    ) -> Result<()> {
+
+        create::create_token(
+            ctx, 
+            token_title, 
+            token_symbol, 
+            token_uri,
+        )
+    }
+
+    pub fn mint_to(
+        ctx: Context<MintTo>, 
+        quantity: u64,
+    ) -> Result<()> {
+
+        mint::mint_to(
+            ctx, 
+            quantity,
+        )
+    }
+}

+ 81 - 0
tokens/spl-token-minter/anchor/tests/test.ts

@@ -0,0 +1,81 @@
+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 { SplTokenMinter } from "../target/types/spl_token_minter";
+
+
+describe("SPL Token Minter", () => {
+  
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = anchor.workspace.SplTokenMinter as anchor.Program<SplTokenMinter>;
+
+  const tokenTitle = "Solana Gold";
+  const tokenSymbol = "GOLDSOL";
+  const tokenUri = "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json";
+
+  const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+  
+  it("Create an SPL Token!", async () => {
+
+    const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
+      [
+        Buffer.from("metadata"),
+        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+        mintKeypair.publicKey.toBuffer(),
+      ],
+      TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const sx = await program.methods.createToken(
+      tokenTitle, tokenSymbol, tokenUri
+    )
+      .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 associatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: mintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+
+    const sx = await program.methods.mintTo(
+      new anchor.BN(150)
+    )
+      .accounts({
+        associatedTokenAccount: associatedTokenAccountAddress,
+        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,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${mintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+});

+ 10 - 0
tokens/spl-token-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/spl-token-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/spl-token-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/spl-token-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/spl-token-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 token_title: String,
+    pub token_symbol: String,
+    pub token_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),
+            9,                          // 9 Decimals for the default SPL Token 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.token_title,
+            args.token_symbol,
+            args.token_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(())
+}

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

@@ -0,0 +1,86 @@
+use {
+    borsh::{ 
+        BorshDeserialize, 
+        BorshSerialize, 
+    },
+    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,
+    },
+};
+
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+pub struct MintToArgs {
+    pub quantity: u64,
+}
+
+
+pub fn mint_to(
+    accounts: &[AccountInfo],
+    args: MintToArgs,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+
+    let mint_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 system_program = next_account_info(accounts_iter)?;
+    let token_program = next_account_info(accounts_iter)?;
+    let associated_token_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(),
+                system_program.clone(),
+                token_program.clone(),
+                associated_token_program.clone(),
+            ]
+        )?;
+    } else {
+        msg!("Associated token account exists.");
+    }
+    msg!("Associated Token Address: {}", associated_token_account.key);
+
+    msg!("Minting {} tokens to associated token account...", args.quantity);
+    invoke(
+        &token_instruction::mint_to(
+            &token_program.key,
+            &mint_account.key,
+            &associated_token_account.key,
+            &mint_authority.key,
+            &[&mint_authority.key],
+            args.quantity,
+        )?,
+        &[
+            mint_account.clone(),
+            mint_authority.clone(),
+            associated_token_account.clone(),
+            token_program.clone(),
+        ],
+    )?;
+
+    msg!("Tokens minted to wallet successfully.");
+
+    Ok(())
+}
+

+ 5 - 0
tokens/spl-token-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/spl-token-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)
+}

+ 44 - 0
tokens/spl-token-minter/native/program/src/processor.rs

@@ -0,0 +1,44 @@
+use {
+    borsh::{ 
+        BorshDeserialize, 
+        BorshSerialize, 
+    },
+    solana_program::{
+        account_info::AccountInfo, 
+        entrypoint::ProgramResult, 
+        pubkey::Pubkey,
+    },
+};
+
+use crate::instructions::{ 
+    create::{
+        CreateTokenArgs,
+        create_token,
+    }, 
+    mint::{
+        MintToArgs,
+        mint_to,
+    }, 
+};
+
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+enum SplMinterIntstruction {
+    Create(CreateTokenArgs),
+    Mint(MintToArgs),
+}
+
+
+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(args) => mint_to(accounts, args),
+    }
+}

+ 51 - 0
tokens/spl-token-minter/native/tests/instructions.ts

@@ -0,0 +1,51 @@
+import * as borsh from "borsh";
+
+
+class Assignable {
+    constructor(properties) {
+        Object.keys(properties).map((key) => {
+            return (this[key] = properties[key]);
+        });
+    };
+};
+
+export enum SplMinterInstruction {
+    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'],
+                ['token_title', 'string'],
+                ['token_symbol', 'string'],
+                ['token_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'],
+                ['quantity', 'u64'],
+            ]
+        }
+    ]
+]);

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

@@ -0,0 +1,124 @@
+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,
+    SplMinterInstruction,
+} from './instructions';
+import { BN } from 'bn.js';
+
+
+function createKeypairFromFile(path: string): Keypair {
+    return Keypair.fromSecretKey(
+        Buffer.from(JSON.parse(require('fs').readFileSync(path, "utf-8")))
+    )
+};
+
+
+describe("SPL 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('os').homedir() + '/.config/solana/id.json');
+    const program = createKeypairFromFile('./program/target/deploy/program-keypair.json');
+
+    const mintKeypair: Keypair = Keypair.generate();
+
+    it("Create an SPL Token!", 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: SplMinterInstruction.Create,
+            token_title: "Solana Gold",
+            token_symbol: "GOLDSOL",
+            token_uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.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 some tokens to your wallet!", async () => {
+
+        const associatedTokenAccountAddress = await getAssociatedTokenAddress(
+            mintKeypair.publicKey,
+            payer.publicKey,
+        );
+        
+        const instructionData = new MintToArgs({
+            instruction: SplMinterInstruction.Mint,
+            quantity: new BN(150),
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: mintKeypair.publicKey, isSigner: false, isWritable: true },           // Mint 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: 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
+            ],
+            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/spl-token-minter/native/tsconfig.json

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

+ 3 - 0
tokens/token-2022/README.md

@@ -0,0 +1,3 @@
+# Token 2022
+
+> Coming soon!

+ 3 - 0
tokens/token-lending/README.md

@@ -0,0 +1,3 @@
+# Token Lending Example
+
+> Coming soon!

+ 7 - 0
tokens/transfer-tokens/README.md

@@ -0,0 +1,7 @@
+# Transfer Tokens
+
+Just like with minting, transfers of SPL Tokens are conducted between Associated Token Accounts.   
+   
+You can use the `transfer()` function provided by the SPL Token Program to conduct a transfer of any SPL Token with the appropriate permissions.   
+   
+Check out [SPL Token Minter](../spl-token-minter) or [NFT Minter](../nft-minter) to learn more about Associated Token Accounts.

+ 14 - 0
tokens/transfer-tokens/anchor/Anchor.toml

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+transfer_tokens = "7p8osL5uUKUFM8sUxRYfmFVGN264PxeBSDEdNv36Khe3"
+
+[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/transfer-tokens/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/transfer-tokens/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"
+    }
+}

+ 21 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "transfer-tokens"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "transfer_tokens"
+
+[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" ] }

+ 2 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/Xargo.toml

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

+ 83 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs

@@ -0,0 +1,83 @@
+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>, 
+    token_title: String, 
+    token_symbol: String, 
+    token_uri: String,
+    _decimals: u8,
+) -> 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
+            token_title,                                    // Name
+            token_symbol,                                   // Symbol
+            token_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!("Token mint created successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+#[instruction(
+    token_title: String, 
+    token_symbol: String, 
+    token_uri: String,
+    decimals: u8,
+)]
+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 = decimals,
+        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>,
+}

+ 128 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_nft.rs

@@ -0,0 +1,128 @@
+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_nft(
+    ctx: Context<MintNft>, 
+) -> 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 MintNft<'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>,
+    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>,
+}

+ 12 - 30
tokens/mint-2/anchor/programs/mint-2/src/instructions/mint_to_another_wallet.rs → tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_spl.rs

@@ -5,33 +5,26 @@ use {
         associated_token,
     },
 };
-use crate::create_token_mint::MintAuthorityPda;
 
 
-pub fn mint_to_another_wallet(
-    ctx: Context<MintToAnotherWallet>, 
-    amount: u64,
-    mint_authority_pda_bump: u8,
+pub fn mint_spl(
+    ctx: Context<MintSpl>, 
+    quantity: u64,
 ) -> Result<()> {
 
     msg!("Minting token to token account...");
     msg!("Mint: {}", &ctx.accounts.mint_account.to_account_info().key());   
-    msg!("Token Address: {}", &ctx.accounts.token_account.key());     
+    msg!("Token Address: {}", &ctx.accounts.associated_token_account.key());     
     token::mint_to(
-        CpiContext::new_with_signer(
+        CpiContext::new(
             ctx.accounts.token_program.to_account_info(),
             token::MintTo {
                 mint: ctx.accounts.mint_account.to_account_info(),
-                to: ctx.accounts.token_account.to_account_info(),
+                to: ctx.accounts.associated_token_account.to_account_info(),
                 authority: ctx.accounts.mint_authority.to_account_info(),
             },
-            &[&[
-                b"mint_authority_", 
-                ctx.accounts.mint_account.key().as_ref(),
-                &[mint_authority_pda_bump],
-            ]]
         ),
-        amount,
+        quantity,
     )?;
 
     msg!("Token minted to wallet successfully.");
@@ -41,32 +34,21 @@ pub fn mint_to_another_wallet(
 
 
 #[derive(Accounts)]
-#[instruction(amount: u64, mint_authority_pda_bump: u8)]
-pub struct MintToAnotherWallet<'info> {
+pub struct MintSpl<'info> {
     #[account(
         mut,
         mint::decimals = 9,
         mint::authority = mint_authority.key(),
     )]
     pub mint_account: Account<'info, token::Mint>,
+    pub mint_authority: SystemAccount<'info>,
     #[account(
-        mut, 
-        seeds = [
-            b"mint_authority_", 
-            mint_account.key().as_ref()
-        ],
-        bump = mint_authority_pda_bump
-    )]
-    pub mint_authority: Account<'info, MintAuthorityPda>,
-    /// CHECK: This is for airdrops
-    pub recipient: UncheckedAccount<'info>,
-    #[account(
-        init,
+        init_if_needed,
         payer = payer,
         associated_token::mint = mint_account,
-        associated_token::authority = recipient,
+        associated_token::authority = payer,
     )]
-    pub token_account: Account<'info, token::TokenAccount>,
+    pub associated_token_account: Account<'info, token::TokenAccount>,
     #[account(mut)]
     pub payer: Signer<'info>,
     pub rent: Sysvar<'info, Rent>,

+ 9 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mod.rs

@@ -0,0 +1,9 @@
+pub mod create;
+pub mod mint_nft;
+pub mod mint_spl;
+pub mod transfer;
+
+pub use create::*;
+pub use mint_nft::*;
+pub use mint_spl::*;
+pub use transfer::*;

+ 20 - 19
tokens/mint-2/anchor/programs/mint-2/src/instructions/transfer_to_another_wallet.rs → tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs

@@ -7,54 +7,55 @@ use {
 };
 
 
-pub fn transfer_to_another_wallet(
-    ctx: Context<TransferToAnotherWallet>, 
-    amount: u64,
+pub fn transfer_tokens(
+    ctx: Context<TransferTokens>, 
+    quantity: u64,
 ) -> Result<()> {
 
-    msg!("Transferring {} tokens to new token account...", amount);
+    msg!("Transferring tokens...");
     msg!("Mint: {}", &ctx.accounts.mint_account.to_account_info().key());   
-    msg!("Owner Token Address: {}", &ctx.accounts.owner_token_account.key());  
-    msg!("Recipient Token Address: {}", &ctx.accounts.recipient_token_account.key());
+    msg!("From Token Address: {}", &ctx.accounts.from_associated_token_account.key());     
+    msg!("To Token Address: {}", &ctx.accounts.to_associated_token_account.key());     
     token::transfer(
         CpiContext::new(
             ctx.accounts.token_program.to_account_info(),
             token::Transfer {
-                from: ctx.accounts.owner_token_account.to_account_info(),
-                to: ctx.accounts.recipient_token_account.to_account_info(),
+                from: ctx.accounts.from_associated_token_account.to_account_info(),
+                to: ctx.accounts.to_associated_token_account.to_account_info(),
                 authority: ctx.accounts.owner.to_account_info(),
             },
         ),
-        amount,
+        quantity,
     )?;
 
-    msg!("Tokens transferred to wallet successfully.");
+    msg!("Tokens transferred successfully.");
 
     Ok(())
 }
 
 
 #[derive(Accounts)]
-pub struct TransferToAnotherWallet<'info> {
+pub struct TransferTokens<'info> {
     #[account(mut)]
     pub mint_account: Account<'info, token::Mint>,
     #[account(
-        mut,
+        init_if_needed,
+        payer = payer,
         associated_token::mint = mint_account,
         associated_token::authority = owner,
     )]
-    pub owner_token_account: Account<'info, token::TokenAccount>,
+    pub from_associated_token_account: Account<'info, token::TokenAccount>,
+    pub owner: SystemAccount<'info>,
     #[account(
-        init,
-        payer = owner,
+        init_if_needed,
+        payer = payer,
         associated_token::mint = mint_account,
         associated_token::authority = recipient,
     )]
-    pub recipient_token_account: Account<'info, token::TokenAccount>,
+    pub to_associated_token_account: Account<'info, token::TokenAccount>,
+    pub recipient: SystemAccount<'info>,
     #[account(mut)]
-    pub owner: Signer<'info>,
-    /// CHECK: Crediting not Debiting
-    pub recipient: UncheckedAccount<'info>,
+    pub payer: Signer<'info>,
     pub rent: Sysvar<'info, Rent>,
     pub system_program: Program<'info, System>,
     pub token_program: Program<'info, token::Token>,

+ 62 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs

@@ -0,0 +1,62 @@
+use anchor_lang::prelude::*;
+
+pub mod instructions;
+
+use instructions::*;
+
+
+declare_id!("7p8osL5uUKUFM8sUxRYfmFVGN264PxeBSDEdNv36Khe3");
+
+
+#[program]
+pub mod transfer_tokens {
+    use super::*;
+
+    pub fn create_token(
+        ctx: Context<CreateToken>, 
+        token_title: String, 
+        token_symbol: String, 
+        token_uri: String,
+        decimals: u8,
+    ) -> Result<()> {
+
+        create::create_token(
+            ctx, 
+            token_title, 
+            token_symbol, 
+            token_uri,
+            decimals,
+        )
+    }
+
+    pub fn mint_spl(
+        ctx: Context<MintSpl>, 
+        quantity: u64,
+    ) -> Result<()> {
+
+        mint_spl::mint_spl(
+            ctx, 
+            quantity,
+        )
+    }
+
+    pub fn mint_nft(
+        ctx: Context<MintNft>, 
+    ) -> Result<()> {
+
+        mint_nft::mint_nft(
+            ctx, 
+        )
+    }
+
+    pub fn transfer_tokens(
+        ctx: Context<TransferTokens>, 
+        quantity: u64,
+    ) -> Result<()> {
+
+        transfer::transfer_tokens(
+            ctx, 
+            quantity,
+        )
+    }
+}

+ 245 - 0
tokens/transfer-tokens/anchor/tests/test.ts

@@ -0,0 +1,245 @@
+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 { TransferTokens } from "../target/types/transfer_tokens";
+
+
+describe("Transfer Tokens", () => {
+  
+  const provider = anchor.AnchorProvider.env();
+  anchor.setProvider(provider);
+  const payer = provider.wallet as anchor.Wallet;
+  const program = anchor.workspace.TransferTokens as anchor.Program<TransferTokens>;
+
+  const tokenMintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+  const nftMintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+  
+  const recipientWallet: anchor.web3.Keypair = anchor.web3.Keypair.generate();
+
+  it("Create an SPL Token!", async () => {
+
+    const metadataAddress = anchor.web3.PublicKey.findProgramAddressSync(
+      [
+        Buffer.from("metadata"),
+        TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+        tokenMintKeypair.publicKey.toBuffer(),
+      ],
+      TOKEN_METADATA_PROGRAM_ID
+    )[0];
+
+    const sx = await program.methods.createToken(
+      "Solana Gold",
+      "GOLDSOL",
+      "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/spl-token.json",
+      9,
+    )
+      .accounts({
+        metadataAccount: metadataAddress,
+        mintAccount: tokenMintKeypair.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([tokenMintKeypair, payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${tokenMintKeypair.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(),
+        nftMintKeypair.publicKey.toBuffer(),
+      ],
+      TOKEN_METADATA_PROGRAM_ID
+    )[0];
+
+    const sx = await program.methods.createToken(
+      "Homer NFT",
+      "HOMR",
+      "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json",
+      0,
+    )
+      .accounts({
+        metadataAccount: metadataAddress,
+        mintAccount: nftMintKeypair.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([nftMintKeypair, payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${nftMintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+
+  it("Mint some tokens to your wallet!", async () => {
+
+    const associatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: tokenMintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+
+    const sx = await program.methods.mintSpl(
+      new anchor.BN(150)
+    )
+      .accounts({
+        associatedTokenAccount: associatedTokenAccountAddress,
+        mintAccount: tokenMintKeypair.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,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${tokenMintKeypair.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(),
+          nftMintKeypair.publicKey.toBuffer(),
+        ],
+        TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const editionAddress = (anchor.web3.PublicKey.findProgramAddressSync(
+        [
+          Buffer.from("metadata"),
+          TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+          nftMintKeypair.publicKey.toBuffer(),
+          Buffer.from("edition"),
+        ],
+        TOKEN_METADATA_PROGRAM_ID
+    ))[0];
+
+    const associatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: nftMintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+
+    const sx = await program.methods.mintNft()
+      .accounts({
+        associatedTokenAccount: associatedTokenAccountAddress,
+        editionAccount: editionAddress,
+        metadataAccount: metadataAddress,
+        mintAccount: nftMintKeypair.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();
+
+      console.log("Success!");
+      console.log(`   ATA Address: ${associatedTokenAccountAddress}`);
+      console.log(`   Tx Signature: ${sx}`);
+  });
+
+  it("Prep a new test wallet for transfers", async () => {
+        
+    await provider.connection.confirmTransaction(
+        await provider.connection.requestAirdrop(
+            recipientWallet.publicKey, 
+            await provider.connection.getMinimumBalanceForRentExemption(0),
+        )
+    );
+    console.log(`Recipient Pubkey: ${recipientWallet.publicKey}`);
+  });
+
+  it("Transfer some tokens to another wallet!", async () => {
+
+    const fromAssociatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: tokenMintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+    const toAssociatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: tokenMintKeypair.publicKey,
+      owner: recipientWallet.publicKey,
+    });
+
+    const sx = await program.methods.transferTokens(
+      new anchor.BN(150)
+    )
+      .accounts({
+        mintAccount: tokenMintKeypair.publicKey,
+        fromAssociatedTokenAccount: fromAssociatedTokenAccountAddress,
+        owner: payer.publicKey,
+        toAssociatedTokenAccount: toAssociatedTokenAccountAddress,
+        recipient: recipientWallet.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,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${tokenMintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+
+  it("Transfer the NFT to another wallet!", async () => {
+
+    const fromAssociatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: nftMintKeypair.publicKey,
+      owner: payer.publicKey,
+    });
+    const toAssociatedTokenAccountAddress = await anchor.utils.token.associatedAddress({
+      mint: nftMintKeypair.publicKey,
+      owner: recipientWallet.publicKey,
+    });
+
+    const sx = await program.methods.transferTokens(
+      new anchor.BN(1)
+    )
+      .accounts({
+        mintAccount: nftMintKeypair.publicKey,
+        fromAssociatedTokenAccount: fromAssociatedTokenAccountAddress,
+        owner: payer.publicKey,
+        toAssociatedTokenAccount: toAssociatedTokenAccountAddress,
+        recipient: recipientWallet.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,
+      })
+      .signers([payer.payer])
+      .rpc();
+
+    console.log("Success!");
+        console.log(`   Mint Address: ${nftMintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+  });
+});

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

+ 127 - 0
tokens/transfer-tokens/native/program/src/instructions/create.rs

@@ -0,0 +1,127 @@
+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 token_title: String,
+    pub token_symbol: String,
+    pub token_uri: String,
+    pub decimals: u8,
+}
+
+
+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),
+            args.decimals,
+        )?,
+        &[
+            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.token_title,
+            args.token_symbol,
+            args.token_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/transfer-tokens/native/program/src/instructions/mint_nft.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_nft(
+    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(())
+}
+

Some files were not shown because too many files changed in this diff