From 85e021586927d9118bcb742bec6fab64e77d087e Mon Sep 17 00:00:00 2001 From: Valentyn Faychuk Date: Fri, 10 Jan 2025 19:16:17 +0200 Subject: [PATCH] token metadata --- Cargo.lock | 49 ++++++++++++++----- Cargo.toml | 1 + src/solana.rs | 133 +++++++++++++++++++++++++++++++++++++++----------- 3 files changed, 142 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 32a3a63..0d77f6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2104,6 +2104,7 @@ dependencies = [ "hyper 1.4.1", "hyper-rustls 0.27.3", "hyper-util", + "mpl-token-metadata", "prost", "prost-types", "public-ip", @@ -2879,6 +2880,19 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" +[[package]] +name = "mpl-token-metadata" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6a3000e761d3b2d685662a3a9ee99826f9369fb033bd1bc7011b1cf02ed9" +dependencies = [ + "borsh 0.10.4", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "thiserror", +] + [[package]] name = "multimap" version = "0.10.0" @@ -2968,6 +2982,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-derive" version = "0.4.2" @@ -4471,7 +4496,7 @@ dependencies = [ "log", "memoffset", "num-bigint 0.4.6", - "num-derive", + "num-derive 0.4.2", "num-traits", "parking_lot", "rand 0.8.5", @@ -4500,7 +4525,7 @@ dependencies = [ "itertools 0.12.1", "libc", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "percentage", "rand 0.8.5", @@ -4586,7 +4611,7 @@ dependencies = [ "console", "dialoguer", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "parking_lot", "qstring", @@ -4906,7 +4931,7 @@ checksum = "cfd8e539a9963c2914ff8426dfe92351a902892aea465cd507e36d638ca0b7d6" dependencies = [ "bincode", "log", - "num-derive", + "num-derive 0.4.2", "num-traits", "rustc_version", "serde", @@ -4933,7 +4958,7 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "merlin", - "num-derive", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -4963,7 +4988,7 @@ dependencies = [ "itertools 0.12.1", "lazy_static", "merlin", - "num-derive", + "num-derive 0.4.2", "num-traits", "rand 0.7.3", "serde", @@ -5017,7 +5042,7 @@ checksum = "68034596cf4804880d265f834af1ff2f821ad5293e41fa0f8f59086c181fc38e" dependencies = [ "assert_matches", "borsh 1.5.1", - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-token", @@ -5032,7 +5057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "714b53f7312c2802c62f14bc8a07916c2c872761e3d6be97e99fd432be7799ca" dependencies = [ "borsh 1.5.1", - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-associated-token-account-client", @@ -5128,7 +5153,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7b28bed65356558133751cc32b48a7a5ddfc59ac4e941314630bbed1ac10532" dependencies = [ - "num-derive", + "num-derive 0.4.2", "num-traits", "solana-program", "spl-program-error-derive", @@ -5183,7 +5208,7 @@ checksum = "70a0f06ac7f23dc0984931b1fe309468f14ea58e32660439c1cef19456f5d0e3" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "solana-program", @@ -5198,7 +5223,7 @@ checksum = "d9c10f3483e48679619c76598d4e4aebb955bc49b0a5cc63323afbf44135c9bf" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "solana-program", @@ -5222,7 +5247,7 @@ checksum = "c0b788a8c34a917b68b4ed2cdec255d03cc09ccba21545dac39c08a97fce640f" dependencies = [ "arrayref", "bytemuck", - "num-derive", + "num-derive 0.4.2", "num-traits", "num_enum", "solana-program", diff --git a/Cargo.toml b/Cargo.toml index 8ac8cfd..5e180de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ solana-program = "2.0" solana-sdk = "2.0" spl-associated-token-account = "5.0" spl-token = "6.0" +mpl-token-metadata = "5.0" tokio = { version = "1.40", features = ["macros", "rt-multi-thread", "fs"] } # this can be "full" tokio-stream = { version = "0.1", features = ["sync"] } # sgx poc dependencies diff --git a/src/solana.rs b/src/solana.rs index c8be706..b03e6c4 100644 --- a/src/solana.rs +++ b/src/solana.rs @@ -1,5 +1,10 @@ #![allow(dead_code)] use crate::{grpc::challenge::Keys, persistence::KeysFile}; +use mpl_token_metadata::{ + instructions::{CreateMetadataAccountV3, CreateMetadataAccountV3InstructionArgs}, + types::DataV2, + ID as mpl_token_metadata_id, +}; use solana_client::nonblocking::rpc_client::RpcClient; use solana_program::program_pack::Pack; use solana_sdk::{ @@ -127,40 +132,48 @@ impl TryFrom for SolClient { } } -async fn wait_for_enough_sol(client: &RpcClient, pubkey: &Pubkey) { - println!("Waiting for at least 0.01 SOL in address {pubkey}"); - loop { - match client.get_balance(pubkey).await { - Ok(balance) => { - println!("Got {balance} lamports."); - if balance > 10_000_000 { - return; - } - } - Err(e) => { - println!("Could not get balance: {e:?}"); - } - } +async fn create_token(client: &RpcClient, payer_keypair: &Keypair) -> Pubkey { + // #1 top up the payer + println!("Waiting for at least 0.01 SOL in address {}", payer_keypair.pubkey()); + while !has_enough_lamports(client, &payer_keypair.pubkey(), 10_000_000).await { sleep(Duration::from_secs(30)).await; } + + // #2 create token mint + println!("Creating Token Mint"); + let mut mint_pubkey = None; + while mint_pubkey.is_none() { + sleep(Duration::from_secs(30)).await; + mint_pubkey = create_mint(client, payer_keypair).await; + } + let mint_pubkey = mint_pubkey.unwrap(); + println!("Token Mint created: {mint_pubkey}"); + + // #3 create token meta + println!("Creating Token Metadata"); + while !create_meta(client, payer_keypair, &mint_pubkey).await { + sleep(Duration::from_secs(30)).await; + } + println!("Token Metadata created"); + + mint_pubkey } -async fn create_token(client: &RpcClient, keypair: &Keypair) -> Pubkey { - loop { - wait_for_enough_sol(client, &keypair.pubkey()).await; - match create_token_int(client, keypair).await { - None => sleep(Duration::from_secs(30)).await, - Some(pubkey) => { - println!("Mint created: {pubkey}"); - return pubkey; - } +async fn has_enough_lamports(client: &RpcClient, pubkey: &Pubkey, threshold: u64) -> bool { + match client.get_balance(pubkey).await { + Ok(balance) => { + println!("{pubkey} needs {threshold} and has {balance}"); + balance >= threshold + } + Err(e) => { + println!("Could not get balance: {e:?}"); + false } } } -async fn create_token_int(client: &RpcClient, keypair: &Keypair) -> Option { +async fn create_mint(client: &RpcClient, payer_keypair: &Keypair) -> Option { let mint_keypair = Keypair::new(); - let payer = Keypair::from_base58_string(&keypair.to_base58_string()); let mint_rent = client .get_minimum_balance_for_rent_exemption(Mint::LEN) .await @@ -168,7 +181,7 @@ async fn create_token_int(client: &RpcClient, keypair: &Keypair) -> Option Option Option Option bool { + create_meta_int(client, payer_keypair, mint_pubkey).await.is_some() +} + +async fn create_meta_int( + client: &RpcClient, + payer_keypair: &Keypair, + mint_pubkey: &Pubkey, +) -> Option<()> { + let metadata_seeds = &[b"metadata", mpl_token_metadata_id.as_ref(), mint_pubkey.as_ref()]; + let (metadata_pda, _bump) = + Pubkey::find_program_address(metadata_seeds, &mpl_token_metadata_id); + + let data_v2 = DataV2 { + name: "DeTEE Hacker Challenge Token".to_string(), + symbol: "DTHC".to_string(), + uri: "https://detee.ltd/dthc/meta.json".to_string(), + seller_fee_basis_points: 0, // usually for NFTs, can be 0 for fungible + creators: None, // or Some(vec![Creator { ... }]) if you want to specify creators + collection: None, + uses: None, + }; + + let create_metadata_account_ix = CreateMetadataAccountV3 { + metadata: metadata_pda, + mint: *mint_pubkey, + mint_authority: payer_keypair.pubkey(), + payer: payer_keypair.pubkey(), + update_authority: (payer_keypair.pubkey(), true), + system_program: solana_program::system_program::id(), + rent: None, + } + .instruction(CreateMetadataAccountV3InstructionArgs { + data: data_v2, + is_mutable: true, + collection_details: None, + }); + + let recent_blockhash = client + .get_latest_blockhash() + .await + .map_err(|e| println!("Can't get latest blockhash: {e}")) + .ok()?; + + let tx = Transaction::new_signed_with_payer( + &[create_metadata_account_ix], + Some(&payer_keypair.pubkey()), + &[&payer_keypair], + recent_blockhash, + ); + + client + .send_and_confirm_transaction(&tx) + .await + .map_err(|e| println!("Can't execute transaction: {e}")) + .map(|s| { + println!("Transaction signature: {}", s); + }) + .ok() +}