icepick workflow cosmos {stake,withdraw}; icepick cosmos {get-delegation,get-validators}
This commit is contained in:
parent
611aad6665
commit
2ed1e64db8
|
@ -6,7 +6,7 @@ use cosmrs::{
|
|||
};
|
||||
use icepick_module::Module;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::str::FromStr;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use cosmrs::crypto::secp256k1;
|
||||
|
||||
|
@ -21,6 +21,18 @@ pub struct GetChainInfo {
|
|||
chain_name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetValidatorNames {
|
||||
blockchain_config: coin_denoms::Blockchain,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GetDelegation {
|
||||
delegator_address: String,
|
||||
validator_address: String,
|
||||
blockchain_config: coin_denoms::Blockchain,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct GenerateWallet {
|
||||
account: Option<String>,
|
||||
|
@ -54,11 +66,30 @@ pub struct Transfer {
|
|||
to_address: String,
|
||||
from_account: Option<String>,
|
||||
from_address: String,
|
||||
// TODO: find a way to simulate transaction and calculate gas necessary
|
||||
// for now, 0.01KYVE seems to be a reasonable mainnet number?
|
||||
// for testing purposes, i'm gonna go much lower. 0.0001.
|
||||
gas_factor: Option<String>,
|
||||
|
||||
gas_factor: Option<String>,
|
||||
blockchain_config: coin_denoms::Blockchain,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Stake {
|
||||
amount: String,
|
||||
denom: String,
|
||||
delegate_address: String,
|
||||
validator_address: String,
|
||||
|
||||
gas_factor: Option<String>,
|
||||
blockchain_config: coin_denoms::Blockchain,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct Withdraw {
|
||||
amount: String,
|
||||
denom: String,
|
||||
delegate_address: String,
|
||||
validator_address: String,
|
||||
|
||||
gas_factor: Option<String>,
|
||||
blockchain_config: coin_denoms::Blockchain,
|
||||
}
|
||||
|
||||
|
@ -92,11 +123,15 @@ pub struct Request {
|
|||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Operation {
|
||||
GetChainInfo(GetChainInfo),
|
||||
GetValidatorNames(GetValidatorNames),
|
||||
GetDelegation(GetDelegation),
|
||||
GenerateWallet(GenerateWallet),
|
||||
GetWalletAddress(GetWalletAddress),
|
||||
GetAccountData(GetAccountData),
|
||||
AwaitFunds(AwaitFunds),
|
||||
Transfer(Transfer),
|
||||
Stake(Stake),
|
||||
Withdraw(Withdraw),
|
||||
Sign(Sign),
|
||||
Broadcast(Broadcast),
|
||||
}
|
||||
|
@ -175,6 +210,30 @@ impl Module for Cosmos {
|
|||
.build(),
|
||||
);
|
||||
|
||||
let get_validators = Operation::builder()
|
||||
.name("get-validator-names")
|
||||
.description("Get a list of all validators, by name (if the validator provides one)")
|
||||
.build();
|
||||
|
||||
let get_delegation = Operation::builder()
|
||||
.name("get-delegation")
|
||||
.description("Get the delegate information for a delegator-validator pair.")
|
||||
.build()
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("delegator_address")
|
||||
.description("The address of the delegator.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("validator_address")
|
||||
.description("The address of the validator.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let generate_wallet = Operation::builder()
|
||||
.name("generate-wallet")
|
||||
.description("Generate a wallet for the given account.")
|
||||
|
@ -261,6 +320,86 @@ impl Module for Cosmos {
|
|||
.build(),
|
||||
);
|
||||
|
||||
let stake = Operation::builder()
|
||||
.name("stake")
|
||||
.description("Delegate coins to a specified validator.")
|
||||
.build()
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("amount")
|
||||
.description("The amount of coins to stake.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("denom")
|
||||
.description("The denomination of coin to stake.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("delegate_address")
|
||||
.description("The address holding funds to be staked.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("validator_address")
|
||||
.description("The address of the validator operator to stake upon.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("gas_factor")
|
||||
.description("The factor to multiply the default gas amount by.")
|
||||
.r#type(ArgumentType::Optional)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let withdraw = Operation::builder()
|
||||
.name("withdraw")
|
||||
.description("Delegate coins to a specified validator.")
|
||||
.build()
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("amount")
|
||||
.description("The amount of coins to withdraw.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("denom")
|
||||
.description("The denomination of coin to withdraw.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("delegate_address")
|
||||
.description("The address holding funds to be withdrawn.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("validator_address")
|
||||
.description("The address of the validator operator to withdraw from.")
|
||||
.r#type(ArgumentType::Required)
|
||||
.build(),
|
||||
)
|
||||
.argument(
|
||||
&Argument::builder()
|
||||
.name("gas_factor")
|
||||
.description("The factor to multiply the default gas amount by.")
|
||||
.r#type(ArgumentType::Optional)
|
||||
.build(),
|
||||
);
|
||||
|
||||
let sign = Operation::builder()
|
||||
.name("sign")
|
||||
.description("Sign a previously-generated transaction.")
|
||||
|
@ -287,12 +426,16 @@ impl Module for Cosmos {
|
|||
|
||||
vec![
|
||||
get_chain_info,
|
||||
get_validators,
|
||||
get_delegation,
|
||||
generate_wallet,
|
||||
get_wallet_address,
|
||||
get_account_info,
|
||||
await_funds,
|
||||
transfer,
|
||||
sign,
|
||||
stake,
|
||||
withdraw,
|
||||
broadcast,
|
||||
]
|
||||
}
|
||||
|
@ -317,6 +460,84 @@ impl Module for Cosmos {
|
|||
},
|
||||
}))
|
||||
}
|
||||
Operation::GetValidatorNames(GetValidatorNames { blockchain_config }) => {
|
||||
use cosmrs::proto::cosmos::staking::v1beta1::*;
|
||||
let validators = run_async(async {
|
||||
let client =
|
||||
cosmrs::rpc::HttpClient::new(blockchain_config.rpc_url.as_str()).unwrap();
|
||||
|
||||
// TODO: Pagination
|
||||
let validator: QueryValidatorsResponse = abci_query(
|
||||
&client,
|
||||
"/cosmos.staking.v1beta1.Query/Validators",
|
||||
Some(&QueryValidatorsRequest {
|
||||
status: BondStatus::Bonded.as_str_name().to_string(),
|
||||
pagination: None,
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
validator.validators
|
||||
});
|
||||
let id_to_name = validators
|
||||
.iter()
|
||||
.map(|val| {
|
||||
let name = val
|
||||
.description
|
||||
.as_ref()
|
||||
.map(|desc| &desc.moniker)
|
||||
.filter(|moniker| !moniker.is_empty())
|
||||
.unwrap_or(&val.operator_address);
|
||||
(val.operator_address.clone(), name.clone())
|
||||
})
|
||||
.collect::<HashMap<String, String>>();
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"validators": id_to_name,
|
||||
}
|
||||
}))
|
||||
}
|
||||
Operation::GetDelegation(GetDelegation {
|
||||
delegator_address,
|
||||
validator_address,
|
||||
blockchain_config,
|
||||
}) => {
|
||||
use cosmrs::proto::cosmos::staking::v1beta1::*;
|
||||
let delegation = run_async(async {
|
||||
let client =
|
||||
cosmrs::rpc::HttpClient::new(blockchain_config.rpc_url.as_str()).unwrap();
|
||||
|
||||
let delegation: QueryDelegationResponse = abci_query(
|
||||
&client,
|
||||
"/cosmos.staking.v1beta1.Query/Delegation",
|
||||
Some(&QueryDelegationRequest {
|
||||
delegator_addr: delegator_address,
|
||||
validator_addr: validator_address,
|
||||
}),
|
||||
None,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
delegation.delegation_response.unwrap()
|
||||
});
|
||||
let DelegationResponse {
|
||||
delegation: Some(delegation),
|
||||
balance: Some(balance),
|
||||
} = delegation
|
||||
else {
|
||||
panic!("Either delegation or balance were not accessible");
|
||||
};
|
||||
// NOTE: The return value here is an i128. Do not parse it. serde becomes unhappy.
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"shares": delegation.shares,
|
||||
"balance": balance.amount,
|
||||
}
|
||||
}))
|
||||
}
|
||||
Operation::GenerateWallet(GenerateWallet { account }) => {
|
||||
let account = u32::from_str(account.as_deref().unwrap_or("0")).unwrap();
|
||||
Ok(serde_json::json!({
|
||||
|
@ -433,6 +654,158 @@ impl Module for Cosmos {
|
|||
}
|
||||
}))
|
||||
}
|
||||
Operation::Stake(Stake {
|
||||
amount,
|
||||
denom,
|
||||
delegate_address,
|
||||
validator_address,
|
||||
gas_factor,
|
||||
blockchain_config,
|
||||
}) => {
|
||||
// Check if given denom is min denom or normal and adjust accordingly
|
||||
let Some(relevant_denom) = blockchain_config.currencies.iter().find(|c| {
|
||||
[&c.coin_denom, &c.coin_minimal_denom]
|
||||
.iter()
|
||||
.any(|name| **name == denom)
|
||||
}) else {
|
||||
panic!("{denom} not in {blockchain_config:?}");
|
||||
};
|
||||
|
||||
let gas_factor = gas_factor
|
||||
.as_deref()
|
||||
.map(f64::from_str)
|
||||
.transpose()
|
||||
.unwrap()
|
||||
.unwrap_or(1.0);
|
||||
|
||||
let amount = f64::from_str(&amount).unwrap();
|
||||
let adjusted_amount = if relevant_denom.coin_denom == denom {
|
||||
amount * 10f64.powi(i32::from(relevant_denom.coin_decimals))
|
||||
} else if relevant_denom.coin_minimal_denom == denom {
|
||||
amount
|
||||
} else {
|
||||
unreachable!("broke invariant: check denom checker");
|
||||
} as u128;
|
||||
|
||||
let delegate_id = AccountId::from_str(&delegate_address).unwrap();
|
||||
let validator_id = AccountId::from_str(&validator_address).unwrap();
|
||||
|
||||
let coin = cosmrs::Coin {
|
||||
denom: relevant_denom.coin_minimal_denom.parse().unwrap(),
|
||||
amount: adjusted_amount,
|
||||
};
|
||||
|
||||
let msg_delegate = cosmrs::staking::MsgDelegate {
|
||||
delegator_address: delegate_id,
|
||||
validator_address: validator_id,
|
||||
amount: coin,
|
||||
}
|
||||
.to_any()
|
||||
.unwrap();
|
||||
|
||||
let expected_gas = 200_000u64;
|
||||
// convert gas "price" to minimum denom,
|
||||
// multiply by amount of gas required,
|
||||
// multiply by gas factor
|
||||
let expected_fee =
|
||||
blockchain_config.gas_price_step.high * expected_gas as f64 * gas_factor;
|
||||
|
||||
let fee_coin = cosmrs::Coin {
|
||||
denom: relevant_denom.coin_minimal_denom.parse().unwrap(),
|
||||
amount: expected_fee as u128,
|
||||
};
|
||||
|
||||
let fee = Fee::from_amount_and_gas(fee_coin, expected_gas);
|
||||
|
||||
#[allow(clippy::identity_op)]
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"fee": remote_serde::Fee::from(&fee),
|
||||
// TODO: Body does not implement Serialize and
|
||||
// needs to be constructed in Sign
|
||||
"tx_messages": [msg_delegate],
|
||||
// re-export, but in general this should be copied over
|
||||
// using workflows
|
||||
},
|
||||
"derivation_accounts": [0u32 | 1 << 31],
|
||||
}))
|
||||
}
|
||||
Operation::Withdraw(Withdraw {
|
||||
amount,
|
||||
denom,
|
||||
delegate_address,
|
||||
validator_address,
|
||||
gas_factor,
|
||||
blockchain_config,
|
||||
}) => {
|
||||
// Check if given denom is min denom or normal and adjust accordingly
|
||||
let Some(relevant_denom) = blockchain_config.currencies.iter().find(|c| {
|
||||
[&c.coin_denom, &c.coin_minimal_denom]
|
||||
.iter()
|
||||
.any(|name| **name == denom)
|
||||
}) else {
|
||||
panic!("{denom} not in {blockchain_config:?}");
|
||||
};
|
||||
|
||||
let gas_factor = gas_factor
|
||||
.as_deref()
|
||||
.map(f64::from_str)
|
||||
.transpose()
|
||||
.unwrap()
|
||||
.unwrap_or(1.0);
|
||||
|
||||
let amount = f64::from_str(&amount).unwrap();
|
||||
let adjusted_amount = if relevant_denom.coin_denom == denom {
|
||||
amount * 10f64.powi(i32::from(relevant_denom.coin_decimals))
|
||||
} else if relevant_denom.coin_minimal_denom == denom {
|
||||
amount
|
||||
} else {
|
||||
unreachable!("broke invariant: check denom checker");
|
||||
} as u128;
|
||||
|
||||
let delegate_id = AccountId::from_str(&delegate_address).unwrap();
|
||||
let validator_id = AccountId::from_str(&validator_address).unwrap();
|
||||
|
||||
let coin = cosmrs::Coin {
|
||||
denom: relevant_denom.coin_minimal_denom.parse().unwrap(),
|
||||
amount: adjusted_amount,
|
||||
};
|
||||
|
||||
let msg_undelegate = cosmrs::staking::MsgUndelegate {
|
||||
delegator_address: delegate_id,
|
||||
validator_address: validator_id,
|
||||
amount: coin,
|
||||
}
|
||||
.to_any()
|
||||
.unwrap();
|
||||
|
||||
let expected_gas = 250_000u64;
|
||||
// convert gas "price" to minimum denom,
|
||||
// multiply by amount of gas required,
|
||||
// multiply by gas factor
|
||||
let expected_fee =
|
||||
blockchain_config.gas_price_step.high * expected_gas as f64 * gas_factor;
|
||||
|
||||
let fee_coin = cosmrs::Coin {
|
||||
denom: relevant_denom.coin_minimal_denom.parse().unwrap(),
|
||||
amount: expected_fee as u128,
|
||||
};
|
||||
|
||||
let fee = Fee::from_amount_and_gas(fee_coin, expected_gas);
|
||||
|
||||
#[allow(clippy::identity_op)]
|
||||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"fee": remote_serde::Fee::from(&fee),
|
||||
// TODO: Body does not implement Serialize and
|
||||
// needs to be constructed in Sign
|
||||
"tx_messages": [msg_undelegate],
|
||||
// re-export, but in general this should be copied over
|
||||
// using workflows
|
||||
},
|
||||
"derivation_accounts": [0u32 | 1 << 31],
|
||||
}))
|
||||
}
|
||||
Operation::Transfer(Transfer {
|
||||
amount,
|
||||
denom,
|
||||
|
@ -486,11 +859,9 @@ impl Module for Cosmos {
|
|||
let expected_gas = 100_000u64;
|
||||
// convert gas "price" to minimum denom,
|
||||
// multiply by amount of gas required,
|
||||
// multiply by gas factor if necessary.
|
||||
let expected_fee = blockchain_config.gas_price_step.high
|
||||
// * dbg!(10f64.powi(relevant_denom.coin_decimals as i32))
|
||||
* expected_gas as f64
|
||||
* gas_factor;
|
||||
// multiply by gas factor
|
||||
let expected_fee =
|
||||
blockchain_config.gas_price_step.high * expected_gas as f64 * gas_factor;
|
||||
|
||||
let fee_coin = cosmrs::Coin {
|
||||
denom: relevant_denom.coin_minimal_denom.parse().unwrap(),
|
||||
|
@ -508,7 +879,6 @@ impl Module for Cosmos {
|
|||
"tx_messages": [msg_send],
|
||||
// re-export, but in general this should be copied over
|
||||
// using workflows
|
||||
"blockchain_config": blockchain_config,
|
||||
},
|
||||
"derivation_accounts": [0u32 | 1 << 31],
|
||||
}))
|
||||
|
@ -540,7 +910,6 @@ impl Module for Cosmos {
|
|||
Ok(serde_json::json!({
|
||||
"blob": {
|
||||
"transaction": signed_tx.to_bytes().unwrap(),
|
||||
"blockchain_config": blockchain_config,
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
name: stake
|
||||
inputs:
|
||||
- delegate_address
|
||||
- validator_address
|
||||
- chain_name
|
||||
- asset_name
|
||||
- asset_amount
|
||||
optional_inputs:
|
||||
- gas_factor
|
||||
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-stake
|
||||
inputs:
|
||||
delegate_address: delegate_address
|
||||
validator_address: validator_address
|
||||
amount: asset_amount
|
||||
denom: asset_name
|
||||
blockchain_config: blockchain_config
|
||||
gas_factor: gas_factor
|
||||
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
|
|
@ -0,0 +1,46 @@
|
|||
name: withdraw
|
||||
inputs:
|
||||
- delegate_address
|
||||
- validator_address
|
||||
- chain_name
|
||||
- asset_name
|
||||
- asset_amount
|
||||
optional_inputs:
|
||||
- gas_factor
|
||||
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-withdraw
|
||||
inputs:
|
||||
delegate_address: delegate_address
|
||||
validator_address: validator_address
|
||||
amount: asset_amount
|
||||
denom: asset_name
|
||||
blockchain_config: blockchain_config
|
||||
gas_factor: gas_factor
|
||||
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
|
Loading…
Reference in New Issue