Browse Source

transfer tokens

jpcaulfi 2 years ago
parent
commit
26719ab4eb
36 changed files with 1741 additions and 6 deletions
  1. 1 0
      tokens/metaplex-expanded/README.md
  2. 1 0
      tokens/mint-freeze-authorities/README.md
  3. 1 0
      tokens/nft-minter/anchor/programs/nft-minter/src/instructions/mint.rs
  4. 2 2
      tokens/nft-minter/anchor/tests/test.ts
  5. 0 1
      tokens/nft-minter/native/tests/test.ts
  6. 2 2
      tokens/spl-token-minter/anchor/programs/spl-token-minter/src/instructions/mint.rs
  7. 0 1
      tokens/spl-token-minter/native/tests/test.ts
  8. 1 0
      tokens/token-2022/README.md
  9. 1 0
      tokens/token-lending/README.md
  10. 1 0
      tokens/transfer-tokens/README.md
  11. 14 0
      tokens/transfer-tokens/anchor/Anchor.toml
  12. 13 0
      tokens/transfer-tokens/anchor/Cargo.toml
  13. 15 0
      tokens/transfer-tokens/anchor/package.json
  14. 21 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/Cargo.toml
  15. 2 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/Xargo.toml
  16. 83 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/create.rs
  17. 128 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_nft.rs
  18. 58 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_spl.rs
  19. 9 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mod.rs
  20. 63 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/transfer.rs
  21. 62 0
      tokens/transfer-tokens/anchor/programs/transfer-tokens/src/lib.rs
  22. 245 0
      tokens/transfer-tokens/anchor/tests/test.ts
  23. 10 0
      tokens/transfer-tokens/anchor/tsconfig.json
  24. 8 0
      tokens/transfer-tokens/native/cicd.sh
  25. 22 0
      tokens/transfer-tokens/native/package.json
  26. 15 0
      tokens/transfer-tokens/native/program/Cargo.toml
  27. 127 0
      tokens/transfer-tokens/native/program/src/instructions/create.rs
  28. 146 0
      tokens/transfer-tokens/native/program/src/instructions/mint_nft.rs
  29. 86 0
      tokens/transfer-tokens/native/program/src/instructions/mint_spl.rs
  30. 9 0
      tokens/transfer-tokens/native/program/src/instructions/mod.rs
  31. 94 0
      tokens/transfer-tokens/native/program/src/instructions/transfer.rs
  32. 22 0
      tokens/transfer-tokens/native/program/src/lib.rs
  33. 53 0
      tokens/transfer-tokens/native/program/src/processor.rs
  34. 87 0
      tokens/transfer-tokens/native/tests/instructions.ts
  35. 329 0
      tokens/transfer-tokens/native/tests/test.ts
  36. 10 0
      tokens/transfer-tokens/native/tsconfig.json

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

@@ -0,0 +1 @@
+# Metaplex Metadata Expanded

+ 1 - 0
tokens/mint-freeze-authorities/README.md

@@ -0,0 +1 @@
+# Mint & Freeze Authorities

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

@@ -106,6 +106,7 @@ pub struct MintTo<'info> {
         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>,

+ 2 - 2
tokens/nft-minter/anchor/tests/test.ts

@@ -51,7 +51,7 @@ describe("NFT Minter", () => {
         console.log(`   Tx Signature: ${sx}`);
   });
 
-  it("Mint some tokens to your wallet!", async () => {
+  it("Mint the NFT to your wallet!", async () => {
 
     const metadataAddress = (anchor.web3.PublicKey.findProgramAddressSync(
         [
@@ -92,7 +92,7 @@ describe("NFT Minter", () => {
         tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
       })
       .signers([payer.payer])
-      .rpc({skipPreflight: true});
+      .rpc();
 
       console.log("Success!");
       console.log(`   ATA Address: ${associatedTokenAccountAddress}`);

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

@@ -136,7 +136,6 @@ describe("NFT Minter", async () => {
             connection, 
             new Transaction().add(ix),
             [payer],
-            {skipPreflight: true}
         );
 
         console.log("Success!");

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

@@ -9,7 +9,7 @@ use {
 
 pub fn mint_to(
     ctx: Context<MintTo>, 
-    amount: u64,
+    quantity: u64,
 ) -> Result<()> {
 
     msg!("Minting token to token account...");
@@ -24,7 +24,7 @@ pub fn mint_to(
                 authority: ctx.accounts.mint_authority.to_account_info(),
             },
         ),
-        amount,
+        quantity,
     )?;
 
     msg!("Token minted to wallet successfully.");

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

@@ -115,7 +115,6 @@ describe("SPL Token Minter", async () => {
             connection, 
             new Transaction().add(ix),
             [payer],
-            {skipPreflight: true}
         );
 
         console.log("Success!");

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

@@ -0,0 +1 @@
+# Token 2022

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

@@ -0,0 +1 @@
+# Token Lending Example

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

@@ -0,0 +1 @@
+# Transfer Tokens

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

+ 58 - 0
tokens/transfer-tokens/anchor/programs/transfer-tokens/src/instructions/mint_spl.rs

@@ -0,0 +1,58 @@
+use {
+    anchor_lang::prelude::*,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+};
+
+
+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.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(),
+            },
+        ),
+        quantity,
+    )?;
+
+    msg!("Token minted to wallet successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+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(
+        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>,
+}

+ 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::*;

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

@@ -0,0 +1,63 @@
+use {
+    anchor_lang::prelude::*,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+};
+
+
+pub fn transfer_tokens(
+    ctx: Context<TransferTokens>, 
+    quantity: u64,
+) -> Result<()> {
+
+    msg!("Transferring tokens...");
+    msg!("Mint: {}", &ctx.accounts.mint_account.to_account_info().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.from_associated_token_account.to_account_info(),
+                to: ctx.accounts.to_associated_token_account.to_account_info(),
+                authority: ctx.accounts.owner.to_account_info(),
+            },
+        ),
+        quantity,
+    )?;
+
+    msg!("Tokens transferred successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+pub struct TransferTokens<'info> {
+    #[account(mut)]
+    pub mint_account: Account<'info, token::Mint>,
+    #[account(
+        init_if_needed,
+        payer = payer,
+        associated_token::mint = mint_account,
+        associated_token::authority = owner,
+    )]
+    pub from_associated_token_account: Account<'info, token::TokenAccount>,
+    pub owner: SystemAccount<'info>,
+    #[account(
+        init_if_needed,
+        payer = payer,
+        associated_token::mint = mint_account,
+        associated_token::authority = recipient,
+    )]
+    pub to_associated_token_account: Account<'info, token::TokenAccount>,
+    pub recipient: 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>,
+    pub associated_token_program: Program<'info, associated_token::AssociatedToken>,
+}

+ 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(())
+}
+

+ 86 - 0
tokens/transfer-tokens/native/program/src/instructions/mint_spl.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 MintSplArgs {
+    pub quantity: u64,
+}
+
+
+pub fn mint_spl(
+    accounts: &[AccountInfo],
+    args: MintSplArgs,
+) -> 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(())
+}
+

+ 9 - 0
tokens/transfer-tokens/native/program/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::*;

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

@@ -0,0 +1,94 @@
+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 TransferTokensArgs {
+    pub quantity: u64,
+}
+
+
+pub fn transfer_tokens(
+    accounts: &[AccountInfo],
+    args: TransferTokensArgs,
+) -> ProgramResult {
+
+    let accounts_iter = &mut accounts.iter();
+
+    let mint_account = next_account_info(accounts_iter)?;
+    let from_associated_token_account = next_account_info(accounts_iter)?;
+    let to_associated_token_account = next_account_info(accounts_iter)?;
+    let owner = next_account_info(accounts_iter)?;
+    let recipient = 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 to_associated_token_account.lamports() == 0 {
+        msg!("Creating associated token account for recipient...");
+        invoke(
+            &associated_token_account_instruction::create_associated_token_account(
+                &payer.key,
+                &recipient.key,
+                &mint_account.key,
+            ),
+            &[
+                mint_account.clone(),
+                to_associated_token_account.clone(),
+                recipient.clone(),
+                payer.clone(),
+                system_program.clone(),
+                token_program.clone(),
+                associated_token_program.clone(),
+            ]
+        )?;
+    } else {
+        msg!("Associated token account exists.");
+    }
+    msg!("Recipient Associated Token Address: {}", to_associated_token_account.key);
+
+    msg!("Transferring {} tokens...", args.quantity);
+    msg!("Mint: {}", mint_account.key);   
+    msg!("Owner Token Address: {}", from_associated_token_account.key);
+    msg!("Recipient Token Address: {}", to_associated_token_account.key);
+    invoke(
+        &token_instruction::transfer(
+            &token_program.key,
+            &from_associated_token_account.key,
+            &to_associated_token_account.key,
+            &owner.key,
+            &[&owner.key, &recipient.key],
+            args.quantity,
+        )?,
+        &[
+            mint_account.clone(),
+            from_associated_token_account.clone(),
+            to_associated_token_account.clone(),
+            owner.clone(),
+            recipient.clone(),
+            token_program.clone(),
+        ]
+    )?;
+
+    msg!("Tokens transferred successfully.");
+
+    Ok(())
+}
+

+ 22 - 0
tokens/transfer-tokens/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)
+}

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

@@ -0,0 +1,53 @@
+use {
+    borsh::{ 
+        BorshDeserialize, 
+        BorshSerialize, 
+    },
+    solana_program::{
+        account_info::AccountInfo, 
+        entrypoint::ProgramResult, 
+        pubkey::Pubkey,
+    },
+};
+
+use crate::instructions::{ 
+    create::{
+        CreateTokenArgs,
+        create_token,
+    }, 
+    mint_nft::mint_nft,
+    mint_spl::{
+        MintSplArgs,
+        mint_spl,
+    }, 
+    transfer::{
+        TransferTokensArgs,
+        transfer_tokens,
+    }
+};
+
+
+#[derive(BorshSerialize, BorshDeserialize, Debug)]
+enum MyInstruction {
+    Create(CreateTokenArgs),
+    MintNft,
+    MintSpl(MintSplArgs),
+    TransferTokens(TransferTokensArgs),
+}
+
+
+pub fn process_instruction(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    instruction_data: &[u8],
+) -> ProgramResult {
+
+    let instruction = MyInstruction::try_from_slice(instruction_data)?;
+
+    match instruction {
+        MyInstruction::Create(args) => create_token(accounts, args),
+        MyInstruction::MintNft => mint_nft(accounts),
+        MyInstruction::MintSpl(args) => mint_spl(accounts, args),
+        MyInstruction::TransferTokens(args) => transfer_tokens(accounts, args),
+    }
+}

+ 87 - 0
tokens/transfer-tokens/native/tests/instructions.ts

@@ -0,0 +1,87 @@
+import * as borsh from "borsh";
+
+
+class Assignable {
+    constructor(properties) {
+        Object.keys(properties).map((key) => {
+            return (this[key] = properties[key]);
+        });
+    };
+};
+
+export enum MyInstruction {
+    Create,
+    MintNft,
+    MintSpl,
+    TransferTokens,
+}
+
+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'],
+                ['decimals', 'u8'],
+            ]
+        }
+    ]
+]);
+
+export class MintNftArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(MintNftArgsSchema, this));
+    }
+};
+const MintNftArgsSchema = new Map([
+    [
+        MintNftArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+            ]
+        }
+    ]
+]);
+
+export class MintSplArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(MintSplArgsSchema, this));
+    }
+};
+const MintSplArgsSchema = new Map([
+    [
+        MintSplArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+                ['quantity', 'u64'],
+            ]
+        }
+    ]
+]);
+
+export class TransferTokensArgs extends Assignable {
+    toBuffer() {
+        return Buffer.from(borsh.serialize(TransferTokensArgsSchema, this));
+    }
+};
+const TransferTokensArgsSchema = new Map([
+    [
+        TransferTokensArgs, {
+            kind: 'struct',
+            fields: [
+                ['instruction', 'u8'],
+                ['quantity', 'u64'],
+            ]
+        }
+    ]
+]);

+ 329 - 0
tokens/transfer-tokens/native/tests/test.ts

@@ -0,0 +1,329 @@
+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,
+    MintNftArgs,
+    MintSplArgs,
+    TransferTokensArgs,
+    MyInstruction,
+} 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("Transferring 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');
+
+    const tokenMintKeypair: Keypair = Keypair.generate();
+    const nftMintKeypair: Keypair = Keypair.generate();
+
+    const recipientWallet = Keypair.generate();
+
+    it("Create an SPL Token!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              tokenMintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        const instructionData = new CreateTokenArgs({
+            instruction: MyInstruction.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",
+            decimals: 9,
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: tokenMintKeypair.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, tokenMintKeypair]
+        );
+
+        console.log("Success!");
+        console.log(`   Mint Address: ${tokenMintKeypair.publicKey}`);
+        console.log(`   Tx Signature: ${sx}`);
+    });
+
+    it("Create an NFT!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              nftMintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        const instructionData = new CreateTokenArgs({
+            instruction: MyInstruction.Create,
+            token_title: "Homer NFT",
+            token_symbol: "HOMR",
+            token_uri: "https://raw.githubusercontent.com/solana-developers/program-examples/new-examples/tokens/tokens/.assets/nft.json",
+            decimals: 0,
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: nftMintKeypair.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, nftMintKeypair]
+        );
+
+        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 getAssociatedTokenAddress(
+            tokenMintKeypair.publicKey,
+            payer.publicKey,
+        );
+        
+        const instructionData = new MintSplArgs({
+            instruction: MyInstruction.MintSpl,
+            quantity: new BN(150),
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: tokenMintKeypair.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}`);
+    });
+
+    it("Mint the NFT to your wallet!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+                Buffer.from("metadata"),
+                TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+                nftMintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+
+        const editionAddress = (PublicKey.findProgramAddressSync(
+            [
+                Buffer.from("metadata"),
+                TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+                nftMintKeypair.publicKey.toBuffer(),
+                Buffer.from("edition"),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+
+        const associatedTokenAccountAddress = await getAssociatedTokenAddress(
+            nftMintKeypair.publicKey,
+            payer.publicKey,
+        );
+        
+        const instructionData = new MintNftArgs({
+            instruction: MyInstruction.MintNft,
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: nftMintKeypair.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}`);
+    });
+
+    it("Prep a new test wallet for transfers", async () => {
+        
+        await connection.confirmTransaction(
+            await connection.requestAirdrop(
+                recipientWallet.publicKey, 
+                await connection.getMinimumBalanceForRentExemption(0),
+            )
+        );
+        console.log(`Recipient Pubkey: ${recipientWallet.publicKey}`);
+    });
+
+    it("Transfer tokens to another wallet!", async () => {
+        
+        const fromAssociatedTokenAddress = await getAssociatedTokenAddress(
+            tokenMintKeypair.publicKey,
+            payer.publicKey
+        );
+        console.log(`Owner Token Address: ${fromAssociatedTokenAddress}`);
+        const toAssociatedTokenAddress = await getAssociatedTokenAddress(
+            tokenMintKeypair.publicKey,
+            recipientWallet.publicKey
+        );
+        console.log(`Recipient Token Address: ${toAssociatedTokenAddress}`);
+
+        const transferToInstructionData = new TransferTokensArgs({
+            instruction: MyInstruction.TransferTokens,
+            quantity: new BN(15),
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: tokenMintKeypair.publicKey, isSigner: false, isWritable: true },      // Mint account
+                { pubkey: fromAssociatedTokenAddress, isSigner: false, isWritable: true },      // Owner Token account
+                { pubkey: toAssociatedTokenAddress, isSigner: false, isWritable: true },        // Recipient Token account
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Owner
+                { pubkey: recipientWallet.publicKey, isSigner: true, isWritable: true },        // Recipient
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Payer
+                { 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],
+            { skipPreflight: true }
+        );
+    });
+
+    it("Transfer NFT to another wallet!", async () => {
+        
+        const fromAssociatedTokenAddress = await getAssociatedTokenAddress(
+            nftMintKeypair.publicKey,
+            payer.publicKey
+        );
+        console.log(`Owner Token Address: ${fromAssociatedTokenAddress}`);
+        const toAssociatedTokenAddress = await getAssociatedTokenAddress(
+            nftMintKeypair.publicKey,
+            recipientWallet.publicKey
+        );
+        console.log(`Recipient Token Address: ${toAssociatedTokenAddress}`);
+
+        const transferToInstructionData = new TransferTokensArgs({
+            instruction: MyInstruction.TransferTokens,
+            quantity: new BN(1),
+        });
+
+        let ix = new TransactionInstruction({
+            keys: [
+                { pubkey: nftMintKeypair.publicKey, isSigner: false, isWritable: true },        // Mint account
+                { pubkey: fromAssociatedTokenAddress, isSigner: false, isWritable: true },      // Owner Token account
+                { pubkey: toAssociatedTokenAddress, isSigner: false, isWritable: true },        // Recipient Token account
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Owner
+                { pubkey: recipientWallet.publicKey, isSigner: true, isWritable: true },        // Recipient
+                { pubkey: payer.publicKey, isSigner: true, isWritable: true },                  // Payer
+                { 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],
+            { skipPreflight: true }
+        );
+    });
+});

+ 10 - 0
tokens/transfer-tokens/native/tsconfig.json

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