diff --git a/Cargo.lock b/Cargo.lock index ff80b41..fb9d7ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1058,6 +1058,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "dialoguer" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" +dependencies = [ + "console", + "shell-words", + "tempfile", + "thiserror", + "zeroize", +] + [[package]] name = "digest" version = "0.9.0" @@ -2035,6 +2048,7 @@ dependencies = [ "bs58", "chrono", "console", + "dialoguer 0.11.0", "dirs 5.0.1", "env_logger", "futures", @@ -2072,9 +2086,9 @@ dependencies = [ [[package]] name = "metaboss_lib" -version = "0.17.1" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff5b6202d5b896961bde8c1d0a2f42dc76a663e0f5d4f3207f2cd8cd074f3cee" +checksum = "8523d7c03fd1188ac24b1fba5edb4ecb4237bddd92b6a75476a36c394c902c9c" dependencies = [ "anyhow", "borsh 0.10.3", @@ -4017,7 +4031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56a5a567539d6585acc0a0038da5f1350cfca6574272fef545988a469e87286b" dependencies = [ "console", - "dialoguer", + "dialoguer 0.10.4", "log", "num-derive 0.3.3", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index e982469..9a2b427 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ borsh = "0.10.3" bs58 = "0.4.0" chrono = "0.4.31" console = "0.15.7" +dialoguer = "0.11.0" dirs = "5.0.1" env_logger = "0.9.3" futures = "0.3.29" @@ -24,7 +25,7 @@ indicatif = { version = "0.16.2", features = ["rayon"] } jib = "0.8.0" lazy_static = "1.4.0" log = "0.4.20" -metaboss_lib = "0.17.1" +metaboss_lib = "0.17.2" mpl-token-metadata = { version = "3.2.3", features = ["serde"] } num_cpus = "1.16.0" once_cell = "1.19.0" diff --git a/src/create/methods.rs b/src/create/methods.rs index d0420af..7cc534f 100644 --- a/src/create/methods.rs +++ b/src/create/methods.rs @@ -6,11 +6,11 @@ use mpl_token_metadata::{ instructions::{CreateBuilder, CreateMasterEditionV3Builder}, types::{CreateArgs, DataV2, TokenStandard}, }; -use solana_sdk::signature::read_keypair_file; +use solana_sdk::{compute_budget::ComputeBudgetInstruction, signature::read_keypair_file}; use spl_associated_token_account::get_associated_token_address; use spl_token::instruction::mint_to; -use crate::utils::create_token_if_missing_instruction; +use crate::utils::{calculate_priority_fees, create_token_if_missing_instruction}; use super::*; @@ -68,7 +68,13 @@ pub fn create_metadata(args: CreateMetadataArgs) -> Result<()> { .create_args(create_args) .instruction(); - let instructions = vec![ix]; + let priority_fee = calculate_priority_fees(&args.client, vec![&keypair], ix.clone())?; + + let instructions = vec![ + ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.compute), + ComputeBudgetInstruction::set_compute_unit_price(priority_fee.fee), + ix, + ]; let sig = send_and_confirm_transaction(&args.client, keypair, &instructions)?; @@ -150,7 +156,13 @@ pub fn create_fungible(args: CreateFungibleArgs) -> Result<()> { .create_args(create_args) .instruction(); - let mut instructions = vec![ix]; + let priority_fee = calculate_priority_fees(&args.client, vec![&keypair, &mint], ix.clone())?; + + let mut instructions = vec![ + ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.compute), + ComputeBudgetInstruction::set_compute_unit_price(priority_fee.fee), + ix, + ]; if let Some(initial_supply) = args.initial_supply { // Convert float to native token units @@ -233,9 +245,17 @@ pub fn create_master_edition(args: CreateMasterEditionArgs) -> Result<()> { } let ix = builder.instruction(); + let priority_fee = calculate_priority_fees(&args.client, vec![&keypair], ix.clone())?; + + let instructions = vec![ + ComputeBudgetInstruction::set_compute_unit_limit(priority_fee.compute), + ComputeBudgetInstruction::set_compute_unit_price(priority_fee.fee), + ix, + ]; + let recent_blockhash = args.client.get_latest_blockhash()?; let tx = Transaction::new_signed_with_payer( - &[ix], + &instructions, Some(&keypair.pubkey()), &[&keypair, &mint_authority], recent_blockhash, diff --git a/src/utils.rs b/src/utils.rs index dd47bcd..0f9a426 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,7 @@ use anyhow::{anyhow, Result}; use borsh::{BorshDeserialize, BorshSerialize}; +use dialoguer::Confirm; +use metaboss_lib::data::{ComputeUnits, PriorityFee}; use retry::{delay::Exponential, retry}; use serde::Deserialize; use serde_json::json; @@ -10,6 +12,7 @@ use solana_program::program_pack::Pack; use solana_program::system_program; use solana_program::{pubkey, pubkey::Pubkey}; use solana_sdk::commitment_config::CommitmentConfig; +use solana_sdk::compute_budget::ComputeBudgetInstruction; use solana_sdk::{ instruction::Instruction, signature::Keypair, signer::Signer, transaction::Transaction, }; @@ -22,6 +25,82 @@ use crate::wtf_errors::{ ANCHOR_ERROR, AUCTIONEER_ERROR, AUCTION_HOUSE_ERROR, CANDY_CORE_ERROR, CANDY_ERROR, CANDY_GUARD_ERROR, METADATA_ERROR, }; +pub fn calculate_priority_fees( + client: &RpcClient, + signers: Vec<&Keypair>, + instruction: Instruction, +) -> Result { + let compute_units = calculate_units_consumed(client, signers, vec![instruction.clone()])?; + + let write_lock_accounts = instruction + .accounts + .into_iter() + .filter(|am| am.is_writable) + .map(|am| am.pubkey) + .collect::>(); + + // Get recent prioritization fees. + let fees = client.get_recent_prioritization_fees(&write_lock_accounts)?; + + let max_fee = fees.iter().map(|pf| pf.prioritization_fee).max(); + let max_fee = max_fee.unwrap_or(0); + + println!("Max fee: {}", max_fee); + println!("Compute units: {}", compute_units); + + // At least 1 lamport priority fee. + let priority_fee_lamports = std::cmp::max(max_fee * compute_units as u64 / 1_000_000, 1); + let priority_fee_sol = priority_fee_lamports as f64 / 1_000_000_000.0; + + let confirmation = Confirm::new() + .with_prompt(format!( + "The priority fee for this transaction is {} SOL. Continue?", + priority_fee_sol, + )) + .interact()?; + + if !confirmation { + return Err(anyhow!("Transaction cancelled")); + } + + // Pad compute units a bit. + let compute = compute_units + 20_000; + // Ensure that fee * compute / 1_000_000 is at least 1 lamport. + let fee = std::cmp::max(max_fee, 1_400_000 / compute as u64); + + Ok(PriorityFee { fee, compute }) +} + +pub fn calculate_units_consumed( + client: &RpcClient, + signers: Vec<&Keypair>, + instructions: Vec, +) -> Result { + // Simulate the transaction and see how much compute it used + let mut ixs = vec![ + ComputeBudgetInstruction::set_compute_unit_limit(1_000_000), + ComputeBudgetInstruction::set_compute_unit_price(1), + ]; + ixs.extend(instructions); + + let blockhash = client.get_latest_blockhash()?; + + let tx = Transaction::new_signed_with_payer( + &ixs, + Some(&signers[0].pubkey()), + signers.as_slice(), + blockhash, + ); + + let tx_simulation = client.simulate_transaction(&tx)?; + + let fee = tx_simulation + .value + .units_consumed + .ok_or_else(|| anyhow!("No compute units in simulation response"))? as u32; + + Ok(fee) +} pub fn send_and_confirm_transaction( client: &RpcClient,