diff --git a/crates/cmds-solana/src/mint_token.rs b/crates/cmds-solana/src/mint_token.rs index 0a7d085..3036f5e 100644 --- a/crates/cmds-solana/src/mint_token.rs +++ b/crates/cmds-solana/src/mint_token.rs @@ -3,6 +3,7 @@ use crate::{ utils::{execute, submit_transaction, ui_amount_to_amount}, }; use solana_program::instruction::Instruction; +use solana_sdk::transaction::Transaction; use spl_token::instruction::mint_to_checked; #[derive(Debug)] @@ -64,12 +65,14 @@ pub struct Input { amount: Decimal, #[serde(default = "value::default::bool_true")] submit: bool, + generate_instructions: bool, } #[derive(Serialize, Deserialize, Debug)] pub struct Output { #[serde(with = "value::signature::opt")] signature: Option, + instructions: Option>, } const SOLANA_MINT_TOKEN: &str = "mint_token"; @@ -129,15 +132,29 @@ impl CommandTrait for SolanaMintToken { required: false, passthrough: false, }, + CmdInput { + name: "generate_instructions".into(), + type_bounds: [ValueType::Bool].to_vec(), + required: false, // TODO: should this be required? + passthrough: false, + }, ] .to_vec() } fn outputs(&self) -> Vec { - [CmdOutput { - name: SIGNATURE.into(), - r#type: ValueType::String, - }] + [ + CmdOutput { + name: SIGNATURE.into(), + r#type: ValueType::String, + }, + CmdOutput { + name: "instructions".into(), + // We can add Instruction as a new variant to the ValueType enum + // or we could just use Json or a String to represent the encoded instructions. + r#type: ValueType::Json, + }, + ] .to_vec() } @@ -155,6 +172,71 @@ impl CommandTrait for SolanaMintToken { ) .await?; + if input.generate_instructions { + return Ok(value::to_map(&Output { + signature: None, + instructions: Some(instructions), + })?); + } + + // BUNDLING + // + // + // Option 1 - add execute as command + // - We keep what have and add the utils/execute() as a command + // - User deciding to bundle transactions changes instruction input to true on commands and + // connects the output(instruction, signers?) to the execute input + // which bundles the instructions into one transaction for the user to sign. + // + // Option 2 - add execute as command with auto-execute + // - We allow commands to auto-execute without having to add the execute command/connect all the edges. + // - We still add execute() as a command as it is really useful to have for users to bundle and run instructions + // + // + // + // + // Implementation + // + // 1. output instructions in the commands + // + // 2. collect instructions from nodes that created instructions and collect the list of required signers, + // somewhere after the flow graph is constructed and all of the commands have run, + // + // 3. have a way of knowing if we need to dispatch a transaction after the flow has run. + // maybe we could add a new node to the graph and run that. + // + // 4. batch the instructions into as few transactions as possible (1232 bytes per tx), ideally only one + // https://solana.wiki/docs/solidity-guide/transactions/#:~:text=The%20entire%20encoded%20size%20of%20a%20Solana%20transaction%20cannot%20exceed%201232%20bytes. + // + // If 1232 bytes are not enough, we can later upgrade to Solana new transaction version + // TxV0/TxV1 to batch instructions into a single transaction, + // using the solana Look Up Table Program. + // + // client.get_recent_blockhash(); + // let tx = Transaction::new(&[signers], &[instructions], recent_blockhash); + // let serialized = tx.s + // + // 5. For later. Validation will be required to prevent tampering + // - we send transaction unsigned to user + // - we receive signed transaction, serialize, and check that message has not be tampered with + // - we partial sign and submit + + // PDA + // + // update_an_nft(which_nft, authority) { + // is authority amir? + // if no { abort! } + // + // pda_signer = &[seed, seed2, persons_pubkey]; + // update_nft(which_nft, pda_signer_seeds) + // } + // + + // new_program(args, outside_signers, persons_pubkey) { + // pda_signer = &[seed, seed2, persons_pubkey]; + // let signers = [...outside_signers, pda_signer]; + // token_metadata_program(args, [signers]) + // } let (mut transaction, recent_blockhash) = execute( &ctx.solana_client, &input.fee_payer.pubkey(), @@ -177,7 +259,10 @@ impl CommandTrait for SolanaMintToken { None }; - Ok(value::to_map(&Output { signature })?) + Ok(value::to_map(&Output { + signature, + instructions: None, + })?) } } diff --git a/crates/cmds-solana/src/utils.rs b/crates/cmds-solana/src/utils.rs index 1b8ca0e..961ceee 100644 --- a/crates/cmds-solana/src/utils.rs +++ b/crates/cmds-solana/src/utils.rs @@ -40,6 +40,9 @@ impl KeypairExt for Keypair { } } +// TODO: Make this into a new command? +// the new Execute command can wrap the output of nodes that generate instructions, +// the output of this command could be a signature that can be used to track the status of the transaction pub async fn execute( client: &RpcClient, fee_payer: &Pubkey,