├── .rustfmt.toml ├── terra-rust-api ├── .rustfmt.toml ├── resources │ ├── sc101.json │ ├── sc101.wasm │ ├── terraswap_pair.wasm │ ├── terraswap_token.wasm │ └── terraswap_token.json ├── src │ ├── keys.rs │ ├── client │ │ ├── bank.rs │ │ ├── lcd_types.rs │ │ ├── auth_types.rs │ │ ├── fcd.rs │ │ ├── oracle_types.rs │ │ ├── wasm_types.rs │ │ ├── auth.rs │ │ ├── oracle.rs │ │ ├── rpc.rs │ │ ├── rpc_types.rs │ │ ├── market.rs │ │ ├── staking.rs │ │ ├── tendermint.rs │ │ ├── staking_types.rs │ │ ├── tendermint_types.rs │ │ ├── wasm.rs │ │ └── tx.rs │ ├── messages │ │ ├── slashing.rs │ │ ├── market.rs │ │ ├── bank.rs │ │ ├── distribution.rs │ │ ├── staking.rs │ │ ├── oracle.rs │ │ └── wasm.rs │ ├── messages.rs │ ├── addressbook.rs │ ├── keys │ │ ├── signature.rs │ │ └── private.rs │ ├── errors.rs │ └── lib.rs ├── Cargo.toml ├── README.md └── Changelog.md ├── terra-rust-cli ├── .rustfmt.toml ├── src │ ├── lib.rs │ └── errors.rs ├── README.md ├── Changelog.md └── Cargo.toml ├── .gitattributes ├── terra-rust-wallet ├── .rustfmt.toml ├── Changelog.md ├── Cargo.toml ├── README.md └── src │ └── errors.rs ├── .gitignore ├── .env.default ├── src ├── fcd.rs ├── wasm.rs ├── wallet.rs ├── tendermint.rs ├── tx.rs ├── slashing.rs ├── bin │ ├── terra_query.rs │ └── terra_exec.rs ├── rpc.rs ├── bank.rs ├── contract.rs ├── auth.rs ├── oracle.rs ├── terra_rust.rs ├── validator.rs ├── market.rs ├── distribution.rs └── keys.rs ├── examples ├── pubkey.rs ├── sign_message.rs ├── do_swap.rs └── vstat.rs ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── README.md └── Smart-Contract-dev.md /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /terra-rust-api/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /terra-rust-cli/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.wasm binary 2 | -------------------------------------------------------------------------------- /terra-rust-wallet/.rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /terra-rust-api/resources/sc101.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 0 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.idea 3 | /*/target 4 | /*/Cargo.lock 5 | .DS_Store 6 | /*/.idea 7 | /.env -------------------------------------------------------------------------------- /terra-rust-cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cli_helpers; 2 | pub mod errors; 3 | 4 | //pub use cli::*; 5 | -------------------------------------------------------------------------------- /terra-rust-api/resources/sc101.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFC-Validator/terra-rust/HEAD/terra-rust-api/resources/sc101.wasm -------------------------------------------------------------------------------- /terra-rust-api/resources/terraswap_pair.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFC-Validator/terra-rust/HEAD/terra-rust-api/resources/terraswap_pair.wasm -------------------------------------------------------------------------------- /terra-rust-api/resources/terraswap_token.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PFC-Validator/terra-rust/HEAD/terra-rust-api/resources/terraswap_token.wasm -------------------------------------------------------------------------------- /terra-rust-api/src/keys.rs: -------------------------------------------------------------------------------- 1 | // mod address; 2 | // pub mod mnemonic_key; 3 | mod private; 4 | mod public; 5 | mod signature; 6 | 7 | pub use private::PrivateKey; 8 | 9 | pub use public::PublicKey; 10 | pub use signature::Signature; 11 | -------------------------------------------------------------------------------- /terra-rust-api/resources/terraswap_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "decimals": 6, 3 | "initial_balances": [], 4 | "mint": { 5 | "minter": "##SENDER##" 6 | }, 7 | "name": "TerraRust Example Token", 8 | "symbol": "TERRARUST" 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.env.default: -------------------------------------------------------------------------------- 1 | TERRARUST_WALLET=tequilla 2 | #TERRARUST_WALLET=production 3 | RUST_LOG=error 4 | TERRARUST_CHAIN=bombay-12 5 | TERRARUST_LCD=https://bombay-lcd.terra.dev 6 | TERRARUST_FCD=https://bombay-fcd.terra.dev 7 | #TERRARUST_LCD=http://127.0.0.1:1317 8 | TERRARUST_CONTRACT=terra1qdg78ja9xenjny6rn6nmtv2lj3u8jht0x3w5zd -------------------------------------------------------------------------------- /terra-rust-api/src/client/bank.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::Coin; 2 | use crate::{LCDResultVec, Terra}; 3 | 4 | pub struct Bank<'a> { 5 | terra: &'a Terra, 6 | } 7 | impl Bank<'_> { 8 | pub fn create(terra: &'_ Terra) -> Bank<'_> { 9 | Bank { terra } 10 | } 11 | pub async fn balances(&self, account_address: &str) -> anyhow::Result> { 12 | let response = self 13 | .terra 14 | .send_cmd::>(&format!("/bank/balances/{}", account_address), None) 15 | .await?; 16 | Ok(response) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/slashing.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::MsgInternal; 2 | 3 | use crate::messages::Message; 4 | use serde::Serialize; 5 | 6 | /// Unjail message 7 | #[derive(Serialize, Debug)] 8 | pub struct MsgUnjail { 9 | pub address: String, 10 | } 11 | impl MsgInternal for MsgUnjail {} 12 | impl MsgUnjail { 13 | pub fn create(address: String) -> anyhow::Result { 14 | let internal = MsgUnjail { address }; 15 | Ok(Message { 16 | s_type: "slashing/MsgUnjail".into(), 17 | value: serde_json::to_value(internal)?, 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages.rs: -------------------------------------------------------------------------------- 1 | /// Bank API Transactions 2 | pub mod bank; 3 | pub mod distribution; 4 | /// market messages 5 | pub mod market; 6 | /// Oracle API Transactions 7 | pub mod oracle; 8 | /// slashing messages 9 | pub mod slashing; 10 | /// messages around staking 11 | pub mod staking; 12 | /// regular contract interactions 13 | pub mod wasm; 14 | 15 | pub use bank::MsgSend; 16 | use serde::Serialize; 17 | pub use wasm::MsgExecuteContract; 18 | 19 | #[derive(Serialize)] 20 | /// Message: Send N coins from an address to another 21 | pub struct Message { 22 | #[allow(missing_docs)] 23 | #[serde(rename = "type")] 24 | s_type: String, 25 | #[allow(missing_docs)] 26 | value: serde_json::Value, 27 | } 28 | -------------------------------------------------------------------------------- /terra-rust-cli/README.md: -------------------------------------------------------------------------------- 1 | # Terra Rust CLI 2 | A command line helper for Terra-Rust 3 | 4 | most things need the same kind of arguments out of terra. So I put them in a library. 5 | 6 | # Help ? 7 | There is a [CLI](https://github.com/pfc-validator/terra-rust) that uses this, which may be helpful. 8 | We have also setup a [Discord](https://discord.gg/zKVWs4HhJD) channel to discuss this, and other PFC things 9 | 10 | If you think this was useful, feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. It will help defray the costs. 11 | 12 | [PFC](https://twitter.com/PFC_Validator) - Terra/Luna is Pretty Freaking Cool right... feel free to drop me a line 13 | -------------------------------------------------------------------------------- /terra-rust-wallet/Changelog.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | ## 1.1 3 | ### 1.1.5 - 15-Mar-21 4 | * Make Wallet Cloneable 5 | ### 1.1.4 - 17-Feb-21 6 | * Secp256k parameter passing 7 | * switch from anyhow::Result to TerraRustWalletError 8 | ### 1.1.2 9 | * error returns 10 | ### 1.1.1 11 | * terra-rust-api incompatible changes 12 | ## 1.0 13 | ### 1.0.2 - 1-Oct-2021 14 | * convenience function - wallet.get_account( ... ) gets the account associated with the stored key 15 | ## 0.1 16 | ### 0.1.5 - devel 17 | ### 0.1.4 - 24-Aug-2021 18 | * Upgrade to terra-rust-api 0.3 19 | ### 0.1.3 - 29-July-2021 20 | * Fix compilations on linux around KeyringError - PR#2/3 by [@snoberg](https://github.com/snoyberg) 21 | * new wallet returns error for keys list 22 | * switch to anyhow/thiserror -------------------------------------------------------------------------------- /src/fcd.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | /// FCD commands 5 | #[derive(Parser)] 6 | pub struct FCDCommand { 7 | #[clap(subcommand)] 8 | command: FCDEnum, 9 | } 10 | #[derive(Subcommand)] 11 | pub enum FCDEnum { 12 | #[clap(name = "gas-prices", about = "gas prices to use to calculate fees")] 13 | GasPrices, 14 | } 15 | impl FCDCommand { 16 | pub async fn parse(&self, terra: &Terra, fcd_url: &str) -> Result<()> { 17 | match &self.command { 18 | FCDEnum::GasPrices => { 19 | let resp = terra.fcd(fcd_url).gas_prices().await?; 20 | 21 | println!("{}", serde_json::to_string(&resp)?) 22 | } 23 | } 24 | Ok(()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/lcd_types.rs: -------------------------------------------------------------------------------- 1 | use crate::terra_u64_format; 2 | use serde::{Deserialize, Serialize}; 3 | #[derive(Deserialize, Serialize, Debug)] 4 | /// ALL interactions with LCD will return a LCD result 5 | pub struct LCDResult { 6 | /// height of chain 7 | #[serde(with = "terra_u64_format")] 8 | pub height: u64, 9 | /// the LCD response 10 | pub result: T, 11 | } 12 | #[derive(Deserialize, Serialize, Debug)] 13 | /// ALL interactions with LCD will return a LCD result 14 | pub struct LCDResultVec { 15 | /// height of chain 16 | #[serde(with = "terra_u64_format")] 17 | pub height: u64, 18 | /// the LCD response 19 | pub result: Vec, 20 | } 21 | #[allow(missing_docs)] 22 | #[derive(Deserialize, Serialize, Debug)] 23 | pub struct LCDTypeValue { 24 | #[serde(rename = "type")] 25 | pub stype: String, 26 | pub value: T, 27 | } 28 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/market.rs: -------------------------------------------------------------------------------- 1 | //use crate::client::client_types::terra_u64_format; 2 | use crate::core_types::{Coin, MsgInternal}; 3 | 4 | use crate::messages::Message; 5 | use serde::Serialize; 6 | 7 | #[derive(Serialize, Debug)] 8 | /// swap a coin, and send it to someone 9 | 10 | pub struct MsgSwap { 11 | pub ask_denom: String, 12 | pub offer_coin: Coin, 13 | /// to account 14 | pub trader: String, 15 | } 16 | 17 | impl MsgInternal for MsgSwap {} 18 | impl MsgSwap { 19 | /// swap a coin, and send it to someone 20 | pub fn create(offer_coin: Coin, ask_denom: String, trader: String) -> anyhow::Result { 21 | let internal = MsgSwap { 22 | ask_denom, 23 | offer_coin, 24 | trader, 25 | }; 26 | Ok(Message { 27 | s_type: "market/MsgSwap".into(), 28 | value: serde_json::to_value(internal)?, 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /terra-rust-wallet/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-rust-wallet" 3 | version = "1.1.5" 4 | authors = ["PFC-Validator "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description="Terra Rust Wallet API" 8 | readme = "README.md" 9 | homepage = "https://github.com/PFC-Validator/terra-rust/tree/main/terra-rust-wallet" 10 | repository = "https://github.com/PFC-Validator/terra-rust/" 11 | keywords = ["terra", "blockchain"] 12 | categories = ["api-bindings"] 13 | documentation = "https://docs.rs/terra-rust-wallet" 14 | 15 | [dependencies] 16 | thiserror = "1.0" 17 | anyhow="1.0" 18 | terra-rust-api = { path = "../terra-rust-api", version = "1.1" } 19 | secp256k1 = { version = "0.20.3", default-features = false } 20 | keyring = "1.1.2" 21 | #keyring = "0.10.1" 22 | log="0.4" 23 | serde_json = "1.0" 24 | serde = { version = "1.0", features = ["derive"] } 25 | 26 | reqwest = { version ="0.11", features=["json"], default-features = false } -------------------------------------------------------------------------------- /src/wasm.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | 5 | use serde_json::Value; 6 | 7 | #[derive(Subcommand)] 8 | enum WasmEnum { 9 | #[clap(name = "query", about = "exec a query against a smart contract")] 10 | Query { 11 | /// the contract address 12 | contract: String, 13 | /// the json to send 14 | json: String, 15 | }, 16 | } 17 | /// WASM commands 18 | #[derive(Parser)] 19 | pub struct WasmCommand { 20 | #[clap(subcommand)] 21 | command: WasmEnum, 22 | } 23 | impl WasmCommand { 24 | pub async fn parse(self, terra: &Terra) -> Result<()> { 25 | match self.command { 26 | WasmEnum::Query { contract, json } => { 27 | let resp = terra.wasm().query::(&contract, &json).await?; 28 | println!("{}", serde_json::to_string_pretty(&resp)?); 29 | } 30 | }; 31 | Ok(()) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/auth_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::{terra_opt_u64_format, terra_u64_format}; 2 | 3 | use crate::core_types::PubKeySig; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | /// This structure serves a few purposes 7 | /// one.. to get the public key (which can be used to validate our private key calcs 8 | /// two.. the account number and sequence fields that are used to generate a signed message 9 | #[derive(Deserialize, Serialize, Debug, Clone)] 10 | pub struct AuthAccount { 11 | /// The account address 12 | pub address: String, 13 | /// The balance in the account. Does not include delegated coins 14 | pub public_key: Option, 15 | #[serde(with = "terra_u64_format")] 16 | /// The account number 17 | pub account_number: u64, 18 | /// The sequence. This is used to avoid 'double transmitting' a transaction 19 | #[serde(default, with = "terra_opt_u64_format")] 20 | pub sequence: Option, 21 | } 22 | -------------------------------------------------------------------------------- /terra-rust-wallet/README.md: -------------------------------------------------------------------------------- 1 | # Terra Rust Wallet 2 | A rust wallet manager for Terra-Rust 3 | 4 | # Warning 5 | This is a WIP. 6 | 7 | No security audit has been performed. 8 | 9 | # About 10 | This implements a key storage mechanism, which uses the keyring API which is cross-platform. 11 | 12 | ## Disclaimer 13 | 14 | This may steal your money. 15 | 16 | This is not investment advice. 17 | 18 | Do your own research 19 | 20 | # Linux 21 | $ sudo apt-get install libdbus-1-dev 22 | 23 | # Help ? 24 | There is a [CLI](https://github.com/pfc-validator/terra-rust) that uses this, which may be helpful. 25 | We have also setup a [Discord](https://discord.gg/zKVWs4HhJD) channel to discuss this, and other PFC things 26 | 27 | If you think this was useful, feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. It will help defray the costs. 28 | 29 | [PFC](https://twitter.com/PFC_Validator) - Terra/Luna is Pretty Freaking Cool right... feel free to drop me a line 30 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/fcd.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::TerraRustAPIError; 2 | use crate::Terra; 3 | use rust_decimal::Decimal; 4 | use std::collections::HashMap; 5 | 6 | pub struct FCD<'a> { 7 | terra: &'a Terra, 8 | fcd_url: &'a str, 9 | } 10 | impl FCD<'_> { 11 | pub fn create<'a>(terra: &'a Terra, fcd_url: &'a str) -> FCD<'a> { 12 | FCD { terra, fcd_url } 13 | } 14 | pub async fn gas_prices(&self) -> Result, TerraRustAPIError> { 15 | Ok(self 16 | .terra 17 | .send_cmd_url::>(self.fcd_url, "/v1/txs/gas_prices", None) 18 | .await?) 19 | } 20 | pub async fn fetch_gas_prices( 21 | client: &reqwest::Client, 22 | fcd_url: &str, 23 | ) -> Result, TerraRustAPIError> { 24 | Ok(Terra::fetch_url::>( 25 | client, 26 | fcd_url, 27 | "/v1/txs/gas_prices", 28 | None, 29 | ) 30 | .await?) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /terra-rust-cli/src/errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use std::num::{ParseFloatError, ParseIntError}; 4 | use terra_rust_api::errors::TerraRustAPIError; 5 | use terra_rust_wallet::errors::TerraRustWalletError; 6 | use thiserror::Error; 7 | 8 | #[derive(Error, Debug)] 9 | pub enum TerraRustCLIError { 10 | #[error("Bad Implementation. Missing CLI Argument {0}")] 11 | MissingArgument(String), 12 | #[error("IO Error")] 13 | IOErr(#[from] ::std::io::Error), 14 | #[error("Number Float Error")] 15 | NumberFloatErr(#[from] ParseFloatError), 16 | #[error("Number Int Error")] 17 | NumberIntErr(#[from] ParseIntError), 18 | #[error(transparent)] 19 | TerraRustAPIError(#[from] TerraRustAPIError), 20 | #[error(transparent)] 21 | TerraRustWalletError(#[from] TerraRustWalletError), 22 | #[error(transparent)] 23 | SerdeJson(#[from] ::serde_json::Error), 24 | #[error(transparent)] 25 | Regex(#[from] ::regex::Error), 26 | #[error("missing environment variable {0}")] 27 | MissingEnv(String), 28 | } 29 | -------------------------------------------------------------------------------- /terra-rust-cli/Changelog.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | ## 1.0 3 | ### 1.0.13 - 16-Mar-2002 4 | * expand block now can expand from a list of variables 'V:xxx' passed into command. optionally ignoring the failure (so we can multipass) 5 | ### 1.0.11 - 15-Mar-2022 6 | * seed_from_args / wallet_opt_from_args functions 7 | * expand block now expands A:key and O:key to account/operator address for key in wallet 8 | ### 1.0.9 - 15-Mar-2022 9 | * ###SENDER### will only be replaced if sender is specified. Tighten Regex 10 | ### 1.0.7 - 13-Mar-2022 11 | * new functions 'get_json_block_expanded/expand_block' replaces ###E:xxx### with environment variable xxx & ###SENDER### with sender 12 | ### 1.0.6 - 25-Feb-2022 13 | * additional helpers for reading private key 14 | ### 1.0.4 - 21-Feb-2022 15 | * remove depreciation warnings 16 | ### 1.0.3 - 20-Feb-2022 17 | * get_json_block - reads stdin/file/or json passed as an argument 18 | ### 1.0.2 - 16-Feb-2022 19 | * error returns 20 | ### 1.0.1 21 | * add more helpers 22 | ### 1.0.0 - 14-Feb-2022 23 | * moved to own library to help other utilities -------------------------------------------------------------------------------- /terra-rust-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-rust-cli" 3 | version = "1.0.13" 4 | authors = ["PFC-Validator "] 5 | edition = "2021" 6 | license = "Apache-2.0" 7 | description="Terra Rust CLI parser" 8 | readme = "README.md" 9 | homepage = "https://github.com/PFC-Validator/terra-rust/tree/main/terra-rust-cli" 10 | repository = "https://github.com/PFC-Validator/terra-rust/" 11 | keywords = ["terra", "blockchain"] 12 | categories = ["api-bindings"] 13 | documentation = "https://docs.rs/terra-rust-cli" 14 | 15 | [dependencies] 16 | anyhow="1.0" 17 | thiserror = "1.0" 18 | terra-rust-api = { path = "../terra-rust-api", version = "1.2.2" } 19 | terra-rust-wallet = { path = "../terra-rust-wallet", version = "1.1.1" } 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_json = "1.0.79" 22 | secp256k1 = { version = "0.20.3", default-features = false } 23 | log="0.4" 24 | 25 | reqwest = { version ="0.11", features=["json"], default-features = false } 26 | clap={version="3.0.14", features=["env","derive"]} 27 | lazy_static = "1.4.0" 28 | regex = "1.5.4" -------------------------------------------------------------------------------- /terra-rust-api/src/addressbook.rs: -------------------------------------------------------------------------------- 1 | use crate::terra_datetime_format; 2 | use chrono::{DateTime, Utc}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[derive(Deserialize, Serialize, Debug, Clone, Hash, PartialEq, Eq)] 6 | pub struct NodeIDIPPort { 7 | pub id: String, 8 | pub ip: String, 9 | pub port: usize, 10 | } 11 | impl ToString for NodeIDIPPort { 12 | fn to_string(&self) -> String { 13 | format!("{}@{}:{}", self.id, self.ip, self.port) 14 | } 15 | } 16 | 17 | #[derive(Deserialize, Serialize, Debug, Clone)] 18 | pub struct NodeAddr { 19 | pub addr: NodeIDIPPort, 20 | pub src: NodeIDIPPort, 21 | pub buckets: Vec, 22 | pub attempts: usize, 23 | #[serde(with = "terra_datetime_format")] 24 | pub last_attempt: DateTime, 25 | #[serde(with = "terra_datetime_format")] 26 | pub last_success: DateTime, 27 | #[serde(with = "terra_datetime_format")] 28 | pub last_ban_time: DateTime, 29 | } 30 | 31 | #[derive(Deserialize, Serialize, Debug)] 32 | pub struct AddressBook { 33 | pub key: String, 34 | pub addrs: Vec, 35 | } 36 | -------------------------------------------------------------------------------- /examples/pubkey.rs: -------------------------------------------------------------------------------- 1 | use dotenv::dotenv; 2 | use secp256k1::Secp256k1; 3 | 4 | use terra_rust_cli::cli_helpers; 5 | 6 | async fn run() -> anyhow::Result<()> { 7 | let cli = cli_helpers::gen_cli("pubkey", "pubkey"); 8 | let matches = cli.get_matches(); 9 | let secp = Secp256k1::new(); 10 | let private_key = cli_helpers::get_private_key(&secp, &matches)?; 11 | 12 | let msg = "mary had a little lamb. I ate, coz lamb is delicious"; 13 | // let signature = from_key.sign(&secp, &cli.message)?; 14 | let signature = private_key.sign(&secp, &msg)?; 15 | 16 | println!("PubKeySig={}", signature.pub_key.value); 17 | 18 | Ok(()) 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | dotenv().ok(); 24 | env_logger::init(); 25 | 26 | if let Err(ref err) = run().await { 27 | log::error!("{}", err); 28 | err.chain() 29 | .skip(1) 30 | .for_each(|cause| log::error!("because: {}", cause)); 31 | 32 | // The backtrace is not always generated. Try to run this example 33 | // with `$env:RUST_BACKTRACE=1`. 34 | // if let Some(backtrace) = e.backtrace() { 35 | // log::debug!("backtrace: {:?}", backtrace); 36 | // } 37 | 38 | ::std::process::exit(1); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/bank.rs: -------------------------------------------------------------------------------- 1 | //use crate::client::client_types::terra_u64_format; 2 | use crate::core_types::{Coin, MsgInternal}; 3 | 4 | use crate::errors::TerraRustAPIError; 5 | use crate::messages::Message; 6 | use serde::Serialize; 7 | 8 | #[derive(Serialize, Debug)] 9 | /// Message: Send N coins from an address to another 10 | 11 | pub struct MsgSend { 12 | pub amount: Vec, 13 | pub from_address: String, 14 | pub to_address: String, 15 | } 16 | 17 | impl MsgInternal for MsgSend {} 18 | impl MsgSend { 19 | /// Send amount coins from from_address to to_address 20 | pub fn create_single( 21 | from_address: String, 22 | to_address: String, 23 | amount: Coin, 24 | ) -> Result { 25 | MsgSend::create(from_address, to_address, vec![amount]) 26 | } 27 | /// send multiple coins from from_address to to_address 28 | pub fn create( 29 | from_address: String, 30 | to_address: String, 31 | amount: Vec, 32 | ) -> Result { 33 | let internal = MsgSend { 34 | amount, 35 | from_address, 36 | to_address, 37 | }; 38 | Ok(Message { 39 | s_type: "bank/MsgSend".into(), 40 | value: serde_json::to_value(internal)?, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /terra-rust-wallet/src/errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | #![allow(missing_docs)] 3 | use terra_rust_api::errors::TerraRustAPIError; 4 | use thiserror::Error; 5 | 6 | #[derive(Error, Debug)] 7 | pub enum TerraRustWalletError { 8 | #[error(transparent)] 9 | KeyringError(#[from] KeyringErrorAdapter), 10 | #[error(transparent)] 11 | SerdeJsonError(#[from] ::serde_json::Error), 12 | #[error(transparent)] 13 | KeyringErrorAdapter(#[from] ::keyring::Error), 14 | #[error(transparent)] 15 | TerraRustAPIError(#[from] TerraRustAPIError), 16 | #[error("Terra Wallet `{key}` Key not found Error")] 17 | KeyNotFound { 18 | key: String, 19 | source: KeyringErrorAdapter, 20 | }, 21 | 22 | #[error("unknown Terra-Rust Wallet error")] 23 | Unknown, 24 | } 25 | 26 | /// Workaround type to provide [Sync] on Linux. 27 | /// 28 | /// On Linux, [keyring::KeyringError] does not implement `Sync` due to depending 29 | /// on an older version of the `dbus` crate. This prevents usage of `anyhow`. This 30 | /// wrapper is used to bypass that issue on Linux. 31 | #[derive(Error, Debug)] 32 | #[error(transparent)] 33 | pub struct KeyringErrorAdapter(anyhow::Error); 34 | 35 | impl From for KeyringErrorAdapter { 36 | fn from(e: keyring::Error) -> Self { 37 | KeyringErrorAdapter(anyhow::anyhow!("Keyring error: {:?}", e)) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/distribution.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::MsgInternal; 2 | use crate::messages::Message; 3 | use serde::Serialize; 4 | 5 | /// withdraw commission from a validator 6 | #[derive(Serialize, Debug)] 7 | pub struct MsgWithdrawValidatorCommission { 8 | pub validator_address: String, 9 | } 10 | impl MsgInternal for MsgWithdrawValidatorCommission {} 11 | impl MsgWithdrawValidatorCommission { 12 | pub fn create(validator_address: String) -> anyhow::Result { 13 | let internal = MsgWithdrawValidatorCommission { validator_address }; 14 | Ok(Message { 15 | s_type: "distribution/MsgWithdrawValidatorCommission".into(), 16 | value: serde_json::to_value(internal)?, 17 | }) 18 | } 19 | } 20 | /// withdraw reward from a validator 21 | #[derive(Serialize, Debug)] 22 | pub struct MsgWithdrawDelegationReward { 23 | pub delegator_address: String, 24 | pub validator_address: String, 25 | } 26 | impl MsgInternal for MsgWithdrawDelegationReward {} 27 | impl MsgWithdrawDelegationReward { 28 | pub fn create(delegator_address: String, validator_address: String) -> anyhow::Result { 29 | let internal = MsgWithdrawDelegationReward { 30 | delegator_address, 31 | validator_address, 32 | }; 33 | Ok(Message { 34 | s_type: "distribution/MsgWithdrawDelegationReward".into(), 35 | value: serde_json::to_value(internal)?, 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/oracle_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::{terra_decimal_format, terra_f64_format, terra_u64_format}; 2 | 3 | use rust_decimal::Decimal; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | #[derive(Deserialize, Serialize, Debug)] 7 | pub struct OracleParameterWhiteList { 8 | pub name: String, 9 | #[serde(with = "terra_f64_format")] 10 | pub tobin_tax: f64, 11 | } 12 | #[derive(Deserialize, Serialize, Debug)] 13 | pub struct OracleParameters { 14 | #[serde(with = "terra_u64_format")] 15 | pub vote_period: u64, 16 | #[serde(with = "terra_f64_format")] 17 | pub vote_threshold: f64, 18 | #[serde(with = "terra_f64_format")] 19 | pub reward_band: f64, 20 | #[serde(with = "terra_u64_format")] 21 | pub reward_distribution_window: u64, 22 | pub whitelist: Vec, 23 | #[serde(with = "terra_f64_format")] 24 | pub slash_fraction: f64, 25 | #[serde(with = "terra_u64_format")] 26 | pub slash_window: u64, 27 | #[serde(with = "terra_f64_format")] 28 | pub min_valid_per_window: f64, 29 | } 30 | 31 | #[derive(Deserialize, Serialize, Debug)] 32 | pub struct OracleVotes { 33 | #[serde(with = "terra_decimal_format")] 34 | pub exchange_rate: Decimal, 35 | pub denom: String, 36 | pub voter: String, 37 | } 38 | 39 | #[derive(Deserialize, Serialize, Debug)] 40 | pub struct OraclePreVotes { 41 | pub hash: String, 42 | pub denom: String, 43 | pub voter: String, 44 | #[serde(with = "terra_u64_format")] 45 | pub submit_block: u64, 46 | } 47 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: [main] 4 | pull_request: 5 | 6 | name: Continuous integration 7 | 8 | jobs: 9 | ci: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | os: 15 | - ubuntu-latest 16 | - macos-latest 17 | - windows-latest 18 | rust: 19 | - stable 20 | - nightly 21 | #- 1.53.0 22 | 23 | steps: 24 | - name: Install deps 25 | if: matrix.os == 'ubuntu-latest' 26 | run: sudo apt-get install -y libdbus-1-dev 27 | 28 | - uses: actions/checkout@v2 29 | 30 | - uses: actions-rs/toolchain@v1 31 | with: 32 | profile: minimal 33 | toolchain: ${{ matrix.rust }} 34 | override: true 35 | components: rustfmt, clippy 36 | 37 | - uses: Swatinem/rust-cache@v1 38 | with: 39 | key: ${{ runner.os }}-${{ matrix.rust }}-${{ hashFiles('Cargo.lock') }} 40 | 41 | - uses: actions-rs/cargo@v1 42 | name: Build 43 | with: 44 | command: build 45 | 46 | - uses: actions-rs/cargo@v1 47 | name: Test 48 | with: 49 | command: test 50 | 51 | - uses: actions-rs/cargo@v1 52 | name: Format 53 | with: 54 | command: fmt 55 | args: --all -- --check 56 | 57 | - uses: actions-rs/cargo@v1 58 | name: Clippy 59 | if: matrix.rust == 'stable' 60 | with: 61 | command: clippy 62 | args: -- -D warnings 63 | -------------------------------------------------------------------------------- /src/wallet.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_wallet::Wallet; 4 | #[derive(Parser)] 5 | /// wallet ops 6 | pub struct WalletCommand { 7 | #[clap(subcommand)] 8 | command: WalletEnum, 9 | } 10 | #[derive(Subcommand)] 11 | enum WalletEnum { 12 | /// create a wallet 13 | #[clap(name = "create")] 14 | Create { 15 | /// name of the wallet 16 | #[clap(name = "name")] 17 | name: String, 18 | }, 19 | /// list available wallets 20 | #[clap(name = "list")] 21 | List, 22 | /// delete a wallet 23 | #[clap(name = "delete")] 24 | Delete { 25 | /// name of wallet 26 | #[clap(name = "name")] 27 | name: String, 28 | }, 29 | } 30 | impl WalletCommand { 31 | pub fn parse(self, wallet: &Wallet) -> Result<()> { 32 | match self.command { 33 | WalletEnum::Create { name } => { 34 | Wallet::new(&name)?; 35 | println!("Wallet {} created", name); 36 | Ok(()) 37 | } 38 | WalletEnum::List => { 39 | let wallets = Wallet::get_wallets()?; 40 | println!("{:#?}", wallets); 41 | Ok(()) 42 | } 43 | WalletEnum::Delete { name } => { 44 | if name.eq(&wallet.name) { 45 | wallet.delete()?; 46 | Ok(()) 47 | } else { 48 | eprintln!("you will need to specify the wallet as a --wallet parameter as well as the wallet name"); 49 | eprintln!("Wallet name is currently set to {}", &wallet.name); 50 | Ok(()) 51 | } 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/tendermint.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use terra_rust_api::Terra; 4 | 5 | /// Block commands 6 | #[derive(Parser)] 7 | pub struct BlockCommand { 8 | #[clap(name = "height", default_value = "latest", help = "height (optional)")] 9 | height: String, 10 | } 11 | impl BlockCommand { 12 | pub async fn parse(self, terra: &Terra) -> Result<()> { 13 | let block = if self.height.to_lowercase().trim() == "latest" { 14 | terra.tendermint().blocks().await 15 | } else { 16 | let height: u64 = self.height.parse::()?; 17 | terra.tendermint().blocks_at_height(height).await 18 | }?; 19 | println!("{:#?}", block); 20 | Ok(()) 21 | } 22 | } 23 | /// Tendermint ValidatorSets commands 24 | #[derive(Parser)] 25 | pub struct ValidatorSetsCommand { 26 | #[clap(name = "height", default_value = "latest", help = "height (optional)")] 27 | height: String, 28 | #[clap(name = "page", default_value = "0", help = "page (optional)")] 29 | page: usize, 30 | #[clap(name = "limit", default_value = "9999", help = "limit (optional)")] 31 | limit: usize, 32 | } 33 | impl ValidatorSetsCommand { 34 | pub async fn parse(self, terra: &Terra) -> Result<()> { 35 | let vset = if self.height.to_lowercase().trim() == "latest" { 36 | terra 37 | .tendermint() 38 | .validatorsets(self.page, self.limit) 39 | .await 40 | } else { 41 | let height: u64 = self.height.parse::()?; 42 | terra 43 | .tendermint() 44 | .validatorsets_at_height(height, self.page, self.limit) 45 | .await 46 | }?; 47 | println!("{:#?}", vset); 48 | Ok(()) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/tx.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use terra_rust_api::errors::TerraRustAPIError; 3 | use terra_rust_api::Terra; 4 | 5 | /// Input to the /txs/XXXX query 6 | #[derive(Subcommand)] 7 | enum TxEnum { 8 | #[clap(name = "hash", about = "look up TX by hash")] 9 | Hash { 10 | #[clap(name = "hash", help = "hash to inquire about")] 11 | /// The hash to inquire about 12 | hash: String, 13 | }, 14 | #[clap(name = "block", about = "look up TXs in a block")] 15 | Block { 16 | #[clap(name = "height", help = "block height to inquire about")] 17 | height: u64, 18 | offset: Option, 19 | limit: Option, 20 | }, 21 | } 22 | #[derive(Parser)] 23 | /// Transaction Commands 24 | pub struct TxCommand { 25 | #[clap(subcommand)] 26 | command: TxEnum, 27 | } 28 | impl TxCommand { 29 | pub async fn parse(self, terra: &Terra) -> anyhow::Result<()> { 30 | match self.command { 31 | TxEnum::Hash { hash } => { 32 | let tx_r = terra.tx().get_v1(&hash).await; 33 | match tx_r { 34 | Ok(tx) => println!("{}", serde_json::to_string_pretty(&tx)?), 35 | Err(e) => match e { 36 | TerraRustAPIError::TerraLCDResponse(s, x) => println!("{}/{}", s, x), 37 | _ => println!("{:?}", e), 38 | }, 39 | } 40 | } 41 | TxEnum::Block { 42 | height, 43 | offset, 44 | limit, 45 | } => { 46 | let txs = terra.tx().get_txs_in_block(height, offset, limit).await?; 47 | println!("{}", serde_json::to_string_pretty(&txs)?); 48 | } 49 | } 50 | Ok(()) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /terra-rust-api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-rust-api" 3 | version = "1.2.20" 4 | authors = ["PFC-Validator "] 5 | edition = "2018" 6 | license = "Apache-2.0" 7 | description="Terra Rust API" 8 | readme = "README.md" 9 | homepage = "https://github.com/PFC-Validator/terra-rust/tree/main/terra-rust-api" 10 | repository = "https://github.com/PFC-Validator/terra-rust/" 11 | keywords = ["terra", "blockchain"] 12 | categories = ["api-bindings"] 13 | documentation = "https://docs.rs/terra-rust-api" 14 | 15 | [features] 16 | default = ["native-tls"] 17 | native-tls = ["reqwest/native-tls"] 18 | rustls-tls = ["reqwest/rustls-tls"] 19 | 20 | [dependencies] 21 | thiserror = "1.0" 22 | anyhow="1.0" 23 | reqwest = { version ="0.11", features=["json"], default-features = false } 24 | serde_json = "1.0" 25 | serde = { version = "1.0", features = ["derive"] } 26 | #rustc-serialize="0.3.24" 27 | erased-serde = "0.3" 28 | chrono= "0.4" 29 | hex="0.4.3" 30 | rust-crypto = "^0.2" 31 | log="0.4.14" 32 | rand_core = { version = "0.5", default-features = false } 33 | hkd32= { version="0.5.0", features=["bip39","mnemonic","bech32"] } 34 | subtle-encoding="0.5.1" 35 | bitcoin="0.27.1" 36 | secp256k1 = { version = "0.20.3", default-features = false } 37 | regex="1" 38 | lazy_static="1.4" 39 | num-traits="0.2" 40 | rust_decimal="1.12.2" 41 | rust_decimal_macros="1.12.2" 42 | futures="0.3.14" 43 | # https://crates.io/crates/prost-amino 44 | # tendermint = { version = "0.19", features = ["secp256k1"] } 45 | # ed25519 is requirement for Tendermint Consensus keys. 46 | # so far the only need for this is in the message to create a validator 47 | ed25519 = "1" 48 | ed25519-dalek = { version = "1", features = ["serde"] } 49 | #tendermint = "0.21.0.0" 50 | base64 = "0.13.0" 51 | tokio = { version = "1.14", features = ["full"] } 52 | [dev-dependencies] 53 | 54 | env_logger = "0.8.3" 55 | dotenv="0.15.0" 56 | -------------------------------------------------------------------------------- /src/slashing.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | 5 | use secp256k1::Secp256k1; 6 | use terra_rust_api::messages::Message; 7 | 8 | use crate::{NAME, VERSION}; 9 | use terra_rust_api::messages::slashing::MsgUnjail; 10 | use terra_rust_wallet::Wallet; 11 | 12 | #[derive(Subcommand)] 13 | enum SlashingEnum { 14 | #[clap(name = "unjail", about = "unjail a validator")] 15 | UnJail { 16 | /// validator account (specify the key name in the wallet) 17 | validator: String, 18 | }, 19 | } 20 | 21 | /// Slashing Commands 22 | #[derive(Parser)] 23 | pub struct SlashingCommand { 24 | #[clap(subcommand)] 25 | command: SlashingEnum, 26 | } 27 | impl SlashingCommand { 28 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 29 | match self.command { 30 | SlashingEnum::UnJail { validator } => { 31 | let secp = Secp256k1::new(); 32 | let from_key = wallet.get_private_key(&secp, &validator, seed)?; 33 | let from_public_key = from_key.public_key(&secp); 34 | 35 | let from_account = from_public_key.operator_address()?; 36 | let un_jail = MsgUnjail::create(from_account)?; 37 | 38 | let messages: Vec = vec![un_jail]; 39 | let resp = terra 40 | .submit_transaction_sync( 41 | &secp, 42 | &from_key, 43 | messages, 44 | Some(format!( 45 | "PFC-{}/{}", 46 | NAME.unwrap_or("TERRARUST"), 47 | VERSION.unwrap_or("DEV") 48 | )), 49 | ) 50 | .await?; 51 | 52 | println!("{}", resp.txhash); 53 | log::info!("{}", resp.raw_log); 54 | } 55 | }; 56 | Ok(()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /terra-rust-api/src/keys/signature.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::TerraRustAPIError; 2 | use crypto::sha2::Sha256; 3 | use secp256k1::Message; 4 | use secp256k1::Secp256k1; 5 | 6 | use crypto::digest::Digest; 7 | 8 | pub struct Signature {} 9 | impl Signature { 10 | pub fn verify( 11 | secp: &Secp256k1, 12 | pub_key: &str, 13 | signature: &str, 14 | blob: &str, 15 | ) -> Result<(), TerraRustAPIError> { 16 | let public = base64::decode(pub_key)?; 17 | let sig = base64::decode(signature)?; 18 | let pk = secp256k1::PublicKey::from_slice(public.as_slice())?; 19 | let mut sha = Sha256::new(); 20 | let mut sha_result: [u8; 32] = [0; 32]; 21 | sha.input_str(blob); 22 | sha.result(&mut sha_result); 23 | 24 | let message: Message = Message::from_slice(&sha_result)?; 25 | let secp_sig = secp256k1::Signature::from_compact(sig.as_slice())?; 26 | secp.verify(&message, &secp_sig, &pk)?; 27 | Ok(()) 28 | } 29 | } 30 | #[cfg(test)] 31 | mod tst { 32 | use super::*; 33 | #[allow(unused_imports)] 34 | use dotenv::dotenv; 35 | #[allow(unused_imports)] 36 | use env_logger; 37 | #[test] 38 | pub fn test_verify() -> anyhow::Result<()> { 39 | let secp = Secp256k1::new(); 40 | 41 | let message = r#"{"account_number":"45","chain_id":"columbus-3-testnet","fee":{"amount":[{"amount":"698","denom":"uluna"}],"gas":"46467"},"memo":"","msgs":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"100000000","denom":"uluna"}],"from_address":"terra1n3g37dsdlv7ryqftlkef8mhgqj4ny7p8v78lg7","to_address":"terra1wg2mlrxdmnnkkykgqg4znky86nyrtc45q336yv"}}],"sequence":"0"}"#; 42 | let signature = "FJKAXRxNB5ruqukhVqZf3S/muZEUmZD10fVmWycdVIxVWiCXXFsUy2VY2jINEOUGNwfrqEZsT2dUfAvWj8obLg=="; 43 | let pub_key = "AiMzHaA2bvnDXfHzkjMM+vkSE/p0ymBtAFKUnUtQAeXe"; 44 | Signature::verify(&secp, pub_key, signature, message)?; 45 | Ok(()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/wasm_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::terra_u64_format; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | #[allow(missing_docs)] 6 | #[derive(Deserialize, Serialize, Debug)] 7 | pub struct WasmCode { 8 | #[serde(with = "terra_u64_format")] 9 | pub code_id: u64, 10 | pub code_hash: String, 11 | pub creator: String, 12 | } 13 | #[allow(missing_docs)] 14 | #[derive(Deserialize, Serialize, Debug)] 15 | pub struct WasmCodeResult { 16 | #[serde(with = "terra_u64_format")] 17 | pub height: u64, 18 | pub result: WasmCode, 19 | } 20 | #[allow(missing_docs)] 21 | #[derive(Deserialize, Serialize, Debug)] 22 | pub struct WasmContractInfo { 23 | pub address: String, 24 | pub owner: String, 25 | #[serde(with = "terra_u64_format")] 26 | pub code_id: u64, 27 | pub init_msg: String, 28 | pub migratable: bool, 29 | } 30 | #[allow(missing_docs)] 31 | #[derive(Deserialize, Serialize, Debug)] 32 | pub struct WasmContractInfoResult { 33 | #[serde(with = "terra_u64_format")] 34 | pub height: u64, 35 | pub result: WasmContractInfo, 36 | } 37 | #[allow(missing_docs)] 38 | #[derive(Deserialize, Serialize, Debug)] 39 | pub struct WasmParameter { 40 | #[serde(with = "terra_u64_format")] 41 | pub max_contract_size: u64, 42 | #[serde(with = "terra_u64_format")] 43 | pub max_contract_gas: u64, 44 | #[serde(with = "terra_u64_format")] 45 | pub max_contract_msg_size: u64, 46 | } 47 | #[allow(missing_docs)] 48 | #[derive(Deserialize, Serialize, Debug)] 49 | pub struct WasmParameterResult { 50 | #[serde(with = "terra_u64_format")] 51 | pub height: u64, 52 | pub result: WasmParameter, 53 | } 54 | #[allow(missing_docs)] 55 | #[derive(Deserialize, Serialize, Debug)] 56 | pub struct WasmQueryRaw { 57 | pub key: String, 58 | pub value: String, 59 | } 60 | #[allow(missing_docs)] 61 | #[derive(Deserialize, Serialize, Debug)] 62 | pub struct WasmQueryRawResult { 63 | #[serde(with = "terra_u64_format")] 64 | pub height: u64, 65 | pub result: WasmQueryRaw, 66 | } 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "terra-rust" 3 | version = "1.1.9-alpha.0" 4 | authors = ["PFC-Validator "] 5 | edition = "2018" 6 | readme = "README.md" 7 | homepage = "https://github.com/PFC-Validator/terra-rust/" 8 | repository = "https://github.com/PFC-Validator/terra-rust/" 9 | keywords = ["terra", "blockchain"] 10 | categories = ["command-line-utilities"] 11 | license = "Apache-2.0" 12 | description="CLI for Terra blockchain network, that is multi-platform" 13 | default-run="terra-rust" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | [workspace] 17 | members = [ 18 | "terra-rust-api", 19 | "terra-rust-wallet", 20 | "terra-rust-cli", 21 | ] 22 | exclude = [ 23 | ] 24 | 25 | [features] 26 | default = ["native-tls"] 27 | native-tls = ["terra-rust-api/native-tls"] 28 | rustls-tls = ["terra-rust-api/rustls-tls"] 29 | 30 | 31 | [[bin]] 32 | name="terra-rust" 33 | path= "src/terra_rust.rs" 34 | 35 | [[bin]] 36 | name="terra-exec" 37 | path= "src/bin/terra_exec.rs" 38 | 39 | [[bin]] 40 | name="terra-query" 41 | path= "src/bin/terra_query.rs" 42 | 43 | [[bin]] 44 | name="cargo-terra" 45 | path="src/bin/cargo_terra.rs" 46 | 47 | [dependencies] 48 | terra-rust-api = { path = "./terra-rust-api", version = "1.2", default-features = false } 49 | terra-rust-wallet = { path = "./terra-rust-wallet", version = "1.1" } 50 | terra-rust-cli = { path = "./terra-rust-cli", version = "1.0.6" } 51 | anyhow="1.0" 52 | tokio = { version = "1.14.0", features = ["full"] } 53 | clap = { version = "3.0.14", features = ["derive","env"] } 54 | env_logger = "0.8.3" 55 | log="0.4" 56 | serde = { version = "1.0", features = ["derive"] } 57 | dotenv="0.15.0" 58 | # held back by our use of bitcoin-27. bitcoin-28 upgrades this 59 | secp256k1 = { version = "0.20.3", default-features = false } 60 | serde_json = "1.0" 61 | rust_decimal="1.12.2" 62 | rust_decimal_macros="1.12.2" 63 | futures="0.3.14" 64 | # required for example sign_message 65 | base64='0.13.0' 66 | reqwest = { version ="0.11", features=["json"], default-features = false } 67 | -------------------------------------------------------------------------------- /src/bin/terra_query.rs: -------------------------------------------------------------------------------- 1 | use dotenv::dotenv; 2 | 3 | use clap::Arg; 4 | use terra_rust_cli::cli_helpers; 5 | 6 | /// VERSION number of package 7 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 8 | /// NAME of package 9 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 10 | 11 | async fn run() -> anyhow::Result<()> { 12 | let cli = cli_helpers::gen_cli_read_only("terra query", "terra-query") 13 | .arg( 14 | Arg::new("contract") 15 | .long("contract") 16 | .value_name("contract") 17 | .takes_value(true) 18 | .env("TERRARUST_CONTRACT") 19 | .help("the contract address"), 20 | ) 21 | .arg(Arg::new("json").takes_value(true).value_name("json")) 22 | .get_matches(); 23 | let terra = cli_helpers::lcd_no_tx_from_args(&cli)?; 24 | let contract = cli_helpers::get_arg_value(&cli, "contract")?; 25 | let json_str = cli_helpers::get_arg_value(&cli, "json")?; 26 | let secp = secp256k1::Secp256k1::new(); 27 | let wallet = cli_helpers::wallet_opt_from_args(&cli); 28 | let seed = cli_helpers::seed_from_args(&cli); 29 | let json: serde_json::Value = 30 | cli_helpers::get_json_block_expanded(json_str, None, &secp, wallet, seed, None, false)?; 31 | 32 | // let json: serde_json::Value = serde_json::from_str(json_str)?; 33 | 34 | let qry = terra 35 | .wasm() 36 | .query::(contract, &json.to_string()) 37 | .await?; 38 | println!("{}", qry); 39 | 40 | Ok(()) 41 | } 42 | #[tokio::main] 43 | async fn main() { 44 | dotenv().ok(); 45 | env_logger::init(); 46 | 47 | if let Err(ref err) = run().await { 48 | log::error!("{}", err); 49 | err.chain() 50 | .skip(1) 51 | .for_each(|cause| log::error!("because: {}", cause)); 52 | 53 | // The backtrace is not always generated. Try to run this example 54 | // with `$env:RUST_BACKTRACE=1`. 55 | // if let Some(backtrace) = e.backtrace() { 56 | // log::debug!("backtrace: {:?}", backtrace); 57 | // } 58 | 59 | ::std::process::exit(1); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/auth.rs: -------------------------------------------------------------------------------- 1 | use crate::auth_types::AuthAccount; 2 | use crate::errors::TerraRustAPIError; 3 | use crate::staking_types::{Validator, ValidatorDelegation, ValidatorUnbondingDelegation}; 4 | use crate::{LCDResult, LCDResultVec, LCDTypeValue, Terra}; 5 | 6 | pub struct Auth<'a> { 7 | terra: &'a Terra, 8 | } 9 | impl Auth<'_> { 10 | pub fn create(terra: &'_ Terra) -> Auth<'_> { 11 | Auth { terra } 12 | } 13 | pub async fn account( 14 | &self, 15 | account_address: &str, 16 | ) -> Result>, TerraRustAPIError> { 17 | self.terra 18 | .send_cmd::>>( 19 | &format!("/auth/accounts/{}", account_address), 20 | None, 21 | ) 22 | .await 23 | } 24 | /// all delegations for a given account 25 | pub async fn validator_delegations( 26 | &self, 27 | account_address: &str, 28 | ) -> Result, TerraRustAPIError> { 29 | self.terra 30 | .send_cmd::>( 31 | &format!("/staking/delegators/{}/delegations", account_address), 32 | None, 33 | ) 34 | .await 35 | } 36 | /// all unbonding delegations for a given account 37 | pub async fn validator_unbonding_delegations( 38 | &self, 39 | account_address: &str, 40 | ) -> Result, TerraRustAPIError> { 41 | self.terra 42 | .send_cmd::>( 43 | &format!( 44 | "/staking/delegators/{}/unbonding_delegations", 45 | account_address 46 | ), 47 | None, 48 | ) 49 | .await 50 | } 51 | /// all validators for a given account 52 | pub async fn delegated_validators( 53 | &self, 54 | account_address: &str, 55 | ) -> Result>, TerraRustAPIError> { 56 | self.terra 57 | .send_cmd::>>( 58 | &format!("/staking/delegators/{}/validators", account_address), 59 | None, 60 | ) 61 | .await 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/oracle.rs: -------------------------------------------------------------------------------- 1 | use crate::client::oracle_types::{OracleParameters, OraclePreVotes, OracleVotes}; 2 | use crate::{LCDResult, Terra}; 3 | 4 | pub struct Oracle<'a> { 5 | terra: &'a Terra, 6 | } 7 | impl<'a> Oracle<'a> { 8 | pub fn create(terra: &'a Terra) -> Oracle<'a> { 9 | Oracle { terra } 10 | } 11 | pub async fn parameters(&self) -> anyhow::Result> { 12 | let response = self 13 | .terra 14 | .send_cmd::>("/oracle/parameters", None) 15 | .await?; 16 | Ok(response) 17 | } 18 | pub fn voters(&self, validator: &'a str) -> Voters<'a> { 19 | Voters::create(self.terra, validator) 20 | } 21 | } 22 | pub struct Voters<'a> { 23 | terra: &'a Terra, 24 | pub validator: &'a str, 25 | } 26 | impl<'a> Voters<'a> { 27 | pub fn create(terra: &'a Terra, validator: &'a str) -> Voters<'a> { 28 | Voters { terra, validator } 29 | } 30 | pub async fn votes(&self) -> anyhow::Result>> { 31 | let response = self 32 | .terra 33 | .send_cmd::>>( 34 | &format!("/oracle/voters/{}/votes", &self.validator), 35 | None, 36 | ) 37 | .await?; 38 | Ok(response) 39 | } 40 | pub async fn prevotes(&self) -> anyhow::Result>> { 41 | let response = self 42 | .terra 43 | .send_cmd::>>( 44 | &format!("/oracle/voters/{}/prevotes", &self.validator), 45 | None, 46 | ) 47 | .await?; 48 | Ok(response) 49 | } 50 | 51 | pub async fn feeder(&self) -> anyhow::Result> { 52 | let response = self 53 | .terra 54 | .send_cmd::>( 55 | &format!("/oracle/voters/{}/feeder", &self.validator), 56 | None, 57 | ) 58 | .await?; 59 | Ok(response) 60 | } 61 | pub async fn miss(&self) -> anyhow::Result> { 62 | let response = self 63 | .terra 64 | .send_cmd::>( 65 | &format!("/oracle/voters/{}/miss", &self.validator), 66 | None, 67 | ) 68 | .await?; 69 | Ok(response) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/rpc.rs: -------------------------------------------------------------------------------- 1 | use crate::client::rpc_types::{RPCNetInfo, RPCResult, RPCStatus, RPCUnconfirmedTXS}; 2 | use crate::tendermint_types::{BlockResult, BlockResultsResult}; 3 | use crate::Terra; 4 | pub struct RPC<'a> { 5 | terra: &'a Terra, 6 | rpc_url: &'a str, 7 | } 8 | impl RPC<'_> { 9 | pub fn create<'a>(terra: &'a Terra, tendermint_url: &'a str) -> RPC<'a> { 10 | RPC { 11 | terra, 12 | rpc_url: tendermint_url, 13 | } 14 | } 15 | pub async fn status(&self) -> anyhow::Result { 16 | Ok(self 17 | .terra 18 | .send_cmd_url::>(self.rpc_url, "/status", None) 19 | .await? 20 | .result) 21 | } 22 | pub async fn net_info(&self) -> anyhow::Result { 23 | Ok(self 24 | .terra 25 | .send_cmd_url::>(self.rpc_url, "/net_info", None) 26 | .await? 27 | .result) 28 | } 29 | pub async fn unconfirmed_txs(&self) -> anyhow::Result { 30 | Ok(self 31 | .terra 32 | .send_cmd_url::>(self.rpc_url, "/unconfirmed_txs", None) 33 | .await? 34 | .result) 35 | } 36 | pub async fn block(&self) -> anyhow::Result { 37 | Ok(self 38 | .terra 39 | .send_cmd_url::>(self.rpc_url, "/block", None) 40 | .await? 41 | .result) 42 | } 43 | pub async fn block_at_height(&self, height: u64) -> anyhow::Result { 44 | Ok(self 45 | .terra 46 | .send_cmd_url::>( 47 | self.rpc_url, 48 | &format!("/block?height={}", height), 49 | None, 50 | ) 51 | .await? 52 | .result) 53 | } 54 | pub async fn block_results(&self) -> anyhow::Result { 55 | Ok(self 56 | .terra 57 | .send_cmd_url::>(self.rpc_url, "/block_results", None) 58 | .await? 59 | .result) 60 | } 61 | pub async fn block_results_at_height(&self, height: u64) -> anyhow::Result { 62 | Ok(self 63 | .terra 64 | .send_cmd_url::>( 65 | self.rpc_url, 66 | &format!("/block_results?height={}", height), 67 | None, 68 | ) 69 | .await? 70 | .result) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/rpc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | 5 | #[derive(Parser)] 6 | /// Tendermint ValidatorSets commands/RPC endpoint 7 | pub struct RPCCommand { 8 | #[clap( 9 | name = "endpoint", 10 | long = "rpc-endpoint", 11 | env = "TERRARUST_RPC_ENDPOINT", 12 | default_value = "http://127.0.0.1:26657" 13 | )] 14 | endpoint: String, 15 | #[clap(subcommand)] 16 | cmd: RPCSubCommand, 17 | } 18 | 19 | #[derive(Subcommand)] 20 | pub enum RPCSubCommand { 21 | #[clap(name = "status", about = "status via RPC")] 22 | Status, 23 | #[clap(name = "net-info", about = "network information from RPC endpoint")] 24 | NetInfo, 25 | #[clap( 26 | name = "unconfirmed-txs", 27 | about = "Get the list of unconfirmed transactions" 28 | )] 29 | UnconfirmedTXS, 30 | #[clap(name = "block", about = "Get the block at a given height")] 31 | Block { height: Option }, 32 | #[clap(name = "block-results", about = "Get the block at a given height")] 33 | BlockResults { height: Option }, 34 | } 35 | impl RPCCommand { 36 | pub async fn parse(self, terra: &Terra) -> Result<()> { 37 | let rpc_endpoint = terra.rpc(&self.endpoint); 38 | match self.cmd { 39 | RPCSubCommand::Status => { 40 | let resp = rpc_endpoint.status().await?; 41 | 42 | println!("{}", serde_json::to_string(&resp)?) 43 | } 44 | RPCSubCommand::NetInfo => { 45 | let resp = rpc_endpoint.net_info().await?; 46 | 47 | println!("{}", serde_json::to_string(&resp)?) 48 | } 49 | RPCSubCommand::UnconfirmedTXS => { 50 | let resp = rpc_endpoint.unconfirmed_txs().await?; 51 | 52 | println!("{}", serde_json::to_string(&resp)?) 53 | } 54 | RPCSubCommand::Block { height } => { 55 | let resp = match height { 56 | Some(h) => rpc_endpoint.block_at_height(h).await?, 57 | None => rpc_endpoint.block().await?, 58 | }; 59 | 60 | println!("{}", serde_json::to_string(&resp)?) 61 | } 62 | RPCSubCommand::BlockResults { height } => { 63 | let resp = match height { 64 | Some(h) => rpc_endpoint.block_results_at_height(h).await?, 65 | None => rpc_endpoint.block_results().await?, 66 | }; 67 | 68 | println!("{}", serde_json::to_string(&resp)?) 69 | } 70 | } 71 | Ok(()) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/rpc_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::terra_u64_format; 2 | 3 | use serde::Deserialize; 4 | use serde::Serialize; 5 | 6 | /// Information provided by the validator for their node info 7 | #[derive(Deserialize, Serialize, Clone, Debug)] 8 | pub struct RPCProtocolVersion { 9 | pub p2p: String, 10 | pub block: String, 11 | pub app: String, 12 | } 13 | /// Information provided by the validator for their node info 14 | #[derive(Deserialize, Serialize, Clone, Debug)] 15 | pub struct RPCProtocolOther { 16 | pub tx_index: String, 17 | pub rpc_address: String, 18 | } 19 | #[derive(Deserialize, Serialize, Clone, Debug)] 20 | pub struct RPCNodeInfo { 21 | pub protocol_version: RPCProtocolVersion, 22 | pub id: String, 23 | pub listen_addr: String, 24 | pub network: String, 25 | pub version: String, 26 | pub channels: String, 27 | pub moniker: String, 28 | pub other: RPCProtocolOther, 29 | } 30 | #[derive(Deserialize, Serialize, Clone, Debug)] 31 | pub struct RPCSyncInfo { 32 | pub catching_up: bool, 33 | #[serde(with = "terra_u64_format")] 34 | pub latest_block_height: u64, 35 | #[serde(with = "terra_u64_format")] 36 | pub earliest_block_height: u64, 37 | } 38 | #[derive(Deserialize, Serialize, Clone, Debug)] 39 | pub struct RPCValidatorInfo { 40 | pub address: String, 41 | #[serde(with = "terra_u64_format")] 42 | pub voting_power: u64, 43 | } 44 | /// Information provided by the validator for their status 45 | #[derive(Deserialize, Serialize, Clone, Debug)] 46 | pub struct RPCStatus { 47 | pub node_info: RPCNodeInfo, 48 | pub sync_info: RPCSyncInfo, 49 | pub validator_info: RPCValidatorInfo, 50 | } 51 | 52 | #[derive(Deserialize, Serialize, Clone, Debug)] 53 | pub struct RPCConnectionStatus { 54 | #[serde(with = "terra_u64_format", rename = "Duration")] 55 | pub duration: u64, 56 | } 57 | #[derive(Deserialize, Serialize, Clone, Debug)] 58 | pub struct RPCNetPeer { 59 | pub node_info: RPCNodeInfo, 60 | pub is_outbound: bool, 61 | pub connection_status: RPCConnectionStatus, 62 | pub remote_ip: String, 63 | } 64 | #[derive(Deserialize, Serialize, Clone, Debug)] 65 | pub struct RPCNetInfo { 66 | pub listening: bool, 67 | #[serde(with = "terra_u64_format")] 68 | pub n_peers: u64, 69 | pub peers: Vec, 70 | } 71 | #[derive(Deserialize, Serialize, Clone, Debug)] 72 | pub struct RPCUnconfirmedTXS { 73 | #[serde(with = "terra_u64_format")] 74 | pub n_txs: u64, 75 | #[serde(with = "terra_u64_format")] 76 | pub total: u64, 77 | #[serde(with = "terra_u64_format")] 78 | pub total_bytes: u64, 79 | pub txs: Vec, 80 | } 81 | 82 | #[allow(missing_docs)] 83 | #[derive(Deserialize, Debug)] 84 | pub struct RPCResult { 85 | pub jsonrpc: String, 86 | pub id: i64, 87 | pub result: T, 88 | } 89 | -------------------------------------------------------------------------------- /src/bank.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use secp256k1::Secp256k1; 4 | use terra_rust_api::messages::{Message, MsgSend}; 5 | use terra_rust_api::Terra; 6 | 7 | use crate::{NAME, VERSION}; 8 | use rust_decimal::Decimal; 9 | use terra_rust_api::core_types::Coin; 10 | use terra_rust_wallet::Wallet; 11 | /// Bank Transactions 12 | #[derive(Parser)] 13 | pub struct BankCommand { 14 | #[clap(subcommand)] 15 | command: BankEnum, 16 | } 17 | #[derive(Subcommand)] 18 | pub enum BankEnum { 19 | #[clap(name = "send", about = "send some coin[s] to an account")] 20 | Send { 21 | /// from account (specify the key name in the wallet 22 | from: String, 23 | /// the to account in 'terra' format 24 | to: String, 25 | /// the amount 26 | amount: Decimal, 27 | /// denom 28 | denom: String, 29 | }, 30 | Balance { 31 | account: String, 32 | }, 33 | } 34 | impl BankCommand { 35 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 36 | match self.command { 37 | BankEnum::Send { 38 | from, 39 | to, 40 | amount, 41 | denom, 42 | } => { 43 | let secp = Secp256k1::new(); 44 | let from_key = wallet.get_private_key(&secp, &from, seed)?; 45 | let from_public_key = from_key.public_key(&secp); 46 | let coin: Coin = Coin::create(&denom, amount); 47 | let from_account = from_public_key.account()?; 48 | let send = MsgSend::create(from_account, to.clone(), vec![coin])?; 49 | 50 | let messages: Vec = vec![send]; 51 | let resp = terra 52 | .submit_transaction_sync( 53 | &secp, 54 | &from_key, 55 | messages, 56 | Some(format!( 57 | "PFC-{}/{}", 58 | NAME.unwrap_or("TERRARUST"), 59 | VERSION.unwrap_or("DEV") 60 | )), 61 | ) 62 | .await?; 63 | 64 | println!("{}", resp.txhash); 65 | log::info!("{}", resp.raw_log); 66 | } 67 | BankEnum::Balance { account } => { 68 | let account_id = if !account.starts_with("terra1") { 69 | let secp = Secp256k1::new(); 70 | wallet.get_account(&secp, &account, seed)? 71 | } else { 72 | account.clone() 73 | }; 74 | let sw = terra.bank().balances(&account_id).await?; 75 | println!("{}", serde_json::to_string_pretty(&sw)?); 76 | } 77 | }; 78 | Ok(()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terra Rust 2 | 3 | This is a WIP. 4 | 5 | No security audit has been performed. 6 | 7 | There are currently 5 interesting things 8 | 9 | * [Terra-Rust](https://github.com/PFC-Validator/terra-rust/blob/main/src/main.rs) A cross-platform CLI tool 10 | * [Terra-Rust-API](https://crates.io/crates/terra-rust-api) an API you can integrate into your own code 11 | * [Terra-Rust-Wallet](https://crates.io/crates/terra-rust-wallet) a secure OS agnostic wallet, using [keyring](https://crates.io/crates/keyring) 12 | * [Terra-Rust-Cli](https://crates.io/crates/terra-rust-cli) a collection of helper functions to help get arguments in a common way 13 | * [Terra-exec](./src/bin/terra_exec.rs) & [Terra-query](./src/bin/terra_query.rs) which make it much simpler to do smart contract development 14 | 15 | ## Terra-Rust to help smart contract development 16 | 17 | see [Smart Contract Dev Notes](./Smart-Contract-dev.md) 18 | 19 | ## Randomness 20 | The API is currently using random numbers via 21 | ` 22 | let mut rng = rand::thread_rng(); 23 | ` 24 | ## Disclaimer 25 | 26 | This may steal your money. 27 | 28 | This is not investment advice. 29 | 30 | Do you own research 31 | 32 | ## Why? 33 | 34 | I built this for 2 main reasons. 35 | 1. There was no easy way for me to get the default terra-cli to work on Windows 36 | 1. I wanted a RUST api to use in other things. The CLI is just cream on top. 37 | 38 | # Environment Variables 39 | some things are used often and repeatedly, so we decided to use environment variables. 40 | 41 | **TERRARUST_LCD** sets the LCD URL. e.g. https://tequila-lcd.terra.dev 42 | 43 | **TERRARUST_CHAIN** set the CHAIN to use e.g. tequila-0004 44 | 45 | **TERRARUST_SEED_PHRASE** the passphrase used in combination with the 24-words to generate the private key 46 | 47 | **TERRARUST_WALLET** the default wallet to use 48 | 49 | **TERRARUST_GAS_PRICES** the gas price to use. e.g. 50ukrw 50 | 51 | **TERRARUST_GAS_ADJUSTMENT** the gas adjustment multiplier to use 52 | 53 | **TERRARUST_CONTRACT** (for smart contract development) your contract to migrate 54 | you can also set these in a file called '.env' if you prefer 55 | # Documentation 56 | * [API docs](https://docs.rs/terra-rust-api) are available here 57 | * [Wallet docs](https://docs.rs/terra-rust-wallet) 58 | 59 | on first install you may want to: 60 | 61 | ``` 62 | $ terra-rust wallet create default 63 | ``` 64 | 65 | # Help ? 66 | ``` 67 | $ terra-rust --help 68 | ``` 69 | If you think this was useful, feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. It will help defray the costs. 70 | 71 | [PFC](https://twitter.com/PFC_Validator) - Terra/Luna is Pretty Freaking Cool right... feel free to drop me a line 72 | 73 | # Contribute 74 | Feel free to submit patches/comments/Pull Requests. 75 | 76 | We have also set up a [Discord](https://discord.gg/zKVWs4HhJD) channel to discuss this, and other PFC things 77 | -------------------------------------------------------------------------------- /terra-rust-api/README.md: -------------------------------------------------------------------------------- 1 | # Terra Rust API 2 | A rust API for Terrad's LCD system. 3 | 4 | # Warning 5 | This is a WIP. 6 | 7 | No security audit has been performed. 8 | 9 | ## Randomness 10 | The API is currently using random numbers via 11 | ` 12 | let mut rng = rand::thread_rng(); 13 | ` 14 | ## Disclaimer 15 | 16 | This may steal your money. 17 | 18 | This is not investment advice. 19 | 20 | Do your own research 21 | 22 | 23 | # Help ? 24 | There is a [CLI](https://github.com/pfc-validator/terra-rust) that uses this, which may be helpful. 25 | 26 | We have also set up a [Discord](https://discord.gg/zKVWs4HhJD) channel to discuss this, and other PFC things 27 | 28 | If you think this was useful, feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. It will help defray the costs. 29 | 30 | [PFC](https://twitter.com/PFC_Validator) - Terra/Luna is Pretty Freaking Cool right... feel free to drop me a line 31 | 32 | # An Example 33 | ```rust 34 | use terra_rust_api::{Terra, GasOptions, PrivateKey}; 35 | use terra_rust_api::core_types::{Coin, Msg, StdSignMsg, StdSignature}; 36 | use terra_rust_api::messages::MsgSend; 37 | use terra_rust_api::auth_types::AuthAccountResult; 38 | 39 | // set up the LCD client 40 | let gas_opts = GasOptions::create_with_gas_estimate("50ukrw",1.4); 41 | let t = Terra::lcd_client("https://bombay-lcd.terra.dev/", "bombay-12", &gas_opts).await?; 42 | // generate a private key 43 | let secp = Secp256k1::new(); 44 | let from_key = PrivateKey::from_words(&secp,"your secret words"); 45 | let from_public_key = from_key.public_key(&secp); 46 | // generate the message SEND 1000 uluna from your private key to someone else 47 | let coin: Coin = Coin::parse("1000uluna")?.unwrap(); 48 | let from_account = from_public_key.account()?; 49 | let send: MsgSend = MsgSend::create(from_account, "terra1usws7c2c6cs7nuc8vma9qzaky5pkgvm2uag6rh", vec![coin]); 50 | // generate the transaction & calc fees 51 | let messages = vec![send]; 52 | // and submit the message(s) to the chain 53 | let resp = terra.submit_transaction_sync( 54 | &secp, 55 | &from_key, 56 | &messages, 57 | None 58 | ) 59 | .await?; 60 | println!("{}", resp.txhash) 61 | 62 | ``` 63 | # Examples 64 | are located in the main 'terra-rust' repo 65 | * [do_swap](../examples/do_swap.rs) -- example on how to execute a custom contract. in this case a swap. 66 | ```shell 67 | 68 | cargo run --example do_swap -- --wallet tequilla test terra13e4jmcjnwrauvl2fnjdwex0exuzd8zrh5xk29v 1.0 1000000 uluna --max-spread 69 | 0.10 --coins 1000000uluna -l https://bombay-lcd.terra.dev -c bombay-12 70 | 71 | ``` 72 | **(note) coins and amount need to be the same 73 | 74 | * [sign_message](../examples/sign_message.rs) -- sign a random string with your key 75 | 76 | # Docs 77 | * [API Documentation](https://docs.rs/terra-rust-api) 78 | * see [Change log](https://github.com/PFC-Validator/terra-rust/blob/main/terra-rust-api/Changelog.md) for more detailed change summaries -------------------------------------------------------------------------------- /terra-rust-api/src/client/market.rs: -------------------------------------------------------------------------------- 1 | use crate::client::core_types::Coin; 2 | 3 | use crate::{LCDResult, Message, Terra}; 4 | use rust_decimal::Decimal; 5 | 6 | use crate::messages::market::MsgSwap; 7 | use futures::future::join_all; 8 | 9 | /// Market functions. mainly around swapping tokens 10 | pub struct Market<'a> { 11 | terra: &'a Terra, 12 | } 13 | impl Market<'_> { 14 | pub fn create(terra: &'_ Terra) -> Market<'_> { 15 | Market { terra } 16 | } 17 | /// obtain how much a coin is worth in a secondary coin 18 | pub async fn swap(&self, offer: &Coin, ask_denom: &str) -> anyhow::Result> { 19 | let response = self 20 | .terra 21 | .send_cmd::>( 22 | "/market/swap", 23 | Some(&format!("?offer_coin={}&ask_denom={}", offer, ask_denom)), 24 | ) 25 | .await?; 26 | Ok(response) 27 | } 28 | /// generate a set of transactions to swap a account's tokens into another, as long as they are above a certain threshold 29 | pub async fn generate_sweep_messages( 30 | &self, 31 | from: String, 32 | to_coin: String, 33 | threshold: Decimal, 34 | ) -> anyhow::Result> { 35 | let account_balances = self.terra.bank().balances(&from).await?; 36 | let potential_coins = account_balances 37 | .result 38 | .into_iter() 39 | .filter(|c| c.denom != to_coin); 40 | //.collect::>(); 41 | let into_currency_futures = potential_coins 42 | .into_iter() 43 | .map(|c| async { 44 | let resp = self 45 | .terra 46 | .market() 47 | .swap(&c.clone(), &to_coin) 48 | .await 49 | .map(|f| (c, f.result)); 50 | resp 51 | }) 52 | .collect::>(); 53 | 54 | let into_currency = join_all(into_currency_futures).await; 55 | 56 | let mut err = None; 57 | let to_convert = &into_currency 58 | .into_iter() 59 | .flat_map(|f| match f { 60 | Ok(coins) => { 61 | if coins.1.amount > threshold { 62 | Some(coins) 63 | } else { 64 | None 65 | } 66 | } 67 | Err(e) => { 68 | eprintln!("Error {}", e); 69 | err = Some(e); 70 | None 71 | } 72 | }) 73 | .collect::>(); 74 | match err { 75 | Some(e) => Err(e), 76 | None => { 77 | let mut messages = Vec::new(); 78 | for swap_coins in to_convert { 79 | let message = 80 | MsgSwap::create(swap_coins.0.clone(), to_coin.clone(), from.clone())?; 81 | messages.push(message); 82 | } 83 | Ok(messages) 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /terra-rust-api/src/errors.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use reqwest::StatusCode; 4 | use std::string::FromUtf8Error; 5 | use thiserror::Error; 6 | 7 | #[derive(Error, Debug)] 8 | pub enum TerraRustAPIError { 9 | #[error("Reqwest HTTP(s) Error")] 10 | ReqwestError(#[from] ::reqwest::Error), 11 | #[error("Decimal Conversion Error")] 12 | RustDecimal(#[from] ::rust_decimal::Error), 13 | #[error("JSON Conversion Error")] 14 | SerdeJson(#[from] ::serde_json::Error), 15 | #[error(transparent)] 16 | IOErr(#[from] ::std::io::Error), 17 | #[error(transparent)] 18 | ED25519(#[from] ::ed25519_dalek::ed25519::Error), 19 | #[error(transparent)] 20 | DecodeError(#[from] ::base64::DecodeError), 21 | #[error(transparent)] 22 | SubtleError(#[from] ::subtle_encoding::Error), 23 | #[error(transparent)] 24 | UTF8Error(#[from] FromUtf8Error), 25 | #[error(transparent)] 26 | BitCoinBip32(#[from] ::bitcoin::util::bip32::Error), 27 | #[error(transparent)] 28 | HexError(#[from] ::hex::FromHexError), 29 | #[error(transparent)] 30 | Secp256k1(#[from] ::secp256k1::Error), 31 | 32 | #[error("Terra `{0}` CLI Error")] 33 | Terra(String), 34 | #[error("Terra `{0}` LCD - {1}")] 35 | TerraLCDResponse(StatusCode, String), 36 | #[error("Bech32 Decode Error")] 37 | Bech32DecodeErr, 38 | #[error("Bech32 Decode Error: Key Failed prefix {0} or length {1} Wanted:{2}/{3}")] 39 | Bech32DecodeExpanded(String, usize, String, usize), 40 | #[error("Mnemonic - Bad Phrase")] 41 | Phrasing, 42 | #[error("Mnemonic - Missing Phrase")] 43 | MissingPhrase, 44 | #[error("Bad Implementation. Missing Component")] 45 | Implementation, 46 | #[error("Unable to convert into public key `{key}`")] 47 | Conversion { 48 | key: String, 49 | source: bitcoin::bech32::Error, 50 | }, 51 | #[error("83 length-missing SECP256K1 prefix")] 52 | ConversionSECP256k1, 53 | #[error("82 length-missing ED25519 prefix")] 54 | ConversionED25519, 55 | #[error("Expected Key length of 82 or 83 length was {0}")] 56 | ConversionLength(usize), 57 | #[error("Expected Key length of 40 length was {0}")] 58 | ConversionLengthED25519Hex(usize), 59 | #[error("Expected ED25519 key of length 32 with a BECH32 ED25519 prefix of 5 chars - Len {0} - Hex {1}")] 60 | ConversionPrefixED25519(usize, String), 61 | #[error("Can't call Transactions without some gas rules")] 62 | NoGasOpts, 63 | #[error("Can't parse `{parse}` into a coin")] 64 | CoinParseErrV { parse: String }, 65 | #[error("Can't parse `{0}` into a coin")] 66 | CoinParseErr(String), 67 | #[error("TX submit returned `{0}` - {1} '{2}'")] 68 | TxResultError(usize, String, String), 69 | #[error("No price found for Gas using denom {0}")] 70 | GasPriceError(String), 71 | #[error("Attempting to fetch validator set in parts, and failed Height mismatch {0} {1}")] 72 | TendermintValidatorSet(u64, u64), 73 | #[error("Transaction {0} not found after {1} attempts")] 74 | TXNotFound(String, usize), 75 | #[error("unknown Terra-Rust API error")] 76 | Unknown, 77 | #[error("Generic Error {0}")] 78 | StdErr(String), 79 | } 80 | -------------------------------------------------------------------------------- /src/contract.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use serde_json::Value; 4 | use terra_rust_api::Terra; 5 | 6 | #[derive(Subcommand)] 7 | enum ContractEnum { 8 | #[clap(name = "codes", about = "Get code info based on the code ID")] 9 | Codes { 10 | #[clap(name = "codeId", help = "code id you want to obtain info about")] 11 | code_id: u64, 12 | }, 13 | #[clap(name = "info", about = "Get code info based on the contract address")] 14 | Info { 15 | #[clap( 16 | name = "contract_address", 17 | help = "code address you want to obtain info about" 18 | )] 19 | contract_address: String, 20 | }, 21 | 22 | #[clap(name = "query", about = "query contract with json")] 23 | Query { 24 | #[clap(name = "contract_address", help = "code address you want to query")] 25 | contract_address: String, 26 | #[clap(name = "query", help = "json formatted query message")] 27 | query: String, 28 | }, 29 | #[clap(name = "raw", about = "query via key/subkey")] 30 | Raw { 31 | #[clap(name = "contract_address", help = "code address you want to query")] 32 | contract_address: String, 33 | #[clap(name = "key", help = "the key you want to query")] 34 | key: String, 35 | #[clap(name = "subkey", help = "the sub key you want to query")] 36 | subkey: Option, 37 | }, 38 | #[clap(name = "parameters", about = "Get parameters for contracts")] 39 | Parameters, 40 | } 41 | #[derive(Parser)] 42 | /// WASM Module / Smart Contract commands 43 | pub struct ContractCommand { 44 | #[clap(subcommand)] 45 | command: ContractEnum, 46 | } 47 | impl ContractCommand { 48 | pub async fn parse(self, terra: &Terra) -> Result<()> { 49 | match self.command { 50 | ContractEnum::Codes { code_id } => { 51 | let code_result = terra.wasm().codes(code_id).await?; 52 | println!("{}", serde_json::to_string_pretty(&code_result)?); 53 | Ok(()) 54 | } 55 | ContractEnum::Info { contract_address } => { 56 | let code_result = terra.wasm().info(&contract_address).await?; 57 | println!("{}", serde_json::to_string_pretty(&code_result)?); 58 | Ok(()) 59 | } 60 | ContractEnum::Parameters => { 61 | let code_result = terra.wasm().parameters().await?; 62 | println!("{}", serde_json::to_string_pretty(&code_result)?); 63 | Ok(()) 64 | } 65 | ContractEnum::Query { 66 | contract_address, 67 | query, 68 | } => { 69 | let code_result = terra 70 | .wasm() 71 | .query::(&contract_address, &query) 72 | .await?; 73 | println!("{}", serde_json::to_string_pretty(&code_result)?); 74 | Ok(()) 75 | } 76 | ContractEnum::Raw { 77 | contract_address, 78 | key, 79 | subkey, 80 | } => { 81 | let (key, value) = terra 82 | .wasm() 83 | .query_raw(&contract_address, &key, &subkey) 84 | .await?; 85 | println!("{} {}", key, value); 86 | Ok(()) 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/auth.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | 4 | use terra_rust_api::Terra; 5 | 6 | use secp256k1::Secp256k1; 7 | use terra_rust_wallet::Wallet; 8 | #[derive(Parser)] 9 | /// Auth operations 10 | pub struct AuthCommand { 11 | #[clap(subcommand)] 12 | command: AuthEnum, 13 | } 14 | #[derive(Subcommand)] 15 | pub enum AuthEnum { 16 | #[clap(name = "account")] 17 | Account { 18 | #[clap(name = "address", help = "the address to query")] 19 | address: String, 20 | }, 21 | /// list delegations of account 22 | #[clap(name = "delegations")] 23 | Delegations { 24 | #[clap(name = "address", help = "the terra address")] 25 | // the address to get more info on. 26 | address: String, 27 | }, 28 | /// list unbonding delegations of account 29 | #[clap(name = "unbonding")] 30 | Unbonding { 31 | #[clap(name = "address", help = "the terra address")] 32 | // the address to get more info on. 33 | address: String, 34 | }, 35 | /// list validators of account 36 | #[clap(name = "validators")] 37 | Validators { 38 | #[clap(name = "address", help = "the terra address")] 39 | // the address to get more info on. 40 | address: String, 41 | }, 42 | } 43 | impl AuthCommand { 44 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 45 | match self.command { 46 | AuthEnum::Account { address } => { 47 | let account_id = if !address.starts_with("terra1") { 48 | let secp = Secp256k1::new(); 49 | wallet.get_account(&secp, &address, seed)? 50 | } else { 51 | address 52 | }; 53 | let sw = terra.auth().account(&account_id).await?; 54 | 55 | println!("{}", serde_json::to_string_pretty(&sw)?); 56 | } 57 | AuthEnum::Delegations { address } => { 58 | let account_id = if !address.starts_with("terra1") { 59 | let secp = Secp256k1::new(); 60 | wallet.get_account(&secp, &address, seed)? 61 | } else { 62 | address 63 | }; 64 | let v = terra.auth().validator_delegations(&account_id).await?; 65 | println!("{:#?}", v.result); 66 | } 67 | AuthEnum::Unbonding { address } => { 68 | let account_id = if !address.starts_with("terra1") { 69 | let secp = Secp256k1::new(); 70 | wallet.get_account(&secp, &address, seed)? 71 | } else { 72 | address 73 | }; 74 | let v = terra 75 | .auth() 76 | .validator_unbonding_delegations(&account_id) 77 | .await?; 78 | println!("{:#?}", v.result); 79 | } 80 | AuthEnum::Validators { address } => { 81 | let account_id = if !address.starts_with("terra1") { 82 | let secp = Secp256k1::new(); 83 | wallet.get_account(&secp, &address, seed)? 84 | } else { 85 | address 86 | }; 87 | let v = terra.auth().delegated_validators(&account_id).await?; 88 | println!("{:#?}", v.result); 89 | } 90 | }; 91 | Ok(()) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/staking.rs: -------------------------------------------------------------------------------- 1 | use crate::client::staking_types::{Validator, ValidatorDelegation, ValidatorUnbondingDelegation}; 2 | use crate::errors::TerraRustAPIError; 3 | use crate::staking_types::ValidatorDelegationsV1Response; 4 | use crate::{LCDResult, Terra}; 5 | 6 | pub struct Staking<'a> { 7 | terra: &'a Terra, 8 | } 9 | impl Staking<'_> { 10 | pub fn create(terra: &'_ Terra) -> Staking<'_> { 11 | Staking { terra } 12 | } 13 | pub async fn validator(&self, key: &str) -> Result, TerraRustAPIError> { 14 | // let url = self.terra.url.to_owned() + "/staking/validators/" + key; 15 | Ok(self 16 | .terra 17 | .send_cmd::>("/staking/validators/", Some(key)) 18 | .await?) 19 | } 20 | /// Get list of validators 21 | pub async fn validators(&self) -> Result>, TerraRustAPIError> { 22 | Ok(self 23 | .terra 24 | .send_cmd::>>("/staking/validators", None) 25 | .await?) 26 | } 27 | /// Get list of validators at a given height 28 | pub async fn validators_at_height( 29 | &self, 30 | height: u64, 31 | limit: Option, 32 | ) -> Result>, TerraRustAPIError> { 33 | Ok(self 34 | .terra 35 | .send_cmd::>>( 36 | &format!( 37 | "/staking/validators?height={}&limit={}", 38 | height, 39 | limit.unwrap_or(200u64) 40 | ), 41 | None, 42 | ) 43 | .await?) 44 | } 45 | pub async fn validator_by_moniker( 46 | &self, 47 | moniker: &str, 48 | ) -> Result, TerraRustAPIError> { 49 | let lst = self 50 | .terra 51 | .send_cmd::>>("/staking/validators", None) 52 | .await? 53 | .result; 54 | match lst.iter().find(|&p| p.description.moniker == moniker) { 55 | None => Ok(None), 56 | Some(v) => Ok(Some(v.to_owned())), 57 | } 58 | } 59 | /// all delegations for a given validator 60 | pub async fn validator_delegations( 61 | &self, 62 | key: &str, 63 | ) -> Result>, TerraRustAPIError> { 64 | self.terra 65 | .send_cmd::>>( 66 | &format!("/staking/validators/{}/delegations", key), 67 | None, 68 | ) 69 | .await 70 | } 71 | /// all delegations for a given validator (limit) (new format) 72 | pub async fn validator_delegations_limit( 73 | &self, 74 | key: &str, 75 | limit: u64, 76 | ) -> Result { 77 | self.terra 78 | .send_cmd::( 79 | &format!( 80 | "/cosmos/staking/v1beta1/validators/{}/delegations?pagination.limit={}", 81 | key, limit 82 | ), 83 | None, 84 | ) 85 | .await 86 | } 87 | 88 | /// all unbondings for a given validator 89 | pub async fn validator_unbonding_delegations( 90 | &self, 91 | key: &str, 92 | ) -> Result>, TerraRustAPIError> { 93 | self.terra 94 | .send_cmd::>>( 95 | &format!("/staking/validators/{}/unbonding_delegations", key), 96 | None, 97 | ) 98 | .await 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /terra-rust-api/src/lib.rs: -------------------------------------------------------------------------------- 1 | // `error_chain!` can recurse deeply 2 | // #![recursion_limit = "1024"] 3 | #![allow(missing_docs)] 4 | /*! 5 | * This crate provides an interface into the Terra LCD HTTP service. 6 | * # PFC 7 | * 8 | * This work is sponsored by the PFC (Pretty Freaking Cool) Validator, 9 | * feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. 10 | * 11 | * It will help defray the costs. 12 | * 13 | * # Warning 14 | * This uses crytpographic routines that have not gone through any security audit. 15 | * 16 | * The manner which it stores private keys may be unsecure/subject to hacks, and if you use it, may put the assets behind those keys at risk. 17 | * 18 | * This is ALPHA software. 19 | * 20 | * # Usage 21 | * ```toml 22 | * [dependencies] 23 | * terra-rust-api="0.1" 24 | * tokio = { version = "1.4", features = ["full"] } 25 | * ``` 26 | * ``` 27 | * use terra_rust_api::{Terra, GasOptions, PrivateKey}; 28 | * use terra_rust_api::core_types::{Coin, StdSignMsg, StdSignature}; 29 | * use terra_rust_api::messages::{MsgSend, Message}; 30 | * use bitcoin::secp256k1::Secp256k1; 31 | * 32 | * 33 | * async fn demo() -> anyhow::Result<()> { 34 | * // set up the LCD client 35 | * let gas_opts = GasOptions::create_with_gas_estimate("50ukrw",1.4)?; 36 | * let terra = Terra::lcd_client("https://bombay-lcd.terra.dev/", "bombay-12", &gas_opts,None); 37 | * // generate a private key 38 | * let secp = Secp256k1::new(); 39 | * let from_key = PrivateKey::from_words(&secp,"your secret words",0,0)?; 40 | * let from_public_key = from_key.public_key(&secp); 41 | * // generate the message SEND 1000 uluna from your private key to someone else 42 | * let coin: Coin = Coin::parse("1000uluna")?.unwrap(); 43 | * let from_account = from_public_key.account()?; 44 | * let send: Message = MsgSend::create(from_account, String::from("terra1usws7c2c6cs7nuc8vma9qzaky5pkgvm2uag6rh"), vec![coin])?; 45 | * // generate the transaction & calc fees 46 | * let messages: Vec = vec![send]; 47 | * let (std_sign_msg, sigs) = terra 48 | * .generate_transaction_to_broadcast( 49 | * &secp, 50 | * &from_key, 51 | * messages, 52 | * None 53 | * ) 54 | * .await?; 55 | * // send it out 56 | * let resp = terra.tx().broadcast_sync(&std_sign_msg, &sigs).await?; 57 | * match resp.code { 58 | * Some(code) => { 59 | * log::error!("{}", serde_json::to_string(&resp)?); 60 | * eprintln!("Transaction returned a {} {}", code, resp.txhash) 61 | * } 62 | * None => { 63 | * println!("{}", resp.txhash) 64 | * } 65 | * } 66 | * Ok(()) 67 | * } 68 | * ``` 69 | */ 70 | /// address book definition 71 | pub mod addressbook; 72 | /// APIs 73 | pub mod client; 74 | /// Error Messages 75 | pub mod errors; 76 | mod keys; 77 | /// definitions of the different type of Messages we have implemented 78 | pub mod messages; 79 | 80 | #[macro_use] 81 | extern crate lazy_static; 82 | #[macro_use] 83 | extern crate erased_serde; 84 | //extern crate rustc_serialize; 85 | // 86 | //#[macro_use] 87 | //extern crate error_chain; 88 | extern crate reqwest; 89 | //use crate::Error; 90 | 91 | pub use crate::client_types::{ 92 | terra_datetime_format, terra_decimal_format, terra_f64_format, terra_opt_decimal_format, 93 | terra_opt_u64_format, terra_u64_format, 94 | }; 95 | pub use addressbook::AddressBook; 96 | pub use client::lcd_types::{LCDResult, LCDResultVec, LCDTypeValue}; 97 | pub use client::{auth_types, client_types, core_types, staking_types, tendermint_types}; 98 | pub use client::{GasOptions, Terra}; 99 | pub use keys::{PrivateKey, PublicKey, Signature}; 100 | pub use messages::bank; 101 | pub use messages::wasm::MsgExecuteContract; 102 | pub use messages::Message; 103 | -------------------------------------------------------------------------------- /Smart-Contract-dev.md: -------------------------------------------------------------------------------- 1 | # Terra Rust - How to use in smart contract development 2 | get the package ;-) 3 | 4 | `cargo install terra-rust` 5 | 6 | ## Setup your repo 7 | 1. In your contract directory create a '.env' file similar to the [.env.default](.env.default) 8 | 2. install 'run-script' ```cargo install run-script``` 9 | 10 | 3. create a .cargo file with the following 11 | ```toml 12 | [alias] 13 | wasm = "build --release --target wasm32-unknown-unknown" 14 | unit-test = "test --lib" 15 | schema = "run --example schema" 16 | optimize = "run-script optimize" 17 | optimize-w32 = "run-script optimize-w32" 18 | store = "run-script store" 19 | instantiate = "run-script instantiate" 20 | migrate = "run-script migrate" 21 | ``` 22 | 4. in your `Cargo.toml` file add 23 | ```toml 24 | [package.metadata.scripts] 25 | optimize = """docker run --rm -v "$(pwd)":/code \ 26 | --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ 27 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 28 | cosmwasm/rust-optimizer:0.12.3""" 29 | optimize-w32 = """docker run --rm -v c:\\:/code \ 30 | --mount type=volume,source=project_name_cache,target=/code/target \ 31 | --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ 32 | cosmwasm/rust-optimizer:0.12.3""" 33 | store = """terra-rust code store test-owner ..\\..\\artifacts\\xxx.wasm""" 34 | instantiate = """terra-rust code instantiate test-owner ..\\..\\artifacts\\xxx.wasm .\\pool_init.json --admin same""" 35 | migrate = """terra-rust code migrate test-owner ..\\..\\artifacts\\xxx.wasm """ 36 | 37 | ``` 38 | In the above example, you will need to hard code your path in optimize-w32, and ideally make the project_name_cache unique for your contract 39 | also I have used the 'test-owner' account as the 'owner/admin' of the contract. ('same' just means use the same account as the owner) 40 | 41 | The pool_init.json is the 'init' json file ... 42 | The code will replace `##SENDER##` `##CODE_ID##` `###NEW_CODE_ID###` and `##ADMIN##` fields with their respective values 43 | 44 | The migrate command takes a json file as well, but defaults to '{}' 45 | 46 | The first time you instantiate your code, you will be given a contract. put that in your [.env](.env.default) as **TERRARUST_CONTRACT** file so migrates are easier. 47 | **TERRARUST_GAS_DENOM**=uusd might be useful as well. 48 | 49 | 50 | ## Setup your environment 51 | 52 | ### create a test wallet to store all your test keys in. 53 | 1. `terra-rust create testing` 54 | 2. `terra-rust --wallet testing keys new xxx` (if you are in the .env directory the wallet can be filled in from the .env file) 55 | 3. probably go to [faucet](https://faucet.terra.money/) and put some $ into them? 56 | 57 | # tools 58 | * [terra-exec](src/bin/terra_exec.rs) is a command line tool to make contract exec really simple. 59 | * [terra-query](src/bin/terra_query.rs) is a command line tool to make contract query really simple. 60 | 61 | 62 | ## terra_exec 63 | set **TERRARUST_CONTRACT** in your environment, usually via placing a line in your .env file. 64 | 65 | to query (notice the escaped double quotes) 66 | ``` 67 | 68 | C:> terra-query --contract terra13fs83g5atgjwuh7c5ydzh6n7gecel6xyhhy2t5 '{\"token_info\":{}}' 69 | 70 | ``` 71 | 72 | to exec a smart contract method add the 'sender' 73 | ``` 74 | C:> terra-exec --sender test-owner --contract terra13fs83g5atgjwuh7c5ydzh6n7gecel6xyhhy2t5 '{\"set_minter\":\"terra1fmqjnum0ftyuc72mapg3wqpf9m7jwdtnkp7960\"}' 75 | 76 | ``` 77 | # How can you help? 78 | If you think this was useful, feel free to delegate to the [PFC](https://station.terra.money/validator/terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q) validator. It will help defray the costs. 79 | 80 | Feel free to submit patches/comments/Pull Requests. 81 | 82 | We have also set up a [Discord](https://discord.gg/zKVWs4HhJD) channel to discuss this, and other PFC things 83 | -------------------------------------------------------------------------------- /src/bin/terra_exec.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Arg, ArgMatches}; 3 | use dotenv::dotenv; 4 | use secp256k1::Secp256k1; 5 | use terra_rust_api::core_types::Coin; 6 | use terra_rust_api::{Message, MsgExecuteContract}; 7 | use terra_rust_cli::cli_helpers; 8 | //use tokio::runtime::Handle; 9 | 10 | /// VERSION number of package 11 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 12 | /// NAME of package 13 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 14 | 15 | async fn run() -> Result<()> { 16 | let app = cli_helpers::gen_cli("terra exec", "terra-exec").args(&[ 17 | Arg::new("contract") 18 | .long("contract") 19 | .takes_value(true) 20 | .value_name("contract") 21 | .env("TERRARUST_CONTRACT") 22 | .required(true) 23 | .help("the contract address"), 24 | Arg::new("coins") 25 | .long("coins") 26 | .takes_value(true) 27 | .value_name("coins") 28 | .required(false) 29 | .help("coins you want to send (optional)"), 30 | Arg::new("json") 31 | .takes_value(true) 32 | .value_name("json") 33 | .required(true) 34 | .help("json string"), 35 | ]); 36 | 37 | Ok(run_it(&app.get_matches()).await?) 38 | } 39 | pub async fn run_it(cli: &ArgMatches) -> Result<()> { 40 | //let cli = app.get_matches(); 41 | 42 | // let wallet = cli_helpers::wallet_from_args(&cli)?; 43 | let terra = cli_helpers::lcd_from_args(cli).await?; 44 | 45 | let json_str = cli.value_of("json").expect("json be in the CLI"); 46 | //let json_str = cli.value_of("json").expect("json be in the CLI"); 47 | let coins_str = cli.value_of("coins"); 48 | let contract = cli.value_of("contract").expect("Need a contract"); 49 | let secp = Secp256k1::new(); 50 | let from_key = cli_helpers::get_private_key(&secp, cli)?; 51 | let from_public_key = from_key.public_key(&secp); 52 | let sender_account = from_public_key.account()?; 53 | let wallet = cli_helpers::wallet_from_args(cli)?; 54 | let seed = cli_helpers::seed_from_args(cli); 55 | let json: serde_json::Value = cli_helpers::get_json_block_expanded( 56 | json_str, 57 | Some(sender_account), 58 | &secp, 59 | Some(wallet), 60 | seed, 61 | None, 62 | false, 63 | )?; 64 | 65 | let coins = if let Some(coins) = coins_str { 66 | Coin::parse_coins(coins)? 67 | } else { 68 | vec![] 69 | }; 70 | 71 | let exec_message = MsgExecuteContract::create_from_value( 72 | &from_public_key.account()?, 73 | contract, 74 | &json, 75 | &coins, 76 | )?; 77 | let messages: Vec = vec![exec_message]; 78 | 79 | let resp = terra 80 | .submit_transaction_sync( 81 | &secp, 82 | &from_key, 83 | messages, 84 | Some(format!( 85 | "PFC-{}/{}", 86 | NAME.unwrap_or("TERRARUST"), 87 | VERSION.unwrap_or("DEV") 88 | )), 89 | ) 90 | .await?; 91 | 92 | log::debug!("{:?}", &resp.txhash); 93 | if terra.chain_id.contains("bombay") { 94 | println!( 95 | "https://finder.extraterrestrial.money/testnet/tx/{}", 96 | resp.txhash 97 | ); 98 | } else { 99 | println!( 100 | "https://finder.extraterrestrial.money/mainnet/tx/{}", 101 | resp.txhash 102 | ); 103 | } 104 | 105 | Ok(()) 106 | } 107 | #[tokio::main] 108 | async fn main() { 109 | dotenv().ok(); 110 | env_logger::init(); 111 | 112 | if let Err(ref err) = run().await { 113 | eprintln!("{}", err); 114 | //log::error!("{}", err); 115 | err.chain() 116 | .skip(1) 117 | .for_each(|cause| log::error!("because: {}", cause)); 118 | 119 | // The backtrace is not always generated. Try to run this example 120 | // with `$env:RUST_BACKTRACE=1`. 121 | // if let Some(backtrace) = e.backtrace() { 122 | // log::debug!("backtrace: {:?}", backtrace); 123 | // } 124 | 125 | ::std::process::exit(1); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/oracle.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | //use crate::errors::Result; 5 | //use crate::keys::get_private_key; 6 | use crate::{NAME, VERSION}; 7 | use secp256k1::Secp256k1; 8 | use terra_rust_api::client::oracle::Voters; 9 | use terra_rust_api::messages::oracle::MsgDelegateFeedConsent; 10 | use terra_rust_api::messages::Message; 11 | use terra_rust_wallet::Wallet; 12 | 13 | #[derive(Subcommand)] 14 | enum OracleEnum { 15 | #[clap(name = "parameters", about = "Get Oracle Parameters")] 16 | Parameters, 17 | #[clap( 18 | name = "set-feeder", 19 | about = "set account that can submit exchange rate updates on behalf of your validator" 20 | )] 21 | SetFeeder { 22 | /// validator account (specify the key name in the wallet) 23 | validator: String, 24 | /// the delegate account 25 | #[clap(name = "delegate", help = "The account name of the feeder account")] 26 | delegate: String, 27 | }, 28 | #[clap(name = "voters", about = "commands related to exchange rate voting")] 29 | Voters { 30 | /// validator account (the terravaloper one) 31 | validator: String, 32 | #[clap(subcommand)] 33 | cmd: VotersCommand, 34 | }, 35 | } 36 | #[derive(Subcommand)] 37 | pub enum VotersCommand { 38 | Votes, 39 | PreVotes, 40 | Feeder, 41 | Miss, 42 | AggregatePreVote, 43 | AggregateVote, 44 | } 45 | 46 | #[derive(Parser)] 47 | /// Oracle Transactions 48 | pub struct OracleCommand { 49 | #[clap(subcommand)] 50 | command: OracleEnum, 51 | } 52 | impl OracleCommand { 53 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 54 | match self.command { 55 | OracleEnum::Parameters => { 56 | let resp = terra.oracle().parameters().await?; 57 | 58 | println!("{}", serde_json::to_string(&resp)?) 59 | } 60 | OracleEnum::SetFeeder { 61 | validator, 62 | delegate, 63 | } => { 64 | println!("Set Feeder {}", delegate); 65 | let secp = Secp256k1::new(); 66 | let from_key = wallet.get_private_key(&secp, &validator, seed)?; 67 | let from_public_key = from_key.public_key(&secp); 68 | let from_operator = from_public_key.operator_address()?; 69 | let delegate_msg = MsgDelegateFeedConsent::create(from_operator, delegate)?; 70 | 71 | let messages: Vec = vec![delegate_msg]; 72 | let resp = terra 73 | .submit_transaction_sync( 74 | &secp, 75 | &from_key, 76 | messages, 77 | Some(format!( 78 | "PFC-{}/{}", 79 | NAME.unwrap_or("TERRARUST"), 80 | VERSION.unwrap_or("DEV") 81 | )), 82 | ) 83 | .await?; 84 | 85 | println!("{}", resp.txhash); 86 | log::info!("{}", resp.raw_log); 87 | } 88 | OracleEnum::Voters { validator, cmd } => { 89 | let voter = terra.oracle().voters(&validator); 90 | voter_cmd_parse(&voter, wallet, seed, cmd).await?; 91 | } 92 | } 93 | Ok(()) 94 | } 95 | } 96 | pub async fn voter_cmd_parse<'a>( 97 | voters: &Voters<'a>, 98 | _wallet: &Wallet<'a>, 99 | _seed: Option<&str>, 100 | cmd: VotersCommand, 101 | ) -> Result<()> { 102 | match cmd { 103 | VotersCommand::Votes => { 104 | let x = voters.votes().await?; 105 | println!("{:#?}", x) 106 | } 107 | VotersCommand::PreVotes => { 108 | let x = voters.prevotes().await?; 109 | println!("{:#?}", x) 110 | } 111 | VotersCommand::Feeder => { 112 | let x = voters.feeder().await?; 113 | println!("{:#?}", x) 114 | } 115 | VotersCommand::Miss => { 116 | let x = voters.miss().await?; 117 | println!("{:#?}", x) 118 | } 119 | VotersCommand::AggregatePreVote => { 120 | todo!() 121 | } 122 | VotersCommand::AggregateVote => { 123 | todo!() 124 | } 125 | } 126 | Ok(()) 127 | } 128 | -------------------------------------------------------------------------------- /src/terra_rust.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | CLI for terrad networks 3 | */ 4 | // (Buf) Uncomment these lines to have the output buffered, this can provide 5 | // better performance but is not always intuitive behaviour. 6 | // use std::io::BufWriter; 7 | #![warn(missing_docs)] 8 | use clap::{Parser, Subcommand}; 9 | use dotenv::dotenv; 10 | mod bank; 11 | mod contract; 12 | 13 | mod auth; 14 | 15 | mod code; 16 | mod distribution; 17 | mod fcd; 18 | mod keys; 19 | mod market; 20 | mod oracle; 21 | mod rpc; 22 | mod slashing; 23 | mod staking; 24 | mod tendermint; 25 | mod tx; 26 | mod validator; 27 | mod wallet; 28 | mod wasm; 29 | 30 | use crate::auth::AuthCommand; 31 | use crate::bank::BankCommand; 32 | use crate::code::CodeCommand; 33 | use crate::contract::ContractCommand; 34 | use crate::distribution::DistributionCommand; 35 | use crate::fcd::FCDCommand; 36 | use crate::keys::KeysCommand; 37 | use crate::market::MarketCommand; 38 | use crate::oracle::OracleCommand; 39 | use crate::rpc::RPCCommand; 40 | use crate::slashing::SlashingCommand; 41 | use crate::staking::StakingCommand; 42 | use crate::tendermint::{BlockCommand, ValidatorSetsCommand}; 43 | use crate::tx::TxCommand; 44 | use crate::validator::ValidatorCommand; 45 | use crate::wallet::WalletCommand; 46 | use crate::wasm::WasmCommand; 47 | use terra_rust_api::{GasOptions, Terra}; 48 | use terra_rust_cli::cli_helpers; 49 | use terra_rust_wallet::Wallet; 50 | 51 | /// VERSION number of package 52 | /// 53 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 54 | /// NAME of package 55 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 56 | 57 | #[derive(Subcommand)] 58 | #[allow(clippy::upper_case_acronyms)] 59 | enum Command { 60 | Keys(KeysCommand), 61 | Validator(ValidatorCommand), 62 | Market(MarketCommand), 63 | Auth(AuthCommand), 64 | Wallet(WalletCommand), 65 | Bank(BankCommand), 66 | Oracle(OracleCommand), 67 | Block(BlockCommand), 68 | Tx(TxCommand), 69 | Slashing(SlashingCommand), 70 | Staking(StakingCommand), 71 | Distribution(DistributionCommand), 72 | Contract(ContractCommand), 73 | ValidatorSets(ValidatorSetsCommand), 74 | RPC(RPCCommand), 75 | FCD(FCDCommand), 76 | WASM(WasmCommand), 77 | CODE(CodeCommand), 78 | } 79 | 80 | async fn run() -> anyhow::Result<()> { 81 | let cli = cli_helpers::Cli::::parse(); 82 | 83 | let gas_opts: GasOptions = cli.gas_opts().await?; 84 | let t = Terra::lcd_client( 85 | &cli.lcd, 86 | &cli.chain_id, 87 | &gas_opts, 88 | Some(cli.debug.into_inner()), 89 | ); 90 | let seed: Option<&str> = if cli.seed.is_empty() { 91 | None 92 | } else { 93 | Some(&cli.seed) 94 | }; 95 | let wallet = Wallet::create(&cli.wallet); 96 | match cli.cmd { 97 | Command::Keys(key_cmd) => key_cmd.parse(&wallet, seed), 98 | Command::Bank(bank_cmd) => bank_cmd.parse(&t, &wallet, seed).await, 99 | Command::Oracle(cmd) => cmd.parse(&t, &wallet, seed).await, 100 | Command::Validator(cmd) => cmd.parse(&t, &wallet, seed).await, 101 | Command::Block(cmd) => cmd.parse(&t).await, 102 | Command::Contract(cmd) => cmd.parse(&t).await, 103 | Command::Market(cmd) => cmd.parse(&t, &wallet, seed).await, 104 | Command::Tx(cmd) => cmd.parse(&t).await, 105 | Command::Auth(auth_cmd) => auth_cmd.parse(&t, &wallet, seed).await, 106 | Command::Wallet(cmd) => cmd.parse(&wallet), 107 | Command::Slashing(cmd) => cmd.parse(&t, &wallet, seed).await, 108 | Command::Staking(cmd) => cmd.parse(&t, &wallet, seed).await, 109 | Command::Distribution(cmd) => cmd.parse(&t, &wallet, seed).await, 110 | Command::ValidatorSets(cmd) => cmd.parse(&t).await, 111 | Command::RPC(cmd) => cmd.parse(&t).await, 112 | Command::FCD(cmd) => cmd.parse(&t, &cli.fcd).await, 113 | Command::WASM(cmd) => cmd.parse(&t).await, 114 | Command::CODE(cmd) => cmd.parse(&t, &wallet, seed).await, 115 | } 116 | } 117 | #[tokio::main] 118 | async fn main() { 119 | dotenv().ok(); 120 | env_logger::init(); 121 | 122 | if let Err(ref err) = run().await { 123 | log::error!("{}", err); 124 | err.chain() 125 | .skip(1) 126 | .for_each(|cause| log::error!("because: {}", cause)); 127 | 128 | // The backtrace is not always generated. Try to run this example 129 | // with `$env:RUST_BACKTRACE=1`. 130 | // if let Some(backtrace) = e.backtrace() { 131 | // log::debug!("backtrace: {:?}", backtrace); 132 | // } 133 | 134 | ::std::process::exit(1); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/tendermint.rs: -------------------------------------------------------------------------------- 1 | use crate::client::tendermint_types::{BlockResult, ValidatorSetResult}; 2 | use crate::errors::TerraRustAPIError::TendermintValidatorSet; 3 | use crate::{LCDResult, Terra}; 4 | 5 | pub struct Tendermint<'a> { 6 | terra: &'a Terra, 7 | } 8 | impl Tendermint<'_> { 9 | pub fn create(terra: &'_ Terra) -> Tendermint<'_> { 10 | Tendermint { terra } 11 | } 12 | /// get the latest block 13 | pub async fn blocks(&self) -> anyhow::Result { 14 | let response = self 15 | .terra 16 | .send_cmd::("/blocks/latest", None) 17 | .await?; 18 | Ok(response) 19 | } 20 | /// get a block at a specific height 21 | pub async fn blocks_at_height(&self, height: u64) -> anyhow::Result { 22 | let response = self 23 | .terra 24 | .send_cmd::(&format!("/blocks/{}", height), None) 25 | .await?; 26 | Ok(response) 27 | } 28 | 29 | /// get the latest validatorset 30 | /// @warn the maximum limit (at time of development is 100) 31 | pub async fn validatorsets( 32 | &self, 33 | page: usize, 34 | limit: usize, 35 | ) -> anyhow::Result> { 36 | let args = if page == 0 { 37 | format!("?limit={}", limit) 38 | } else { 39 | format!("?page={}&limit={}", page, limit) 40 | }; 41 | let response = self 42 | .terra 43 | .send_cmd::>("/validatorsets/latest", Some(&args)) 44 | .await?; 45 | Ok(response) 46 | } 47 | /// get the latest full validatorset 48 | /// 49 | pub async fn validatorsets_full(&self) -> anyhow::Result> { 50 | // the interesting thing here is that the height returned is not available for the 2nd one.. so need to fire them off at the same time. 51 | let part_1_f = self.validatorsets(1, 100); 52 | let part_2 = self.validatorsets(2, 100).await?; 53 | let part_1 = part_1_f.await?; 54 | if part_1.result.block_height != part_2.result.block_height { 55 | return Err(TendermintValidatorSet( 56 | part_1.result.block_height, 57 | part_2.result.block_height, 58 | ) 59 | .into()); 60 | } 61 | let mut combined = part_1.result.validators; 62 | combined.extend(part_2.result.validators); 63 | let vs_combined = ValidatorSetResult { 64 | block_height: part_1.result.block_height, 65 | validators: combined, 66 | }; 67 | Ok(LCDResult { 68 | height: part_1.height, 69 | result: vs_combined, 70 | }) 71 | } 72 | /// get the full validatorset at a certain height 73 | /// 74 | pub async fn validatorsets_full_at_height( 75 | &self, 76 | height: u64, 77 | ) -> anyhow::Result> { 78 | let part_1 = self.validatorsets_at_height(height, 1, 100).await?; 79 | let part_2 = self.validatorsets_at_height(height, 2, 100).await?; 80 | if part_1.result.block_height != part_2.result.block_height { 81 | return Err(TendermintValidatorSet( 82 | part_1.result.block_height, 83 | part_2.result.block_height, 84 | ) 85 | .into()); 86 | } 87 | let mut combined = part_1.result.validators; 88 | combined.extend(part_2.result.validators); 89 | let vs_combined = ValidatorSetResult { 90 | block_height: part_1.result.block_height, 91 | validators: combined, 92 | }; 93 | Ok(LCDResult { 94 | height: part_1.height, 95 | result: vs_combined, 96 | }) 97 | } 98 | 99 | /// get a validatorset at a specific height 100 | /// @warn the maximum limit (at time of development is 100) 101 | pub async fn validatorsets_at_height( 102 | &self, 103 | height: u64, 104 | page: usize, 105 | limit: usize, 106 | ) -> anyhow::Result> { 107 | let args = if page == 0 { 108 | format!("?limit={}", limit) 109 | } else { 110 | format!("?page={}&limit={}", page, limit) 111 | }; 112 | let response = self 113 | .terra 114 | .send_cmd::>( 115 | &format!("/validatorsets/{}", height), 116 | Some(&args), 117 | ) 118 | .await?; 119 | Ok(response) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/validator.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | use terra_rust_wallet::Wallet; 5 | 6 | use secp256k1::Secp256k1; 7 | 8 | #[derive(Subcommand)] 9 | pub enum VoterCommand { 10 | Feeder, 11 | Miss, 12 | PreVote, 13 | Vote, 14 | AggregatorPreVote, 15 | AggregatorVote, 16 | } 17 | 18 | /// validator operations 19 | #[derive(Parser)] 20 | pub struct ValidatorCommand { 21 | #[clap(subcommand)] 22 | command: ValidatorEnum, 23 | } 24 | #[derive(Subcommand)] 25 | pub enum ValidatorEnum { 26 | #[clap(name = "list")] 27 | // list all validators. Including Jailed ones 28 | List, 29 | #[clap(name = "describe")] 30 | Describe { 31 | #[clap(name = "validator", help = "the validator's terravaloper address")] 32 | // the validator to get more info on. hint: use the terravaloper address. try terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q 33 | validator: String, 34 | }, 35 | #[clap(name = "moniker")] 36 | Moniker { 37 | #[clap(name = "moniker", help = "the validator's moniker")] 38 | /// the validator to get more info on. try PFC 39 | moniker: String, 40 | }, 41 | #[clap(name = "delegations")] 42 | Delegations { 43 | #[clap(name = "validator", help = "the validator's terravaloper address")] 44 | // the validator to get more info on. hint: use the terravaloper address. try terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q 45 | validator: String, 46 | }, 47 | #[clap(name = "unbonding")] 48 | Unbonding { 49 | #[clap(name = "validator", help = "the validator's terravaloper address")] 50 | // the validator to get more info on. hint: use the terravaloper address. try terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q 51 | validator: String, 52 | }, 53 | 54 | #[clap(name = "voter")] 55 | Voters { 56 | /// the validator to get more info on. hint: use the terravaloper address. try terravaloper12g4nkvsjjnl0t7fvq3hdcw7y8dc9fq69nyeu9q 57 | validator: String, 58 | #[clap(subcommand)] 59 | cmd: VoterCommand, 60 | }, 61 | } 62 | impl ValidatorCommand { 63 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 64 | match self.command { 65 | ValidatorEnum::List => { 66 | let list = terra.staking().validators().await?; 67 | println!("{:#?}", list.result); 68 | } 69 | ValidatorEnum::Describe { validator } => { 70 | let account_id = if !validator.starts_with("terravaloper1") { 71 | let secp = Secp256k1::new(); 72 | wallet 73 | .get_public_key(&secp, &validator, seed)? 74 | .operator_address()? 75 | } else { 76 | validator 77 | }; 78 | let v = terra.staking().validator(&account_id).await?; 79 | println!("{:#?}", v.result); 80 | } 81 | ValidatorEnum::Moniker { moniker } => { 82 | let v = terra.staking().validator_by_moniker(&moniker).await?; 83 | println!("{:#?}", v); 84 | } 85 | ValidatorEnum::Delegations { validator } => { 86 | let account_id = if !validator.starts_with("terravaloper1") { 87 | let secp = Secp256k1::new(); 88 | wallet 89 | .get_public_key(&secp, &validator, seed)? 90 | .operator_address()? 91 | } else { 92 | validator 93 | }; 94 | let v = terra.staking().validator_delegations(&account_id).await?; 95 | println!("{:#?}", v.result); 96 | } 97 | ValidatorEnum::Unbonding { validator } => { 98 | let account_id = if !validator.starts_with("terravaloper1") { 99 | let secp = Secp256k1::new(); 100 | wallet 101 | .get_public_key(&secp, &validator, seed)? 102 | .operator_address()? 103 | } else { 104 | validator 105 | }; 106 | let v = terra 107 | .staking() 108 | .validator_unbonding_delegations(&account_id) 109 | .await?; 110 | println!("{:#?}", v.result); 111 | } 112 | 113 | ValidatorEnum::Voters { .. } => { 114 | todo!(); 115 | } 116 | } 117 | Ok(()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /examples/sign_message.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use dotenv::dotenv; 3 | use rust_decimal::Decimal; 4 | use secp256k1::Secp256k1; 5 | use serde::Serialize; 6 | use terra_rust_api::core_types::Coin; 7 | use terra_rust_api::terra_u64_format; 8 | use terra_rust_api::{Message, MsgExecuteContract}; 9 | use terra_rust_wallet::Wallet; 10 | 11 | /// VERSION number of package 12 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 13 | /// NAME of package 14 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 15 | 16 | #[derive(Parser)] 17 | struct Cli { 18 | // Wallet name 19 | #[clap( 20 | name = "wallet", 21 | env = "TERRARUST_WALLET", 22 | default_value = "default", 23 | short, 24 | long = "wallet", 25 | help = "the default wallet to look for keys in" 26 | )] 27 | wallet: String, 28 | #[clap( 29 | name = "seed", 30 | env = "TERRARUST_SEED_PHRASE", 31 | default_value = "", 32 | short, 33 | long = "seed", 34 | help = "the seed phrase to use with this private key" 35 | )] 36 | seed: String, 37 | 38 | #[clap(name = "from", help = "the account to sign from")] 39 | from: String, 40 | #[clap(name = "message", help = "The address of the contract")] 41 | message: String, 42 | } 43 | 44 | async fn run() -> anyhow::Result<()> { 45 | let cli: Cli = Cli::parse(); 46 | 47 | let secp = Secp256k1::new(); 48 | let wallet = Wallet::create(&cli.wallet); 49 | 50 | let seed: Option<&str> = if cli.seed.is_empty() { 51 | None 52 | } else { 53 | Some(&cli.seed) 54 | }; 55 | //let msg = r#"random/{"token_uri":"https://www.merriam-webster.com/dictionary/token4","image":null,"image_data":null,"external_url":null,"description":null,"name":null,"attributes":[{"display_type":null,"trait_type":"gender","value":"female"},{"display_type":null,"trait_type":"name","value":"James T. Kirk"}],"background_color":null,"animation_url":null,"youtube_url":null}"#; 56 | let msg = cli.message; 57 | let from_key = wallet.get_private_key(&secp, &cli.from, seed)?; 58 | // let signature = from_key.sign(&secp, &cli.message)?; 59 | let signature = from_key.sign(&secp, &msg)?; 60 | 61 | println!("Message={}", &msg); 62 | println!("Signature={}", signature.signature); 63 | println!("PubKeySig={}", signature.pub_key.value); 64 | 65 | Ok(()) 66 | } 67 | 68 | #[tokio::main] 69 | async fn main() { 70 | dotenv().ok(); 71 | env_logger::init(); 72 | 73 | if let Err(ref err) = run().await { 74 | log::error!("{}", err); 75 | err.chain() 76 | .skip(1) 77 | .for_each(|cause| log::error!("because: {}", cause)); 78 | 79 | // The backtrace is not always generated. Try to run this example 80 | // with `$env:RUST_BACKTRACE=1`. 81 | // if let Some(backtrace) = e.backtrace() { 82 | // log::debug!("backtrace: {:?}", backtrace); 83 | // } 84 | 85 | ::std::process::exit(1); 86 | } 87 | } 88 | 89 | #[derive(Serialize, Debug)] 90 | pub struct MsgSwapNativeToken { 91 | pub denom: String, 92 | } 93 | #[derive(Serialize, Debug)] 94 | pub struct MsgSwapOfferInfo { 95 | pub native_token: MsgSwapNativeToken, 96 | } 97 | #[derive(Serialize, Debug)] 98 | pub struct MsgSwapOfferAsset { 99 | #[serde(with = "terra_u64_format")] 100 | pub amount: u64, 101 | pub info: MsgSwapOfferInfo, 102 | } 103 | #[derive(Serialize, Debug)] 104 | pub struct MsgSwapData { 105 | pub belief_price: Decimal, 106 | pub max_spread: Decimal, 107 | pub offer_asset: MsgSwapOfferAsset, 108 | } 109 | #[derive(Serialize, Debug)] 110 | /// Message: Swap 111 | pub struct MsgSwap { 112 | pub swap: MsgSwapData, 113 | } 114 | 115 | impl MsgSwap { 116 | pub fn create( 117 | sender: &str, 118 | contract: &str, 119 | belief_price: Decimal, 120 | max_spread: Decimal, 121 | amount: u64, 122 | denom: &str, 123 | coins: &Vec, 124 | ) -> anyhow::Result { 125 | let offer_info = MsgSwapOfferInfo { 126 | native_token: MsgSwapNativeToken { 127 | denom: denom.to_string(), 128 | }, 129 | }; 130 | let offer_asset = MsgSwapOfferAsset { 131 | amount, 132 | info: offer_info, 133 | }; 134 | 135 | let swap_data = MsgSwapData { 136 | belief_price, 137 | max_spread, 138 | offer_asset, 139 | }; 140 | let swap = MsgSwap { swap: swap_data }; 141 | let swap_json = serde_json::to_string(&swap)?; 142 | Ok(MsgExecuteContract::create_from_json( 143 | sender, contract, &swap_json, coins, 144 | )?) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /terra-rust-api/Changelog.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | ## Debt 3 | ### support Variant::Bech32m in keygen? (https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) 4 | ## 1.2 5 | ### 1.2.20 - 20-Mar-22 6 | * Block result can return a different datetime string format. 7 | ### 1.2.19 - 16-Mar-22 8 | * change annoying info message to debug 9 | ### 1.2.18 - 8-Mar-22 10 | * Add Signature::verify API call (just a wrapper to secp) 11 | ### 1.2.17 -25-Feb-22 12 | * validators_at_height() function 13 | * RPCValidatorUpdate / power is field is not used at the moment.. so just ignore it 14 | ### 1.2.13 - 25-Feb-22 15 | * V1 hash retrieval has different error code when hash not found 16 | * rustc_serialize removed and using regular base64 module 17 | * clippy warnings (minor) 18 | ### 1.2.12 19 | * add V1 hash retrieval. 20 | ### 1.2.11 - 20-Feb-22 21 | * fix TxNotFound error 22 | ### 1.2.9 23 | * move store/instantiate/migrate commands to API (from cli) 24 | ### 1.2.8 - 17-Feb-22 25 | * upgraded to bitcoin 0.27.1 26 | * changed Secp256 args to be a bit more minimalistic (in prep for Secp256 0.21.x) 27 | * switching errors from anyhow::Result to TerraRustApiError (still compatible) 28 | ### 1.2.4 - 16-Feb-22 29 | * Serialize some structures 30 | * new fn to get delegators for a validator with a limit 31 | * Error messages converted to use TerraRustAPIError 32 | ### 1.2.3 - 14-Feb-22 33 | * PR#8 : use default for sequence deserialize thanks @patternmachine! 34 | ### 1.2.2 - 6-Feb-22 35 | * Block Result txs_result can be null 36 | ### 1.2.1 - 31-Jan-22 37 | * TX block doesn't require contract/execute_msg parts when it is returned (backwards incompatible) 38 | * MsgMigrateContract message added 39 | ## 1.1 40 | ### 1.1.8 - 17-Dec-21 41 | * extra fields in SyncInfo area in RPC 42 | * added helper function to EventAttributes 43 | * added serialization to some structures 44 | ### 1.1.3 - 16-Dec-21 45 | * RPC Block/Block_Result api 46 | * New deserializer for base64 encoded strings, base64_encoded_format & base64_opt_encoded_format 47 | ### 1.1.2 - 14-Dec-21 48 | * parse IBC coins 49 | * tx.get_txs_in_block() function 50 | ### 1.1.1 - 22-Nov-21 51 | * PR #6 - Thanks @Pronvis 52 | * PR #6 - return Result in most cases 53 | * PR #6 - better usage of async/await 54 | * PR #6 - lifetime fixes 55 | ## 1.0 56 | ### 1.0.12 - 3-Nov-21 57 | * add 'tx' component of TxResult 58 | ### 1.0.11 - 28-Oct-21 59 | * tx attribute finder helper function 60 | ### 1.0.10 - 27-Oct-21 61 | * tx.timestamp now visible 62 | * tx.get_events() helper function 63 | ### 1.0.9 - 26-Oct-21 64 | * tx.logs can be null 65 | ### 1.0.8 - 9-Oct-21 66 | * WASM Exec contract working again - (Thx @sambarboza) 67 | * HTTP responses now TerraLCDResponse error types 68 | * TX response work. 69 | * new example 'set_code' .. instantiate code TBD 70 | * Error's returned are now TerraRustAPIError 71 | ### 1.0.7 - 2-Oct-21 72 | * fix up some documentation links (Thx @PSchlutermann) 73 | ### 1.0.6 - 1-Oct-21 74 | * fix up estimation/can send basic txs 75 | ### 1.0.1 - 30-Sep-21 76 | * Switch to Col-5 77 | ## 0.3 78 | ### 0.3.8 - (never released) 79 | * parse tendermint hex-addresses and display tendermint_address() 80 | * API function PublicKey::from_tendermint_address() 81 | ### 0.3.7 - 17-Sep-21 82 | * new API(s) tendermint.validatorsets_full(_at_height) - fetch ALL the validator, bypassing the hardcoded limit 83 | ### 0.3.6 - 16-Sep-21 84 | * Add 'Clone' to tendermint/validator 85 | ### 0.3.4 - 13-Sep-21 86 | * TX Block Attributes can have 'value' as null 87 | * Add 'Clone' to various structures 88 | ### 0.3.3 - 27-Aug-21 89 | * tendermint_types:Block structure can have 'data' as null. 90 | ### 0.3.2 - 24-Aug-21 91 | * Switched LCD query responses to generic type (API Change) 92 | * RPC endpoint API started 93 | * FCD endpoint API started 94 | * can now fetch gas prices from FCD 95 | * add Eq/ToString to NodeIpPort 96 | ## 0.2 97 | ### 0.2.9 - 18-Aug-21 98 | * Add slashing/Unjail 99 | * Add market/Swap 100 | * Add "sweep" function to mass swap tokens above a certain threshold 101 | ### 0.2.8 - not usable 102 | ### 0.2.7 - 12-Aug-21 103 | * Add timezone support (used on tendermint dates in addressbook) 104 | * Add functions to fetch address book 105 | * Add functions to display delegations/undelegations 106 | * Helper functions submit_transaction_{async|sync} 107 | * Add messages for delegator rewards, and around staking/unstaking 108 | 109 | ### 0.2.6 - 29-July-21 110 | * PR #2 linux build by [@snoberg](https://github.com/snoyberg) 111 | * PR #3 ability to use rusttls [@snoberg](https://github.com/snoyberg) 112 | * clippy warnings 113 | ### 0.2.5 - 8-July-21 114 | * added some more documentation 115 | * [BUG] Tendermint/blocks - signatures can be null 116 | * added tendermint /validatorsets API call 117 | * added terra_i64_format 118 | ### 0.2.4 - 8-July-21 119 | * Switch to thiserror (api) & anyhow (command) error handling 120 | * EditValidator Message working 121 | ### 0.2.3 - 1-Jun-21 122 | * contract execution/queries 123 | * terra_opt_u64_format / Option json formatting 124 | 125 | ### 0.2.1 - 24-May-21 126 | * Restructured messages API to hopefully make it easier 127 | * Added basic support for ED25519 keys (which are used in tendermint consensus) 128 | * Wallet functionality taken out to separate library 129 | * Oracle & Staking messages added. still in beta 130 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/staking_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::{ 2 | terra_datetime_format, 3 | terra_decimal_format, 4 | terra_f64_format, // terra_opt_u64_format, 5 | terra_u64_format, 6 | }; 7 | use crate::tendermint_types::TendermintPublicKey; 8 | use chrono::{DateTime, Utc}; 9 | use rust_decimal::Decimal; 10 | use serde::{Deserialize, Serialize}; 11 | 12 | /// Information provided by the validator for their validation node. 13 | #[derive(Serialize, Deserialize, Clone, Debug)] 14 | pub struct ValidatorDescription { 15 | /// Displayed in public 16 | pub moniker: String, 17 | /// link to keybase.io ID 18 | pub identity: Option, 19 | /// web URL 20 | pub website: Option, 21 | /// a way to contact the human behind the validator 22 | pub security_contact: Option, 23 | /// a blurb describing how fantastic the validator is, and why you should validate with them 24 | pub details: Option, 25 | } 26 | /// Commission Rates 27 | #[derive(Serialize, Deserialize, Clone, Debug)] 28 | pub struct ValidatorCommissionRates { 29 | /// The current commission rate 30 | #[serde(with = "terra_f64_format")] 31 | pub rate: f64, 32 | /// The maximum rate the validator can charge. This can not be altered once set 33 | #[serde(with = "terra_f64_format")] 34 | pub max_rate: f64, 35 | /// How much the rate can change in 24 hours 36 | #[serde(with = "terra_f64_format")] 37 | pub max_change_rate: f64, 38 | } 39 | #[allow(missing_docs)] 40 | #[derive(Serialize, Deserialize, Clone, Debug)] 41 | pub struct ValidatorCommission { 42 | pub commission_rates: ValidatorCommissionRates, 43 | #[serde(with = "terra_datetime_format")] 44 | pub update_time: DateTime, 45 | } 46 | /// Top level Validator response 47 | #[derive(Serialize, Deserialize, Clone, Debug)] 48 | pub struct Validator { 49 | /// The reference address for the validator 50 | pub operator_address: String, 51 | /// used in block generation 52 | pub consensus_pubkey: TendermintPublicKey, 53 | /// Is this validator in the active validator set 54 | pub jailed: Option, 55 | /// represents the process of being jailed. 56 | pub status: u16, 57 | 58 | /// Total amount of tokens delegated to the validator 59 | #[serde(with = "terra_u64_format")] 60 | pub tokens: u64, 61 | /// Total amount the validator has delegated themselves. 62 | #[serde(with = "terra_f64_format")] 63 | pub delegator_shares: f64, 64 | /// The validator description structure 65 | pub description: ValidatorDescription, 66 | // TODO: FIX OPT 67 | // #[serde(with = "terra_opt_u64_format")] 68 | /// For Jailed / soon to be jailed validators. The height of the chain that it occurred 69 | // pub unbonding_height: Option, 70 | /// For Jailed / soon to be jailed validators. When that occurred 71 | #[serde(with = "terra_datetime_format")] 72 | pub unbonding_time: DateTime, 73 | /// Commission structure 74 | pub commission: ValidatorCommission, 75 | /// Minimum amount the validator requires to delegate. Going under this removes the validator from the set 76 | #[serde(with = "terra_u64_format")] 77 | pub min_self_delegation: u64, 78 | } 79 | 80 | /// Validator Delegation amount 81 | #[derive(Serialize, Deserialize, Clone, Debug)] 82 | pub struct ValidatorDelegationBalance { 83 | /// token name 84 | pub denom: String, 85 | /// amount delegated 86 | #[serde(with = "terra_decimal_format")] 87 | pub amount: Decimal, 88 | } 89 | /// Validator Delegation amount 90 | #[derive(Serialize, Deserialize, Clone, Debug)] 91 | pub struct ValidatorDelegationComponent { 92 | /// account delegating the funds 93 | pub delegator_address: String, 94 | /// validator oper address 95 | pub validator_address: String, 96 | /// amount delegated 97 | #[serde(with = "terra_decimal_format")] 98 | pub shares: Decimal, 99 | } 100 | /// Validator Delegation amount 101 | #[derive(Serialize, Deserialize, Clone, Debug)] 102 | pub struct ValidatorDelegation { 103 | pub delegation: ValidatorDelegationComponent, 104 | 105 | // balance amount 106 | pub balance: ValidatorDelegationBalance, 107 | } 108 | 109 | /// Validator Delegation amount 110 | #[derive(Serialize, Deserialize, Clone, Debug)] 111 | pub struct ValidatorUnbondingDelegationEntry { 112 | /// block # when unbonding happened 113 | #[serde(with = "terra_u64_format")] 114 | pub creation_height: u64, 115 | /// time when it will complete 116 | #[serde(with = "terra_datetime_format")] 117 | pub completion_time: DateTime, 118 | /// initial balance 119 | #[serde(with = "terra_u64_format")] 120 | pub initial_balance: u64, 121 | /// balance 122 | #[serde(with = "terra_u64_format")] 123 | pub balance: u64, 124 | } 125 | /// Validator Delegation amount 126 | #[derive(Deserialize, Clone, Debug)] 127 | pub struct ValidatorUnbondingDelegation { 128 | /// account delegating the funds 129 | pub delegator_address: String, 130 | /// validator 'oper' address 131 | pub validator_address: String, 132 | /// delegation entries 133 | pub entries: Vec, 134 | } 135 | 136 | /// Validator Delegation amount 137 | #[derive(Serialize, Deserialize, Clone, Debug)] 138 | pub struct ValidatorDelegationsV1Response { 139 | pub delegation_responses: Vec, 140 | } 141 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/tendermint_types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::{ 2 | base64_encoded_format, base64_opt_encoded_format, terra_datetime_format, terra_i64_format, 3 | terra_u64_format, 4 | }; 5 | use chrono::{DateTime, Utc}; 6 | use serde::{Deserialize, Serialize}; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Deserialize, Serialize, Debug)] 10 | pub struct BlockIdParts { 11 | // #[serde(with = "terra_u64_format")] 12 | pub total: usize, 13 | pub hash: String, 14 | } 15 | 16 | #[derive(Deserialize, Serialize, Debug)] 17 | pub struct BlockId { 18 | pub hash: String, 19 | pub parts: BlockIdParts, 20 | } 21 | #[derive(Deserialize, Serialize, Debug)] 22 | pub struct BlockHeaderVersion { 23 | #[serde(with = "terra_u64_format")] 24 | pub block: u64, 25 | // #[serde(with = "terra_u64_format")] 26 | // pub app: u64, 27 | } 28 | #[derive(Deserialize, Serialize, Debug)] 29 | pub struct BlockHeader { 30 | pub version: BlockHeaderVersion, 31 | pub chain_id: String, 32 | #[serde(with = "terra_u64_format")] 33 | pub height: u64, 34 | #[serde(with = "terra_datetime_format")] 35 | pub time: DateTime, 36 | pub last_block_id: BlockId, 37 | pub last_commit_hash: String, 38 | pub data_hash: String, 39 | pub validators_hash: String, 40 | pub next_validators_hash: String, 41 | pub consensus_hash: String, 42 | pub app_hash: String, 43 | pub last_results_hash: String, 44 | pub evidence_hash: String, 45 | pub proposer_address: String, 46 | } 47 | #[derive(Deserialize, Serialize, Debug)] 48 | pub struct BlockEvidence { 49 | //pub evidence: Option<...> 50 | } 51 | #[derive(Deserialize, Serialize, Debug)] 52 | pub struct BlockSignature { 53 | /// 1 -no signature .. 2 -- signature 54 | pub block_id_flag: usize, 55 | /// HEX/Bytes version of string 56 | pub validator_address: String, 57 | #[serde(with = "terra_datetime_format")] 58 | pub timestamp: DateTime, 59 | pub signature: Option, 60 | } 61 | #[derive(Deserialize, Serialize, Debug)] 62 | pub struct BlockCommit { 63 | #[serde(with = "terra_u64_format")] 64 | pub height: u64, 65 | // #[serde(with = "terra_u64_format")] 66 | pub round: usize, 67 | pub block_id: BlockId, 68 | pub signatures: Vec, 69 | } 70 | #[derive(Deserialize, Serialize, Debug)] 71 | pub struct BlockData { 72 | pub txs: Option>, 73 | } 74 | #[derive(Deserialize, Serialize, Debug)] 75 | pub struct Block { 76 | pub header: BlockHeader, 77 | pub data: BlockData, 78 | pub evidence: BlockEvidence, 79 | pub last_commit: BlockCommit, 80 | } 81 | 82 | #[derive(Deserialize, Serialize, Debug)] 83 | pub struct BlockResult { 84 | pub block_id: BlockId, 85 | pub block: Block, 86 | } 87 | #[derive(Deserialize, Serialize, Debug, Clone)] 88 | pub struct EventAttribute { 89 | #[serde(with = "base64_encoded_format")] 90 | pub key: String, 91 | #[serde(with = "base64_opt_encoded_format")] 92 | pub value: Option, 93 | pub index: bool, 94 | } 95 | #[derive(Deserialize, Serialize, Debug)] 96 | pub struct EventType { 97 | #[serde(rename = "type")] 98 | pub s_type: String, 99 | pub attributes: Vec, 100 | } 101 | impl EventType { 102 | pub fn attribute_map(&self) -> HashMap> { 103 | self.attributes 104 | .iter() 105 | .map(|attr| (attr.key.clone(), attr.value.clone())) 106 | .collect::>>() 107 | } 108 | } 109 | 110 | #[derive(Deserialize, Serialize, Debug)] 111 | pub struct RPCTXResult { 112 | pub code: usize, 113 | pub data: Option, 114 | pub log: String, 115 | pub info: String, 116 | pub gas_wanted: String, 117 | pub gas_used: String, 118 | pub events: Vec, 119 | pub codespace: String, 120 | } 121 | 122 | #[derive(Deserialize, Serialize, Debug)] 123 | pub struct RPCPubKeyOuter { 124 | #[serde(rename = "Sum")] 125 | pub sum: RPCPubKey, 126 | } 127 | #[derive(Deserialize, Serialize, Debug)] 128 | pub struct RPCPubKeyValue { 129 | pub ed25519: String, 130 | } 131 | #[derive(Deserialize, Serialize, Debug)] 132 | pub struct RPCPubKey { 133 | #[serde(rename = "type")] 134 | pub s_type: String, 135 | pub value: RPCPubKeyValue, 136 | } 137 | 138 | #[derive(Deserialize, Serialize, Debug)] 139 | pub struct RPCValidatorUpdate { 140 | pub pub_key: RPCPubKeyOuter, 141 | // #[serde(with = "terra_u64_format")] 142 | // pub power: u64, 143 | } 144 | 145 | #[derive(Deserialize, Serialize, Debug)] 146 | pub struct BlockResultsResult { 147 | #[serde(with = "terra_u64_format")] 148 | pub height: u64, 149 | pub txs_results: Option>, 150 | pub begin_block_events: Option>, 151 | pub end_block_events: Option>, 152 | pub validator_updates: Option>, 153 | pub consensus_param_updates: Option, 154 | } 155 | 156 | #[derive(Deserialize, Serialize, Debug)] 157 | pub struct ValidatorSetResult { 158 | #[serde(with = "terra_u64_format")] 159 | pub block_height: u64, 160 | pub validators: Vec, 161 | } 162 | #[derive(Serialize, Deserialize, Clone, Debug)] 163 | pub struct TendermintPublicKey { 164 | #[serde(rename = "type")] 165 | pub s_type: String, 166 | pub value: String, 167 | } 168 | #[derive(Deserialize, Clone, Serialize, Debug)] 169 | pub struct Validator { 170 | pub address: String, 171 | pub pub_key: TendermintPublicKey, 172 | #[serde(with = "terra_i64_format")] 173 | pub proposer_priority: i64, 174 | #[serde(with = "terra_u64_format")] 175 | pub voting_power: u64, 176 | } 177 | -------------------------------------------------------------------------------- /src/market.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::{PublicKey, Terra}; 4 | //use crate::errors::Result; 5 | //use crate::keys::get_private_key; 6 | 7 | use secp256k1::Secp256k1; 8 | use terra_rust_api::messages::Message; 9 | 10 | use crate::{NAME, VERSION}; 11 | use rust_decimal::Decimal; 12 | use terra_rust_api::core_types::Coin; 13 | use terra_rust_api::messages::market::MsgSwap; 14 | use terra_rust_wallet::Wallet; 15 | #[derive(Parser)] 16 | /// Market Operations 17 | pub struct MarketCommand { 18 | #[clap(subcommand)] 19 | command: MarketEnum, 20 | } 21 | #[derive(Subcommand)] 22 | pub enum MarketEnum { 23 | #[clap(name = "swap-rate", about = "see the exchange rate")] 24 | SwapRate { 25 | #[clap(name = "denom", help = "token symbol. remember we are uXXX not XXX")] 26 | denom: String, 27 | #[clap(name = "amount", help = "the amount. remember we are uXXX not XXX")] 28 | amount: Decimal, 29 | #[clap(name = "ask", help = "what to swap the amount into")] 30 | ask: String, 31 | }, 32 | #[clap(name = "swap", about = "swap/exchange token")] 33 | Swap { 34 | /// from. The nickname in the wallet used to sign the transaction, do the token exchange 35 | from: String, 36 | #[clap( 37 | name = "offer", 38 | help = "amount & type of token to switch uXXX not XXX. eg 1000ukrw" 39 | )] 40 | offer_coin: String, 41 | #[clap( 42 | name = "ask", 43 | help = "what to exchange it into. remember we are uXXX not XXX. eg uluna" 44 | )] 45 | ask_denom: String, 46 | #[clap(name = "to", help = "who to send it too. defaults to yourself")] 47 | to: Option, 48 | }, 49 | #[clap( 50 | name = "sweep", 51 | about = "swap all tokens above a certain threshold in your wallet into a single token" 52 | )] 53 | Sweep { 54 | /// from. The nickname in the wallet used to sign the transaction, do the token exchange 55 | from: String, 56 | #[clap(name = "to-coin", help = "the token to exchange into")] 57 | to_coin: String, 58 | #[clap( 59 | name = "threshold", 60 | help = "the minimum amount to consider in 'to-coin' denomination. (ie. don't swap if there is only 10c worth)" 61 | )] 62 | threshold: Decimal, 63 | }, 64 | } 65 | impl MarketCommand { 66 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 67 | match self.command { 68 | MarketEnum::SwapRate { denom, ask, amount } => { 69 | let coin = Coin::create(&denom, amount); 70 | let sw = terra.market().swap(&coin, &ask).await?; 71 | 72 | println!("{}", serde_json::to_string_pretty(&sw)?); 73 | } 74 | 75 | MarketEnum::Swap { 76 | from, 77 | offer_coin, 78 | to, 79 | ask_denom, 80 | } => { 81 | let secp = Secp256k1::new(); 82 | let from_key = wallet.get_private_key(&secp, &from, seed)?; 83 | let from_public_key = from_key.public_key(&secp); 84 | let coin: Coin = Coin::parse(&offer_coin) 85 | .expect("invalid offer coin. hint: 1000ukrw") 86 | .unwrap(); 87 | let from_account = from_public_key.account()?; 88 | let to_account = match to { 89 | Some(to_k) => { 90 | let valid_tok = PublicKey::from_account(&to_k)?; 91 | valid_tok.account()? 92 | } 93 | None => from_account.clone(), 94 | }; 95 | let swap = MsgSwap::create(coin, ask_denom, to_account)?; 96 | 97 | let messages: Vec = vec![swap]; 98 | let resp = terra 99 | .submit_transaction_sync( 100 | &secp, 101 | &from_key, 102 | messages, 103 | Some(format!( 104 | "PFC-{}/{}", 105 | NAME.unwrap_or("TERRARUST"), 106 | VERSION.unwrap_or("DEV") 107 | )), 108 | ) 109 | .await?; 110 | 111 | println!("{}", resp.txhash); 112 | log::info!("{}", resp.raw_log); 113 | } 114 | MarketEnum::Sweep { 115 | from, 116 | to_coin, 117 | threshold, 118 | } => { 119 | let secp = Secp256k1::new(); 120 | let from_key = wallet.get_private_key(&secp, &from, seed)?; 121 | let from_public_key = from_key.public_key(&secp); 122 | let from_account = from_public_key.account()?; 123 | let messages = terra 124 | .market() 125 | .generate_sweep_messages(from_account, to_coin, threshold) 126 | .await?; 127 | 128 | if messages.is_empty() { 129 | println!("No coins match your threshold") 130 | } else { 131 | let resp = terra 132 | .submit_transaction_sync( 133 | &secp, 134 | &from_key, 135 | messages, 136 | Some(format!( 137 | "PFC-{}/{}", 138 | NAME.unwrap_or("TERRARUST"), 139 | VERSION.unwrap_or("DEV") 140 | )), 141 | ) 142 | .await?; 143 | 144 | println!("{}", resp.txhash); 145 | log::info!("{}", resp.raw_log); 146 | } 147 | } 148 | }; 149 | Ok(()) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /examples/do_swap.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use dotenv::dotenv; 4 | use rust_decimal::Decimal; 5 | use secp256k1::Secp256k1; 6 | use serde::Serialize; 7 | use terra_rust_api::core_types::Coin; 8 | use terra_rust_api::terra_u64_format; 9 | use terra_rust_api::{GasOptions, Message, MsgExecuteContract, Terra}; 10 | use terra_rust_cli::cli_helpers::Cli; 11 | use terra_rust_wallet::Wallet; 12 | 13 | /// VERSION number of package 14 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 15 | /// NAME of package 16 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 17 | 18 | #[derive(Subcommand)] 19 | pub enum Swap { 20 | Swap(SwapCmd), 21 | } 22 | #[derive(Parser, Debug)] 23 | pub struct SwapCmd { 24 | #[clap( 25 | name = "contract", 26 | help = "the contract", 27 | long = "contract", 28 | env = "TERRARUST_CONTRACT" 29 | )] 30 | pub contract: String, 31 | #[clap( 32 | name = "sender", 33 | long = "sender", 34 | help = "the sender account", 35 | env = "TERRARUST_SENDER" 36 | )] 37 | pub sender: String, 38 | #[clap(name = "coins", long = "coins")] 39 | pub coins: Option, 40 | 41 | #[clap( 42 | name = "belief-price", 43 | help = "exchange rate/price you believe is true" 44 | )] 45 | belief_price: Decimal, 46 | #[clap(name = "amount", help = "amount")] 47 | amount: u64, 48 | #[clap(name = "denom", help = "Denomination")] 49 | denom: String, 50 | #[clap( 51 | name = "max-spread", 52 | long = "max-spread", 53 | help = "exchange rate/price you believe is true" 54 | )] 55 | max_spread: Decimal, 56 | } 57 | async fn run() -> Result<()> { 58 | let cli = Cli::::parse(); 59 | let gas_opts: GasOptions = cli.gas_opts().await?; 60 | let terra = Terra::lcd_client(&cli.lcd, &cli.chain_id, &gas_opts, None); 61 | let secp = Secp256k1::new(); 62 | let wallet = Wallet::create(&cli.wallet); 63 | 64 | let seed: Option<&str> = if cli.seed.is_empty() { 65 | None 66 | } else { 67 | Some(&cli.seed) 68 | }; 69 | match cli.cmd { 70 | Swap::Swap(swap) => { 71 | let coins: Vec = if let Some(coin_str) = swap.coins { 72 | Coin::parse_coins(&coin_str)? 73 | } else { 74 | vec![] 75 | }; 76 | 77 | let from_key = wallet.get_private_key(&secp, &swap.sender, seed)?; 78 | 79 | let from_public_key = from_key.public_key(&secp); 80 | 81 | let store_message = MsgSwap::create( 82 | &from_public_key.account()?, 83 | &swap.contract, 84 | swap.belief_price, 85 | swap.max_spread, 86 | swap.amount, 87 | &swap.denom, 88 | &coins, 89 | )?; 90 | let messages: Vec = vec![store_message]; 91 | 92 | let json = serde_json::to_string(&messages)?; 93 | log::info!("Message:\n{}", json); 94 | let resp = terra 95 | .submit_transaction_sync( 96 | &secp, 97 | &from_key, 98 | messages, 99 | Some(format!( 100 | "PFC-{}/{}", 101 | NAME.unwrap_or("TERRARUST"), 102 | VERSION.unwrap_or("DEV") 103 | )), 104 | ) 105 | .await?; 106 | 107 | let hash = resp.txhash; 108 | log::info!("{}", hash); 109 | } 110 | } 111 | 112 | Ok(()) 113 | } 114 | 115 | #[tokio::main] 116 | async fn main() { 117 | dotenv().ok(); 118 | env_logger::init(); 119 | 120 | if let Err(ref err) = run().await { 121 | log::error!("{}", err); 122 | err.chain() 123 | .skip(1) 124 | .for_each(|cause| log::error!("because: {}", cause)); 125 | 126 | // The backtrace is not always generated. Try to run this example 127 | // with `$env:RUST_BACKTRACE=1`. 128 | // if let Some(backtrace) = e.backtrace() { 129 | // log::debug!("backtrace: {:?}", backtrace); 130 | // } 131 | 132 | ::std::process::exit(1); 133 | } 134 | } 135 | 136 | #[derive(Serialize, Debug)] 137 | pub struct MsgSwapNativeToken { 138 | pub denom: String, 139 | } 140 | #[derive(Serialize, Debug)] 141 | pub struct MsgSwapOfferInfo { 142 | pub native_token: MsgSwapNativeToken, 143 | } 144 | #[derive(Serialize, Debug)] 145 | pub struct MsgSwapOfferAsset { 146 | #[serde(with = "terra_u64_format")] 147 | pub amount: u64, 148 | pub info: MsgSwapOfferInfo, 149 | } 150 | #[derive(Serialize, Debug)] 151 | pub struct MsgSwapData { 152 | pub belief_price: Decimal, 153 | pub max_spread: Decimal, 154 | pub offer_asset: MsgSwapOfferAsset, 155 | } 156 | #[derive(Serialize, Debug)] 157 | /// Message: Swap 158 | pub struct MsgSwap { 159 | pub swap: MsgSwapData, 160 | } 161 | 162 | impl MsgSwap { 163 | pub fn create( 164 | sender: &str, 165 | contract: &str, 166 | belief_price: Decimal, 167 | max_spread: Decimal, 168 | amount: u64, 169 | denom: &str, 170 | coins: &Vec, 171 | ) -> anyhow::Result { 172 | let offer_info = MsgSwapOfferInfo { 173 | native_token: MsgSwapNativeToken { 174 | denom: denom.to_string(), 175 | }, 176 | }; 177 | let offer_asset = MsgSwapOfferAsset { 178 | amount, 179 | info: offer_info, 180 | }; 181 | 182 | let swap_data = MsgSwapData { 183 | belief_price, 184 | max_spread, 185 | offer_asset, 186 | }; 187 | let swap = MsgSwap { swap: swap_data }; 188 | let swap_json = serde_json::to_string(&swap)?; 189 | Ok(MsgExecuteContract::create_from_json( 190 | sender, contract, &swap_json, coins, 191 | )?) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/wasm.rs: -------------------------------------------------------------------------------- 1 | use crate::client::wasm_types::{ 2 | WasmCodeResult, WasmContractInfoResult, WasmParameterResult, WasmQueryRawResult, 3 | }; 4 | use crate::{Message, PrivateKey, Terra}; 5 | use secp256k1::{Secp256k1, Signing}; 6 | use std::path::Path; 7 | 8 | use crate::client::tx_types::TXResultSync; 9 | use crate::core_types::Coin; 10 | use crate::errors::TerraRustAPIError; 11 | use crate::messages::wasm::{MsgInstantiateContract, MsgMigrateContract, MsgStoreCode}; 12 | use serde::Deserialize; 13 | 14 | pub struct Wasm<'a> { 15 | terra: &'a Terra, 16 | } 17 | 18 | impl Wasm<'_> { 19 | pub fn create(terra: &'_ Terra) -> Wasm<'_> { 20 | Wasm { terra } 21 | } 22 | pub async fn codes(&self, code_id: u64) -> Result { 23 | let code = self 24 | .terra 25 | .send_cmd::(&format!("/wasm/codes/{}", code_id), None) 26 | .await?; 27 | Ok(code) 28 | } 29 | pub async fn info( 30 | &self, 31 | contract_address: &str, 32 | ) -> Result { 33 | let code = self 34 | .terra 35 | .send_cmd::( 36 | &format!("/wasm/contracts/{}", contract_address), 37 | None, 38 | ) 39 | .await?; 40 | Ok(code) 41 | } 42 | pub async fn parameters(&self) -> Result { 43 | let code = self 44 | .terra 45 | .send_cmd::("/wasm/parameters", None) 46 | .await?; 47 | Ok(code) 48 | } 49 | pub async fn query Deserialize<'de>>( 50 | &self, 51 | contract_address: &str, 52 | json_query: &str, 53 | ) -> Result { 54 | let code = self 55 | .terra 56 | .send_cmd::( 57 | &format!("/wasm/contracts/{}/store?", contract_address), 58 | Some(&format!("query_msg={}", json_query)), 59 | ) 60 | .await?; 61 | Ok(code) 62 | } 63 | pub async fn query_raw( 64 | &self, 65 | contract_address: &str, 66 | key: &str, 67 | sub_key: &Option, 68 | ) -> Result<(String, String), TerraRustAPIError> { 69 | let json_query = match sub_key { 70 | Some(sub_key_str) => format!("key={}&subkey={}", key, &sub_key_str), 71 | None => format!("key={}", key), 72 | }; 73 | 74 | let code = self 75 | .terra 76 | .send_cmd::( 77 | &format!("/wasm/contracts/{}/store/raw?", contract_address), 78 | Some(&json_query), 79 | ) 80 | .await?; 81 | let key_vec = subtle_encoding::base64::decode(code.result.key.as_bytes())?; 82 | let key = String::from_utf8(key_vec)?; 83 | eprintln!("{}", code.result.key); 84 | let value_vec = subtle_encoding::base64::decode(code.result.value)?; 85 | let value = String::from_utf8(value_vec)?; 86 | 87 | Ok((key, value)) 88 | } 89 | /// store a wasm file onto the chain. 90 | pub async fn store( 91 | &self, 92 | secp: &Secp256k1, 93 | from: &PrivateKey, 94 | wasm: &str, 95 | memo: Option, 96 | ) -> Result { 97 | let from_public_key = from.public_key(secp); 98 | 99 | let wasm_path = Path::new(wasm); 100 | 101 | let store_message = MsgStoreCode::create_from_file(&from_public_key.account()?, wasm_path)?; 102 | let messages: Vec = vec![store_message]; 103 | 104 | let resp = self 105 | .terra 106 | .submit_transaction_sync(secp, from, messages, memo) 107 | .await; 108 | resp 109 | } 110 | /// create a contract using code_id, json init args, and optionally admin on the chain 111 | #[allow(clippy::too_many_arguments)] 112 | pub async fn instantiate( 113 | &self, 114 | secp: &Secp256k1, 115 | from: &PrivateKey, 116 | code_id: u64, 117 | json: String, 118 | coins: Vec, 119 | admin: Option, 120 | memo: Option, 121 | ) -> Result { 122 | let from_public_key = from.public_key(secp); 123 | let init_message = MsgInstantiateContract::create_from_json( 124 | &from_public_key.account()?, 125 | admin, 126 | code_id, 127 | &json, 128 | coins, 129 | )?; 130 | let messages: Vec = vec![init_message]; 131 | 132 | let resp = self 133 | .terra 134 | .submit_transaction_sync(secp, from, messages, memo) 135 | .await; 136 | 137 | resp 138 | } 139 | 140 | /// migrate an existing contract to new_code_id, optionally with a migrate args 141 | pub async fn migrate( 142 | &self, 143 | secp: &Secp256k1, 144 | from: &PrivateKey, 145 | contract: &str, 146 | new_code_id: u64, 147 | migrate: Option, 148 | memo: Option, 149 | ) -> Result { 150 | let from_public_key = from.public_key(secp); 151 | 152 | let migrate_message = if let Some(migrate_string) = migrate { 153 | MsgMigrateContract::create_from_json( 154 | &from_public_key.account()?, 155 | contract, 156 | new_code_id, 157 | &migrate_string, 158 | )? 159 | } else { 160 | MsgMigrateContract::create_from_json( 161 | &from_public_key.account()?, 162 | contract, 163 | new_code_id, 164 | "{}", 165 | )? 166 | }; 167 | 168 | let messages: Vec = vec![migrate_message]; 169 | 170 | let resp = self 171 | .terra 172 | .submit_transaction_sync(secp, from, messages, memo) 173 | .await; 174 | resp 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/distribution.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use terra_rust_api::Terra; 4 | 5 | use crate::{NAME, VERSION}; 6 | use secp256k1::Secp256k1; 7 | use terra_rust_api::messages::distribution::{ 8 | MsgWithdrawDelegationReward, MsgWithdrawValidatorCommission, 9 | }; 10 | 11 | use terra_rust_api::messages::Message; 12 | use terra_rust_wallet::Wallet; 13 | 14 | #[derive(Subcommand)] 15 | enum DistributionEnum { 16 | #[clap(name = "withdraw-reward")] 17 | /// Withdraw delegation reward 18 | Reward { 19 | /// delegator. The nickname in the wallet used to sign the transaction, and transfer the initial amount 20 | delegator: String, 21 | #[clap( 22 | name = "validator", 23 | help = "the validator's terravaloper address. If blank withdraws from ALL" 24 | )] 25 | /// the validator's oper terravaloper1XXXXXXXXX. if blank, withdraws from ALL validators (todo) 26 | validator: Option, 27 | }, 28 | #[clap(name = "withdraw-commission")] 29 | /// Withdraw commission 30 | Commission { 31 | /// delegator. The nickname in the wallet used to sign the transaction, and transfer the initial amount 32 | delegator: String, 33 | #[clap(name = "validator", help = "the validator's terravaloper address.")] 34 | /// the validator's oper terravaloper1XXXXXXXXX. 35 | validator: String, 36 | }, 37 | #[clap(name = "withdraw")] 38 | /// Withdraw commission & delegation reward 39 | Withdraw { 40 | /// delegator. The nickname in the wallet used to sign the transaction, and transfer the initial amount 41 | delegator: String, 42 | }, 43 | } 44 | #[derive(Parser)] 45 | /// Validator Distribution (Reward) Commands 46 | pub struct DistributionCommand { 47 | #[clap(subcommand)] 48 | command: DistributionEnum, 49 | } 50 | impl DistributionCommand { 51 | pub async fn parse(self, terra: &Terra, wallet: &Wallet<'_>, seed: Option<&str>) -> Result<()> { 52 | let secp = Secp256k1::new(); 53 | match self.command { 54 | DistributionEnum::Reward { 55 | delegator, 56 | validator, 57 | } => { 58 | log::info!("Delegator {}", &delegator); 59 | let delegator_key = wallet.get_private_key(&secp, &delegator, seed)?; 60 | let delegator_account = delegator_key.public_key(&secp).account()?; 61 | 62 | match validator { 63 | Some(v) => { 64 | log::info!("Validator {}", &v); 65 | let msg = MsgWithdrawDelegationReward::create(delegator_account, v)?; 66 | let messages: Vec = vec![msg]; 67 | let resp = terra 68 | .submit_transaction_sync( 69 | &secp, 70 | &delegator_key, 71 | messages, 72 | Some(format!( 73 | "PFC-{}/{}", 74 | NAME.unwrap_or("TERRARUST"), 75 | VERSION.unwrap_or("DEV") 76 | )), 77 | ) 78 | .await?; 79 | 80 | println!("{}", resp.txhash); 81 | log::info!("{}", resp.raw_log); 82 | } 83 | None => todo!("withdrawing from ALL validators not implemented yet"), 84 | } 85 | Ok(()) 86 | } 87 | DistributionEnum::Commission { 88 | delegator, 89 | validator, 90 | } => { 91 | log::info!("Delegator {}", &delegator); 92 | let delegator_key = wallet.get_private_key(&secp, &delegator, seed)?; 93 | 94 | log::info!("Validator {}", &validator); 95 | let msg = MsgWithdrawValidatorCommission::create(validator)?; 96 | let messages: Vec = vec![msg]; 97 | let resp = terra 98 | .submit_transaction_sync( 99 | &secp, 100 | &delegator_key, 101 | messages, 102 | Some(format!( 103 | "PFC-{}/{}", 104 | NAME.unwrap_or("TERRARUST"), 105 | VERSION.unwrap_or("DEV") 106 | )), 107 | ) 108 | .await?; 109 | 110 | println!("{}", resp.txhash); 111 | log::info!("{}", resp.raw_log); 112 | Ok(()) 113 | } 114 | DistributionEnum::Withdraw { 115 | delegator, 116 | //validator, 117 | } => { 118 | log::info!("Delegator {}", &delegator); 119 | let delegator_key = wallet.get_private_key(&secp, &delegator, seed)?; 120 | let delegator_account = delegator_key.public_key(&secp).account()?; 121 | let validator = delegator_key.public_key(&secp).operator_address()?; 122 | 123 | log::info!("Validator {}", &validator); 124 | let msg_commission = MsgWithdrawValidatorCommission::create(validator.clone())?; 125 | let msg_rewards = 126 | MsgWithdrawDelegationReward::create(delegator_account, validator)?; 127 | let messages: Vec = vec![msg_commission, msg_rewards]; 128 | let resp = terra 129 | .submit_transaction_sync( 130 | &secp, 131 | &delegator_key, 132 | messages, 133 | Some(format!( 134 | "PFC-{}/{}", 135 | NAME.unwrap_or("TERRARUST"), 136 | VERSION.unwrap_or("DEV") 137 | )), 138 | ) 139 | .await?; 140 | 141 | println!("{}", resp.txhash); 142 | log::info!("{}", resp.raw_log); 143 | Ok(()) 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/keys.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::{Parser, Subcommand}; 3 | use std::io::{self, BufRead}; 4 | use terra_rust_api::{PrivateKey, Signature}; 5 | 6 | use secp256k1::Secp256k1; 7 | use terra_rust_wallet::Wallet; 8 | /// Key Operations 9 | #[derive(Parser)] 10 | 11 | pub struct KeysCommand { 12 | #[clap(subcommand)] 13 | command: KeysEnum, 14 | } 15 | 16 | #[derive(Subcommand)] 17 | pub enum KeysEnum { 18 | #[clap(name = "parse", about = "parse a hex key into a terra account")] 19 | Parse { 20 | #[clap(name = "hex")] 21 | /// hex public key to convert 22 | hex: String, 23 | }, 24 | /// Create a new key to the wallet 25 | New { 26 | #[clap(name = "name", help = "a memorable name to use in this client")] 27 | name: String, 28 | }, 29 | /// Recover an existing key to the wallet using the recovery words 30 | Recover { 31 | #[clap(name = "name", help = "a memorable name to use in this client")] 32 | name: String, 33 | }, 34 | /// Delete a key from the wallet 35 | Delete { 36 | #[clap(name = "name", help = "delete the private key with this name.")] 37 | name: String, 38 | }, 39 | /// Get Public Key representation of the private key 40 | /// 41 | Get { 42 | #[clap(name = "name", help = "the key with this name.")] 43 | name: String, 44 | }, 45 | /// Sign a arbitrary string 46 | /// 47 | Sign { 48 | #[clap(name = "signer", help = "the signer to sign the message.")] 49 | signer: String, 50 | #[clap(name = "message", help = "the message to sign.")] 51 | message: String, 52 | }, 53 | /// Sign a arbitrary string 54 | /// 55 | Verify { 56 | #[clap( 57 | name = "public_key", 58 | help = "the public key raw value (just the hex string)." 59 | )] 60 | public_key: String, 61 | #[clap(name = "signature", help = "the signature")] 62 | signature: String, 63 | #[clap(name = "message", help = "the message to verify.")] 64 | message: String, 65 | }, 66 | /// List keys in the wallet 67 | List, 68 | } 69 | 70 | impl KeysCommand { 71 | pub fn parse(self, wallet: &Wallet, seed: Option<&str>) -> Result<()> { 72 | match self.command { 73 | KeysEnum::Parse { .. } => { 74 | todo!() 75 | } 76 | KeysEnum::Recover { name } => { 77 | let secp = Secp256k1::new(); 78 | 79 | println!("Please input the set of the recovery words, followed by the passphrase (which is passed via --seed)"); 80 | if seed.is_some() { 81 | println!("Your Passphrase is {}", seed.unwrap()); 82 | } else { 83 | println!("No Passphrase is being used"); 84 | } 85 | println!("These will be stored in your computer's vault (win10)/secret service (linux)/keyring (os/x)"); 86 | println!(); 87 | println!("We take NO responsibility for the safety/security of this."); 88 | println!("This software is ALPHA and has not undergone a security audit"); 89 | println!( 90 | "For high value keys, we suggest you always use a hardware wallet, like ledger" 91 | ); 92 | println!(); 93 | println!("Recovery words:"); 94 | let stdin = io::stdin(); 95 | let mut iterator = stdin.lock().lines(); 96 | 97 | let words = iterator.next().unwrap().unwrap(); 98 | 99 | let pk = match seed { 100 | Some(seed_str) => PrivateKey::from_words_seed(&secp, &words, seed_str)?, 101 | None => PrivateKey::from_words(&secp, &words, 0, 0)?, 102 | }; 103 | wallet.store_key(&name, &pk)?; 104 | } 105 | 106 | KeysEnum::New { name } => { 107 | let secp = Secp256k1::new(); 108 | 109 | println!("This key will be stored in your computer's vault (win10)/secret service (linux)/keyring (os/x)"); 110 | 111 | let pk = match seed { 112 | None => PrivateKey::new(&secp)?, 113 | Some(seed_str) => PrivateKey::new_seed(&secp, seed_str)?, 114 | }; 115 | println!("Please write these down and save these in a secure location."); 116 | println!("These words can be used to transfer all your coins out of your account"); 117 | println!("NO ONE has a need for these keys except you. If they are asking for them it is a scam."); 118 | println!(); 119 | println!("Your recovery words are:"); 120 | println!("{}", pk.words().unwrap()); 121 | if seed.is_some() { 122 | println!("Please also take note of your seed phrase"); 123 | } 124 | 125 | wallet.store_key(&name, &pk)?; 126 | 127 | let pub_key = wallet.get_public_key(&secp, &name, seed)?; 128 | 129 | println!("{}", pub_key.account()?) 130 | } 131 | KeysEnum::Delete { name } => { 132 | wallet.delete_key(&name)?; 133 | } 134 | KeysEnum::Get { name } => { 135 | let secp = Secp256k1::new(); 136 | let pub_key = wallet.get_public_key(&secp, &name, seed)?; 137 | 138 | println!("{}", pub_key.account()?); 139 | } 140 | KeysEnum::List => { 141 | let keys = wallet.list()?; 142 | println!("{:#?}", keys); 143 | } 144 | KeysEnum::Sign { signer, message } => { 145 | let secp = Secp256k1::new(); 146 | 147 | let from_key = wallet.get_private_key(&secp, &signer, seed)?; 148 | // let signature = from_key.sign(&secp, &cli.message)?; 149 | let signature = from_key.sign(&secp, &message)?; 150 | println!("Signature: {}", signature.signature); 151 | println!("Public Key: {}", signature.pub_key.value); 152 | } 153 | KeysEnum::Verify { 154 | public_key, 155 | signature, 156 | message, 157 | } => { 158 | let secp = Secp256k1::new(); 159 | Signature::verify(&secp, &public_key, &signature, &message)?; 160 | println!("OK"); 161 | } 162 | } 163 | Ok(()) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/staking.rs: -------------------------------------------------------------------------------- 1 | use crate::client::client_types::{terra_decimal_format, terra_opt_decimal_format}; 2 | use crate::core_types::{Coin, MsgInternal}; 3 | 4 | use crate::messages::Message; 5 | use rust_decimal::Decimal; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | #[derive(Deserialize, Serialize, Debug)] 9 | /// Description of the validator which appears in public 10 | /// on MsgEditValidator messages for fields that don't change you should put "[do-not-modify]" 11 | /// 12 | pub struct ValidatorDescription { 13 | pub details: String, 14 | pub identity: String, 15 | pub moniker: String, 16 | pub security_contact: String, 17 | pub website: String, 18 | } 19 | impl ValidatorDescription { 20 | pub fn create_create( 21 | details: Option, 22 | identity: Option, 23 | moniker: String, 24 | security_contact: Option, 25 | website: Option, 26 | ) -> ValidatorDescription { 27 | ValidatorDescription { 28 | details: details.unwrap_or_else(|| "".into()), 29 | identity: identity.unwrap_or_else(|| "".into()), 30 | moniker, 31 | security_contact: security_contact.unwrap_or_else(|| "".into()), 32 | website: website.unwrap_or_else(|| "".into()), 33 | } 34 | } 35 | pub fn create_edit( 36 | details: Option, 37 | identity: Option, 38 | moniker: Option, 39 | security_contact: Option, 40 | website: Option, 41 | ) -> ValidatorDescription { 42 | ValidatorDescription { 43 | details: details.unwrap_or_else(|| "[do-not-modify]".into()), 44 | identity: identity.unwrap_or_else(|| "[do-not-modify]".into()), 45 | moniker: moniker.unwrap_or_else(|| "[do-not-modify]".into()), 46 | security_contact: security_contact.unwrap_or_else(|| "[do-not-modify]".into()), 47 | website: website.unwrap_or_else(|| "[do-not-modify]".into()), 48 | } 49 | } 50 | } 51 | /// Description of the validator commission structure 52 | #[derive(Deserialize, Serialize, Debug)] 53 | pub struct ValidatorCommission { 54 | #[serde(with = "terra_decimal_format")] 55 | pub max_change_rate: Decimal, 56 | #[serde(with = "terra_decimal_format")] 57 | pub max_rate: Decimal, 58 | #[serde(with = "terra_decimal_format")] 59 | pub rate: Decimal, 60 | } 61 | /// create validator message 62 | #[derive(Serialize, Debug)] 63 | pub struct MsgCreateValidator { 64 | pub commission: ValidatorCommission, 65 | pub delegator_address: String, 66 | pub description: ValidatorDescription, 67 | #[serde(with = "terra_decimal_format")] 68 | pub min_self_delegation: Decimal, 69 | pub pubkey: String, 70 | pub value: Coin, 71 | pub validator_address: String, 72 | } 73 | impl MsgInternal for MsgCreateValidator {} 74 | impl MsgCreateValidator { 75 | pub fn create( 76 | description: ValidatorDescription, 77 | commission: ValidatorCommission, 78 | min_self_delegation: Decimal, 79 | delegator_address: String, 80 | validator_address: String, 81 | pubkey: String, 82 | value: Coin, 83 | ) -> Message { 84 | let internal = MsgCreateValidator { 85 | commission, 86 | delegator_address, 87 | description, 88 | min_self_delegation, 89 | pubkey, 90 | value, 91 | validator_address, 92 | }; 93 | Message { 94 | s_type: "staking/MsgCreateValidator".into(), 95 | value: serde_json::to_value(internal).unwrap(), 96 | } 97 | } 98 | } 99 | /// edit validator message 100 | #[derive(Serialize, Debug)] 101 | pub struct MsgEditValidator { 102 | pub address: String, 103 | #[serde(with = "terra_opt_decimal_format")] 104 | pub commission_rate: Option, 105 | pub description: ValidatorDescription, 106 | #[serde(with = "terra_opt_decimal_format")] 107 | pub min_self_delegation: Option, 108 | } 109 | impl MsgInternal for MsgEditValidator {} 110 | impl MsgEditValidator { 111 | pub fn create( 112 | description: ValidatorDescription, 113 | address: String, 114 | commission_rate: Option, 115 | min_self_delegation: Option, 116 | ) -> anyhow::Result { 117 | let internal = MsgEditValidator { 118 | address, 119 | commission_rate, 120 | description, 121 | min_self_delegation, 122 | }; 123 | Ok(Message { 124 | s_type: "staking/MsgEditValidator".into(), 125 | value: serde_json::to_value(internal)?, 126 | }) 127 | } 128 | } 129 | 130 | /// edit undelegate message 131 | #[derive(Serialize, Debug)] 132 | pub struct MsgUndelegate { 133 | pub amount: Coin, 134 | pub delegator_address: String, 135 | pub validator_address: String, 136 | } 137 | impl MsgInternal for MsgUndelegate {} 138 | impl MsgUndelegate { 139 | pub fn create( 140 | delegator_address: String, 141 | validator_address: String, 142 | amount: Coin, 143 | ) -> anyhow::Result { 144 | let internal = MsgUndelegate { 145 | amount, 146 | delegator_address, 147 | validator_address, 148 | }; 149 | Ok(Message { 150 | s_type: "staking/MsgUndelegate".into(), 151 | value: serde_json::to_value(internal)?, 152 | }) 153 | } 154 | } 155 | /// edit undelegate message 156 | #[derive(Serialize, Debug)] 157 | pub struct MsgDelegate { 158 | pub amount: Coin, 159 | pub delegator_address: String, 160 | pub validator_address: String, 161 | } 162 | impl MsgInternal for MsgDelegate {} 163 | impl MsgDelegate { 164 | pub fn create( 165 | delegator_address: String, 166 | validator_address: String, 167 | amount: Coin, 168 | ) -> anyhow::Result { 169 | let internal = MsgDelegate { 170 | amount, 171 | delegator_address, 172 | validator_address, 173 | }; 174 | Ok(Message { 175 | s_type: "staking/MsgDelegate".into(), 176 | value: serde_json::to_value(internal)?, 177 | }) 178 | } 179 | } 180 | /// edit undelegate message 181 | #[derive(Serialize, Debug)] 182 | pub struct MsgBeginRedelegate { 183 | pub amount: Coin, 184 | pub delegator_address: String, 185 | pub validator_dst_address: String, 186 | pub validator_src_address: String, 187 | } 188 | impl MsgInternal for MsgBeginRedelegate {} 189 | impl MsgBeginRedelegate { 190 | pub fn create( 191 | delegator_address: String, 192 | validator_dst_address: String, 193 | validator_src_address: String, 194 | amount: Coin, 195 | ) -> anyhow::Result { 196 | let internal = MsgBeginRedelegate { 197 | amount, 198 | delegator_address, 199 | validator_src_address, 200 | validator_dst_address, 201 | }; 202 | Ok(Message { 203 | s_type: "staking/MsgBeginRedelegate".into(), 204 | value: serde_json::to_value(internal)?, 205 | }) 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/oracle.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::{Coin, MsgInternal}; 2 | 3 | use crate::messages::Message; 4 | use crypto::digest::Digest; 5 | use crypto::sha2::Sha256; 6 | use serde::{Deserialize, Serialize}; 7 | use std::ops::Add; 8 | 9 | #[derive(Deserialize, Serialize, Debug)] 10 | 11 | /// used in feeder oracle 12 | pub struct MsgAggregateExchangeRatePreVote { 13 | pub feeder: String, 14 | pub hash: String, 15 | pub validator: String, 16 | } 17 | 18 | impl MsgInternal for MsgAggregateExchangeRatePreVote {} 19 | impl MsgAggregateExchangeRatePreVote { 20 | /// Create a pre vote message 21 | pub fn create(hash: String, feeder: String, validator: String) -> anyhow::Result { 22 | let internal = MsgAggregateExchangeRatePreVote { 23 | feeder, 24 | hash, 25 | validator, 26 | }; 27 | Ok(Message { 28 | s_type: "oracle/MsgAggregateExchangeRatePrevote".into(), 29 | value: serde_json::to_value(internal)?, 30 | }) 31 | } 32 | } 33 | 34 | #[derive(Deserialize, Serialize, Debug)] 35 | 36 | /// used in feeder oracle to submit exchange rates 37 | pub struct MsgAggregateExchangeRateVote { 38 | pub exchange_rates: String, 39 | pub feeder: String, 40 | /// The salt is used in the next round's 'PreVote' 41 | pub salt: String, 42 | pub validator: String, 43 | } 44 | 45 | /// put out into a separate function to facilitate better testing 46 | fn generate_hash<'a>(salt: &'a str, exchange_string: &'a str, validator: &'a str) -> String { 47 | let mut sha = Sha256::new(); 48 | let mut to_hash: String = String::new(); 49 | to_hash = to_hash.add(salt); 50 | to_hash = to_hash.add(":"); 51 | to_hash = to_hash.add(exchange_string); 52 | to_hash = to_hash.add(":"); 53 | to_hash = to_hash.add(validator); 54 | sha.input_str(&to_hash); 55 | let full_hash = sha.result_str(); 56 | full_hash.split_at(40).0.parse().unwrap() 57 | } 58 | impl MsgInternal for MsgAggregateExchangeRateVote {} 59 | impl MsgAggregateExchangeRateVote { 60 | fn generate_hash(&self, previous_salt: &str) -> String { 61 | generate_hash(previous_salt, &self.exchange_rates, &self.validator) 62 | } 63 | 64 | pub fn create_internal( 65 | salt: String, 66 | exchange_rates: Vec, 67 | feeder: String, 68 | validator: String, 69 | ) -> MsgAggregateExchangeRateVote { 70 | let mut new_rates: Vec = Vec::with_capacity(exchange_rates.len()); 71 | for rate in exchange_rates { 72 | new_rates.push(rate); 73 | } 74 | new_rates.sort_by(|a, b| a.denom.cmp(&b.denom)); 75 | let coins = new_rates 76 | .iter() 77 | .map(|f| f.to_string()) 78 | .collect::>() 79 | .join(","); 80 | MsgAggregateExchangeRateVote { 81 | salt, 82 | exchange_rates: coins, 83 | feeder, 84 | validator, 85 | } 86 | } 87 | /// Create a vote message 88 | pub fn create( 89 | salt: String, 90 | exchange_rates: Vec, 91 | feeder: String, 92 | validator: String, 93 | ) -> anyhow::Result { 94 | let internal = 95 | MsgAggregateExchangeRateVote::create_internal(salt, exchange_rates, feeder, validator); 96 | Ok(Message { 97 | s_type: "oracle/MsgAggregateExchangeRateVote".into(), 98 | value: serde_json::to_value(internal)?, 99 | }) 100 | } 101 | /// Create a vote message from internal message 102 | pub fn create_from_internal(internal: MsgAggregateExchangeRateVote) -> anyhow::Result { 103 | // let internal = 104 | // MsgAggregateExchangeRateVote::create_internal(salt, exchange_rates, feeder, validator); 105 | Ok(Message { 106 | s_type: "oracle/MsgAggregateExchangeRateVote".into(), 107 | value: serde_json::to_value(internal)?, 108 | }) 109 | } 110 | 111 | /// Pre-Vote messages are like a 'linked list'. 112 | /// they use the salt of the previous 'RateVote' to hash the current prices, to ensure continuity 113 | pub fn gen_pre_vote(&self, previous_salt: &str) -> anyhow::Result { 114 | MsgAggregateExchangeRatePreVote::create( 115 | self.generate_hash(previous_salt), 116 | self.feeder.clone(), 117 | self.validator.clone(), 118 | ) 119 | } 120 | } 121 | 122 | #[derive(Deserialize, Serialize, Debug)] 123 | /// used in feeder oracle 124 | 125 | pub struct MsgDelegateFeedConsent { 126 | pub delegate: String, 127 | pub operator: String, 128 | } 129 | impl MsgInternal for MsgDelegateFeedConsent {} 130 | impl MsgDelegateFeedConsent { 131 | /// Create a pre vote message 132 | pub fn create(operator: String, delegate: String) -> anyhow::Result { 133 | let internal = MsgDelegateFeedConsent { delegate, operator }; 134 | Ok(Message { 135 | s_type: "oracle/MsgDelegateFeedConsent".into(), 136 | value: serde_json::to_value(internal)?, 137 | }) 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tst { 143 | use super::*; 144 | #[test] 145 | pub fn test_agg() -> anyhow::Result<()> { 146 | let exchange_rate_str = "22.540203133218404887uaud,21.645596278923282692ucad,15.966787551658593971uchf,113.167767068332957759ucny,14.449845494375560683ueur,12.582839885411827405ugbp,135.474594500430895984uhkd,1300.213822842493250029uinr,1900.8256376511075722ujpy,20351.150811544637337767ukrw,49749.106615326838874584umnt,12.154984433357638529usdr,23.143090361112943758usgd,0.0uthb,17.444833658754882816uusd"; 147 | let exchange_rates = Coin::parse_coins(exchange_rate_str)?; 148 | let salt = String::from("df59"); 149 | let feeder = String::from("terra1824vxwh43h9d3qczj4jvc3qphlf2evfp9w0ph9"); 150 | let validator = String::from("terravaloper1usws7c2c6cs7nuc8vma9qzaky5pkgvm2ujy8ny"); 151 | let hash = "36681b69da96623a6ae12c2a51448b7426fdd64e"; 152 | let coins = exchange_rates 153 | .iter() 154 | .map(|f| f.to_string()) 155 | .collect::>() 156 | .join(","); 157 | assert_eq!(coins, exchange_rate_str); 158 | 159 | assert_eq!(generate_hash(&salt, exchange_rate_str, &validator), hash); 160 | let vote_1 = MsgAggregateExchangeRateVote::create_internal( 161 | salt.clone(), 162 | exchange_rates, 163 | feeder, 164 | validator, 165 | ); 166 | 167 | assert_eq!(vote_1.generate_hash(&salt), hash); 168 | // let pre_vote = vote_1.gen_pre_vote(&salt); 169 | // assert_eq!(pre_vote.s_type, "oracle/MsgAggregateExchangeRatePrevote"); 170 | 171 | Ok(()) 172 | } 173 | #[test] 174 | pub fn tst_hash() -> anyhow::Result<()> { 175 | let exchange_rates= "22.540203133218404887uaud,21.645596278923282692ucad,15.966787551658593971uchf,113.167767068332957759ucny,14.449845494375560683ueur,12.582839885411827405ugbp,135.474594500430895984uhkd,1300.213822842493250029uinr,1900.8256376511075722ujpy,20351.150811544637337767ukrw,49749.106615326838874584umnt,12.154984433357638529usdr,23.143090361112943758usgd,0.0uthb,17.444833658754882816uusd"; 176 | let salt = "df59"; 177 | let validator = "terravaloper1usws7c2c6cs7nuc8vma9qzaky5pkgvm2ujy8ny"; 178 | let hash = "36681b69da96623a6ae12c2a51448b7426fdd64e"; 179 | assert_eq!(hash, generate_hash(salt, exchange_rates, validator)); 180 | let exchange_rates2="22.548222362821767308uaud,21.653297230216244188ucad,15.972468127589880034uchf,113.208029274598674692ucny,14.454986380997906048ueur,12.58731653903855345ugbp,135.522792907175522923uhkd,1300.676405771192471765uinr,1901.501902950678788445ujpy,20358.286256112132944846ukrw,49766.806079087327983387umnt,12.159308866922868479usdr,23.151324082621141584usgd,0.0uthb,17.451040085807700841uusd"; 181 | let salt2 = "6dd4"; 182 | let validator2 = "terravaloper1usws7c2c6cs7nuc8vma9qzaky5pkgvm2ujy8ny"; 183 | let hash2 = "54a849b1b3b510f5f0b7c5405ed2cc74cd283251"; 184 | assert_eq!(hash2, generate_hash(salt2, exchange_rates2, validator2)); 185 | 186 | Ok(()) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /terra-rust-api/src/messages/wasm.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::{Coin, MsgInternal}; 2 | use crate::terra_u64_format; 3 | use std::path::Path; 4 | 5 | use crate::errors::TerraRustAPIError; 6 | use crate::messages::Message; 7 | use serde::Serialize; 8 | 9 | #[derive(Serialize, Debug)] 10 | /// Message: Exec Contract 11 | pub struct MsgExecuteContract { 12 | pub coins: Vec, 13 | pub contract: String, 14 | pub execute_msg: serde_json::Value, 15 | pub sender: String, 16 | } 17 | 18 | impl MsgInternal for MsgExecuteContract {} 19 | impl MsgExecuteContract { 20 | /// use provided base64 exec message 21 | pub fn create_from_value( 22 | sender: &str, 23 | contract: &str, 24 | execute_msg: &serde_json::Value, 25 | coins: &[Coin], 26 | ) -> Result { 27 | let internal = MsgExecuteContract { 28 | sender: sender.into(), 29 | contract: contract.into(), 30 | execute_msg: execute_msg.clone(), 31 | coins: coins.to_vec(), 32 | }; 33 | Ok(Message { 34 | s_type: "wasm/MsgExecuteContract".into(), 35 | value: serde_json::to_value(internal)?, 36 | }) 37 | } 38 | /// use provided base64 exec message 39 | pub fn create_from_json( 40 | sender: &str, 41 | contract: &str, 42 | execute_msg_json: &str, 43 | coins: &[Coin], 44 | ) -> Result { 45 | let exec_b64: serde_json::Value = serde_json::from_str(execute_msg_json)?; 46 | MsgExecuteContract::create_from_value(sender, contract, &exec_b64, coins) 47 | } 48 | } 49 | 50 | #[derive(Serialize, Debug)] 51 | /// Message: Exec Contract 52 | pub struct MsgStoreCode { 53 | pub sender: String, 54 | pub wasm_byte_code: String, 55 | } 56 | 57 | impl MsgInternal for MsgStoreCode {} 58 | impl MsgStoreCode { 59 | /// use provided base64 exec message 60 | pub fn create_from_b64( 61 | sender: &str, 62 | wasm_byte_code: &str, 63 | ) -> Result { 64 | let internal = MsgStoreCode { 65 | sender: sender.into(), 66 | wasm_byte_code: wasm_byte_code.into(), 67 | }; 68 | Ok(Message { 69 | s_type: "wasm/MsgStoreCode".into(), 70 | value: serde_json::to_value(internal)?, 71 | }) 72 | } 73 | /// use provided base64 exec message 74 | pub fn create_from_file(sender: &str, file_name: &Path) -> Result { 75 | let file_contents = std::fs::read(file_name)?; 76 | let exec_b64 = base64::encode(file_contents); 77 | MsgStoreCode::create_from_b64(sender, &exec_b64) 78 | } 79 | } 80 | 81 | #[derive(Serialize, Debug)] 82 | /// Message: Exec Contract 83 | pub struct MsgInstantiateContract { 84 | pub admin: Option, 85 | #[serde(with = "terra_u64_format")] 86 | pub code_id: u64, 87 | pub sender: String, 88 | pub init_coins: Vec, 89 | pub init_msg: serde_json::Value, 90 | } 91 | 92 | impl MsgInternal for MsgInstantiateContract {} 93 | impl MsgInstantiateContract { 94 | /* 95 | /// use provided base64 exec message 96 | pub fn create_from_b64( 97 | sender: &str, 98 | admin: Option, 99 | code_id: usize, 100 | init_msg: &str, 101 | init_coins: Vec, 102 | ) -> Message { 103 | let internal = MsgInstantiateContract { 104 | admin: admin.map(|f| f.into()), 105 | code_id: code_id.to_string(), 106 | sender: sender.into(), 107 | init_coins, 108 | init_msg: init_msg.into(), 109 | }; 110 | Message { 111 | s_type: "wasm/MsgInstantiateContract".into(), 112 | value: Box::new(internal), 113 | } 114 | } 115 | 116 | */ 117 | /// create from JSON 118 | pub fn create_from_json( 119 | sender: &str, 120 | admin: Option, 121 | code_id: u64, 122 | init_msg: &str, 123 | init_coins: Vec, 124 | ) -> Result { 125 | // panic!("This message does not function"); 126 | 127 | let contents: serde_json::Value = serde_json::from_str(init_msg)?; 128 | //let exec_b64 = base64::encode(contents.to_string()); 129 | 130 | let internal = MsgInstantiateContract { 131 | admin, //: admin.unwrap_or_else(|| "".into()), 132 | code_id, 133 | sender: sender.into(), 134 | init_coins, 135 | init_msg: contents, 136 | }; 137 | Ok(Message { 138 | s_type: "wasm/MsgInstantiateContract".into(), 139 | value: serde_json::to_value(internal)?, 140 | }) 141 | } 142 | /// use provided base64 exec message 143 | /// switches ##SENDER##, ##ADMIN##, ##CODE_ID## with respective values 144 | pub fn create_from_file( 145 | sender: &str, 146 | admin: Option, 147 | code_id: u64, 148 | init_file: &Path, 149 | init_coins: Vec, 150 | ) -> Result { 151 | let contents = std::fs::read_to_string(init_file)?; 152 | let new_contents = Self::replace_parameters(sender, admin.clone(), code_id, &contents); 153 | Self::create_from_json(sender, admin, code_id, &new_contents, init_coins) 154 | } 155 | /// replace parts of the string with current values 156 | pub fn replace_parameters( 157 | sender: &str, 158 | admin: Option, 159 | code_id: u64, 160 | instantiate_str: &str, 161 | ) -> String { 162 | let part_1 = instantiate_str 163 | .replace("##SENDER##", sender) 164 | .replace("##CODE_ID##", &format!("{}", code_id)); 165 | match admin { 166 | Some(admin_str) => part_1.replace("##ADMIN##", &admin_str), 167 | None => part_1.replace("##ADMIN##", ""), 168 | } 169 | } 170 | } 171 | 172 | #[derive(Serialize, Debug)] 173 | /// Message: Exec Contract 174 | pub struct MsgMigrateContract { 175 | pub admin: String, 176 | pub contract: String, 177 | #[serde(with = "terra_u64_format")] 178 | pub new_code_id: u64, 179 | pub migrate_msg: serde_json::Value, 180 | } 181 | 182 | impl MsgInternal for MsgMigrateContract {} 183 | impl MsgMigrateContract { 184 | /// create from JSON 185 | pub fn create_from_json( 186 | admin: &str, 187 | contract: &str, 188 | new_code_id: u64, 189 | migrate_msg: &str, 190 | ) -> Result { 191 | let contents: serde_json::Value = serde_json::from_str(migrate_msg)?; 192 | 193 | let internal = MsgMigrateContract { 194 | admin: String::from(admin), 195 | contract: String::from(contract), 196 | new_code_id, 197 | migrate_msg: contents, 198 | }; 199 | Ok(Message { 200 | s_type: "wasm/MsgMigrateContract".into(), 201 | value: serde_json::to_value(internal)?, 202 | }) 203 | } 204 | /// use provided base64 exec message 205 | /// switches ##SENDER##, ##ADMIN##, ##CODE_ID## with respective values 206 | pub fn create_from_file( 207 | admin: &str, 208 | contract: &str, 209 | new_code_id: u64, 210 | migrate_file: &Path, 211 | ) -> Result { 212 | let contents = std::fs::read_to_string(migrate_file)?; 213 | let new_contents = Self::replace_parameters(admin, contract, new_code_id, &contents); 214 | 215 | Self::create_from_json(admin, contract, new_code_id, &new_contents) 216 | } 217 | /// switches ##SENDER##, ##ADMIN##, ##CODE_ID## with respective values 218 | pub fn replace_parameters( 219 | admin: &str, 220 | contract: &str, 221 | new_code_id: u64, 222 | migrate_str: &str, 223 | ) -> String { 224 | migrate_str 225 | .replace("##ADMIN##", admin) 226 | .replace("##CONTRACT##", contract) 227 | .replace("##NEW_CODE_ID##", &format!("{}", new_code_id)) 228 | } 229 | } 230 | 231 | #[cfg(test)] 232 | mod tst { 233 | use super::*; 234 | /* 235 | #[test] 236 | pub fn test_b64() -> anyhow::Result<()> { 237 | let vote_1 = MsgExecuteContract::create_from_b64( 238 | "terra1vr0e7kylhu9am44v0s3gwkccmz7k3naxysrwew", 239 | "terra1f32xyep306hhcxxxf7mlyh0ucggc00rm2s9da5", 240 | "eyJjYXN0X3ZvdGUiOnsicG9sbF9pZCI6NDQsInZvdGUiOiJ5ZXMiLCJhbW91bnQiOiIxMDAwMDAwMCJ9fQ==", 241 | &vec![], 242 | ); 243 | let vote_2 = MsgExecuteContract::create_from_json( 244 | "terra1vr0e7kylhu9am44v0s3gwkccmz7k3naxysrwew", 245 | "terra1f32xyep306hhcxxxf7mlyh0ucggc00rm2s9da5", 246 | r#"{"cast_vote":{"poll_id":44,"vote":"yes","amount":"10000000"}}"#, 247 | &vec![], 248 | ); 249 | 250 | let js_1 = serde_json::to_string(&vote_1)?; 251 | let js_2 = serde_json::to_string(&vote_2)?; 252 | 253 | assert_eq!(js_1, js_2); 254 | let js_real = r#"{"type":"wasm/MsgExecuteContract","value":{"coins":[],"contract":"terra1f32xyep306hhcxxxf7mlyh0ucggc00rm2s9da5","execute_msg":"eyJjYXN0X3ZvdGUiOnsicG9sbF9pZCI6NDQsInZvdGUiOiJ5ZXMiLCJhbW91bnQiOiIxMDAwMDAwMCJ9fQ==","sender":"terra1vr0e7kylhu9am44v0s3gwkccmz7k3naxysrwew"}}"#; 255 | assert_eq!(js_1, js_real); 256 | Ok(()) 257 | } 258 | 259 | */ 260 | #[test] 261 | pub fn test_file_conversion() -> anyhow::Result<()> { 262 | let token_file = Path::new("./resources/terraswap_pair.wasm"); 263 | let output = Path::new("./resources/terraswap_pair.output"); 264 | let out_json = std::fs::read_to_string(output)?; 265 | let msg = MsgStoreCode::create_from_file( 266 | "terra1vr0e7kylhu9am44v0s3gwkccmz7k3naxysrwew", 267 | token_file, 268 | )?; 269 | 270 | let js = serde_json::to_string(&msg)?; 271 | log::debug!("Test file conversion: {} - {}", out_json.len(), js.len()); 272 | assert_eq!(out_json.trim(), js); 273 | Ok(()) 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /terra-rust-api/src/keys/private.rs: -------------------------------------------------------------------------------- 1 | use crate::core_types::StdSignature; 2 | use crate::keys::PublicKey; 3 | use bitcoin::util::bip32::{ExtendedPrivKey, IntoDerivationPath}; 4 | use bitcoin::Network; 5 | use crypto::sha2::Sha256; 6 | use secp256k1::Message; 7 | use secp256k1::Secp256k1; 8 | 9 | use crypto::digest::Digest; 10 | use hkd32::mnemonic::{Phrase, Seed}; 11 | 12 | use crate::errors::TerraRustAPIError; 13 | use rand_core::OsRng; 14 | 15 | /// This is the coin type used in most derivations 16 | pub static LUNA_COIN_TYPE: u32 = 330; 17 | 18 | /// The Private key structure that is used to generate signatures and public keys 19 | /// WARNING: No Security Audit has been performed 20 | #[derive(Clone)] 21 | pub struct PrivateKey { 22 | #[allow(missing_docs)] 23 | pub account: u32, 24 | #[allow(missing_docs)] 25 | pub index: u32, 26 | #[allow(missing_docs)] 27 | pub coin_type: u32, 28 | /// The 24 words used to generate this private key 29 | mnemonic: Option, 30 | #[allow(dead_code)] 31 | /// This is used for testing 32 | root_private_key: ExtendedPrivKey, 33 | /// The private key 34 | private_key: ExtendedPrivKey, 35 | } 36 | impl PrivateKey { 37 | /// Generate a new private key 38 | pub fn new( 39 | secp: &Secp256k1, 40 | ) -> Result { 41 | let phrase = 42 | hkd32::mnemonic::Phrase::random(&mut OsRng, hkd32::mnemonic::Language::English); 43 | 44 | PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, LUNA_COIN_TYPE, "") 45 | } 46 | /// generate a new private key with a seed phrase 47 | pub fn new_seed( 48 | secp: &Secp256k1, 49 | seed_phrase: &str, 50 | ) -> Result { 51 | let phrase = 52 | hkd32::mnemonic::Phrase::random(&mut OsRng, hkd32::mnemonic::Language::English); 53 | 54 | PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, LUNA_COIN_TYPE, seed_phrase) 55 | } 56 | /// for private key recovery. This is also used by wallet routines to re-hydrate the structure 57 | pub fn from_words( 58 | secp: &Secp256k1, 59 | words: &str, 60 | account: u32, 61 | index: u32, 62 | ) -> Result { 63 | match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) { 64 | Ok(phrase) => { 65 | PrivateKey::gen_private_key_phrase(secp, phrase, account, index, LUNA_COIN_TYPE, "") 66 | } 67 | Err(_) => Err(TerraRustAPIError::Phrasing), 68 | } 69 | } 70 | 71 | /// for private key recovery with seed phrase 72 | pub fn from_words_seed( 73 | secp: &Secp256k1, 74 | words: &str, 75 | seed_pass: &str, 76 | ) -> Result { 77 | match hkd32::mnemonic::Phrase::new(words, hkd32::mnemonic::Language::English) { 78 | Ok(phrase) => { 79 | PrivateKey::gen_private_key_phrase(secp, phrase, 0, 0, LUNA_COIN_TYPE, seed_pass) 80 | } 81 | Err(_) => Err(TerraRustAPIError::Phrasing), 82 | } 83 | } 84 | 85 | /// generate the public key for this private key 86 | pub fn public_key( 87 | &self, 88 | secp: &Secp256k1, 89 | ) -> PublicKey { 90 | let x = &self.private_key.private_key.public_key(secp); 91 | PublicKey::from_bitcoin_public_key(x) 92 | } 93 | 94 | fn gen_private_key_phrase( 95 | secp: &Secp256k1, 96 | phrase: Phrase, 97 | account: u32, 98 | index: u32, 99 | coin_type: u32, 100 | seed_phrase: &str, 101 | ) -> Result { 102 | let seed = phrase.to_seed(seed_phrase); 103 | let root_private_key = 104 | ExtendedPrivKey::new_master(Network::Bitcoin, seed.as_bytes()).unwrap(); 105 | let path = format!("m/44'/{}'/{}'/0/{}", coin_type, account, index); 106 | let derivation_path = path.into_derivation_path()?; 107 | 108 | let private_key = root_private_key.derive_priv(secp, &derivation_path)?; 109 | Ok(PrivateKey { 110 | account, 111 | index, 112 | coin_type, 113 | mnemonic: Some(phrase), 114 | root_private_key, 115 | private_key, 116 | }) 117 | } 118 | 119 | /// the words used to generate this private key 120 | pub fn words(&self) -> Option<&str> { 121 | self.mnemonic.as_ref().map(|phrase| phrase.phrase()) 122 | } 123 | /// signs a blob of data and returns a [StdSignature] 124 | pub fn sign( 125 | &self, 126 | secp: &Secp256k1, 127 | blob: &str, 128 | ) -> Result { 129 | let pub_k = &self.private_key.private_key.public_key(secp); 130 | 131 | let priv_k = self.private_key.private_key.key; 132 | let mut sha = Sha256::new(); 133 | let mut sha_result: [u8; 32] = [0; 32]; 134 | sha.input_str(blob); 135 | sha.result(&mut sha_result); 136 | 137 | let message: Message = Message::from_slice(&sha_result)?; 138 | let signature = secp.sign(&message, &priv_k); 139 | 140 | //eprintln!("SIG:{}", hex::encode(&signature.serialize_compact())); 141 | let sig: StdSignature = StdSignature::create(&signature.serialize_compact(), pub_k); 142 | Ok(sig) 143 | } 144 | /// used for testing 145 | /// could potentially be used to recreate the private key instead of words 146 | #[allow(dead_code)] 147 | pub(crate) fn seed(&self, passwd: &str) -> Option { 148 | self.mnemonic.as_ref().map(|phrase| phrase.to_seed(passwd)) 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tst { 154 | use super::*; 155 | 156 | #[test] 157 | pub fn tst_gen_mnemonic() -> Result<(), TerraRustAPIError> { 158 | // this test just makes sure the default will call it. 159 | let s = Secp256k1::new(); 160 | PrivateKey::new(&s).map(|_| ()) 161 | } 162 | #[test] 163 | pub fn tst_words() -> anyhow::Result<()> { 164 | let str_1 = "notice oak worry limit wrap speak medal online prefer cluster roof addict wrist behave treat actual wasp year salad speed social layer crew genius"; 165 | let seed_1 = "a2ae8846397b55d266af35acdbb18ba1d005f7ddbdd4ca7a804df83352eaf373f274ba0dc8ac1b2b25f19dfcb7fa8b30a240d2c6039d88963defc2f626003b2f"; 166 | let s = Secp256k1::new(); 167 | let pk = PrivateKey::from_words(&s, str_1, 0, 0)?; 168 | assert_eq!(hex::encode(pk.seed("").unwrap().as_bytes()), seed_1); 169 | match pk.words() { 170 | Some(words) => { 171 | assert_eq!(words, str_1); 172 | Ok(()) 173 | } 174 | None => Err(TerraRustAPIError::MissingPhrase.into()), 175 | } 176 | } 177 | #[test] 178 | pub fn tst_root_priv_key() -> anyhow::Result<()> { 179 | let str_1 = "wonder caution square unveil april art add hover spend smile proud admit modify old copper throw crew happy nature luggage reopen exhibit ordinary napkin"; 180 | let secp = Secp256k1::new(); 181 | let pk = PrivateKey::from_words(&secp, str_1, 0, 0)?; 182 | let root_key = "xprv9s21ZrQH143K2ep3BpYRRMjSqjLHZAPAzxfVVS3NBuGKBVtCrK3C8mE8TcmTjYnLm7SJxdLigDFWGAMnctKxc3p5QKNWXdprcFSQzGzQqTW"; 183 | assert_eq!(pk.root_private_key.to_string(), root_key); 184 | 185 | let derived_key = "4804e2bdce36d413206ccf47cc4c64db2eff924e7cc9e90339fa7579d2bd9d5b"; 186 | assert_eq!(pk.private_key.private_key.key.to_string(), derived_key); 187 | 188 | Ok(()) 189 | } 190 | #[test] 191 | pub fn tst_words_to_pub() -> anyhow::Result<()> { 192 | let str_1 = "wonder caution square unveil april art add hover spend smile proud admit modify old copper throw crew happy nature luggage reopen exhibit ordinary napkin"; 193 | let secp = Secp256k1::new(); 194 | let pk = PrivateKey::from_words(&secp, str_1, 0, 0)?; 195 | let pub_k = pk.public_key(&secp); 196 | 197 | let account = pub_k.account()?; 198 | assert_eq!(&account, "terra1jnzv225hwl3uxc5wtnlgr8mwy6nlt0vztv3qqm"); 199 | assert_eq!( 200 | &pub_k.operator_address_public_key()?, 201 | "terravaloperpub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5y7accr" 202 | ); 203 | assert_eq!( 204 | &pub_k.application_public_key()?, 205 | "terrapub1addwnpepqt8ha594svjn3nvfk4ggfn5n8xd3sm3cz6ztxyugwcuqzsuuhhfq5nwzrf9" 206 | ); 207 | 208 | Ok(()) 209 | } 210 | #[test] 211 | pub fn test_sign() -> anyhow::Result<()> { 212 | // This test is using message from python SDK.. so these keys generate same sigs as they do. 213 | let str_1 = "island relax shop such yellow opinion find know caught erode blue dolphin behind coach tattoo light focus snake common size analyst imitate employ walnut"; 214 | let secp = Secp256k1::new(); 215 | let pk = PrivateKey::from_words(&secp, str_1, 0, 0)?; 216 | let _pub_k = pk.public_key(&secp); 217 | let to_sign = r#"{"account_number":"45","chain_id":"columbus-3-testnet","fee":{"amount":[{"amount":"698","denom":"uluna"}],"gas":"46467"},"memo":"","msgs":[{"type":"bank/MsgSend","value":{"amount":[{"amount":"100000000","denom":"uluna"}],"from_address":"terra1n3g37dsdlv7ryqftlkef8mhgqj4ny7p8v78lg7","to_address":"terra1wg2mlrxdmnnkkykgqg4znky86nyrtc45q336yv"}}],"sequence":"0"}"#; 218 | 219 | let sig = pk.sign(&secp, to_sign)?; 220 | 221 | assert_eq!( 222 | sig.pub_key.value, 223 | "AiMzHaA2bvnDXfHzkjMM+vkSE/p0ymBtAFKUnUtQAeXe" 224 | ); 225 | assert_eq!(sig.signature, "FJKAXRxNB5ruqukhVqZf3S/muZEUmZD10fVmWycdVIxVWiCXXFsUy2VY2jINEOUGNwfrqEZsT2dUfAvWj8obLg=="); 226 | 227 | Ok(()) 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /terra-rust-api/src/client/tx.rs: -------------------------------------------------------------------------------- 1 | use reqwest::StatusCode; 2 | //use crate::client::core_types::Msg; 3 | #[allow(deprecated)] 4 | use crate::client::tx_types::TXResultBlock; 5 | use crate::client::tx_types::{ 6 | TXResultAsync, TXResultSync, TxEstimate, TxFeeResult, V1TXResult, V1TXSResult, 7 | }; 8 | 9 | use crate::core_types::{Coin, StdSignMsg, StdSignature, StdTx}; 10 | use crate::errors::TerraRustAPIError; 11 | use crate::errors::TerraRustAPIError::TXNotFound; 12 | use crate::messages::Message; 13 | use crate::{LCDResult, Terra}; 14 | 15 | #[allow(clippy::upper_case_acronyms)] 16 | pub struct TX<'a> { 17 | terra: &'a Terra, 18 | } 19 | impl<'a> TX<'a> { 20 | pub fn create(terra: &'a Terra) -> TX<'a> { 21 | TX { terra } 22 | } 23 | /// perform an Async submission to the blockchain. This returns the TXHash 24 | /// This is not guaranteed to successfully create a transaction record, due to numerous factors 25 | pub async fn broadcast_async( 26 | &self, 27 | std_sign_msg: &StdSignMsg, 28 | sigs: &[StdSignature], 29 | ) -> Result { 30 | let std_tx: StdTx = StdTx::from_StdSignMsg(std_sign_msg, sigs, "async"); 31 | 32 | // let js_sig = serde_json::to_string(&std_tx)?; 33 | let response = self 34 | .terra 35 | .post_cmd::("/txs", &std_tx) 36 | .await?; 37 | Ok(response) 38 | } 39 | /// perform a sync submission to the blockchain. This will return more validation logic than async 40 | /// but you wait. It is still not guaranteed to create a blockchain transaction 41 | #[allow(deprecated)] 42 | pub async fn broadcast_sync( 43 | &self, 44 | std_sign_msg: &StdSignMsg, 45 | sigs: &[StdSignature], 46 | ) -> Result { 47 | let std_tx: StdTx = StdTx::from_StdSignMsg(std_sign_msg, sigs, "sync"); 48 | // let js_sig = serde_json::to_string(&std_tx)?; 49 | log::debug!("TX broadcast #messages ={}", &std_tx.tx.msg.len()); 50 | if self.terra.debug { 51 | log::info!("TX broadcast #messages ={}", &std_tx.tx.msg.len()); 52 | log::debug!("{}", serde_json::to_string(&std_tx)?); 53 | } 54 | let response = self 55 | .terra 56 | .post_cmd::("/txs", &std_tx) 57 | .await?; 58 | Ok(response) 59 | } 60 | /// perform a 'blocking' submission to the blockchain. This will only return once the transaction 61 | /// is executed on the blockchain. This is great for debugging, but not recommended to be used otherwise 62 | #[allow(deprecated)] 63 | pub async fn broadcast_block( 64 | &self, 65 | std_sign_msg: &StdSignMsg, 66 | sigs: &[StdSignature], 67 | ) -> Result { 68 | log::warn!("Broadcast_block is not recommended to be used in production situations"); 69 | let std_tx: StdTx = StdTx::from_StdSignMsg(std_sign_msg, sigs, "block"); 70 | // let js_sig = serde_json::to_string(&std_tx)?; 71 | let response = self 72 | .terra 73 | .post_cmd::("/txs", &std_tx) 74 | .await?; 75 | Ok(response) 76 | } 77 | #[deprecated( 78 | since = "1.2.12", 79 | note = "terra has deprecated this API endpoint. use get_v1" 80 | )] 81 | /// get TX result 82 | #[allow(deprecated)] 83 | pub async fn get(&self, hash: &str) -> Result { 84 | let resp = self 85 | .terra 86 | .send_cmd::(&format!("/txs/{}", hash), None) 87 | .await?; 88 | Ok(resp) 89 | } 90 | /// use v1 API 91 | pub async fn get_v1(&self, hash: &str) -> Result { 92 | let resp = self 93 | .terra 94 | .send_cmd::(&format!("/cosmos/tx/v1beta1/txs/{}", hash), None) 95 | .await?; 96 | Ok(resp) 97 | } 98 | #[deprecated( 99 | since = "1.2.12", 100 | note = "terra has deprecated this API endpoint. Use get_and_wait_v1" 101 | )] 102 | /// get TX result, retrying a few times. 103 | #[allow(deprecated)] 104 | pub async fn get_and_wait( 105 | &self, 106 | hash: &str, 107 | max_times: usize, 108 | sleep_amount: tokio::time::Duration, 109 | ) -> Result { 110 | let mut times = 0; 111 | while times < max_times { 112 | #[allow(deprecated)] 113 | let tx = self.get(hash).await; 114 | 115 | match tx { 116 | Ok(tx_response) => return Ok(tx_response), 117 | Err(e) => { 118 | times += 1; 119 | match &e { 120 | TerraRustAPIError::TerraLCDResponse(statuscode, out) => { 121 | if statuscode == &StatusCode::NOT_FOUND { 122 | log::debug!( 123 | "Transaction not applied .. retry #{} sleeping {} seconds", 124 | times, 125 | sleep_amount.as_secs() 126 | ); 127 | tokio::time::sleep(sleep_amount).await; 128 | } else { 129 | log::error!("Invalid Response TX: {} {}", statuscode, out); 130 | break; 131 | } 132 | } 133 | _ => { 134 | log::error!("Invalid Response TX: {:?}", e); 135 | break; 136 | } 137 | } 138 | } 139 | } 140 | } 141 | Err(TXNotFound(hash.to_string(), max_times)) 142 | } 143 | /// get TX result, retrying a few times. 144 | pub async fn get_and_wait_v1( 145 | &self, 146 | hash: &str, 147 | max_times: usize, 148 | sleep_amount: tokio::time::Duration, 149 | ) -> Result { 150 | let mut times = 0; 151 | while times < max_times { 152 | let tx = self.get_v1(hash).await; 153 | 154 | match tx { 155 | Ok(tx_response) => return Ok(tx_response), 156 | Err(e) => { 157 | times += 1; 158 | match &e { 159 | TerraRustAPIError::TerraLCDResponse(statuscode, out) => { 160 | if statuscode == &StatusCode::BAD_REQUEST { 161 | log::debug!( 162 | "Transaction not applied .. retry #{} sleeping {} seconds", 163 | times, 164 | sleep_amount.as_secs() 165 | ); 166 | tokio::time::sleep(sleep_amount).await; 167 | } else { 168 | log::error!("Invalid Response TX: {} {}", statuscode, out); 169 | break; 170 | } 171 | } 172 | _ => { 173 | log::error!("Invalid Response TX: {:?}", e); 174 | break; 175 | } 176 | } 177 | } 178 | } 179 | } 180 | Err(TXNotFound(hash.to_string(), max_times)) 181 | } 182 | 183 | /// Estimate the StdFee structure based on the gas used 184 | pub async fn estimate_fee( 185 | &self, 186 | sender: &str, 187 | msgs: &[Message], 188 | gas_adjustment: f64, 189 | gas_prices: &[&Coin], 190 | ) -> Result, TerraRustAPIError> { 191 | let tx_est = TxEstimate::create( 192 | &self.terra.chain_id, 193 | sender, 194 | msgs, 195 | gas_adjustment, 196 | gas_prices, 197 | ); 198 | 199 | if self.terra.debug { 200 | log::info!("Estimate Transaction = {}", serde_json::to_string(&tx_est)?); 201 | } else { 202 | log::debug!( 203 | "Estimate Transaction = {:#?} #messages={}", 204 | tx_est.base_req, 205 | tx_est.msgs.len() 206 | ) 207 | } 208 | let resp = self 209 | .terra 210 | .post_cmd::>("/txs/estimate_fee", &tx_est) 211 | .await?; 212 | Ok(resp) 213 | } 214 | /// simulate transaction for estimating gas usage 215 | pub async fn simulate_v1( 216 | &self, 217 | sender: &str, 218 | msgs: &[Message], 219 | gas_adjustment: f64, 220 | gas_prices: &[&Coin], 221 | ) -> Result, TerraRustAPIError> { 222 | let tx_est = TxEstimate::create( 223 | &self.terra.chain_id, 224 | sender, 225 | msgs, 226 | gas_adjustment, 227 | gas_prices, 228 | ); 229 | 230 | log::debug!( 231 | "simulate Transaction = {:#?} #messages={}", 232 | tx_est.base_req, 233 | tx_est.msgs.len() 234 | ); 235 | 236 | let resp = self 237 | .terra 238 | .post_cmd::>("/cosmos/tx/v1beta1/simulate", &tx_est) 239 | .await?; 240 | Ok(resp) 241 | } 242 | /// Get a list of transactions in a given block 243 | pub async fn get_txs_in_block( 244 | &self, 245 | height: u64, 246 | offset: Option, 247 | limit: Option, 248 | ) -> Result { 249 | let resp = self 250 | .terra 251 | .send_cmd::( 252 | &format!( 253 | "/cosmos/tx/v1beta1/txs?events=tx.height={}&order_by=ORDER_BY_ASC&pagination.limit={}&pagination.offset={}", 254 | height, 255 | limit.unwrap_or(100), 256 | offset.unwrap_or_default() 257 | ), 258 | None, 259 | ) 260 | .await?; 261 | Ok(resp) 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /examples/vstat.rs: -------------------------------------------------------------------------------- 1 | use dotenv::dotenv; 2 | use rust_decimal::prelude::Zero; 3 | use rust_decimal::Decimal; 4 | use std::collections::{HashMap, HashSet}; 5 | use std::fs::File; 6 | use std::io::{BufReader, BufWriter, Write}; 7 | use std::ops::Div; 8 | 9 | use terra_rust_api::staking_types::ValidatorDelegation; 10 | 11 | use terra_rust_cli::cli_helpers; 12 | 13 | /// VERSION number of package 14 | pub const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 15 | /// NAME of package 16 | pub const NAME: Option<&'static str> = option_env!("CARGO_PKG_NAME"); 17 | 18 | #[derive(Debug)] 19 | pub struct Wallet { 20 | pub max_contrib: Decimal, 21 | pub min_contrib: Decimal, 22 | pub num_contrib: u64, 23 | pub sum_contrib: Decimal, 24 | pub validators: Vec, 25 | pub monikers: Vec, 26 | } 27 | #[derive(Debug)] 28 | pub struct ValidatorDeet { 29 | pub moniker: String, 30 | pub num_delegators: usize, 31 | pub num_whales: usize, 32 | pub num_sharks: usize, 33 | pub total_delegations: Decimal, 34 | pub sharks_delegations: Decimal, 35 | pub whale_delegations: Decimal, 36 | pub whales: Vec, 37 | } 38 | async fn run() -> anyhow::Result<()> { 39 | let cli = cli_helpers::gen_cli_read_only("vstat", "vstat").get_matches(); 40 | let terra = cli_helpers::lcd_no_tx_from_args(&cli)?; 41 | let mut whale_set: HashSet = Default::default(); 42 | let mut shark_set: HashSet = Default::default(); 43 | let mut wallet_map: HashMap = Default::default(); 44 | let mut validator_map: HashMap = Default::default(); 45 | println!("chain={}", terra.chain_id); 46 | 47 | let validators = terra.staking().validators().await?.result; 48 | let prefix = format!("{}{}", std::env::temp_dir().display(), "V_"); 49 | let wallet_prefix = format!("{}{}", std::env::temp_dir().display(), "wallet_list.csv"); 50 | let whale_prefix = format!("{}{}", std::env::temp_dir().display(), "whale_list.csv"); 51 | let shark_prefix = format!("{}{}", std::env::temp_dir().display(), "shark_list.csv"); 52 | let v_prefix = format!("{}{}", std::env::temp_dir().display(), "validator_list.csv"); 53 | println!("{}", prefix); 54 | let mut total_tokens: Decimal = Decimal::zero(); 55 | for v in &validators { 56 | let filename = &format!("{}{}.json", prefix, v.operator_address.clone()); 57 | let delegates: Vec = if let Ok(file) = File::open(filename) { 58 | serde_json::from_reader(BufReader::new(file))? 59 | } else { 60 | let deets = terra 61 | .staking() 62 | .validator_delegations_limit(&v.operator_address, 16000) 63 | .await?; 64 | let file = File::create(filename)?; 65 | serde_json::to_writer_pretty(BufWriter::new(file), &deets.delegation_responses)?; 66 | deets.delegation_responses 67 | }; 68 | 69 | println!( 70 | "{} {} {}", 71 | v.description.moniker, 72 | delegates.len(), 73 | Decimal::from(v.tokens).div(Decimal::from(1_000_000u64)), 74 | ); 75 | total_tokens += Decimal::from(v.tokens).div(Decimal::from(1_000_000u64)); 76 | validator_map.insert( 77 | v.operator_address.clone(), 78 | ValidatorDeet { 79 | moniker: v.description.moniker.clone(), 80 | num_delegators: delegates.len(), 81 | total_delegations: Decimal::from(v.tokens).div(Decimal::from(1_000_000u64)), 82 | sharks_delegations: Default::default(), 83 | whale_delegations: Default::default(), 84 | whales: Default::default(), 85 | num_whales: 0, 86 | num_sharks: 0, 87 | }, 88 | ); 89 | for delegate in delegates { 90 | let from = delegate.delegation.delegator_address; 91 | let amount = delegate.balance.amount.div(Decimal::from(1_000_000u64)); 92 | wallet_map 93 | .entry(from) 94 | .and_modify(|entry| { 95 | entry.num_contrib += 1; 96 | entry.sum_contrib += amount; 97 | if entry.max_contrib < amount { 98 | entry.max_contrib = amount; 99 | } 100 | if entry.min_contrib > amount { 101 | entry.min_contrib = amount 102 | } 103 | entry.validators.push(v.operator_address.clone()); 104 | entry.monikers.push(v.description.moniker.clone()); 105 | }) 106 | .or_insert(Wallet { 107 | max_contrib: amount, 108 | min_contrib: amount, 109 | num_contrib: 1, 110 | sum_contrib: amount, 111 | validators: vec![v.operator_address.clone()], 112 | monikers: vec![v.description.moniker.clone()], 113 | }); 114 | } 115 | } 116 | let mut wallet_file = File::create(wallet_prefix)?; 117 | let whale_size = total_tokens.div(Decimal::from(100u64)); /* > 1% */ 118 | let shark_size = total_tokens.div(Decimal::from(1000u64)); /* > 0.1% */ 119 | writeln!( 120 | wallet_file, 121 | "address,max_contrib,min_contrib,num_contrib,sum_contrib, validators,monikers" 122 | )?; 123 | let mut shark_file = File::create(shark_prefix)?; 124 | writeln!( 125 | shark_file, 126 | "address,max_contrib,min_contrib,num_contrib,sum_contrib, validators,monikers" 127 | )?; 128 | let mut whale_file = File::create(whale_prefix)?; 129 | writeln!( 130 | whale_file, 131 | "address,max_contrib,min_contrib,num_contrib,sum_contrib, validators,monikers" 132 | )?; 133 | for d in wallet_map { 134 | let wallet = d.1; 135 | let address = d.0.clone(); 136 | let validator_join = wallet.validators.join("|"); 137 | let moniker_join = wallet.monikers.join("|"); 138 | writeln!( 139 | wallet_file, 140 | "{},{},{},{},{},{},{}", 141 | address.clone(), 142 | wallet.max_contrib, 143 | wallet.min_contrib, 144 | wallet.num_contrib, 145 | wallet.sum_contrib, 146 | validator_join, 147 | moniker_join 148 | )?; 149 | if wallet.sum_contrib > whale_size { 150 | whale_set.insert(address.clone()); 151 | writeln!( 152 | whale_file, 153 | "{},{},{},{},{},{},{}", 154 | address.clone(), 155 | wallet.max_contrib, 156 | wallet.min_contrib, 157 | wallet.num_contrib, 158 | wallet.sum_contrib, 159 | validator_join, 160 | moniker_join 161 | )?; 162 | for whale_holding in &wallet.validators { 163 | validator_map 164 | .entry(whale_holding.into()) 165 | .and_modify(|entry| { 166 | entry.num_whales += 1; 167 | entry.whales.push(address.clone()) 168 | }); 169 | } 170 | } else if wallet.sum_contrib > shark_size { 171 | shark_set.insert(address.clone()); 172 | writeln!( 173 | shark_file, 174 | "{},{},{},{},{},{},{}", 175 | address.clone(), 176 | wallet.max_contrib, 177 | wallet.min_contrib, 178 | wallet.num_contrib, 179 | wallet.sum_contrib, 180 | validator_join, 181 | moniker_join 182 | )?; 183 | for shark_holding in &wallet.validators { 184 | validator_map 185 | .entry(shark_holding.into()) 186 | .and_modify(|entry| { 187 | entry.num_sharks += 1; 188 | }); 189 | } 190 | } 191 | } 192 | /* ok.. in the first pass we have calculated whales. now go through delegations and adjust whale_delegations */ 193 | for v in validators { 194 | let filename = &format!("{}{}.json", prefix, v.operator_address.clone()); 195 | let file = File::open(filename)?; 196 | let delegates: Vec = serde_json::from_reader(BufReader::new(file))?; 197 | let mut whale_delegation = Decimal::zero(); 198 | let mut shark_delegation = Decimal::zero(); 199 | for d in delegates { 200 | if whale_set.contains(&d.delegation.delegator_address) { 201 | whale_delegation += d.balance.amount.div(Decimal::from(1_000_000u64)); 202 | } else if shark_set.contains(&d.delegation.delegator_address) { 203 | shark_delegation += d.balance.amount.div(Decimal::from(1_000_000u64)); 204 | } 205 | } 206 | validator_map.entry(v.operator_address).and_modify(|entry| { 207 | entry.whale_delegations = whale_delegation; 208 | entry.sharks_delegations = shark_delegation; 209 | }); 210 | } 211 | let mut v_file = File::create(v_prefix)?; 212 | writeln!( 213 | v_file, 214 | "address,moniker,num delegators,num sharks, num whales,total delegations,shark delegations,whale delegations,whales" 215 | )?; 216 | for d in validator_map { 217 | let validator = d.1; 218 | let address = d.0.clone(); 219 | writeln!( 220 | v_file, 221 | "{},{},{},{},{},{},{},{},{}", 222 | address.clone(), 223 | validator.moniker, 224 | validator.num_delegators, 225 | validator.num_sharks, 226 | validator.num_whales, 227 | validator.total_delegations, 228 | validator.sharks_delegations, 229 | validator.whale_delegations, 230 | validator.whales.join("|") 231 | )?; 232 | } 233 | Ok(()) 234 | } 235 | #[tokio::main] 236 | async fn main() { 237 | dotenv().ok(); 238 | env_logger::init(); 239 | 240 | if let Err(ref err) = run().await { 241 | log::error!("{}", err); 242 | err.chain() 243 | .skip(1) 244 | .for_each(|cause| log::error!("because: {}", cause)); 245 | 246 | // The backtrace is not always generated. Try to run this example 247 | // with `$env:RUST_BACKTRACE=1`. 248 | // if let Some(backtrace) = e.backtrace() { 249 | // log::debug!("backtrace: {:?}", backtrace); 250 | // } 251 | 252 | ::std::process::exit(1); 253 | } 254 | } 255 | --------------------------------------------------------------------------------