Browse Source

create tokens examples

jpcaulfi 2 years ago
parent
commit
a66c8b2f78
28 changed files with 1265 additions and 0 deletions
  1. 1 0
      tokens/create-nft/README.md
  2. 8 0
      tokens/create-nft/native/cicd.sh
  3. 22 0
      tokens/create-nft/native/package.json
  4. 15 0
      tokens/create-nft/native/program/Cargo.toml
  5. 165 0
      tokens/create-nft/native/program/src/lib.rs
  6. 108 0
      tokens/create-nft/native/tests/test.ts
  7. 10 0
      tokens/create-nft/native/tsconfig.json
  8. 1 0
      tokens/create-spl-token/README.md
  9. 14 0
      tokens/create-spl-token/anchor/Anchor.toml
  10. 13 0
      tokens/create-spl-token/anchor/Cargo.toml
  11. 14 0
      tokens/create-spl-token/anchor/package.json
  12. 21 0
      tokens/create-spl-token/anchor/programs/mint-2/Cargo.toml
  13. 2 0
      tokens/create-spl-token/anchor/programs/mint-2/Xargo.toml
  14. 95 0
      tokens/create-spl-token/anchor/programs/mint-2/src/instructions/create_token_mint.rs
  15. 76 0
      tokens/create-spl-token/anchor/programs/mint-2/src/instructions/mint_to_another_wallet.rs
  16. 74 0
      tokens/create-spl-token/anchor/programs/mint-2/src/instructions/mint_to_your_wallet.rs
  17. 9 0
      tokens/create-spl-token/anchor/programs/mint-2/src/instructions/mod.rs
  18. 62 0
      tokens/create-spl-token/anchor/programs/mint-2/src/instructions/transfer_to_another_wallet.rs
  19. 68 0
      tokens/create-spl-token/anchor/programs/mint-2/src/lib.rs
  20. 175 0
      tokens/create-spl-token/anchor/tests/test.ts
  21. 6 0
      tokens/create-spl-token/anchor/tests/token_metadata.json
  22. 10 0
      tokens/create-spl-token/anchor/tsconfig.json
  23. 8 0
      tokens/create-spl-token/native/cicd.sh
  24. 22 0
      tokens/create-spl-token/native/package.json
  25. 15 0
      tokens/create-spl-token/native/program/Cargo.toml
  26. 133 0
      tokens/create-spl-token/native/program/src/lib.rs
  27. 108 0
      tokens/create-spl-token/native/tests/test.ts
  28. 10 0
      tokens/create-spl-token/native/tsconfig.json

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

@@ -0,0 +1 @@
+# Create an NFT

+ 8 - 0
tokens/create-nft/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 --bpf-out-dir=./program/target/so
+solana program deploy ./program/target/so/program.so

+ 22 - 0
tokens/create-nft/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.2.0",
+    "@solana/web3.js": "^1.47.3",
+    "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/create-nft/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"]

+ 165 - 0
tokens/create-nft/native/program/src/lib.rs

@@ -0,0 +1,165 @@
+use {
+    borsh::{ 
+        BorshSerialize, 
+        BorshDeserialize 
+    },
+    solana_program::{
+        account_info::{next_account_info, AccountInfo}, 
+        entrypoint,
+        entrypoint::ProgramResult, 
+        msg, 
+        program::invoke,
+        program_pack::Pack,
+        pubkey::Pubkey,
+        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,
+}
+
+
+entrypoint!(process_instruction);
+
+
+fn process_instruction(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    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)?;
+    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_v2(
+    //         *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,
+    //     ),
+    //     &[
+    //         metadata_account.clone(),
+    //         mint_account.clone(),
+    //         mint_authority.clone(),
+    //         payer.clone(),
+    //         token_metadata_program.clone(),
+    //         rent.clone(),
+    //     ]
+    // )?;
+
+    // // Optionally, we can make this NFT a "Master Edition"
+    // //
+    // msg!("Creating metadata account...");
+    // msg!("Metadata account address: {}", metadata_account.key);
+    // invoke(
+    //     &mpl_instruction::create_metadata_accounts_v2(
+    //         *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,
+    //     ),
+    //     &[
+    //         metadata_account.clone(),
+    //         mint_account.clone(),
+    //         mint_authority.clone(),
+    //         payer.clone(),
+    //         token_metadata_program.clone(),
+    //         rent.clone(),
+    //     ]
+    // )?;
+
+    msg!("NFT mint created successfully.");
+
+    Ok(())
+}

+ 108 - 0
tokens/create-nft/native/tests/test.ts

@@ -0,0 +1,108 @@
+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'],
+            ]
+        }
+    ]
+]);
+
+
+describe("Create an NFT!", async () => {
+
+    // const connection = new Connection(`https://api.devnet.solana.com/`, 'confirmed');
+    const connection = new Connection(`http://localhost:8899`, '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("Create!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        const metadataInstructionData = 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",
+        });
+
+        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: metadataInstructionData.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}`);
+    });
+  });
+  

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

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

+ 1 - 0
tokens/create-spl-token/README.md

@@ -0,0 +1 @@
+# Create an SPL Token

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

@@ -0,0 +1,14 @@
+[features]
+seeds = false
+[programs.devnet]
+mint_2 = "8vbaY8zv9r3AgeLjyAr7LEprJwLN5Jjus97crJBD2AV2"
+
+[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/create-spl-token/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

+ 14 - 0
tokens/create-spl-token/anchor/package.json

@@ -0,0 +1,14 @@
+{
+    "dependencies": {
+        "@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/create-spl-token/anchor/programs/mint-2/Cargo.toml

@@ -0,0 +1,21 @@
+[package]
+name = "mint-2"
+version = "0.1.0"
+description = "Created with Anchor"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "lib"]
+name = "mint_2"
+
+[features]
+no-entrypoint = []
+no-idl = []
+no-log-ix-name = []
+cpi = ["no-entrypoint"]
+default = []
+
+[dependencies]
+anchor-lang = "0.25.0"
+anchor-spl = "0.25.0"
+mpl-token-metadata = { version="1.2.5", features = [ "no-entrypoint" ] }

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

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

+ 95 - 0
tokens/create-spl-token/anchor/programs/mint-2/src/instructions/create_token_mint.rs

@@ -0,0 +1,95 @@
+use {
+    anchor_lang::{
+        prelude::*,
+        solana_program::program::invoke_signed,
+    },
+    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,
+) -> Result<()> {
+
+    msg!("Creating metadata account...");
+    msg!("Metadata account address: {}", &ctx.accounts.metadata_account.key());
+    invoke_signed(
+        &mpl_instruction::create_metadata_accounts_v2(
+            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
+            None,                                           // Creators
+            0,                                              // Seller fee basis points
+            true,                                           // Update authority is signer
+            false,                                          // Is mutable
+            None,                                           // Collection
+            None,                                           // Uses
+        ),
+        &[
+            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(),
+        ],
+        &[
+            &[
+                b"mint_authority_", 
+                ctx.accounts.mint_account.key().as_ref(),
+                &[mint_authority_pda_bump],
+            ]
+        ]
+    )?;
+
+    msg!("Token mint created successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+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 = 9,
+        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>,
+    #[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>,
+}
+
+#[account]
+pub struct MintAuthorityPda {}

+ 76 - 0
tokens/create-spl-token/anchor/programs/mint-2/src/instructions/mint_to_another_wallet.rs

@@ -0,0 +1,76 @@
+use {
+    anchor_lang::prelude::*,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+};
+use crate::create_token_mint::MintAuthorityPda;
+
+
+pub fn mint_to_another_wallet(
+    ctx: Context<MintToAnotherWallet>, 
+    amount: u64,
+    mint_authority_pda_bump: u8,
+) -> 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());     
+    token::mint_to(
+        CpiContext::new_with_signer(
+            ctx.accounts.token_program.to_account_info(),
+            token::MintTo {
+                mint: ctx.accounts.mint_account.to_account_info(),
+                to: ctx.accounts.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,
+    )?;
+
+    msg!("Token minted to wallet successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+#[instruction(amount: u64, mint_authority_pda_bump: u8)]
+pub struct MintToAnotherWallet<'info> {
+    #[account(
+        mut,
+        mint::decimals = 9,
+        mint::authority = mint_authority.key(),
+    )]
+    pub mint_account: Account<'info, token::Mint>,
+    #[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,
+        payer = payer,
+        associated_token::mint = mint_account,
+        associated_token::authority = recipient,
+    )]
+    pub 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>,
+}

+ 74 - 0
tokens/create-spl-token/anchor/programs/mint-2/src/instructions/mint_to_your_wallet.rs

@@ -0,0 +1,74 @@
+use {
+    anchor_lang::prelude::*,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+};
+use crate::create_token_mint::MintAuthorityPda;
+
+
+pub fn mint_to_your_wallet(
+    ctx: Context<MintToYourWallet>, 
+    amount: u64,
+    mint_authority_pda_bump: u8,
+) -> 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());     
+    token::mint_to(
+        CpiContext::new_with_signer(
+            ctx.accounts.token_program.to_account_info(),
+            token::MintTo {
+                mint: ctx.accounts.mint_account.to_account_info(),
+                to: ctx.accounts.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,
+    )?;
+
+    msg!("Token minted to wallet successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+#[instruction(amount: u64, mint_authority_pda_bump: u8)]
+pub struct MintToYourWallet<'info> {
+    #[account(
+        mut,
+        mint::decimals = 9,
+        mint::authority = mint_authority.key(),
+    )]
+    pub mint_account: Account<'info, token::Mint>,
+    #[account(
+        mut, 
+        seeds = [
+            b"mint_authority_", 
+            mint_account.key().as_ref()
+        ],
+        bump = mint_authority_pda_bump
+    )]
+    pub mint_authority: Account<'info, MintAuthorityPda>,
+    #[account(
+        init,
+        payer = payer,
+        associated_token::mint = mint_account,
+        associated_token::authority = payer,
+    )]
+    pub 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/create-spl-token/anchor/programs/mint-2/src/instructions/mod.rs

@@ -0,0 +1,9 @@
+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::*;

+ 62 - 0
tokens/create-spl-token/anchor/programs/mint-2/src/instructions/transfer_to_another_wallet.rs

@@ -0,0 +1,62 @@
+use {
+    anchor_lang::prelude::*,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
+};
+
+
+pub fn transfer_to_another_wallet(
+    ctx: Context<TransferToAnotherWallet>, 
+    amount: u64,
+) -> Result<()> {
+
+    msg!("Transferring {} tokens to new token account...", amount);
+    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());
+    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(),
+                authority: ctx.accounts.owner.to_account_info(),
+            },
+        ),
+        amount,
+    )?;
+
+    msg!("Tokens transferred to wallet successfully.");
+
+    Ok(())
+}
+
+
+#[derive(Accounts)]
+pub struct TransferToAnotherWallet<'info> {
+    #[account(mut)]
+    pub mint_account: Account<'info, token::Mint>,
+    #[account(
+        mut,
+        associated_token::mint = mint_account,
+        associated_token::authority = owner,
+    )]
+    pub owner_token_account: Account<'info, token::TokenAccount>,
+    #[account(
+        init,
+        payer = owner,
+        associated_token::mint = mint_account,
+        associated_token::authority = recipient,
+    )]
+    pub recipient_token_account: Account<'info, token::TokenAccount>,
+    #[account(mut)]
+    pub owner: Signer<'info>,
+    /// CHECK: Crediting not Debiting
+    pub recipient: UncheckedAccount<'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>,
+}

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

@@ -0,0 +1,68 @@
+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,
+        )
+    }
+}

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

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

+ 6 - 0
tokens/create-spl-token/anchor/tests/token_metadata.json

@@ -0,0 +1,6 @@
+{
+    "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"
+}

+ 10 - 0
tokens/create-spl-token/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/create-spl-token/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 --bpf-out-dir=./program/target/so
+solana program deploy ./program/target/so/program.so

+ 22 - 0
tokens/create-spl-token/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.2.0",
+    "@solana/web3.js": "^1.47.3",
+    "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/create-spl-token/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"]

+ 133 - 0
tokens/create-spl-token/native/program/src/lib.rs

@@ -0,0 +1,133 @@
+use {
+    borsh::{ 
+        BorshSerialize, 
+        BorshDeserialize 
+    },
+    solana_program::{
+        account_info::{next_account_info, AccountInfo}, 
+        entrypoint,
+        entrypoint::ProgramResult, 
+        msg, 
+        program::invoke,
+        program_pack::Pack,
+        pubkey::Pubkey,
+        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,
+}
+
+
+entrypoint!(process_instruction);
+
+
+fn process_instruction(
+    _program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    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)?;
+    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_v2(
+            *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,
+        ),
+        &[
+            metadata_account.clone(),
+            mint_account.clone(),
+            mint_authority.clone(),
+            payer.clone(),
+            token_metadata_program.clone(),
+            rent.clone(),
+        ]
+    )?;
+
+    msg!("Token mint created successfully.");
+
+    Ok(())
+}

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

@@ -0,0 +1,108 @@
+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'],
+            ]
+        }
+    ]
+]);
+
+
+describe("Create an SPL Token!", async () => {
+
+    // const connection = new Connection(`https://api.devnet.solana.com/`, 'confirmed');
+    const connection = new Connection(`http://localhost:8899`, '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("Create!", async () => {
+
+        const metadataAddress = (PublicKey.findProgramAddressSync(
+            [
+              Buffer.from("metadata"),
+              TOKEN_METADATA_PROGRAM_ID.toBuffer(),
+              mintKeypair.publicKey.toBuffer(),
+            ],
+            TOKEN_METADATA_PROGRAM_ID
+        ))[0];
+        
+        const metadataInstructionData = 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",
+        });
+
+        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: metadataInstructionData.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}`);
+    });
+  });
+  

+ 10 - 0
tokens/create-spl-token/native/tsconfig.json

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