208 lines
7.7 KiB
Rust
208 lines
7.7 KiB
Rust
use icepick_module::{
|
|
help::{Argument, ArgumentType},
|
|
Module,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::str::FromStr;
|
|
|
|
// How does this not exist in solana_sdk.
|
|
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 Transfer {
|
|
amount: String,
|
|
blockhash: String,
|
|
to_address: String,
|
|
from_account: Option<String>,
|
|
from_address: String,
|
|
fee: Option<String>,
|
|
fee_payer: Option<String>,
|
|
fee_payer_address: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct Sign {}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub struct Request {
|
|
// NOTE: Can't use the proper XPrv type from Keyfork because Solana's a big stinky
|
|
// and adds in its own derivation constructs that cause type conflicts.
|
|
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)]
|
|
operation: Operation,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
#[serde(tag = "operation", content = "values", rename_all = "kebab-case")]
|
|
pub enum Operation {
|
|
Transfer(Transfer),
|
|
Sign(Sign),
|
|
}
|
|
|
|
pub struct Solana;
|
|
|
|
impl Module for Solana {
|
|
type Error = Error;
|
|
|
|
type Request = Request;
|
|
|
|
fn describe_operations() -> Vec<icepick_module::help::Operation> {
|
|
let account = Argument {
|
|
name: "from-account".to_string(),
|
|
description: "The derivation account used for the transaction.".to_string(),
|
|
r#type: ArgumentType::Optional,
|
|
};
|
|
let fee = Argument {
|
|
name: "fee".to_string(),
|
|
description: "A custom fee for the transaction".to_string(),
|
|
r#type: ArgumentType::Optional,
|
|
};
|
|
let fee_payer_address = Argument {
|
|
name: "fee-payer-address".to_string(),
|
|
description: "The address used to pay the fee.".to_string(),
|
|
r#type: ArgumentType::Optional,
|
|
};
|
|
let fee_payer = Argument {
|
|
name: "fee-payer".to_string(),
|
|
description: "The derivation account used to pay the fee.".to_string(),
|
|
r#type: ArgumentType::Optional,
|
|
};
|
|
let blockhash = Argument {
|
|
name: "blockhash".to_string(),
|
|
description: "A recent blockhash".to_string(),
|
|
r#type: ArgumentType::Required,
|
|
};
|
|
let from_address = Argument {
|
|
name: "from-address".to_string(),
|
|
description: concat!(
|
|
"The address to send SOL from; will be used to verify ",
|
|
"the derivation account."
|
|
)
|
|
.to_string(),
|
|
r#type: ArgumentType::Required,
|
|
};
|
|
vec![
|
|
icepick_module::help::Operation {
|
|
name: "transfer".to_string(),
|
|
description: "Transfer SOL from a Keyfork wallet to an external wallet."
|
|
.to_string(),
|
|
arguments: vec![
|
|
Argument {
|
|
name: "amount".to_string(),
|
|
description: "The amount of SOL to transfer.".to_string(),
|
|
r#type: ArgumentType::Required,
|
|
},
|
|
account.clone(),
|
|
Argument {
|
|
name: "to-address".to_string(),
|
|
description: "The address to send SOL to.".to_string(),
|
|
r#type: ArgumentType::Required,
|
|
},
|
|
from_address.clone(),
|
|
blockhash.clone(),
|
|
fee.clone(),
|
|
fee_payer.clone(),
|
|
fee_payer_address.clone(),
|
|
],
|
|
},
|
|
icepick_module::help::Operation {
|
|
name: "stake".to_string(),
|
|
description: "Stake SOL to earn rewards.".to_string(),
|
|
arguments: vec![
|
|
Argument {
|
|
name: "amount".to_string(),
|
|
description: "The amount of SOL to stake.".to_string(),
|
|
r#type: ArgumentType::Required,
|
|
},
|
|
account.clone(),
|
|
from_address.clone(),
|
|
blockhash.clone(),
|
|
fee.clone(),
|
|
fee_payer.clone(),
|
|
fee_payer_address.clone(),
|
|
],
|
|
},
|
|
icepick_module::help::Operation {
|
|
name: "sign".to_string(),
|
|
description: "Sign a previously-generated transaction.".to_string(),
|
|
arguments: vec![],
|
|
},
|
|
]
|
|
}
|
|
|
|
fn handle_request(request: Self::Request) -> Result<serde_json::Value, Self::Error> {
|
|
match request.operation {
|
|
Operation::Transfer(Transfer {
|
|
amount,
|
|
from_account,
|
|
to_address,
|
|
from_address,
|
|
blockhash,
|
|
fee: _,
|
|
fee_payer,
|
|
fee_payer_address,
|
|
}) => {
|
|
// TODO:
|
|
// parse address for to_address
|
|
|
|
let amount = f64::from_str(&amount).expect("float amount");
|
|
let amount: u64 = (amount * LAMPORTS_PER_SOL as f64) as u64;
|
|
|
|
use solana_sdk::pubkey::Pubkey;
|
|
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
|
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
|
let payer_account_and_pk = {
|
|
// If a fee payer is given, a fee payer address must also be given, since the
|
|
// address must be known before signing the transaction.
|
|
match (&fee_payer, &fee_payer_address) {
|
|
(Some(payer), Some(address)) => {
|
|
// Use the provided account
|
|
Some((payer.clone(), Pubkey::from_str_const(address)))
|
|
}
|
|
(None, None) => {
|
|
// Use the transaction account
|
|
None
|
|
}
|
|
_ => panic!("Invalid combination of fee_payer and fee_payer_address"),
|
|
}
|
|
};
|
|
let instruction =
|
|
solana_sdk::system_instruction::transfer(&from_pk, &to_pk, amount);
|
|
let hash = solana_sdk::hash::Hash::from_str(&blockhash).unwrap();
|
|
let message = solana_sdk::message::Message::new_with_blockhash(
|
|
&[instruction],
|
|
payer_account_and_pk.map(|v| v.1).as_ref(),
|
|
&hash,
|
|
);
|
|
let transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
|
|
let mut required_derivation_indices = vec![];
|
|
// TODO: error handling from_str
|
|
let from_account = from_account.and_then(|a| u32::from_str(&a).ok()).unwrap_or(0);
|
|
required_derivation_indices.push(from_account);
|
|
Ok(serde_json::json!({
|
|
"blob": transaction,
|
|
}))
|
|
}
|
|
Operation::Sign(Sign {}) => {
|
|
let blob = request.blob.expect("passed in instruction blob");
|
|
let transaction: solana_sdk::transaction::Transaction =
|
|
serde_json::from_value(blob).expect("valid message blob");
|
|
dbg!(transaction);
|
|
Ok(serde_json::json!({
|
|
"blob": []
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
}
|