Compare commits

..

No commits in common. "c77f460176b44abdfe27e637857a5ec8e580f402" and "f09fb4dd593e30396c5719e32ede1a839c73b7fa" have entirely different histories.

4 changed files with 32 additions and 47 deletions

View File

@ -72,7 +72,7 @@ const LAMPORTS_PER_SOL: u64 = 1_000_000_000;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error {} pub enum Error {}
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")] #[serde(rename_all = "kebab-case")]
pub enum Cluster { pub enum Cluster {
Devnet, Devnet,
@ -157,13 +157,11 @@ pub struct TransferToken {
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Sign { pub struct Sign {
blockhash: String, blockhash: String,
transaction: solana_sdk::transaction::Transaction,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Broadcast { pub struct Broadcast {
cluster: Option<Cluster>, cluster: Option<Cluster>,
transaction: solana_sdk::transaction::Transaction,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
@ -172,6 +170,9 @@ pub struct Request {
// and adds in its own derivation constructs that cause type conflicts. // and adds in its own derivation constructs that cause type conflicts.
derived_keys: Option<Vec<[u8; 32]>>, derived_keys: Option<Vec<[u8; 32]>>,
// NOTE: This is an opaque type that can be deserialized inside an Operation
blob: Option<serde_json::Value>,
#[serde(flatten)] #[serde(flatten)]
operation: Operation, operation: Operation,
} }
@ -623,10 +624,13 @@ impl Module for Solana {
"derivation_accounts": [0u32 | 1 << 31], "derivation_accounts": [0u32 | 1 << 31],
})) }))
} }
Operation::Sign(Sign { Operation::Sign(Sign { blockhash }) => {
blockhash, let transaction = request
mut transaction, .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");
let keys = request let keys = request
.derived_keys .derived_keys
.unwrap_or_default() .unwrap_or_default()
@ -644,31 +648,26 @@ impl Module for Solana {
} }
})) }))
} }
Operation::Broadcast(Broadcast { Operation::Broadcast(Broadcast { cluster }) => {
cluster,
transaction,
}) => {
let cluster = cluster.unwrap_or(Cluster::MainnetBeta); let cluster = cluster.unwrap_or(Cluster::MainnetBeta);
let cluster_url = format!("https://api.{cluster}.solana.com"); 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"); transaction.verify().expect("invalid signatures");
let client = solana_rpc_client::rpc_client::RpcClient::new(cluster_url); let client = solana_rpc_client::rpc_client::RpcClient::new(cluster_url);
let _simulated_response = client.simulate_transaction(&transaction).unwrap(); let _simulated_response = client.simulate_transaction(&transaction).unwrap();
let response = client.send_and_confirm_transaction(&transaction); let response = client.send_and_confirm_transaction(&transaction);
let cluster_suffix = {
if cluster != Cluster::MainnetBeta {
String::new()
} else {
format!("?cluster={cluster}")
}
};
Ok(match response { Ok(match response {
Ok(s) => { Ok(s) => {
serde_json::json!({ serde_json::json!({
"blob": { "blob": {
"status": "send_and_confirm", "status": "send_and_confirm",
"succcess": s.to_string(), "succcess": s.to_string(),
"url": format!("https://explorer.solana.com/tx/{s}{cluster_suffix}"),
} }
}) })
} }

View File

@ -2,7 +2,6 @@ use clap::command;
use icepick_module::help::*; use icepick_module::help::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
collections::HashMap,
io::{IsTerminal, Write}, io::{IsTerminal, Write},
process::{Command, Stdio}, process::{Command, Stdio},
}; };
@ -169,7 +168,6 @@ pub fn do_cli_thing() {
if !stdin.is_terminal() { if !stdin.is_terminal() {
cli_input = serde_json::from_reader(stdin).ok(); 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 blob = cli_input.as_ref().and_then(|json| json.get("blob"));
let derivation_accounts = cli_input let derivation_accounts = cli_input
@ -184,20 +182,14 @@ pub fn do_cli_thing() {
.find(|(name, ..)| *name == module) .find(|(name, ..)| *name == module)
.and_then(|(.., operations)| operations.iter().find(|o| o.name == subcommand)) .and_then(|(.., operations)| operations.iter().find(|o| o.name == subcommand))
{ {
// all values to be passed to the command let mut args = std::collections::HashMap::<String, Option<&String>>::with_capacity(
let mut args = operation.arguments.len(),
HashMap::<String, serde_json::Value>::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 { for arg in &operation.arguments {
if let Some(value) = matches.get_one::<String>(&arg.name.replace('_', "-")) { args.insert(
args.insert(arg.name.clone(), serde_json::Value::String(value.clone())); arg.name.clone(),
} matches.get_one::<String>(&arg.name.replace('_', "-")),
);
} }
let (algo, path) = config let (algo, path) = config
@ -243,6 +235,7 @@ pub fn do_cli_thing() {
"operation": subcommand, "operation": subcommand,
"values": args, "values": args,
"derived_keys": derived_keys, "derived_keys": derived_keys,
"blob": blob,
}); });
let bin = commands let bin = commands
.iter() .iter()

View File

@ -82,23 +82,22 @@ impl Workflow {
map map
} }
fn simulate_workflow(&self, mut data: HashSet<String>, operations: &[InvocableOperation]) { pub fn simulate_workflow(&self, mut data: HashSet<String>, operations: &[InvocableOperation]) {
// simulate the steps by using a HashSet to traverse the inputs and outputs and ensure // simulate the steps by using a HashSet to traverse the inputs and outputs and ensure
// there's no inconsistencies // there's no inconsistencies
for (i, step) in self.steps.iter().enumerate() { for (i, step) in self.steps.iter().enumerate() {
// NOTE: overflow possible but unlikely // NOTE: overflow possible but unlikely
let step_index = i + 1; let step_index = i + 1;
let step_type = &step.r#type;
// Find the relevant Operation // Find the relevant Operation
let Some(invocable) = operations.iter().find(|op| op.name == *step_type) else { let Some(invocable) = operations.iter().find(|op| op.name == step.r#type) else {
panic!("Could not find operation: {step_type}"); panic!("Could not find operation: {}", step.r#type);
}; };
// Check if we have the keys we want to pass into the module. // Check if we have the keys we want to pass into the module.
for in_memory_name in step.inputs.values() { for in_memory_name in step.inputs.values() {
if !data.contains(in_memory_name) && !step.values.contains_key(in_memory_name) { if !data.contains(in_memory_name) && !step.values.contains_key(in_memory_name) {
panic!("Failed simulation: step #{step_index} ({step_type}): missing value {in_memory_name}"); panic!("Failed simulation: step #{step_index}: missing value {in_memory_name}");
} }
} }
@ -110,7 +109,7 @@ impl Workflow {
.iter() .iter()
.any(|arg| *module_input_name == arg.name) .any(|arg| *module_input_name == arg.name)
{ {
eprintln!("Simulation: step #{step_index} ({step_type}): input value {module_input_name} will be passed through as JSON input"); eprintln!("Simulation: step #{step_index}: input value {module_input_name} will be passed through as JSON input");
} }
} }

View File

@ -59,19 +59,13 @@ from_address = "from_address"
[module.workflow.step.outputs] [module.workflow.step.outputs]
transaction = "unsigned_transaction" transaction = "unsigned_transaction"
# Get a blockhash
[[module.workflow.step]]
type = "sol-get-blockhash"
outputs = { blockhash = "blockhash" }
# Sign the transaction # Sign the transaction
[[module.workflow.step]] [[module.workflow.step]]
type = "sol-sign" type = "sol-sign"
[module.workflow.step.inputs] [module.workflow.step.inputs]
transaction = "unsigned_transaction" transaction = "unsigned_transaction"
blockhash = "blockhash" # blockhash = "blockhash"
[module.workflow.step.outputs] [module.workflow.step.outputs]
transaction = "signed_transaction" transaction = "signed_transaction"