use super::client::NodeClient;
use super::sequencer::Nonce;
use crate::indexer::{Address, Subaccount};
use anyhow::{anyhow as err, Error};
use bip32::{DerivationPath, Language, Mnemonic, Seed};
use cosmrs::{
crypto::{secp256k1::SigningKey, PublicKey},
tx, AccountId,
};
use std::str::FromStr;
const BECH32_PREFIX_DYDX: &str = "dydx";
pub struct Wallet {
seed: Seed,
}
impl Wallet {
pub fn from_mnemonic(mnemonic: &str) -> Result<Self, Error> {
let seed = Mnemonic::new(mnemonic, Language::English)?.to_seed("");
Ok(Self { seed })
}
pub async fn account(&self, index: u32, client: &mut NodeClient) -> Result<Account, Error> {
let mut account = self.account_offline(index)?;
(account.account_number, account.sequence_number) =
client.query_address(account.address()).await?;
Ok(account)
}
pub fn account_offline(&self, index: u32) -> Result<Account, Error> {
self.derive_account(index, BECH32_PREFIX_DYDX)
}
#[cfg(feature = "noble")]
pub fn noble(&self) -> noble::WalletOps<'_> {
noble::WalletOps::new(self)
}
fn derive_account(&self, index: u32, prefix: &str) -> Result<Account, Error> {
let derivation_str = format!("m/44'/118'/0'/0/{index}");
let derivation_path = DerivationPath::from_str(&derivation_str)?;
let private_key = SigningKey::derive_from_path(&self.seed, &derivation_path)?;
let public_key = private_key.public_key();
let account_id = public_key.account_id(prefix).map_err(Error::msg)?;
let address = account_id.to_string().parse()?;
Ok(Account {
index,
account_id,
address,
key: private_key,
account_number: 0,
sequence_number: 0,
next_nonce: None,
})
}
}
pub struct Account {
index: u32,
#[allow(dead_code)] account_id: AccountId,
address: Address,
key: SigningKey,
account_number: u64,
sequence_number: u64,
next_nonce: Option<Nonce>,
}
impl Account {
pub fn address(&self) -> &Address {
&self.address
}
pub fn public_key(&self) -> PublicKey {
self.key.public_key()
}
pub fn index(&self) -> &u32 {
&self.index
}
pub fn subaccount(&self, number: u32) -> Result<Subaccount, Error> {
Ok(Subaccount::new(self.address.clone(), number.try_into()?))
}
pub fn sign(&self, doc: tx::SignDoc) -> Result<tx::Raw, Error> {
doc.sign(&self.key)
.map_err(|e| err!("Failure to sign doc: {e}"))
}
pub fn account_number(&self) -> u64 {
self.account_number
}
pub fn sequence_number(&self) -> u64 {
self.sequence_number
}
pub fn set_sequence_number(&mut self, sequence_number: u64) {
self.sequence_number = sequence_number;
}
pub fn next_nonce(&self) -> Option<&Nonce> {
self.next_nonce.as_ref()
}
pub fn set_next_nonce(&mut self, nonce: Nonce) {
if let Nonce::Sequence(number) = nonce {
self.sequence_number = number
}
self.next_nonce = Some(nonce);
}
}
#[cfg(feature = "noble")]
mod noble {
use super::*;
use crate::noble::NobleClient;
const BECH32_PREFIX_NOBLE: &str = "noble";
pub struct WalletOps<'w> {
wallet: &'w Wallet,
}
impl<'w> WalletOps<'w> {
pub fn new(wallet: &'w Wallet) -> Self {
Self { wallet }
}
pub async fn account(
&self,
index: u32,
client: &mut NobleClient,
) -> Result<Account, Error> {
let mut account = self.account_offline(index)?;
(account.account_number, account.sequence_number) =
client.query_address(account.address()).await?;
Ok(account)
}
pub fn account_offline(&self, index: u32) -> Result<Account, Error> {
self.wallet.derive_account(index, BECH32_PREFIX_NOBLE)
}
}
}