synnax 0.2.0

Cosmos-SDK multichain client
Documentation
use crate::cosmos::tx::{BroadcastMode, BroadcastTxRequest, BroadcastTxResponse, SimulateRequest};
use crate::cosmos::Cosmos;
use cosmrs::bank::MsgSend;
use cosmrs::bip32::Language;
use cosmrs::crypto::secp256k1::SigningKey;
use cosmrs::crypto::PublicKey;
use cosmrs::tendermint::chain;
use cosmrs::tx::{Msg, SignDoc, SignerInfo};
use cosmrs::Coin;
use cosmrs::{tx, AccountId, Any};
use log::debug;
use std::ops::{AddAssign, Div, Mul};
use std::str::FromStr;

const COSMOS_DERIVATION_PATH: &str = "m/44'/118'/0'/0/0";

pub struct Signer<'a> {
    mnemonic: Option<String>,
    cosmos: &'a Cosmos<'a>,
    sequence_number: u64,
    account_number: u64,
    denom: String,
    public_address: AccountId,
    private_key: SigningKey,
    public_key: PublicKey,
    chain_id: chain::Id,
}

impl<'a> Signer<'a> {
    pub fn from_mnemonic(
        cosmos: &'a Cosmos,
        phrase: &str,
        prefix: &str,
        chain_id: &str,
        denom: &str,
    ) -> Result<Signer<'a>, anyhow::Error> {
        let mnemonic = cosmrs::bip32::Mnemonic::new(phrase, Language::English)?;
        let xpriv = cosmrs::bip32::XPrv::derive_from_path(
            &mnemonic.to_seed(""),
            &COSMOS_DERIVATION_PATH.parse()?,
        )
        .unwrap();
        let private_key = SigningKey::from(xpriv);
        let public_key = private_key.public_key();
        let public_address = public_key.account_id(prefix).unwrap();

        let mut signer = Signer {
            mnemonic: Some(phrase.to_string()),
            cosmos,
            sequence_number: 0,
            account_number: 0,
            public_address,
            private_key,
            public_key,
            chain_id: chain_id.parse().unwrap(),
            denom: denom.to_string(),
        };

        debug!("account addr {}", signer.public_address);
        signer.refresh_nonce()?;
        debug!("account nonce {}", signer.sequence_number);

        Ok(signer)
    }

    fn build_msg(
        &mut self,
        any: Any,
        memo: Option<String>,
    ) -> Result<BroadcastTxResponse, anyhow::Error> {
        let gas = 100_000u64;
        let amount = Coin {
            amount: 2500u16.into(),
            denom: self.denom.parse().unwrap(),
        };
        let unwrap_fee = tx::Fee::from_amount_and_gas(amount.clone(), gas);

        let tx_body = tx::BodyBuilder::new()
            .msg(any)
            .memo(memo.unwrap_or_default())
            .finish();
        let auth_info = SignerInfo::single_direct(Some(self.public_key), self.sequence_number)
            .auth_info(unwrap_fee);
        let sign_doc =
            SignDoc::new(&tx_body, &auth_info, &self.chain_id, self.account_number).unwrap();
        let tx_raw = sign_doc.sign(&self.private_key).unwrap();

        let simulation = self.cosmos.tx.simulate(SimulateRequest {
            tx_bytes: tx_raw.to_bytes().unwrap(),
        })?;

        let new_gas =
            tx::Fee::from_amount_and_gas(amount, simulation.gas_info.gas_used.mul(110).div(100));
        let auth_info = SignerInfo::single_direct(Some(self.public_key), self.sequence_number)
            .auth_info(new_gas);
        let sign_doc =
            SignDoc::new(&tx_body, &auth_info, &self.chain_id, self.account_number).unwrap();
        let tx_raw = sign_doc.sign(&self.private_key).unwrap();

        let tx = self.cosmos.tx.broadcast_tx(BroadcastTxRequest {
            tx_bytes: tx_raw.to_bytes().unwrap(),
            mode: BroadcastMode::BroadcastModeSync,
        })?;
        self.sequence_number.add_assign(1u64);

        Ok(tx)
    }

    pub fn send(
        &mut self,
        to_address: String,
        amount: Vec<Coin>,
        memo: Option<String>,
    ) -> Result<BroadcastTxResponse, anyhow::Error> {
        let msg_send = MsgSend {
            from_address: self.public_address.clone(),
            to_address: AccountId::from_str(to_address.as_str()).unwrap(),
            amount,
        }
        .to_any()
        .unwrap();
        self.build_msg(msg_send, memo)
    }

    pub fn mnemonic(&self) -> Option<String> {
        self.mnemonic.clone()
    }

    pub fn refresh_nonce(&mut self) -> Result<(), anyhow::Error> {
        let account_info = self
            .cosmos
            .auth
            .account_by_address(self.public_address.to_string())?;
        self.sequence_number = account_info.account.sequence;
        self.account_number = account_info.account.account_number;

        Ok(())
    }

    pub fn public_address(&self) -> String {
        self.public_address.to_string()
    }
}