jpcaulfi 3 роки тому
батько
коміт
4017cab78a

+ 9 - 6
nfts/mint/README.md

@@ -1,9 +1,12 @@
-# Create a New NFT Mint
+# Create a New SPL Token Mint
 
-:notebook_with_decorative_cover: Note: This example is built off of [Mint Token](../../tokens/mint/README.md) and [Mint Token To](../../tokens/mint-to/README.md). If you get stuck, check out those examples.   
+This example demonstrates how to create an SPl Token on Solana with some metadata such as a token symbol and icon.
 
-___
+### :key: Keys:
 
-An NFT is obviously just a token on Solana! So, the process is the same for creating an NFT. There's just a few additional steps:
-- Decimals are set to 0
-- Minting must be disabled after one token is minted (ie. cap the supply at 1).
+- SPL Tokens by default have **9 decimals**, and **NFTs have 0 decimals**. "Decimals" here means the number of decimal; ie. a token with 3 decimals will be tracked in increments of 0.001.   
+- You can use [Metaplex's Token Metadata Program](https://docs.metaplex.com/) to create metadata for your token.
+- Steps:
+    1. Create an account for the Mint.
+    2. Initialize that account as a Mint Account.
+    3. Create a metadata account associated with that Mint Account.

+ 1 - 1
nfts/mint/anchor/Anchor.toml

@@ -1,7 +1,7 @@
 [features]
 seeds = false
 [programs.devnet]
-mint_nft = "4Bg2L3bHNk2wPszETtqE76hJHVXmnw2pqeUuumSSx7in"
+mint_nft = "AaWo2HWYXs5YtZoV4mPN1ZA8e8gb3wHqrXxQRxTWBJBC"
 
 [registry]
 url = "https://anchor.projectserum.com"

+ 0 - 6
nfts/mint/anchor/assets/token_metadata.json

@@ -1,6 +0,0 @@
-{
-    "name": "Solana Platinum NFT",
-    "symbol": "SOLP",
-    "description": "Solana NFT - Platinum",
-    "image": "https://www.creativefabrica.com/wp-content/uploads/2021/09/27/Solana-Global-Circle-Line-Icon-Graphics-17928498-1.jpg"
-}

+ 2 - 1
nfts/mint/anchor/programs/mint-nft/Cargo.toml

@@ -18,4 +18,5 @@ default = []
 [dependencies]
 anchor-lang = "0.24.2"
 anchor-spl = "0.24.2"
-mpl-token-metadata = { version="1.2.5", features = [ "no-entrypoint" ] }
+mpl-token-metadata = { version="1.2.5", features = [ "no-entrypoint" ] }
+spl-token = { version="3.3.0", features = [ "no-entrypoint" ] }

+ 67 - 10
nfts/mint/anchor/programs/mint-nft/src/lib.rs

@@ -4,12 +4,16 @@ use {
         solana_program::program::invoke,
         system_program,
     },
-    anchor_spl::token,
+    anchor_spl::{
+        token,
+        associated_token,
+    },
     mpl_token_metadata::instruction as mpl_instruction,
+    spl_token::instruction::AuthorityType,
 };
 
 
-declare_id!("4Bg2L3bHNk2wPszETtqE76hJHVXmnw2pqeUuumSSx7in");
+declare_id!("AaWo2HWYXs5YtZoV4mPN1ZA8e8gb3wHqrXxQRxTWBJBC");
 
 
 #[program]
@@ -17,14 +21,12 @@ pub mod mint_nft {
     use super::*;
 
     pub fn mint_token(
-        ctx: Context<MintNft>, 
+        ctx: Context<MintToken>, 
         metadata_title: String, 
         metadata_symbol: String, 
         metadata_uri: String,
     ) -> Result<()> {
 
-        const MINT_SIZE: u64 = 82;
-
         msg!("Creating mint account...");
         msg!("Mint: {}", &ctx.accounts.mint_account.key());
         system_program::create_account(
@@ -35,8 +37,8 @@ pub mod mint_nft {
                     to: ctx.accounts.mint_account.to_account_info(),
                 },
             ),
-            (Rent::get()?).minimum_balance(MINT_SIZE as usize),
-            MINT_SIZE,
+            (Rent::get()?).minimum_balance(token::Mint::LEN),
+            token::Mint::LEN as u64,
             &ctx.accounts.token_program.key(),
         )?;
 
@@ -50,7 +52,7 @@ pub mod mint_nft {
                     rent: ctx.accounts.rent.to_account_info(),
                 },
             ),
-            0,                                              // 0 Decimals
+            0,                                              // 0 Decimals (NFT)
             &ctx.accounts.mint_authority.key(),
             Some(&ctx.accounts.mint_authority.key()),
         )?;
@@ -84,7 +86,58 @@ pub mod mint_nft {
             ],
         )?;
 
-        msg!("Token mint process completed successfully.");
+        msg!("NFT mint created successfully.");
+
+        msg!("Creating token account...");
+        msg!("Token Address: {}", &ctx.accounts.token_account.key());    
+        associated_token::create(
+            CpiContext::new(
+                ctx.accounts.associated_token_program.to_account_info(),
+                associated_token::Create {
+                    payer: ctx.accounts.mint_authority.to_account_info(),
+                    associated_token: ctx.accounts.token_account.to_account_info(),
+                    authority: ctx.accounts.mint_authority.to_account_info(),
+                    mint: ctx.accounts.mint_account.to_account_info(),
+                    system_program: ctx.accounts.system_program.to_account_info(),
+                    token_program: ctx.accounts.token_program.to_account_info(),
+                    rent: ctx.accounts.rent.to_account_info(),
+                },
+            ),
+        )?;
+
+        msg!("Minting NFT to token account...");
+        msg!("NFT Mint: {}", &ctx.accounts.mint_account.to_account_info().key());   
+        msg!("Token Address: {}", &ctx.accounts.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.token_account.to_account_info(),
+                    authority: ctx.accounts.mint_authority.to_account_info(),
+                },
+            ),
+            1,
+        )?;
+
+        msg!("NFT minted to wallet successfully.");
+
+        msg!("Disabling future minting...");
+        msg!("NFT Mint: {}", &ctx.accounts.mint_account.to_account_info().key());   
+        token::set_authority(
+            CpiContext::new(
+                ctx.accounts.token_program.to_account_info(),
+                token::SetAuthority {
+                    current_authority: ctx.accounts.mint_authority.to_account_info(),
+                    account_or_mint: ctx.accounts.mint_account.to_account_info(),
+                },
+            ),
+            AuthorityType::MintTokens,
+            None
+        )?;
+
+        msg!("NFT minting disabled successfully.");
+        msg!("NFT mint process completed successfully.");
 
         Ok(())
     }
@@ -92,17 +145,21 @@ pub mod mint_nft {
 
 
 #[derive(Accounts)]
-pub struct MintNft<'info> {
+pub struct MintToken<'info> {
     /// CHECK: We're about to create this with Metaplex
     #[account(mut)]
     pub metadata_account: UncheckedAccount<'info>,
     #[account(mut)]
     pub mint_account: Signer<'info>,
+    /// CHECK: We're about to create this with Anchor
+    #[account(mut)]
+    pub token_account: UncheckedAccount<'info>,
     #[account(mut)]
     pub mint_authority: 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>,
 }

+ 13 - 9
nfts/mint/anchor/tests/test.ts

@@ -6,8 +6,12 @@ const TOKEN_METADATA_PROGRAM_ID = new anchor.web3.PublicKey(
   "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
 );
 
+const testTokenTitle = "Solana Platinum NFT";
+const testTokenSymbol = "SOLP";
+const testTokenUri = "https://raw.githubusercontent.com/solana-developers/program-examples/main/nfts/nft_metadata.json";
 
-describe("mint-NFT", () => {
+
+describe("mint-token", () => {
   
   const provider = anchor.AnchorProvider.env();
   anchor.setProvider(provider);
@@ -19,8 +23,6 @@ describe("mint-NFT", () => {
     const mintKeypair: anchor.web3.Keypair = anchor.web3.Keypair.generate();
     console.log(`New token: ${mintKeypair.publicKey}`);
 
-    // Derive the metadata account's address and set the metadata
-    //
     const metadataAddress = (await anchor.web3.PublicKey.findProgramAddress(
       [
         Buffer.from("metadata"),
@@ -29,22 +31,24 @@ describe("mint-NFT", () => {
       ],
       TOKEN_METADATA_PROGRAM_ID
     ))[0];
-    const testNftTitle = "Solana Platinum NFT";
-    const testNftSymbol = "SOLP";
-    const testNftUri = "https://raw.githubusercontent.com/solana-developers/program-examples/main/nfts/mint/anchor/assets/token_metadata.json";
 
-    // Transact with the "mint_token" function in our on-chain program
-    //
+    const tokenAddress = await anchor.utils.token.associatedAddress({
+      mint: mintKeypair.publicKey,
+      owner: payer.publicKey
+    });
+
     await program.methods.mintToken(
-      testNftTitle, testNftSymbol, testNftUri
+      testTokenTitle, testTokenSymbol, testTokenUri
     )
     .accounts({
       metadataAccount: metadataAddress,
       mintAccount: mintKeypair.publicKey,
+      tokenAccount: tokenAddress,
       mintAuthority: payer.publicKey,
       systemProgram: anchor.web3.SystemProgram.programId,
       tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
       tokenMetadataProgram: TOKEN_METADATA_PROGRAM_ID,
+      associatedTokenProgram: anchor.utils.token.ASSOCIATED_PROGRAM_ID,
     })
     .signers([payer.payer, mintKeypair])
     .rpc();

+ 0 - 6
nfts/mint/native/assets/token_metadata.json

@@ -1,6 +0,0 @@
-{
-    "name": "Solana Platinum NFT",
-    "symbol": "SOLP",
-    "description": "Solana NFT - Platinum",
-    "image": "https://www.creativefabrica.com/wp-content/uploads/2021/09/27/Solana-Global-Circle-Line-Icon-Graphics-17928498-1.jpg"
-}

+ 1 - 0
nfts/mint/native/program/Cargo.toml

@@ -8,6 +8,7 @@ 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]

+ 75 - 11
nfts/mint/native/program/src/lib.rs

@@ -8,6 +8,7 @@ use {
         entrypoint::ProgramResult, 
         msg, 
         program::invoke,
+        program_pack::Pack,
         pubkey::Pubkey,
         rent::Rent,
         system_instruction,
@@ -15,6 +16,10 @@ use {
     },
     spl_token::{
         instruction as token_instruction,
+        state::Mint,
+    },
+    spl_associated_token_account::{
+        instruction as token_account_instruction,
     },
     mpl_token_metadata::{
         instruction as mpl_instruction,
@@ -31,19 +36,19 @@ fn process_instruction(
     instruction_data: &[u8],
 ) -> ProgramResult {
 
-    const MINT_SIZE: u64 = 82;
-
     let accounts_iter = &mut accounts.iter();
 
-    let mint_account = next_account_info(accounts_iter)?;
     let metadata_account = next_account_info(accounts_iter)?;
+    let mint_account = next_account_info(accounts_iter)?;
+    let token_account = next_account_info(accounts_iter)?;
     let mint_authority = next_account_info(accounts_iter)?;
     let rent = next_account_info(accounts_iter)?;
     let _system_program = next_account_info(accounts_iter)?;
     let token_program = next_account_info(accounts_iter)?;
+    let associated_token_program = next_account_info(accounts_iter)?;
     let token_metadata_program = next_account_info(accounts_iter)?;
 
-    let token_metadata = TokenMetadata::try_from_slice(instruction_data)?;
+    let nft_metadata = NftMetadata::try_from_slice(instruction_data)?;
     
     msg!("Creating mint account...");
     msg!("Mint: {}", mint_account.key);
@@ -51,8 +56,8 @@ fn process_instruction(
         &system_instruction::create_account(
             &mint_authority.key,
             &mint_account.key,
-            (Rent::get()?).minimum_balance(MINT_SIZE as usize),
-            MINT_SIZE,
+            (Rent::get()?).minimum_balance(Mint::LEN),
+            Mint::LEN as u64,
             &token_program.key,
         ),
         &[
@@ -70,7 +75,7 @@ fn process_instruction(
             &mint_account.key,
             &mint_authority.key,
             Some(&mint_authority.key),
-            0,                              // 0 Decimals
+            9,                              // 9 Decimals
         )?,
         &[
             mint_account.clone(),
@@ -90,9 +95,9 @@ fn process_instruction(
             *mint_authority.key,            // Mint Authority
             *mint_authority.key,            // Payer
             *mint_authority.key,            // Update Authority
-            token_metadata.title,           // Name
-            token_metadata.symbol,          // Symbol
-            token_metadata.uri,             // URI
+            nft_metadata.title,           // Name
+            nft_metadata.symbol,          // Symbol
+            nft_metadata.uri,             // URI
             None,                           // Creators
             0,                              // Seller fee basis points
             true,                           // Update authority is signer
@@ -109,13 +114,72 @@ fn process_instruction(
         ],
     )?;
 
+    msg!("NFT mint created successfully.");
+
+    msg!("Creating token account...");
+    msg!("Token Address: {}", token_account.key);    
+    invoke(
+        &token_account_instruction::create_associated_token_account(
+            &mint_authority.key,
+            &mint_authority.key,
+            &mint_account.key,
+        ),
+        &[
+            mint_account.clone(),
+            token_account.clone(),
+            mint_authority.clone(),
+            token_program.clone(),
+            associated_token_program.clone(),
+        ]
+    )?;
+
+    msg!("Minting NFT to token account...");
+    msg!("NFT Mint: {}", mint_account.key);   
+    msg!("Token Address: {}", token_account.key);
+    invoke(
+        &token_instruction::mint_to(
+            &token_program.key,
+            &mint_account.key,
+            &token_account.key,
+            &mint_authority.key,
+            &[&mint_authority.key],
+            1,
+        )?,
+        &[
+            mint_account.clone(),
+            mint_authority.clone(),
+            token_account.clone(),
+            token_program.clone(),
+            rent.clone(),
+        ]
+    )?;
+
+    msg!("Disabling future minting...");
+    msg!("NFT Mint: {}", mint_account.key);   
+    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(),
+        ],
+    )?;
+
+    msg!("NFT minting disabled successfully.");
     msg!("Token mint process completed successfully.");
 
     Ok(())
 }
 
 #[derive(BorshSerialize, BorshDeserialize, Debug)]
-pub struct TokenMetadata {
+pub struct NftMetadata {
     title: String,
     symbol: String,
     uri: String,

+ 34 - 15
nfts/mint/native/tests/test.ts

@@ -8,7 +8,11 @@ import {
     Transaction,
     sendAndConfirmTransaction,
 } from '@solana/web3.js';
-import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
+import { 
+    ASSOCIATED_TOKEN_PROGRAM_ID, 
+    TOKEN_PROGRAM_ID, 
+    getAssociatedTokenAddress 
+} from '@solana/spl-token';
 import * as borsh from "borsh";
 import { Buffer } from "buffer";
 
@@ -57,14 +61,19 @@ describe("mint-token", () => {
             }
         ]
     ]);
+
+
+    const mintKeypair: Keypair = Keypair.generate();
+    console.log(`New token: ${mintKeypair.publicKey}`);
+
+    const metadata = new TokenMetadata({
+        title: "Solana Gold",
+        symbol: "GOLDSOL",
+        uri: "https://raw.githubusercontent.com/solana-developers/program-examples/main/nfts/nft_metadata.json",
+    });
   
     it("Mint!", async () => {
 
-        const mintKeypair: Keypair = Keypair.generate();
-        console.log(`New token: ${mintKeypair.publicKey}`);
-
-        // Derive the metadata account's address and set the metadata
-        //
         const metadataAddress = (await PublicKey.findProgramAddress(
             [
               Buffer.from("metadata"),
@@ -73,25 +82,29 @@ describe("mint-token", () => {
             ],
             TOKEN_METADATA_PROGRAM_ID
         ))[0];
-        const metadata = new TokenMetadata({
-            title: "Solana PLatinum NFT",
-            symbol: "SOLP",
-            uri: "https://raw.githubusercontent.com/solana-developers/program-examples/main/nfts/mint/native/assets/token_metadata.json",
-        });
 
-        // Transact with the "mint_token" function in our on-chain program
-        //
+        const tokenAddress = await getAssociatedTokenAddress(
+            mintKeypair.publicKey,
+            payer.publicKey
+        );
+
         let ix = new TransactionInstruction({
             keys: [
+                // Metadata account
+                {
+                    pubkey: metadataAddress,
+                    isSigner: false,
+                    isWritable: true,
+                },
                 // Mint account
                 {
                     pubkey: mintKeypair.publicKey,
                     isSigner: true,
                     isWritable: true,
                 },
-                // Metadata account
+                // Associated token account
                 {
-                    pubkey: metadataAddress,
+                    pubkey: tokenAddress,
                     isSigner: false,
                     isWritable: true,
                 },
@@ -119,6 +132,12 @@ describe("mint-token", () => {
                     isSigner: false,
                     isWritable: false,
                 },
+                // Associated program
+                {
+                    pubkey: ASSOCIATED_TOKEN_PROGRAM_ID,
+                    isSigner: false,
+                    isWritable: false,
+                },
                 // Token metadata program
                 {
                     pubkey: TOKEN_METADATA_PROGRAM_ID,