Compare commits
2 Commits
main
...
ryansquare
Author | SHA1 | Date |
---|---|---|
|
e8965ebcb3 | |
|
685e4e0388 |
|
@ -1970,7 +1970,6 @@ dependencies = [
|
||||||
name = "icepick"
|
name = "icepick"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"icepick-module",
|
"icepick-module",
|
||||||
|
@ -1984,7 +1983,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"smex",
|
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"toml 0.8.19",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
|
|
@ -52,7 +52,6 @@ impl Bech32Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_similar_prefix(prefix: &'static str) -> Self {
|
fn with_similar_prefix(prefix: &'static str) -> Self {
|
||||||
#[allow(clippy::useless_format)]
|
|
||||||
Self {
|
Self {
|
||||||
account_address_prefix: format!("{prefix}"),
|
account_address_prefix: format!("{prefix}"),
|
||||||
account_address_public_prefix: format!("{prefix}pub"),
|
account_address_public_prefix: format!("{prefix}pub"),
|
||||||
|
@ -168,7 +167,7 @@ fn seda_chains() -> Vec<Blockchain> {
|
||||||
let mut chains = vec![];
|
let mut chains = vec![];
|
||||||
|
|
||||||
let aseda = Currency::builder()
|
let aseda = Currency::builder()
|
||||||
.coin_denom("SEDA")
|
.coin_denom("seda")
|
||||||
.coin_minimal_denom("aseda")
|
.coin_minimal_denom("aseda")
|
||||||
.coin_decimals(18)
|
.coin_decimals(18)
|
||||||
.coin_gecko_id("ID")
|
.coin_gecko_id("ID")
|
||||||
|
@ -195,26 +194,8 @@ fn seda_chains() -> Vec<Blockchain> {
|
||||||
.fee_currencies(&[CurrencyWithGas::builder()
|
.fee_currencies(&[CurrencyWithGas::builder()
|
||||||
.currency(aseda.clone())
|
.currency(aseda.clone())
|
||||||
.gas_price_step(aseda_gas.clone()).build()])
|
.gas_price_step(aseda_gas.clone()).build()])
|
||||||
.gas_price_step(aseda_gas.clone())
|
.gas_price_step(aseda_gas)
|
||||||
.stake_currency(aseda.clone())
|
.stake_currency(aseda)
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
chains.push(
|
|
||||||
Blockchain::builder()
|
|
||||||
.chain_id("seda-1")
|
|
||||||
.chain_name("seda")
|
|
||||||
.rpc_url("https://rpc.seda.xyz")
|
|
||||||
.rest_url("https://lcd.seda.xyz")
|
|
||||||
.explorer_url_format("https://explorer.seda.xyz/txs/%s")
|
|
||||||
.bip44_config(Bip44Config::builder().coin_type(118).build())
|
|
||||||
.bech32_config(Bech32Config::with_similar_prefix("seda"))
|
|
||||||
.currencies(&[aseda.clone()])
|
|
||||||
.fee_currencies(&[CurrencyWithGas::builder()
|
|
||||||
.currency(aseda.clone())
|
|
||||||
.gas_price_step(aseda_gas.clone()).build()])
|
|
||||||
.gas_price_step(aseda_gas.clone())
|
|
||||||
.stake_currency(aseda.clone())
|
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -236,18 +217,6 @@ fn kyve_chains() -> Vec<Blockchain> {
|
||||||
.high(0.03)
|
.high(0.03)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let ukyve = Currency::builder()
|
|
||||||
.coin_denom("KYVE")
|
|
||||||
.coin_minimal_denom("ukyve")
|
|
||||||
.coin_decimals(6)
|
|
||||||
.coin_gecko_id("unknown")
|
|
||||||
.build();
|
|
||||||
let ukyve_gas = GasPriceStep::builder()
|
|
||||||
.low(0.01)
|
|
||||||
.average(0.025)
|
|
||||||
.high(0.03)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
chains.push(
|
chains.push(
|
||||||
Blockchain::builder()
|
Blockchain::builder()
|
||||||
.chain_id("korellia-2")
|
.chain_id("korellia-2")
|
||||||
|
@ -267,44 +236,6 @@ fn kyve_chains() -> Vec<Blockchain> {
|
||||||
.build(),
|
.build(),
|
||||||
);
|
);
|
||||||
|
|
||||||
chains.push(
|
|
||||||
Blockchain::builder()
|
|
||||||
.chain_id("kaon-1")
|
|
||||||
.chain_name("kaon")
|
|
||||||
.rpc_url("https://rpc.kaon.kyve.network")
|
|
||||||
.rest_url("https://api.kaon.kyve.network")
|
|
||||||
.explorer_url_format("https://explorer.kyve.network/kaon/tx/%s")
|
|
||||||
.bip44_config(Bip44Config::builder().coin_type(118).build())
|
|
||||||
.bech32_config(Bech32Config::with_similar_prefix("kyve"))
|
|
||||||
.currencies(&[tkyve.clone()])
|
|
||||||
.fee_currencies(&[CurrencyWithGas::builder()
|
|
||||||
.currency(tkyve.clone())
|
|
||||||
.gas_price_step(tkyve_gas.clone())
|
|
||||||
.build()])
|
|
||||||
.gas_price_step(tkyve_gas.clone())
|
|
||||||
.stake_currency(tkyve.clone())
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
chains.push(
|
|
||||||
Blockchain::builder()
|
|
||||||
.chain_id("kyve-1")
|
|
||||||
.chain_name("kyve")
|
|
||||||
.rpc_url("https://rpc.kyve.network")
|
|
||||||
.rest_url("https://api.kyve.network")
|
|
||||||
.explorer_url_format("https://explorer.kyve.network/kyve/tx/%s")
|
|
||||||
.bip44_config(Bip44Config::builder().coin_type(118).build())
|
|
||||||
.bech32_config(Bech32Config::with_similar_prefix("kyve"))
|
|
||||||
.currencies(&[ukyve.clone()])
|
|
||||||
.fee_currencies(&[CurrencyWithGas::builder()
|
|
||||||
.currency(ukyve.clone())
|
|
||||||
.gas_price_step(ukyve_gas.clone())
|
|
||||||
.build()])
|
|
||||||
.gas_price_step(ukyve_gas.clone())
|
|
||||||
.stake_currency(ukyve.clone())
|
|
||||||
.build(),
|
|
||||||
);
|
|
||||||
|
|
||||||
chains
|
chains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -175,6 +175,8 @@ pub struct GetTokenInfo {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct CreateNonceAccountAndSigningKey {
|
pub struct CreateNonceAccountAndSigningKey {
|
||||||
authorization_address: String,
|
authorization_address: String,
|
||||||
|
from_account: Option<String>,
|
||||||
|
from_address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -224,7 +226,7 @@ pub struct TransferToken {
|
||||||
pub struct Compile {
|
pub struct Compile {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
hashable: Hashable,
|
hashable: Hashable,
|
||||||
derivation_accounts: Option<Vec<u32>>,
|
derivation_accounts: Vec<u32>,
|
||||||
instructions: Vec<solana_sdk::instruction::Instruction>,
|
instructions: Vec<solana_sdk::instruction::Instruction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,11 +238,9 @@ pub struct Inspect {
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Sign {
|
pub struct Sign {
|
||||||
blockhash: String,
|
blockhash: String,
|
||||||
instructions: Vec<solana_sdk::instruction::Instruction>,
|
transaction: solana_sdk::transaction::Transaction,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
signing_keys: Vec<[u8; Keypair::SECRET_KEY_LENGTH]>,
|
signing_keys: Vec<[u8; Keypair::SECRET_KEY_LENGTH]>,
|
||||||
#[serde(default)]
|
|
||||||
payer_address: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
@ -669,6 +669,8 @@ impl Module for Solana {
|
||||||
}
|
}
|
||||||
Operation::CreateNonceAccountAndSigningKey(CreateNonceAccountAndSigningKey {
|
Operation::CreateNonceAccountAndSigningKey(CreateNonceAccountAndSigningKey {
|
||||||
authorization_address,
|
authorization_address,
|
||||||
|
from_account,
|
||||||
|
from_address,
|
||||||
}) => {
|
}) => {
|
||||||
// NOTE: Since this transaction is meant to be run on an online system with a
|
// NOTE: Since this transaction is meant to be run on an online system with a
|
||||||
// freshly generated mnemonic, only designed to live to make the nonce account, we
|
// freshly generated mnemonic, only designed to live to make the nonce account, we
|
||||||
|
@ -681,12 +683,16 @@ impl Module for Solana {
|
||||||
// this uses OsRng, which sources from getrandom() if available, which pulls from
|
// this uses OsRng, which sources from getrandom() if available, which pulls from
|
||||||
// /dev/urandom, or sources from `/dev/urandom` directly.
|
// /dev/urandom, or sources from `/dev/urandom` directly.
|
||||||
let keypair = Keypair::new();
|
let keypair = Keypair::new();
|
||||||
let payer_keypair = Keypair::new();
|
|
||||||
|
|
||||||
|
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
||||||
let authorization_pk = Pubkey::from_str(&authorization_address).unwrap();
|
let authorization_pk = Pubkey::from_str(&authorization_address).unwrap();
|
||||||
|
|
||||||
|
if from_account.is_some() {
|
||||||
|
unimplemented!("alternative derivation accounts are not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
let instructions = system_instruction::create_nonce_account(
|
let instructions = system_instruction::create_nonce_account(
|
||||||
&payer_keypair.pubkey(),
|
&from_pk,
|
||||||
&keypair.pubkey(),
|
&keypair.pubkey(),
|
||||||
&authorization_pk,
|
&authorization_pk,
|
||||||
// just above the approximate rent necessary for a nonce account
|
// just above the approximate rent necessary for a nonce account
|
||||||
|
@ -698,14 +704,9 @@ impl Module for Solana {
|
||||||
"blob": {
|
"blob": {
|
||||||
"nonce_pubkey": keypair.pubkey().to_string(),
|
"nonce_pubkey": keypair.pubkey().to_string(),
|
||||||
"nonce_privkey": [keypair.secret().to_bytes()],
|
"nonce_privkey": [keypair.secret().to_bytes()],
|
||||||
"payer_pubkey": payer_keypair.pubkey().to_string(),
|
|
||||||
"payer_privkey": [payer_keypair.secret().to_bytes()],
|
|
||||||
"privkeys": [
|
|
||||||
keypair.secret().to_bytes(),
|
|
||||||
payer_keypair.secret().to_bytes()
|
|
||||||
],
|
|
||||||
"transaction": instructions,
|
"transaction": instructions,
|
||||||
},
|
},
|
||||||
|
"derivation_accounts": [0u32 | 1 << 31],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Operation::GetNonceAccountData(GetNonceAccountData {
|
Operation::GetNonceAccountData(GetNonceAccountData {
|
||||||
|
@ -986,9 +987,9 @@ impl Module for Solana {
|
||||||
derivation_accounts,
|
derivation_accounts,
|
||||||
mut instructions,
|
mut instructions,
|
||||||
}) => {
|
}) => {
|
||||||
use solana_sdk::hash::Hash;
|
use solana_sdk::{hash::Hash, message::Message, transaction::Transaction};
|
||||||
|
|
||||||
let hash = match hashable {
|
let (hash, transaction) = match hashable {
|
||||||
// We already have the account from GetNonceAccountData,
|
// We already have the account from GetNonceAccountData,
|
||||||
// which also gives us the authority and the nonce itself.
|
// which also gives us the authority and the nonce itself.
|
||||||
Hashable::Nonce {
|
Hashable::Nonce {
|
||||||
|
@ -1004,16 +1005,23 @@ impl Module for Solana {
|
||||||
system_instruction::advance_nonce_account(&account_pk, &authority_pk);
|
system_instruction::advance_nonce_account(&account_pk, &authority_pk);
|
||||||
|
|
||||||
instructions.insert(0, increment_nonce);
|
instructions.insert(0, increment_nonce);
|
||||||
hash
|
let message = Message::new(&instructions, None);
|
||||||
|
let transaction = Transaction::new_unsigned(message);
|
||||||
|
(hash, transaction)
|
||||||
|
}
|
||||||
|
Hashable::Blockhash { blockhash } => {
|
||||||
|
let blockhash = Hash::from_str(&blockhash).unwrap();
|
||||||
|
let message = Message::new(&instructions, None);
|
||||||
|
let transaction = Transaction::new_unsigned(message);
|
||||||
|
(blockhash, transaction)
|
||||||
}
|
}
|
||||||
Hashable::Blockhash { blockhash } => Hash::from_str(&blockhash).unwrap(),
|
|
||||||
};
|
};
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"blob": {
|
"blob": {
|
||||||
"hash": hash,
|
"hash": hash,
|
||||||
"instructions": instructions,
|
"transaction": transaction,
|
||||||
},
|
},
|
||||||
"derivation_accounts": derivation_accounts.as_deref().unwrap_or(&[]),
|
"derivation_accounts": derivation_accounts,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Operation::Inspect(Inspect { transaction }) => {
|
Operation::Inspect(Inspect { transaction }) => {
|
||||||
|
@ -1027,12 +1035,9 @@ impl Module for Solana {
|
||||||
}
|
}
|
||||||
Operation::Sign(Sign {
|
Operation::Sign(Sign {
|
||||||
blockhash,
|
blockhash,
|
||||||
instructions,
|
mut transaction,
|
||||||
signing_keys,
|
signing_keys,
|
||||||
payer_address,
|
|
||||||
}) => {
|
}) => {
|
||||||
use solana_sdk::{message::Message, transaction::Transaction};
|
|
||||||
|
|
||||||
let keys = request
|
let keys = request
|
||||||
.derived_keys
|
.derived_keys
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
@ -1041,21 +1046,10 @@ impl Module for Solana {
|
||||||
.map(|k| Self::keypair_from_bytes(*k))
|
.map(|k| Self::keypair_from_bytes(*k))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let payer_pk = payer_address
|
|
||||||
.as_deref()
|
|
||||||
.map(Pubkey::from_str)
|
|
||||||
.transpose()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let message =
|
|
||||||
Message::new(&instructions, Some(&payer_pk.unwrap_or(keys[0].pubkey())));
|
|
||||||
let mut transaction = Transaction::new_unsigned(message);
|
|
||||||
|
|
||||||
let hash = solana_sdk::hash::Hash::from_str(&blockhash).unwrap();
|
let hash = solana_sdk::hash::Hash::from_str(&blockhash).unwrap();
|
||||||
transaction
|
transaction
|
||||||
.try_sign(&keys, hash)
|
.try_sign(&keys, hash)
|
||||||
.expect("not enough keys provided");
|
.expect("not enough keys provided");
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"blob": {
|
"blob": {
|
||||||
"transaction": transaction,
|
"transaction": transaction,
|
||||||
|
@ -1071,15 +1065,7 @@ impl Module for Solana {
|
||||||
|
|
||||||
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();
|
||||||
if let Some(err) = simulated_response.value.err {
|
|
||||||
return Ok(serde_json::json!({
|
|
||||||
"blob": {
|
|
||||||
"status": "simulate_transaction",
|
|
||||||
"error": err.to_string(),
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
let response = client.send_and_confirm_transaction(&transaction);
|
let response = client.send_and_confirm_transaction(&transaction);
|
||||||
let cluster_suffix = {
|
let cluster_suffix = {
|
||||||
if cluster == Cluster::MainnetBeta {
|
if cluster == Cluster::MainnetBeta {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationIndex, DerivationPath};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum SimulationError {
|
pub enum SimulationError {
|
||||||
|
@ -24,51 +24,21 @@ pub enum WorkflowError {
|
||||||
InvocationError(String),
|
InvocationError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An input for a workflow argument. When inputs are read, they should be referenced by the first
|
|
||||||
/// name. Additional names can be provided as aliases, to allow chaining workflows together when
|
|
||||||
/// names may not make sense - such as a Solana address then being used as an authorization
|
|
||||||
/// address.
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct Input {
|
|
||||||
/// An input with a single identifier.
|
|
||||||
/// The name of the input.
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
/// A description of the input.
|
|
||||||
pub description: String,
|
|
||||||
|
|
||||||
/// Aliases used when loading inputs.
|
|
||||||
#[serde(default)]
|
|
||||||
pub aliases: Vec<String>,
|
|
||||||
|
|
||||||
/// Whether the workflow input is optional.
|
|
||||||
pub optional: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Input {
|
|
||||||
pub fn identifiers(&self) -> impl Iterator<Item = &String> {
|
|
||||||
[&self.name].into_iter().chain(self.aliases.iter())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_required(&self) -> bool {
|
|
||||||
self.optional.is_some_and(|o| !o)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct Workflow {
|
pub struct Workflow {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
pub description: String,
|
#[serde(default)]
|
||||||
|
pub inputs: Vec<String>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub inputs: Vec<Input>,
|
pub optional_inputs: Vec<String>,
|
||||||
|
|
||||||
#[serde(rename = "step")]
|
#[serde(rename = "step")]
|
||||||
steps: Vec<WorkflowStep>,
|
steps: Vec<WorkflowStep>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type StringMap<T = String> = BTreeMap<String, T>;
|
pub type StringMap = HashMap<String, String>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct WorkflowStep {
|
pub struct WorkflowStep {
|
||||||
|
@ -90,7 +60,7 @@ pub struct WorkflowStep {
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct OperationResult {
|
pub struct OperationResult {
|
||||||
// All values returned from an operation.
|
// All values returned from an operation.
|
||||||
blob: StringMap<Value>,
|
blob: HashMap<String, Value>,
|
||||||
|
|
||||||
// Any requested accounts from an operation.
|
// Any requested accounts from an operation.
|
||||||
//
|
//
|
||||||
|
@ -146,10 +116,10 @@ impl Workflow {
|
||||||
|
|
||||||
pub fn run_workflow<T: InvocableOperation>(
|
pub fn run_workflow<T: InvocableOperation>(
|
||||||
&self,
|
&self,
|
||||||
mut data: StringMap<Value>,
|
mut data: HashMap<String, Value>,
|
||||||
operations: &[T],
|
operations: &[T],
|
||||||
derive_keys: DeriveKeys,
|
derive_keys: DeriveKeys,
|
||||||
) -> Result<StringMap<Value>, WorkflowError> {
|
) -> Result<HashMap<String, Value>, WorkflowError> {
|
||||||
let mut derived_keys = vec![];
|
let mut derived_keys = vec![];
|
||||||
let mut derivation_accounts = vec![];
|
let mut derivation_accounts = vec![];
|
||||||
|
|
||||||
|
@ -160,7 +130,7 @@ impl Workflow {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Prepare all inputs for the operation invocation
|
// Prepare all inputs for the operation invocation
|
||||||
let inputs: StringMap<Value> = data
|
let inputs: HashMap<String, Value> = data
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k, v.clone()))
|
.map(|(k, v)| (k, v.clone()))
|
||||||
.filter_map(|(k, v)| {
|
.filter_map(|(k, v)| {
|
||||||
|
@ -221,7 +191,7 @@ pub trait WorkflowHandler {
|
||||||
/// within themselves.
|
/// within themselves.
|
||||||
pub trait InvocableOperation {
|
pub trait InvocableOperation {
|
||||||
/// Invoke the operation with the supplied inputs and derived keys.
|
/// Invoke the operation with the supplied inputs and derived keys.
|
||||||
fn invoke(&self, input: &StringMap<Value>, derived_keys: &[Vec<u8>]) -> OperationResult;
|
fn invoke(&self, input: &HashMap<String, Value>, derived_keys: &[Vec<u8>]) -> OperationResult;
|
||||||
|
|
||||||
/// The name of the operation.
|
/// The name of the operation.
|
||||||
fn name(&self) -> &String;
|
fn name(&self) -> &String;
|
||||||
|
|
|
@ -4,7 +4,6 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bincode = "1.3.3"
|
|
||||||
chrono = { version = "0.4.39", default-features = false, features = ["now", "serde", "std"] }
|
chrono = { version = "0.4.39", default-features = false, features = ["now", "serde", "std"] }
|
||||||
clap = { version = "4.5.20", features = ["cargo", "derive", "string"] }
|
clap = { version = "4.5.20", features = ["cargo", "derive", "string"] }
|
||||||
icepick-module = { version = "0.1.0", path = "../icepick-module" }
|
icepick-module = { version = "0.1.0", path = "../icepick-module" }
|
||||||
|
@ -18,12 +17,9 @@ miniquorum = { version = "0.1.0", path = "../miniquorum", default-features = fal
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
smex = { version = "0.1.0", registry = "distrust" }
|
|
||||||
thiserror = "2.0.3"
|
thiserror = "2.0.3"
|
||||||
toml = "0.8.19"
|
toml = "0.8.19"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bincode = "1.3.3"
|
|
||||||
icepick-workflow = { version = "0.1.0", path = "../icepick-workflow" }
|
icepick-workflow = { version = "0.1.0", path = "../icepick-workflow" }
|
||||||
serde_yaml = "0.9.34"
|
serde_yaml = "0.9.34"
|
||||||
smex = { version = "0.1.0", registry = "distrust" }
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use icepick_workflow::Workflow;
|
use icepick_workflow::Workflow;
|
||||||
use std::{collections::BTreeMap, path::{PathBuf, Path}};
|
use std::{collections::HashMap, path::{PathBuf, Path}};
|
||||||
|
|
||||||
fn env_var(var: &'static str) -> String {
|
fn env_var(var: &'static str) -> String {
|
||||||
println!("cargo::rerun-if-env-changed={var}");
|
println!("cargo::rerun-if-env-changed={var}");
|
||||||
|
@ -11,16 +11,15 @@ fn track_path(path: &Path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let out_dir = env_var("OUT_DIR");
|
let out_dir = env_var("CARGO_TARGET_DIR");
|
||||||
let crate_dir = env_var("CARGO_MANIFEST_DIR");
|
let crate_dir = env_var("CARGO_MANIFEST_DIR");
|
||||||
let workflows_dir = PathBuf::from(crate_dir).join("workflows");
|
let workflows_dir = PathBuf::from(crate_dir).join("workflows");
|
||||||
track_path(&workflows_dir);
|
track_path(&workflows_dir);
|
||||||
|
|
||||||
let mut workflows_by_module: BTreeMap<String, Vec<Workflow>> = Default::default();
|
let mut workflows_by_module: HashMap<String, Vec<Workflow>> = Default::default();
|
||||||
|
|
||||||
for module_dir in std::fs::read_dir(&workflows_dir).unwrap() {
|
for module_dir in std::fs::read_dir(&workflows_dir).unwrap() {
|
||||||
let module_dir = module_dir.unwrap();
|
let module_dir = module_dir.unwrap();
|
||||||
dbg!(&module_dir);
|
|
||||||
let path = module_dir.path();
|
let path = module_dir.path();
|
||||||
if !path.is_dir() {
|
if !path.is_dir() {
|
||||||
panic!("found unexpected file {}", path.to_string_lossy());
|
panic!("found unexpected file {}", path.to_string_lossy());
|
||||||
|
@ -29,7 +28,6 @@ fn main() {
|
||||||
let mut workflows = vec![];
|
let mut workflows = vec![];
|
||||||
|
|
||||||
for workflow_file in std::fs::read_dir(&path).unwrap() {
|
for workflow_file in std::fs::read_dir(&path).unwrap() {
|
||||||
dbg!(&workflow_file);
|
|
||||||
let workflow_file = workflow_file.unwrap();
|
let workflow_file = workflow_file.unwrap();
|
||||||
let path = workflow_file.path();
|
let path = workflow_file.path();
|
||||||
if !path.is_file() {
|
if !path.is_file() {
|
||||||
|
@ -41,15 +39,12 @@ fn main() {
|
||||||
workflows.push(workflow);
|
workflows.push(workflow);
|
||||||
}
|
}
|
||||||
|
|
||||||
workflows.sort_by(|a, b| a.name.cmp(&b.name));
|
|
||||||
|
|
||||||
workflows_by_module.insert(
|
workflows_by_module.insert(
|
||||||
module_dir.file_name().to_str().unwrap().to_owned(),
|
module_dir.file_name().to_str().unwrap().to_owned(),
|
||||||
workflows,
|
workflows,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let out_path = PathBuf::from(out_dir).join("workflows.hex");
|
let out_path = PathBuf::from(out_dir).join("workflows.yaml");
|
||||||
let result = bincode::serialize(&workflows_by_module).unwrap();
|
let out_file = std::fs::File::create(&out_path).unwrap();
|
||||||
let hexed = smex::encode(&result);
|
serde_yaml::to_writer(out_file, &workflows_by_module).unwrap();
|
||||||
std::fs::write(out_path, hexed).unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use icepick_module::help::*;
|
||||||
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationIndex, DerivationPath};
|
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationIndex, DerivationPath};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, BTreeMap},
|
collections::HashMap,
|
||||||
io::{IsTerminal, Write},
|
io::{IsTerminal, Write},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
|
@ -113,11 +113,6 @@ struct Config {
|
||||||
// command name, invocable binary, operations
|
// command name, invocable binary, operations
|
||||||
type Commands<'a> = &'a [(String, String, Vec<Operation>)];
|
type Commands<'a> = &'a [(String, String, Vec<Operation>)];
|
||||||
|
|
||||||
fn default_workflows() -> HashMap<String, Vec<icepick_workflow::Workflow>> {
|
|
||||||
let workflows_hex = include_str!(concat!(env!("OUT_DIR"), "/workflows.hex"));
|
|
||||||
bincode::deserialize(&smex::decode(workflows_hex).unwrap()).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn do_cli_thing() {
|
pub fn do_cli_thing() {
|
||||||
/* parse config file to get module names */
|
/* parse config file to get module names */
|
||||||
let config_file = std::env::vars().find_map(|(k, v)| {
|
let config_file = std::env::vars().find_map(|(k, v)| {
|
||||||
|
@ -126,7 +121,7 @@ pub fn do_cli_thing() {
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
let config_path = config_file.unwrap_or_else(|| "/etc/icepick/icepick.toml".to_string());
|
let config_path = config_file.unwrap_or_else(|| "icepick.toml".to_string());
|
||||||
let config_content = std::fs::read_to_string(config_path).expect("can't read config file");
|
let config_content = std::fs::read_to_string(config_path).expect("can't read config file");
|
||||||
let mut config: Config = match toml::from_str(&config_content) {
|
let mut config: Config = match toml::from_str(&config_content) {
|
||||||
Ok(config) => config,
|
Ok(config) => config,
|
||||||
|
@ -148,13 +143,6 @@ pub fn do_cli_thing() {
|
||||||
workflows: Default::default(),
|
workflows: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let workflows = default_workflows();
|
|
||||||
for module in &mut config.modules {
|
|
||||||
if let Some(module_workflows) = workflows.get(&module.name) {
|
|
||||||
module.workflows.extend(module_workflows.iter().cloned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let workflows_file = std::env::vars().find_map(|(k, v)| {
|
let workflows_file = std::env::vars().find_map(|(k, v)| {
|
||||||
if k == "ICEPICK_WORKFLOWS_FILE" {
|
if k == "ICEPICK_WORKFLOWS_FILE" {
|
||||||
return Some(v);
|
return Some(v);
|
||||||
|
@ -162,14 +150,13 @@ pub fn do_cli_thing() {
|
||||||
None
|
None
|
||||||
});
|
});
|
||||||
let workflows_path = workflows_file.unwrap_or_else(|| "workflows.yaml".to_string());
|
let workflows_path = workflows_file.unwrap_or_else(|| "workflows.yaml".to_string());
|
||||||
|
let workflows_content = std::fs::read(&workflows_path).expect("can't read workflows from file");
|
||||||
|
let workflows: HashMap<String, Vec<icepick_workflow::Workflow>> =
|
||||||
|
serde_yaml::from_slice(&workflows_content).unwrap();
|
||||||
|
|
||||||
if let Ok(content) = std::fs::read(&workflows_path) {
|
for module in &mut config.modules {
|
||||||
let workflows: HashMap<String, Vec<icepick_workflow::Workflow>> =
|
if let Some(module_workflows) = workflows.get(&module.name) {
|
||||||
serde_yaml::from_slice(&content).unwrap();
|
module.workflows.extend(module_workflows.iter().cloned());
|
||||||
for module in &mut config.modules {
|
|
||||||
if let Some(module_workflows) = workflows.get(&module.name) {
|
|
||||||
module.workflows.extend(module_workflows.iter().cloned());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +363,7 @@ pub fn do_cli_thing() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let inputs: BTreeMap<String, serde_json::Value> =
|
let inputs: HashMap<String, serde_json::Value> =
|
||||||
serde_json::from_value(inputs).unwrap();
|
serde_json::from_value(inputs).unwrap();
|
||||||
|
|
||||||
let workflow = workflows
|
let workflow = workflows
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use icepick_workflow::{Input, InvocableOperation, OperationResult, StringMap, Workflow};
|
use icepick_workflow::{InvocableOperation, OperationResult, Workflow};
|
||||||
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationPath};
|
use keyfork_derive_util::{request::DerivationAlgorithm, DerivationPath};
|
||||||
use keyfork_shard::{openpgp::OpenPGP, Format};
|
use keyfork_shard::{openpgp::OpenPGP, Format};
|
||||||
use miniquorum::{Payload, PayloadVerification};
|
use miniquorum::{Payload, PayloadVerification};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
io::Write,
|
io::Write,
|
||||||
process::{Command, Stdio},
|
process::{Command, Stdio},
|
||||||
};
|
};
|
||||||
|
@ -19,6 +20,8 @@ pub enum Purpose {
|
||||||
RunQuorum,
|
RunQuorum,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type StringMap = std::collections::HashMap<String, String>;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct CLIOperation {
|
struct CLIOperation {
|
||||||
/// The name of the operation (i.e. `transfer-token`).
|
/// The name of the operation (i.e. `transfer-token`).
|
||||||
|
@ -38,7 +41,7 @@ struct CLIOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InvocableOperation for CLIOperation {
|
impl InvocableOperation for CLIOperation {
|
||||||
fn invoke(&self, input: &StringMap<Value>, derived_keys: &[Vec<u8>]) -> OperationResult {
|
fn invoke(&self, input: &HashMap<String, Value>, derived_keys: &[Vec<u8>]) -> OperationResult {
|
||||||
let (command, args) = get_command(&self.binary);
|
let (command, args) = get_command(&self.binary);
|
||||||
|
|
||||||
let json = serde_json::json!({
|
let json = serde_json::json!({
|
||||||
|
@ -91,30 +94,31 @@ impl InvocableOperation for CLIOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_command(workflow: &Workflow) -> clap::Command {
|
pub fn generate_command(workflow: &Workflow) -> clap::Command {
|
||||||
let mut command = clap::Command::new(&workflow.name).about(&workflow.description);
|
let mut command = clap::Command::new(&workflow.name).arg(clap::arg!(
|
||||||
// NOTE: all required inputs are still marked as .required(false) since they could be included
|
|
||||||
// in the `--input-file` argument.
|
|
||||||
for input in workflow.inputs.iter() {
|
|
||||||
let name = &input.name;
|
|
||||||
let arg = clap::Arg::new(name)
|
|
||||||
.required(false)
|
|
||||||
.help(&input.description)
|
|
||||||
.long(name.replace('_', "-"))
|
|
||||||
.value_name(name.to_uppercase())
|
|
||||||
.visible_aliases(&input.aliases);
|
|
||||||
command = command.arg(arg);
|
|
||||||
}
|
|
||||||
command.arg(
|
|
||||||
clap::arg!(
|
|
||||||
--"input-file" [FILE]
|
--"input-file" [FILE]
|
||||||
"A file containing any inputs not passed on the command line"
|
"A file containing any inputs not passed on the command line"
|
||||||
)
|
));
|
||||||
.value_parser(clap::value_parser!(std::path::PathBuf)),
|
for input in &workflow.inputs {
|
||||||
)
|
// can also be included in the JSON file, so we won't mark this as required.
|
||||||
|
let arg = clap::Arg::new(input)
|
||||||
|
.required(false)
|
||||||
|
.long(input.replace('_', "-"))
|
||||||
|
.value_name(input.to_uppercase());
|
||||||
|
command = command.arg(arg);
|
||||||
|
}
|
||||||
|
for input in &workflow.optional_inputs {
|
||||||
|
let arg = clap::Arg::new(input)
|
||||||
|
.required(false)
|
||||||
|
.long(input.replace('_', "-"))
|
||||||
|
.value_name(input.to_uppercase());
|
||||||
|
command = command.arg(arg);
|
||||||
|
}
|
||||||
|
command
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_inputs<'a>(
|
fn load_inputs<T: AsRef<str> + Into<String> + std::fmt::Display>(
|
||||||
inputs: impl IntoIterator<Item = &'a Input>,
|
inputs: impl IntoIterator<Item = T>,
|
||||||
|
optional_inputs: impl IntoIterator<Item = T>,
|
||||||
matches: &clap::ArgMatches,
|
matches: &clap::ArgMatches,
|
||||||
) -> StringMap {
|
) -> StringMap {
|
||||||
let mut map = StringMap::default();
|
let mut map = StringMap::default();
|
||||||
|
@ -123,26 +127,33 @@ fn load_inputs<'a>(
|
||||||
.and_then(|p| std::fs::File::open(p).ok())
|
.and_then(|p| std::fs::File::open(p).ok())
|
||||||
.and_then(|f| serde_json::from_reader(f).ok());
|
.and_then(|f| serde_json::from_reader(f).ok());
|
||||||
for input in inputs {
|
for input in inputs {
|
||||||
let identifier = &input.name;
|
match matches.get_one::<String>(input.as_ref()) {
|
||||||
match matches.get_one::<String>(identifier) {
|
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
map.insert(identifier.clone(), value.clone());
|
map.insert(input.into(), value.clone());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
for aliasable_identifier in input.identifiers() {
|
if let Some(value) = input_file.as_ref().and_then(|f| f.get(input.as_ref())) {
|
||||||
if let Some(value) = input_file
|
map.insert(input.into(), value.clone());
|
||||||
.as_ref()
|
continue;
|
||||||
.and_then(|f| f.get(aliasable_identifier))
|
|
||||||
{
|
|
||||||
map.insert(identifier.clone(), value.clone());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if input.is_required() {
|
panic!("Required workflow input was not found: {input}");
|
||||||
panic!("Required workflow input was not found: {identifier}");
|
}
|
||||||
|
|
||||||
|
for input in optional_inputs {
|
||||||
|
match matches.get_one::<String>(input.as_ref()) {
|
||||||
|
Some(value) => {
|
||||||
|
map.insert(input.into(), value.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if let Some(value) = input_file.as_ref().and_then(|f| f.get(input.as_ref())) {
|
||||||
|
map.insert(input.into(), value.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,10 +191,13 @@ pub fn parse_quorum_file(
|
||||||
let threshold = threshold.unwrap_or(u8::try_from(certs.len()).expect("too many certs!"));
|
let threshold = threshold.unwrap_or(u8::try_from(certs.len()).expect("too many certs!"));
|
||||||
let policy = match purpose {
|
let policy = match purpose {
|
||||||
Purpose::AddSignature => {
|
Purpose::AddSignature => {
|
||||||
// All signatures must be valid, but we don't require a minimum.
|
// All signatures must be valid, but we don't require a minimum.
|
||||||
PayloadVerification::new().with_threshold(0)
|
PayloadVerification::new().with_threshold(0)
|
||||||
}
|
}
|
||||||
Purpose::RunQuorum => PayloadVerification::new().with_threshold(threshold),
|
Purpose::RunQuorum => {
|
||||||
|
PayloadVerification::new().with_threshold(threshold)
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
payload.verify_signatures(&certs, &policy, None).unwrap();
|
payload.verify_signatures(&certs, &policy, None).unwrap();
|
||||||
|
|
||||||
|
@ -199,19 +213,20 @@ pub fn parse_quorum_with_shardfile(
|
||||||
let payload: Payload = serde_json::from_reader(payload_file).unwrap();
|
let payload: Payload = serde_json::from_reader(payload_file).unwrap();
|
||||||
|
|
||||||
let opgp = OpenPGP;
|
let opgp = OpenPGP;
|
||||||
let (threshold, certs) = opgp
|
let (threshold, certs) = opgp.decrypt_metadata_from_file(
|
||||||
.decrypt_metadata_from_file(
|
None::<&std::path::Path>,
|
||||||
None::<&std::path::Path>,
|
std::fs::File::open(shardfile_path).unwrap(),
|
||||||
std::fs::File::open(shardfile_path).unwrap(),
|
keyfork_prompt::default_handler().unwrap(),
|
||||||
keyfork_prompt::default_handler().unwrap(),
|
).unwrap();
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let policy = match purpose {
|
let policy = match purpose {
|
||||||
Purpose::AddSignature => {
|
Purpose::AddSignature => {
|
||||||
// All signatures must be valid, but we don't require a minimum.
|
// All signatures must be valid, but we don't require a minimum.
|
||||||
PayloadVerification::new().with_threshold(0)
|
PayloadVerification::new().with_threshold(0)
|
||||||
}
|
}
|
||||||
Purpose::RunQuorum => PayloadVerification::new().with_threshold(threshold),
|
Purpose::RunQuorum => {
|
||||||
|
PayloadVerification::new().with_threshold(threshold)
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
payload.verify_signatures(&certs, &policy, None).unwrap();
|
payload.verify_signatures(&certs, &policy, None).unwrap();
|
||||||
|
@ -221,7 +236,7 @@ pub fn parse_quorum_with_shardfile(
|
||||||
|
|
||||||
pub fn handle_payload(
|
pub fn handle_payload(
|
||||||
workflow: &Workflow,
|
workflow: &Workflow,
|
||||||
inputs: StringMap<Value>,
|
inputs: HashMap<String, Value>,
|
||||||
modules: Commands,
|
modules: Commands,
|
||||||
config: &[ModuleConfig],
|
config: &[ModuleConfig],
|
||||||
) {
|
) {
|
||||||
|
@ -239,8 +254,8 @@ pub fn handle(
|
||||||
modules: Commands,
|
modules: Commands,
|
||||||
config: &[ModuleConfig],
|
config: &[ModuleConfig],
|
||||||
) {
|
) {
|
||||||
let inputs = load_inputs(&workflow.inputs, matches);
|
let inputs = load_inputs(&workflow.inputs, &workflow.optional_inputs, matches);
|
||||||
let data: StringMap<Value> = inputs
|
let data: HashMap<String, Value> = inputs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(k, v)| (k, Value::String(v)))
|
.map(|(k, v)| (k, Value::String(v)))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
name: "broadcast"
|
|
||||||
description: |-
|
|
||||||
Broadcast a transaction on a Cosmos-based blockchain.
|
|
||||||
inputs:
|
|
||||||
- name: "nonce_address"
|
|
||||||
description: >-
|
|
||||||
The address of the account used for the transaction nonce.
|
|
||||||
- name: "chain_name"
|
|
||||||
description: >-
|
|
||||||
The name of the Cosmos chain to broadcast a transaction on.
|
|
||||||
step:
|
|
||||||
- type: "cosmos-get-chain-info"
|
|
||||||
inputs:
|
|
||||||
chain_name: "chain_name"
|
|
||||||
outputs:
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
- type: "cosmos-get-account-data"
|
|
||||||
inputs:
|
|
||||||
account_id: "nonce_address"
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
outputs:
|
|
||||||
account_number: "account_number"
|
|
||||||
sequence_number: "sequence_number"
|
|
||||||
- type: "internal-save-file"
|
|
||||||
values:
|
|
||||||
filename: "account_info.json"
|
|
||||||
inputs:
|
|
||||||
account_number: "account_number"
|
|
||||||
sequence_number: "sequence_number"
|
|
||||||
- type: "internal-load-file"
|
|
||||||
values:
|
|
||||||
filename: "transaction.json"
|
|
||||||
outputs:
|
|
||||||
transaction: "transaction"
|
|
||||||
- type: "cosmos-broadcast"
|
|
||||||
inputs:
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
transaction: "transaction"
|
|
||||||
outputs:
|
|
||||||
status: "status"
|
|
||||||
url: "url"
|
|
||||||
error: "error"
|
|
||||||
error_code: "error_code"
|
|
|
@ -1,14 +1,8 @@
|
||||||
name: generate-address
|
name: generate-address
|
||||||
description: |-
|
|
||||||
Generate an address on a given Cosmos-based blockchain.
|
|
||||||
inputs:
|
inputs:
|
||||||
- name: chain_name
|
- chain_name
|
||||||
description: >-
|
optional_inputs:
|
||||||
The name of the Cosmos chain you'd like to generate an address for.
|
- account
|
||||||
- name: account
|
|
||||||
description: >-
|
|
||||||
The account to use, if not the default account.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: cosmos-get-chain-info
|
- type: cosmos-get-chain-info
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -1,27 +1,12 @@
|
||||||
name: stake
|
name: stake
|
||||||
description: |-
|
|
||||||
Stake coins on the provided chain.
|
|
||||||
inputs:
|
inputs:
|
||||||
- name: delegate_address
|
- delegate_address
|
||||||
description: >-
|
- validator_address
|
||||||
Address holding the coins to be staked to a validator.
|
- chain_name
|
||||||
- name: validator_address
|
- asset_name
|
||||||
description: >-
|
- asset_amount
|
||||||
Address of the validator operator.
|
optional_inputs:
|
||||||
- name: chain_name
|
- gas_factor
|
||||||
description: >-
|
|
||||||
The name of the Cosmos-based chain.
|
|
||||||
- name: asset_name
|
|
||||||
description: >-
|
|
||||||
The name of the asset to stake.
|
|
||||||
- name: asset_amount
|
|
||||||
description: >-
|
|
||||||
The amount of the asset to stake.
|
|
||||||
- name: gas_factor
|
|
||||||
description: >-
|
|
||||||
An amount to multiply the required gas by; necessary if a chain requires
|
|
||||||
more gas for a specific operation.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: cosmos-get-chain-info
|
- type: cosmos-get-chain-info
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
name: "transfer"
|
|
||||||
description: |-
|
|
||||||
Transfer a Cosmos coin.
|
|
||||||
inputs:
|
|
||||||
- name: "from_address"
|
|
||||||
description: >-
|
|
||||||
The address from which to send coin.
|
|
||||||
- name: "to_address"
|
|
||||||
description: >-
|
|
||||||
The address to send coins to.
|
|
||||||
- name: "asset_name"
|
|
||||||
description: >-
|
|
||||||
The name of the asset to send.
|
|
||||||
- name: "chain_name"
|
|
||||||
description: >-
|
|
||||||
The name of the Cosmos chain the asset lives on.
|
|
||||||
- name: "asset_amount"
|
|
||||||
description: >-
|
|
||||||
The amount of the asset to send.
|
|
||||||
- name: gas_factor
|
|
||||||
description: >-
|
|
||||||
An amount to multiply the required gas by; necessary if a chain requires
|
|
||||||
more gas for a specific operation.
|
|
||||||
optional: true
|
|
||||||
step:
|
|
||||||
- type: "cosmos-get-chain-info"
|
|
||||||
inputs:
|
|
||||||
chain_name: "chain_name"
|
|
||||||
outputs:
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
- type: "internal-load-file"
|
|
||||||
values:
|
|
||||||
filename: "account_info.json"
|
|
||||||
outputs:
|
|
||||||
account_number: "account_number"
|
|
||||||
sequence_number: "sequence_number"
|
|
||||||
- type: "cosmos-transfer"
|
|
||||||
inputs:
|
|
||||||
from_address: "from_address"
|
|
||||||
to_address: "to_address"
|
|
||||||
amount: "asset_amount"
|
|
||||||
denom: "asset_name"
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
outputs:
|
|
||||||
fee: "fee"
|
|
||||||
tx_messages: "tx_messages"
|
|
||||||
- type: "cosmos-sign"
|
|
||||||
inputs:
|
|
||||||
fee: "fee"
|
|
||||||
tx_messages: "tx_messages"
|
|
||||||
account_number: "account_number"
|
|
||||||
sequence_number: "sequence_number"
|
|
||||||
blockchain_config: "blockchain_config"
|
|
||||||
outputs:
|
|
||||||
transaction: "signed_transaction"
|
|
||||||
- type: "internal-save-file"
|
|
||||||
values:
|
|
||||||
filename: "transaction.json"
|
|
||||||
inputs:
|
|
||||||
transaction: "signed_transaction"
|
|
|
@ -1,21 +1,10 @@
|
||||||
name: withdraw-rewards
|
name: withdraw-rewards
|
||||||
description: |-
|
|
||||||
Withdraw rewards gained from staking to a validator.
|
|
||||||
inputs:
|
inputs:
|
||||||
- name: delegate_address
|
- delegate_address
|
||||||
description: >-
|
- validator_address
|
||||||
The owner of the staked coins; also, the recipient of rewards.
|
- chain_name
|
||||||
- name: validator_address
|
optional_inputs:
|
||||||
description: >-
|
- gas_factor
|
||||||
The validator from whom coins are staked.
|
|
||||||
- name: chain_name
|
|
||||||
description: >-
|
|
||||||
The name of the Cosmos-based chain.
|
|
||||||
- name: gas_factor
|
|
||||||
description: >-
|
|
||||||
An amount to multiply the required gas by; necessary if a chain requires
|
|
||||||
more gas for a specific operation.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: cosmos-get-chain-info
|
- type: cosmos-get-chain-info
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -1,30 +1,12 @@
|
||||||
name: withdraw
|
name: withdraw
|
||||||
description: |-
|
|
||||||
Withdraw staked coins from a validator.
|
|
||||||
|
|
||||||
Staked coins may be held for an unbonding period, depending on the chain upon
|
|
||||||
which they are staked.
|
|
||||||
inputs:
|
inputs:
|
||||||
- name: delegate_address
|
- delegate_address
|
||||||
description: >-
|
- validator_address
|
||||||
The owner of the staked coins.
|
- chain_name
|
||||||
- name: validator_address
|
- asset_name
|
||||||
description: >-
|
- asset_amount
|
||||||
The validator from whom coins are staked.
|
optional_inputs:
|
||||||
- name: chain_name
|
- gas_factor
|
||||||
description: >-
|
|
||||||
The name of the Cosmos-based chain.
|
|
||||||
- name: asset_name
|
|
||||||
description: >-
|
|
||||||
The name of the asset to withdraw.
|
|
||||||
- name: asset_amount
|
|
||||||
description: >-
|
|
||||||
The amount of the asset to withdraw.
|
|
||||||
- name: gas_factor
|
|
||||||
description: >-
|
|
||||||
An amount to multiply the required gas by; necessary if a chain requires
|
|
||||||
more gas for a specific operation.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: cosmos-get-chain-info
|
- type: cosmos-get-chain-info
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
name: "broadcast"
|
|
||||||
description: |-
|
|
||||||
Broadcast a transaction on the Solana blockchain.
|
|
||||||
inputs:
|
|
||||||
- name: "nonce_address"
|
|
||||||
description: >-
|
|
||||||
The address of the nonce account.
|
|
||||||
- name: "cluster"
|
|
||||||
description: >-
|
|
||||||
The name of the Solana cluster to broadcast the transaction on, if not
|
|
||||||
mainnet-beta.
|
|
||||||
optional: true
|
|
||||||
step:
|
|
||||||
- type: "sol-get-nonce-account-data"
|
|
||||||
inputs:
|
|
||||||
nonce_address: "nonce_address"
|
|
||||||
cluster: "cluster"
|
|
||||||
outputs:
|
|
||||||
authority: "nonce_authority"
|
|
||||||
durable_nonce: "nonce"
|
|
||||||
- type: "internal-save-file"
|
|
||||||
values:
|
|
||||||
filename: "nonce.json"
|
|
||||||
inputs:
|
|
||||||
nonce_authority: "nonce_authority"
|
|
||||||
nonce_data: "nonce"
|
|
||||||
nonce_address: "nonce_address"
|
|
||||||
- type: "internal-load-file"
|
|
||||||
values:
|
|
||||||
filename: "transaction.json"
|
|
||||||
outputs:
|
|
||||||
transaction: "transaction"
|
|
||||||
- type: "sol-broadcast"
|
|
||||||
inputs:
|
|
||||||
cluster: "cluster"
|
|
||||||
transaction: "transaction"
|
|
||||||
outputs:
|
|
||||||
status: "status"
|
|
||||||
url: "url"
|
|
||||||
error: "error"
|
|
|
@ -1,11 +1,6 @@
|
||||||
name: generate-address
|
name: generate-address
|
||||||
description: |-
|
optional_inputs:
|
||||||
Generate a Solana address.
|
- account
|
||||||
inputs:
|
|
||||||
- name: account
|
|
||||||
description: >-
|
|
||||||
The account to use, if not the default account.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: sol-generate-wallet
|
- type: sol-generate-wallet
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
name: "generate-nonce-account"
|
|
||||||
description: |-
|
|
||||||
Using a temporary Keyfork instance, generate a nonce address for the given
|
|
||||||
authorization address.
|
|
||||||
inputs:
|
|
||||||
- name: "cluster"
|
|
||||||
description: >-
|
|
||||||
Name of the Solana cluster to generate the nonce account on, if not
|
|
||||||
mainnet-beta.
|
|
||||||
- name: "authorization_address"
|
|
||||||
description: >-
|
|
||||||
The address used to authorize advancing the nonce.
|
|
||||||
|
|
||||||
The authorization address (also called "address" or "pubkey" in other
|
|
||||||
workflows) is required to be a signer of the transaction, so the
|
|
||||||
authorization address is often the principal address - the one performing
|
|
||||||
the transaction.
|
|
||||||
aliases:
|
|
||||||
- address
|
|
||||||
- primary_address
|
|
||||||
- principal_address
|
|
||||||
- pubkey
|
|
||||||
step:
|
|
||||||
- type: "sol-get-blockhash"
|
|
||||||
inputs:
|
|
||||||
cluster: "cluster"
|
|
||||||
outputs:
|
|
||||||
blockhash: "blockhash"
|
|
||||||
- type: "sol-create-nonce-account-and-signing-key"
|
|
||||||
inputs:
|
|
||||||
authorization_address: "authorization_address"
|
|
||||||
outputs:
|
|
||||||
transaction: "instructions"
|
|
||||||
nonce_pubkey: "nonce_pubkey"
|
|
||||||
payer_pubkey: "payer_pubkey"
|
|
||||||
privkeys: "private_keys"
|
|
||||||
- type: "sol-await-funds"
|
|
||||||
inputs:
|
|
||||||
address: "payer_pubkey"
|
|
||||||
cluster: "cluster"
|
|
||||||
values:
|
|
||||||
lamports: "1510000"
|
|
||||||
- type: "sol-compile"
|
|
||||||
inputs:
|
|
||||||
instructions: "instructions"
|
|
||||||
derivation_accounts: "derivation_accounts"
|
|
||||||
blockhash: "blockhash"
|
|
||||||
outputs:
|
|
||||||
instructions: "nonced_instructions"
|
|
||||||
- type: "sol-sign"
|
|
||||||
inputs:
|
|
||||||
blockhash: "blockhash"
|
|
||||||
signing_keys: "private_keys"
|
|
||||||
instructions: "nonced_instructions"
|
|
||||||
outputs:
|
|
||||||
transaction: "signed_transaction"
|
|
||||||
- type: "sol-broadcast"
|
|
||||||
inputs:
|
|
||||||
cluster: "cluster"
|
|
||||||
transaction: "signed_transaction"
|
|
||||||
outputs:
|
|
||||||
status: "status"
|
|
||||||
url: "url"
|
|
||||||
error: "error"
|
|
||||||
- type: "internal-cat"
|
|
||||||
inputs:
|
|
||||||
status: "status"
|
|
||||||
url: "url"
|
|
||||||
nonce_address: "nonce_pubkey"
|
|
||||||
error: "error"
|
|
||||||
outputs:
|
|
||||||
status: "status"
|
|
||||||
url: "url"
|
|
||||||
nonce_address: "nonce_address"
|
|
||||||
error: "error"
|
|
|
@ -1,19 +1,9 @@
|
||||||
name: transfer-token
|
name: transfer-token
|
||||||
description: |-
|
|
||||||
Transfer SPL tokens held on the Solana blockchain.
|
|
||||||
inputs:
|
inputs:
|
||||||
- name: from_address
|
- from_address
|
||||||
description: >-
|
- to_address
|
||||||
The address from which to send tokens.
|
- token_name
|
||||||
- name: to_address
|
- token_amount
|
||||||
description: >-
|
|
||||||
The address to send coins to.
|
|
||||||
- name: token_name
|
|
||||||
description: >-
|
|
||||||
The name of the token to transfer.
|
|
||||||
- name: token_amount
|
|
||||||
description: >-
|
|
||||||
The amount of the token to transfer.
|
|
||||||
step:
|
step:
|
||||||
- type: sol-get-token-info
|
- type: sol-get-token-info
|
||||||
inputs:
|
inputs:
|
||||||
|
@ -46,10 +36,10 @@ step:
|
||||||
nonce_authority: nonce_authority
|
nonce_authority: nonce_authority
|
||||||
nonce_data: nonce_data
|
nonce_data: nonce_data
|
||||||
outputs:
|
outputs:
|
||||||
instructions: nonced_instructions
|
transaction: unsigned_transaction
|
||||||
- type: sol-sign
|
- type: sol-sign
|
||||||
inputs:
|
inputs:
|
||||||
instructions: nonced_instructions
|
transaction: unsigned_transaction
|
||||||
blockhash: nonce_data
|
blockhash: nonce_data
|
||||||
outputs:
|
outputs:
|
||||||
transaction: transaction
|
transaction: transaction
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
name: "transfer"
|
|
||||||
description: |-
|
|
||||||
Transfer SOL from one address to another.
|
|
||||||
inputs:
|
|
||||||
- name: "to_address"
|
|
||||||
description: >-
|
|
||||||
The address to send SOL to.
|
|
||||||
- name: "from_address"
|
|
||||||
description: >-
|
|
||||||
The address to send SOL from.
|
|
||||||
- name: "amount"
|
|
||||||
description: >-
|
|
||||||
The amount of SOL to send.
|
|
||||||
step:
|
|
||||||
- type: "internal-load-file"
|
|
||||||
values:
|
|
||||||
filename: "nonce.json"
|
|
||||||
outputs:
|
|
||||||
nonce_authority: "nonce_authority"
|
|
||||||
nonce_data: "nonce_data"
|
|
||||||
nonce_address: "nonce_address"
|
|
||||||
- type: "sol-transfer"
|
|
||||||
inputs:
|
|
||||||
from_address: "from_address"
|
|
||||||
to_address: "to_address"
|
|
||||||
amount: "amount"
|
|
||||||
outputs:
|
|
||||||
instructions: "instructions"
|
|
||||||
derivation_accounts: "derivation_accounts"
|
|
||||||
- type: "sol-compile"
|
|
||||||
inputs:
|
|
||||||
instructions: "instructions"
|
|
||||||
derivation_accounts: "derivation_accounts"
|
|
||||||
nonce_address: "nonce_address"
|
|
||||||
nonce_authority: "nonce_authority"
|
|
||||||
nonce_data: "nonce_data"
|
|
||||||
outputs:
|
|
||||||
instructions: "nonced_instructions"
|
|
||||||
- type: "sol-sign"
|
|
||||||
inputs:
|
|
||||||
blockhash: "nonce_data"
|
|
||||||
instructions: "nonced_instructions"
|
|
||||||
outputs:
|
|
||||||
transaction: "signed_transaction"
|
|
||||||
- type: "internal-save-file"
|
|
||||||
values:
|
|
||||||
filename: "transaction.json"
|
|
||||||
inputs:
|
|
||||||
transaction: "signed_transaction"
|
|
|
@ -1,15 +1,7 @@
|
||||||
name: generate-address
|
name: generate-address
|
||||||
description: |-
|
optional_inputs:
|
||||||
Generate a Spacemesh address
|
- account
|
||||||
inputs:
|
- cluster
|
||||||
- name: account
|
|
||||||
description: >-
|
|
||||||
The account to use, if not the default account.
|
|
||||||
optional: true
|
|
||||||
- name: cluster
|
|
||||||
description: >-
|
|
||||||
The Spacemesh cluster to use, if not the mainnet.
|
|
||||||
optional: true
|
|
||||||
step:
|
step:
|
||||||
- type: spacemesh-generate-wallet
|
- type: spacemesh-generate-wallet
|
||||||
inputs:
|
inputs:
|
||||||
|
|
|
@ -61,26 +61,6 @@ pub enum BaseError {
|
||||||
/// The JSON object is not a valid value.
|
/// The JSON object is not a valid value.
|
||||||
#[error("the JSON object is not a valid value")]
|
#[error("the JSON object is not a valid value")]
|
||||||
InvalidJSONValue,
|
InvalidJSONValue,
|
||||||
|
|
||||||
/// No signing key was found on smartcard.
|
|
||||||
#[error("no signing key was found on smartcard")]
|
|
||||||
NoSigningKey,
|
|
||||||
|
|
||||||
/// A signature exists for the current smartcard.
|
|
||||||
#[error("a signature exists for the key on the current smartcard: {0}")]
|
|
||||||
ConflictingSignature(openpgp::Fingerprint),
|
|
||||||
|
|
||||||
/// A bad packet type was encountered.
|
|
||||||
#[error("a bad OpenPGP packet was encountered: {0}")]
|
|
||||||
BadOpenPGPPacket(openpgp::packet::Tag),
|
|
||||||
|
|
||||||
/// A signature could not have been added; a smartcard might not have been pluggedi n.
|
|
||||||
#[error("a signature could not be added")]
|
|
||||||
NoSignatureAdded,
|
|
||||||
|
|
||||||
/// The signature matched a key that was already used to verify another signature.
|
|
||||||
#[error("signature {1} matched key {0} previously used to sign signature {2}")]
|
|
||||||
DuplicateSignature(openpgp::Fingerprint, usize, usize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseError {
|
impl BaseError {
|
||||||
|
@ -171,11 +151,7 @@ impl PayloadVerification {
|
||||||
|
|
||||||
/// Set a threshold for required signatures.
|
/// Set a threshold for required signatures.
|
||||||
pub fn with_threshold(self, threshold: u8) -> Self {
|
pub fn with_threshold(self, threshold: u8) -> Self {
|
||||||
Self {
|
Self { one_each: false, threshold, ..self }
|
||||||
one_each: false,
|
|
||||||
threshold,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Require a single valid signature; other signatures may be invalid.
|
/// Require a single valid signature; other signatures may be invalid.
|
||||||
|
@ -283,12 +259,6 @@ impl Payload {
|
||||||
///
|
///
|
||||||
/// The method may error if a signature could not be created.
|
/// The method may error if a signature could not be created.
|
||||||
pub fn add_signature(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn add_signature(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let signatures = self
|
|
||||||
.signatures
|
|
||||||
.iter()
|
|
||||||
.map(|signature_text| Packet::from_bytes(signature_text.as_bytes()).map_err(Into::into))
|
|
||||||
.collect::<Result<Vec<_>, Box<dyn std::error::Error>>>()?;
|
|
||||||
|
|
||||||
let unhashed = unhashed(serde_json::to_value(&self)?)?;
|
let unhashed = unhashed(serde_json::to_value(&self)?)?;
|
||||||
let builder =
|
let builder =
|
||||||
SignatureBuilder::new(SignatureType::Binary).set_hash_algo(HashAlgorithm::SHA512);
|
SignatureBuilder::new(SignatureType::Binary).set_hash_algo(HashAlgorithm::SHA512);
|
||||||
|
@ -298,26 +268,10 @@ impl Payload {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut has_signed_any = false;
|
|
||||||
for backend in card_backend_pcsc::PcscBackend::cards(None)? {
|
for backend in card_backend_pcsc::PcscBackend::cards(None)? {
|
||||||
let mut card = Card::<Open>::new(backend?)?;
|
let mut card = Card::<Open>::new(backend?)?;
|
||||||
let mut transaction = card.transaction()?;
|
let mut transaction = card.transaction()?;
|
||||||
|
|
||||||
let key_fps = transaction.fingerprints()?;
|
|
||||||
let signing_key_fp = key_fps.signature().ok_or(BaseError::NoSigningKey)?;
|
|
||||||
|
|
||||||
for packet in &signatures {
|
|
||||||
let Packet::Signature(signature) = packet else {
|
|
||||||
return Err(BaseError::BadOpenPGPPacket(packet.tag()).into());
|
|
||||||
};
|
|
||||||
|
|
||||||
for issuer_fp in signature.issuer_fingerprints() {
|
|
||||||
if issuer_fp.as_bytes() == signing_key_fp.as_bytes() {
|
|
||||||
return Err(BaseError::ConflictingSignature(issuer_fp.clone()).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cardholder_name = format_name(transaction.cardholder_name()?);
|
let cardholder_name = format_name(transaction.cardholder_name()?);
|
||||||
let card_id = transaction.application_identifier()?.ident();
|
let card_id = transaction.application_identifier()?.ident();
|
||||||
let mut pin = None;
|
let mut pin = None;
|
||||||
|
@ -375,14 +329,9 @@ impl Payload {
|
||||||
writer.finalize()?;
|
writer.finalize()?;
|
||||||
|
|
||||||
self.signatures.push(String::from_utf8(armored_signature)?);
|
self.signatures.push(String::from_utf8(armored_signature)?);
|
||||||
has_signed_any = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if has_signed_any {
|
Ok(())
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(BaseError::NoSignatureAdded.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Verify the keychain and certificates using either a Key ID or an OpenPGP card.
|
/// Verify the keychain and certificates using either a Key ID or an OpenPGP card.
|
||||||
|
@ -421,23 +370,13 @@ impl Payload {
|
||||||
threshold = certs.len() as u8;
|
threshold = certs.len() as u8;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut seen = std::collections::HashMap::new();
|
for signature in &self.signatures {
|
||||||
|
|
||||||
for (index, signature) in self.signatures.iter().enumerate() {
|
|
||||||
dbg!(&index);
|
|
||||||
let packet = Packet::from_bytes(signature.as_bytes())?;
|
let packet = Packet::from_bytes(signature.as_bytes())?;
|
||||||
let Packet::Signature(signature) = packet else {
|
let Packet::Signature(signature) = packet else {
|
||||||
panic!("bad packet found: {}", packet.tag());
|
panic!("bad packet found: {}", packet.tag());
|
||||||
};
|
};
|
||||||
let mut signature_matched = false;
|
let mut signature_matched = false;
|
||||||
// NOTE: It is allowable, by the specification, to have a packet that doesn't include
|
for issuer in signature.get_issuers() {
|
||||||
// an issuer fingerprint, but instead just a key ID. However, filtering by both key ID
|
|
||||||
// and by fingerprint triggers the "duplicate signature" mechanism. For that reason, we
|
|
||||||
// are only going to filter over fingerprints.
|
|
||||||
//
|
|
||||||
// Any program that makes these signatures should be using fingerprints.
|
|
||||||
for issuer in signature.issuer_fingerprints() {
|
|
||||||
let mut currently_seen = std::collections::HashMap::new();
|
|
||||||
for cert in &certs {
|
for cert in &certs {
|
||||||
match cert
|
match cert
|
||||||
.with_policy(&policy, None)?
|
.with_policy(&policy, None)?
|
||||||
|
@ -448,29 +387,13 @@ impl Payload {
|
||||||
.next()
|
.next()
|
||||||
.map(|signing_key| signature.verify_hash(&signing_key, hashed.clone()))
|
.map(|signing_key| signature.verify_hash(&signing_key, hashed.clone()))
|
||||||
{
|
{
|
||||||
Some(result) => {
|
Some(Ok(())) => {
|
||||||
// matching key found, check for duplicates
|
// key found, signature matched
|
||||||
if let Some(seen_index) = seen.get(&cert.fingerprint()) {
|
signature_matched = true;
|
||||||
return Err(BaseError::DuplicateSignature(
|
}
|
||||||
cert.fingerprint(),
|
Some(Err(e)) => {
|
||||||
index,
|
if error_on_invalid {
|
||||||
*seen_index,
|
return Err(e)?;
|
||||||
)
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(()) => {
|
|
||||||
signature_matched = true;
|
|
||||||
|
|
||||||
// mark the cert as seen, so it isn't reusable
|
|
||||||
currently_seen.insert(cert.fingerprint(), index);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if error_on_invalid {
|
|
||||||
return Err(e)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -478,7 +401,6 @@ impl Payload {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
seen.extend(currently_seen);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if signature_matched {
|
if signature_matched {
|
||||||
|
|
202
icepick.toml
202
icepick.toml
|
@ -3,11 +3,213 @@ name = "sol"
|
||||||
derivation_prefix = "m/44'/501'/0'"
|
derivation_prefix = "m/44'/501'/0'"
|
||||||
algorithm = "Ed25519"
|
algorithm = "Ed25519"
|
||||||
|
|
||||||
|
# NOTE: To get a nonce address, the `generate-nonce-account` workflow should be
|
||||||
|
# run. It is the only workflow that uses a blockhash, which is why a
|
||||||
|
# `broadcast-with-blockhash` or similar is not, and should not be, implemented.
|
||||||
|
[[module.workflow]]
|
||||||
|
name = "broadcast"
|
||||||
|
inputs = ["nonce_address", "cluster"]
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-get-nonce-account-data"
|
||||||
|
inputs = { nonce_address = "nonce_address", cluster = "cluster" }
|
||||||
|
outputs = { authority = "nonce_authority", durable_nonce = "nonce" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-save-file"
|
||||||
|
values = { filename = "nonce.json" }
|
||||||
|
inputs = { nonce_authority = "nonce_authority", nonce_data = "nonce", nonce_address = "nonce_address" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-load-file"
|
||||||
|
values = { filename = "transaction.json" }
|
||||||
|
outputs = { transaction = "transaction" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-broadcast"
|
||||||
|
inputs = { cluster = "cluster", transaction = "transaction" }
|
||||||
|
outputs = { status = "status", url = "url", error = "error" }
|
||||||
|
|
||||||
|
[[module.workflow]]
|
||||||
|
name = "generate-nonce-account"
|
||||||
|
inputs = ["cluster", "authorization_address"]
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-generate-wallet"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-get-wallet-address"
|
||||||
|
outputs = { pubkey = "wallet_pubkey" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-await-funds"
|
||||||
|
inputs = { address = "wallet_pubkey", cluster = "cluster" }
|
||||||
|
# enough to cover two signatures and the 1_500_000 approx. rent fee
|
||||||
|
values = { lamports = "1510000" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-get-blockhash"
|
||||||
|
inputs = { cluster = "cluster" }
|
||||||
|
outputs = { blockhash = "blockhash" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-create-nonce-account-and-signing-key"
|
||||||
|
|
||||||
|
[module.workflow.step.inputs]
|
||||||
|
from_address = "wallet_pubkey"
|
||||||
|
authorization_address = "authorization_address"
|
||||||
|
|
||||||
|
[module.workflow.step.outputs]
|
||||||
|
transaction = "unsigned_transaction"
|
||||||
|
nonce_pubkey = "nonce_pubkey"
|
||||||
|
nonce_privkey = "private_keys"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-sign"
|
||||||
|
|
||||||
|
[module.workflow.step.inputs]
|
||||||
|
blockhash = "blockhash"
|
||||||
|
signing_keys = "private_keys"
|
||||||
|
transaction = "unsigned_transaction"
|
||||||
|
|
||||||
|
[module.workflow.step.outputs]
|
||||||
|
transaction = "signed_transaction"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-broadcast"
|
||||||
|
inputs = { cluster = "cluster", transaction = "signed_transaction" }
|
||||||
|
outputs = { status = "status", url = "url" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-cat"
|
||||||
|
inputs = { status = "status", url = "url", nonce_account = "nonce_pubkey" }
|
||||||
|
outputs = { status = "status", url = "url", nonce_account = "nonce_account" }
|
||||||
|
|
||||||
|
[[module.workflow]]
|
||||||
|
# Transfer SOL from one address to another.
|
||||||
|
name = "transfer"
|
||||||
|
inputs = ["to_address", "from_address", "amount"]
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-load-file"
|
||||||
|
values = { filename = "nonce.json" }
|
||||||
|
outputs = { nonce_authority = "nonce_authority", nonce_data = "nonce_data", nonce_address = "nonce_address" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-transfer"
|
||||||
|
inputs = { from_address = "from_address", to_address = "to_address", amount = "amount" }
|
||||||
|
outputs = { instructions = "instructions", derivation_accounts = "derivation_accounts" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-compile"
|
||||||
|
|
||||||
|
[module.workflow.step.inputs]
|
||||||
|
instructions = "instructions"
|
||||||
|
derivation_accounts = "derivation_accounts"
|
||||||
|
nonce_address = "nonce_address"
|
||||||
|
nonce_authority = "nonce_authority"
|
||||||
|
nonce_data = "nonce_data"
|
||||||
|
|
||||||
|
[module.workflow.step.outputs]
|
||||||
|
transaction = "unsigned_transaction"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "sol-sign"
|
||||||
|
|
||||||
|
inputs = { blockhash = "nonce_data", transaction = "unsigned_transaction" }
|
||||||
|
outputs = { transaction = "signed_transaction" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-save-file"
|
||||||
|
|
||||||
|
values = { filename = "transaction.json" }
|
||||||
|
inputs = { transaction = "signed_transaction" }
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
name = "cosmos"
|
name = "cosmos"
|
||||||
derivation_prefix = "m/44'/118'/0'"
|
derivation_prefix = "m/44'/118'/0'"
|
||||||
algorithm = "Secp256k1"
|
algorithm = "Secp256k1"
|
||||||
|
|
||||||
|
[[module.workflow]]
|
||||||
|
name = "transfer"
|
||||||
|
inputs = ["from_address", "to_address", "asset_name", "chain_name", "asset_amount"]
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
# NOTE: chain_name can't be discoverable by filtering from asset_name, since
|
||||||
|
# some asset devnets reuse the name. There's no difference between KYVE on Kyve
|
||||||
|
# or Korellia (devnet).
|
||||||
|
type = "cosmos-get-chain-info"
|
||||||
|
inputs = { chain_name = "chain_name" }
|
||||||
|
outputs = { blockchain_config = "blockchain_config" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-load-file"
|
||||||
|
values = { filename = "account_info.json" }
|
||||||
|
outputs = { account_number = "account_number", sequence_number = "sequence_number" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "cosmos-transfer"
|
||||||
|
|
||||||
|
[module.workflow.step.inputs]
|
||||||
|
from_address = "from_address"
|
||||||
|
to_address = "to_address"
|
||||||
|
amount = "asset_amount"
|
||||||
|
denom = "asset_name"
|
||||||
|
blockchain_config = "blockchain_config"
|
||||||
|
|
||||||
|
[module.workflow.step.outputs]
|
||||||
|
fee = "fee"
|
||||||
|
tx_messages = "tx_messages"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "cosmos-sign"
|
||||||
|
|
||||||
|
[module.workflow.step.inputs]
|
||||||
|
fee = "fee"
|
||||||
|
tx_messages = "tx_messages"
|
||||||
|
account_number = "account_number"
|
||||||
|
sequence_number = "sequence_number"
|
||||||
|
blockchain_config = "blockchain_config"
|
||||||
|
|
||||||
|
[module.workflow.step.outputs]
|
||||||
|
transaction = "signed_transaction"
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-save-file"
|
||||||
|
values = { filename = "transaction.json" }
|
||||||
|
inputs = { transaction = "signed_transaction" }
|
||||||
|
|
||||||
|
[[module.workflow]]
|
||||||
|
name = "broadcast"
|
||||||
|
# NOTE: For the purpose of Cosmos, the nonce is a direct part of the signer's
|
||||||
|
# account.
|
||||||
|
inputs = ["nonce_address", "chain_name"]
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "cosmos-get-chain-info"
|
||||||
|
inputs = { chain_name = "chain_name" }
|
||||||
|
outputs = { blockchain_config = "blockchain_config" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "cosmos-get-account-data"
|
||||||
|
inputs = { account_id = "nonce_address", blockchain_config = "blockchain_config" }
|
||||||
|
outputs = { account_number = "account_number", sequence_number = "sequence_number" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-save-file"
|
||||||
|
values = { filename = "account_info.json" }
|
||||||
|
inputs = { account_number = "account_number", sequence_number = "sequence_number" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "internal-load-file"
|
||||||
|
values = { filename = "transaction.json" }
|
||||||
|
outputs = { transaction = "transaction" }
|
||||||
|
|
||||||
|
[[module.workflow.step]]
|
||||||
|
type = "cosmos-broadcast"
|
||||||
|
inputs = { blockchain_config = "blockchain_config", transaction = "transaction" }
|
||||||
|
outputs = { status = "status", url = "url", error = "error", error_code = "error_code" }
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
name = "spacemesh"
|
name = "spacemesh"
|
||||||
derivation_prefix = "m/44'/540'/0'/0'"
|
derivation_prefix = "m/44'/540'/0'/0'"
|
||||||
|
|
Loading…
Reference in New Issue