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()
}
}