diff --git a/crates/by-chain/icepick-solana/src/lib.rs b/crates/by-chain/icepick-solana/src/lib.rs index 6e37124..42ccabc 100644 --- a/crates/by-chain/icepick-solana/src/lib.rs +++ b/crates/by-chain/icepick-solana/src/lib.rs @@ -157,11 +157,13 @@ pub struct TransferToken { #[derive(Serialize, Deserialize, Debug)] pub struct Sign { blockhash: String, + transaction: solana_sdk::transaction::Transaction, } #[derive(Serialize, Deserialize, Debug)] pub struct Broadcast { cluster: Option, + transaction: solana_sdk::transaction::Transaction, } #[derive(Serialize, Deserialize, Debug)] @@ -170,9 +172,6 @@ pub struct Request { // and adds in its own derivation constructs that cause type conflicts. derived_keys: Option>, - // NOTE: This is an opaque type that can be deserialized inside an Operation - blob: Option, - #[serde(flatten)] operation: Operation, } @@ -624,13 +623,7 @@ impl Module for Solana { "derivation_accounts": [0u32 | 1 << 31], })) } - Operation::Sign(Sign { blockhash }) => { - let transaction = request - .blob - .and_then(|b| b.get("transaction").cloned()) - .expect("was given transaction"); - let mut transaction: solana_sdk::transaction::Transaction = - serde_json::from_value(transaction).expect("valid message blob"); + Operation::Sign(Sign { blockhash, mut transaction }) => { let keys = request .derived_keys .unwrap_or_default() @@ -648,16 +641,10 @@ impl Module for Solana { } })) } - Operation::Broadcast(Broadcast { cluster }) => { + Operation::Broadcast(Broadcast { cluster, transaction }) => { let cluster = cluster.unwrap_or(Cluster::MainnetBeta); let cluster_url = format!("https://api.{cluster}.solana.com"); - let transaction = request - .blob - .and_then(|b| b.get("transaction").cloned()) - .expect("was given transaction"); - let transaction: solana_sdk::transaction::Transaction = - serde_json::from_value(transaction).expect("valid message blob"); transaction.verify().expect("invalid signatures"); let client = solana_rpc_client::rpc_client::RpcClient::new(cluster_url); let _simulated_response = client.simulate_transaction(&transaction).unwrap(); diff --git a/crates/icepick/src/cli/mod.rs b/crates/icepick/src/cli/mod.rs index 15b9731..5385490 100644 --- a/crates/icepick/src/cli/mod.rs +++ b/crates/icepick/src/cli/mod.rs @@ -2,6 +2,7 @@ use clap::command; use icepick_module::help::*; use serde::{Deserialize, Serialize}; use std::{ + collections::HashMap, io::{IsTerminal, Write}, process::{Command, Stdio}, }; @@ -168,6 +169,7 @@ pub fn do_cli_thing() { if !stdin.is_terminal() { cli_input = serde_json::from_reader(stdin).ok(); } + // TODO: should we rename "blob"? let blob = cli_input.as_ref().and_then(|json| json.get("blob")); let derivation_accounts = cli_input @@ -182,14 +184,20 @@ pub fn do_cli_thing() { .find(|(name, ..)| *name == module) .and_then(|(.., operations)| operations.iter().find(|o| o.name == subcommand)) { - let mut args = std::collections::HashMap::>::with_capacity( - operation.arguments.len(), - ); + // all values to be passed to the command + let mut args = + HashMap::::with_capacity(operation.arguments.len()); + + // add the values from piped input, if any + if let Some(serde_json::Value::Object(blob)) = blob { + args.extend(blob.iter().map(|(k, v)| (k.clone(), v.clone()))); + } + + // add the values from CLI arguments for arg in &operation.arguments { - args.insert( - arg.name.clone(), - matches.get_one::(&arg.name.replace('_', "-")), - ); + if let Some(value) = matches.get_one::(&arg.name.replace('_', "-")) { + args.insert(arg.name.clone(), serde_json::Value::String(value.clone())); + } } let (algo, path) = config @@ -235,7 +243,6 @@ pub fn do_cli_thing() { "operation": subcommand, "values": args, "derived_keys": derived_keys, - "blob": blob, }); let bin = commands .iter() diff --git a/crates/icepick/src/cli/workflow.rs b/crates/icepick/src/cli/workflow.rs index 9bb2f9c..d6df17a 100644 --- a/crates/icepick/src/cli/workflow.rs +++ b/crates/icepick/src/cli/workflow.rs @@ -82,22 +82,23 @@ impl Workflow { map } - pub fn simulate_workflow(&self, mut data: HashSet, operations: &[InvocableOperation]) { + fn simulate_workflow(&self, mut data: HashSet, operations: &[InvocableOperation]) { // simulate the steps by using a HashSet to traverse the inputs and outputs and ensure // there's no inconsistencies for (i, step) in self.steps.iter().enumerate() { // NOTE: overflow possible but unlikely let step_index = i + 1; + let step_type = &step.r#type; // Find the relevant Operation - let Some(invocable) = operations.iter().find(|op| op.name == step.r#type) else { - panic!("Could not find operation: {}", step.r#type); + let Some(invocable) = operations.iter().find(|op| op.name == *step_type) else { + panic!("Could not find operation: {step_type}"); }; // Check if we have the keys we want to pass into the module. for in_memory_name in step.inputs.values() { if !data.contains(in_memory_name) && !step.values.contains_key(in_memory_name) { - panic!("Failed simulation: step #{step_index}: missing value {in_memory_name}"); + panic!("Failed simulation: step #{step_index} ({step_type}): missing value {in_memory_name}"); } } @@ -109,7 +110,7 @@ impl Workflow { .iter() .any(|arg| *module_input_name == arg.name) { - eprintln!("Simulation: step #{step_index}: input value {module_input_name} will be passed through as JSON input"); + eprintln!("Simulation: step #{step_index} ({step_type}): input value {module_input_name} will be passed through as JSON input"); } } diff --git a/icepick.toml b/icepick.toml index 56c3555..995bf1e 100644 --- a/icepick.toml +++ b/icepick.toml @@ -59,13 +59,19 @@ from_address = "from_address" [module.workflow.step.outputs] transaction = "unsigned_transaction" +# Get a blockhash +[[module.workflow.step]] +type = "sol-get-blockhash" + +outputs = { blockhash = "blockhash" } + # Sign the transaction [[module.workflow.step]] type = "sol-sign" [module.workflow.step.inputs] transaction = "unsigned_transaction" -# blockhash = "blockhash" +blockhash = "blockhash" [module.workflow.step.outputs] transaction = "signed_transaction"