diff --git a/Cargo.lock b/Cargo.lock index ae2381b..65dce17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1067,6 +1067,7 @@ dependencies = [ name = "icepick-solana" version = "0.1.0" dependencies = [ + "ed25519-dalek 1.0.1", "icepick-module", "serde", "serde_json", diff --git a/crates/by-chain/icepick-solana/Cargo.toml b/crates/by-chain/icepick-solana/Cargo.toml index 1265881..fec5819 100644 --- a/crates/by-chain/icepick-solana/Cargo.toml +++ b/crates/by-chain/icepick-solana/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +ed25519-dalek = "=1.0.1" icepick-module = { version = "0.1.0", path = "../../icepick-module" } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true diff --git a/crates/by-chain/icepick-solana/src/lib.rs b/crates/by-chain/icepick-solana/src/lib.rs index 9738cb7..1fbe94f 100644 --- a/crates/by-chain/icepick-solana/src/lib.rs +++ b/crates/by-chain/icepick-solana/src/lib.rs @@ -3,6 +3,7 @@ use icepick_module::{ Module, }; use serde::{Deserialize, Serialize}; +use solana_sdk::signer::Signer; use std::str::FromStr; // How does this not exist in solana_sdk. @@ -11,6 +12,16 @@ const LAMPORTS_PER_SOL: u64 = 1_000_000_000; #[derive(thiserror::Error, Debug)] pub enum Error {} +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct GenerateWallet { + account: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub struct GetWalletAddress {} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "kebab-case")] pub struct Transfer { @@ -45,6 +56,8 @@ pub struct Request { #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "operation", content = "values", rename_all = "kebab-case")] pub enum Operation { + GenerateWallet(GenerateWallet), + GetWalletAddress(GetWalletAddress), Transfer(Transfer), Sign(Sign), } @@ -92,6 +105,21 @@ impl Module for Solana { r#type: ArgumentType::Required, }; vec![ + icepick_module::help::Operation { + name: "generate-wallet".to_string(), + description: "Generate the derivation index for a wallet.".to_string(), + arguments: vec![Argument { + name: "account".to_string(), + description: "The derivation account used for generating the wallet." + .to_string(), + r#type: ArgumentType::Optional, + }], + }, + icepick_module::help::Operation { + name: "get-wallet-address".to_string(), + description: "Get the address for a given wallet.".to_string(), + arguments: vec![], + }, icepick_module::help::Operation { name: "transfer".to_string(), description: "Transfer SOL from a Keyfork wallet to an external wallet." @@ -142,6 +170,27 @@ impl Module for Solana { fn handle_request(request: Self::Request) -> Result { match request.operation { + Operation::GenerateWallet(GenerateWallet { account }) => { + let account = u32::from_str(&account).expect("account index"); + Ok(serde_json::json!({ + "blob": null, + "derivation-accounts": [(account | 1 << 31)], + })) + } + Operation::GetWalletAddress(_) => { + use ed25519_dalek::{PublicKey, SecretKey}; + // NOTE: panics if doesn't exist + let key = request.derived_keys.unwrap()[0]; + let secret_key = SecretKey::from_bytes(&key).unwrap(); + let mut bytes = [0u8; 64]; + bytes[..32].clone_from_slice(&key); + bytes[32..].clone_from_slice(PublicKey::from(&secret_key).as_bytes()); + let pk = solana_sdk::signer::keypair::Keypair::from_bytes(&bytes).unwrap(); + let pk = pk.pubkey(); + Ok(serde_json::json!({ + "blob": pk.to_string(), + })) + } Operation::Transfer(Transfer { amount, from_account, @@ -167,7 +216,10 @@ impl Module for Solana { match (&fee_payer, &fee_payer_address) { (Some(payer), Some(address)) => { // Use the provided account - Some((u32::from_str(payer).unwrap(), Pubkey::from_str(address).unwrap())) + Some(( + u32::from_str(payer).unwrap(), + Pubkey::from_str(address).unwrap(), + )) } (None, None) => { // Use the transaction account @@ -186,7 +238,9 @@ impl Module for Solana { ); let transaction = solana_sdk::transaction::Transaction::new_unsigned(message); // TODO: error handling from_str - let from_account = from_account.and_then(|a| u32::from_str(&a).ok()).unwrap_or(0); + let from_account = from_account + .and_then(|a| u32::from_str(&a).ok()) + .unwrap_or(0); let mut requested_accounts = vec![]; requested_accounts.push(from_account | 1 << 31); if let Some((account, _)) = &payer_account_and_pk { @@ -197,11 +251,11 @@ impl Module for Solana { "derivation-accounts": requested_accounts, })) } - Operation::Sign(Sign {}) => { + Operation::Sign(_) => { let blob = request.blob.expect("passed in instruction blob"); - let transaction: solana_sdk::transaction::Transaction = + let _transaction: solana_sdk::transaction::Transaction = serde_json::from_value(blob).expect("valid message blob"); - let keys = request.derived_keys.unwrap_or_default(); + let _keys = request.derived_keys.unwrap_or_default(); Ok(serde_json::json!({ "blob": [] })) diff --git a/crates/icepick/src/cli/mod.rs b/crates/icepick/src/cli/mod.rs index abca32e..d0f51a1 100644 --- a/crates/icepick/src/cli/mod.rs +++ b/crates/icepick/src/cli/mod.rs @@ -8,7 +8,7 @@ use std::{ pub fn get_command(bin_name: &str) -> (&str, Vec<&str>) { if std::env::vars().any(|(k, _)| &k == "ICEPICK_USE_CARGO") { - ("cargo", vec!["run", "--bin", bin_name, "--"]) + ("cargo", vec!["run", "-q", "--bin", bin_name, "--"]) } else { (bin_name, vec![]) }