use serde::{Deserialize, Serialize};
use crate::errors::{ErrorKind, Result};
use crate::json_rpc::EOSRPC;
use eosio_client_keys::{EOSPrivateKey, EOSPublicKey};
use serde_json::Value;
use libabieos_sys::vec_u8_to_hex;
use crate::api_types::{ TransactionIn, TransactionInSigned};
const WALLET_UNLOCKED_EXCEPTION: usize = 3_120_007;
#[allow(dead_code)]
pub const EOSIO_CHAIN_ID: &str = "cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f";
#[derive(Debug, Serialize, Deserialize)]
pub struct WalletList {
name: String,
}
pub struct Wallet {
keos: EOSRPC,
chain_id: Option<String>,
}
impl Wallet {
pub fn create(keos: EOSRPC) -> Wallet {
Wallet {
keos,
chain_id: None,
}
}
pub fn create_with_chain_id(keos: EOSRPC, chain_id: &str) -> Wallet {
Wallet {
keos,
chain_id: Some(String::from(chain_id)),
}
}
pub async fn list(&self) -> Result<Vec<String>> {
let value = serde_json::json!({});
let res = self
.keos
.non_blocking_req("/v1/wallet/list_wallets", value)
.await?;
let list: Vec<String> = serde_json::from_str(&res).unwrap();
Ok(list)
}
pub async fn keys(&self) -> Result<Vec<EOSPublicKey>> {
let value = serde_json::json!({});
let res = self
.keos
.non_blocking_req("/v1/wallet/get_public_keys", value)
.await?;
let list: Vec<String> = serde_json::from_str(&res).unwrap();
let keys = EOSPublicKey::from_eos_strings(&list)?;
Ok(keys)
}
pub async fn private_keys(
&self,
wallet: &str,
pass: &str,
) -> Result<Vec<(EOSPublicKey, EOSPrivateKey)>> {
let value = serde_json::json!([wallet, pass]);
let res = self
.keos
.non_blocking_req("/v1/wallet/list_keys", value)
.await?;
let list: Vec<(String, String)> = serde_json::from_str(&res).unwrap();
let mut r: Vec<(EOSPublicKey, EOSPrivateKey)> = vec![];
for pair in list {
let public: EOSPublicKey = EOSPublicKey::from_eos_string(&pair.0)?;
let private: EOSPrivateKey = EOSPrivateKey::from_string(&pair.1)?;
r.push((public, private));
}
Ok(r)
}
pub async fn unlock(&self, wallet: &str, pass: &str) -> Result<bool> {
let value = serde_json::json!([wallet, pass]);
match self.keos.non_blocking_req("/v1/wallet/unlock", value).await {
Ok(res) => {
let resp: Value = serde_json::from_str(&res).unwrap();
if resp.is_object() {
Ok(true)
} else {
Err("Fail-Wallet Unlock unknown response".into())
}
}
Err(e) => match e.0 {
ErrorKind::InvalidResponseStatus(k) => {
if k.code == WALLET_UNLOCKED_EXCEPTION {
Ok(true)
} else {
eprintln!("{:#?}", k);
Err("Fail-Wallet Unlock".into())
}
},
ErrorKind::InvalidResponseErr(k) => {
eprintln!("{:#?}", k);
panic!("Wallet unlock Fail-Err");
}
_ => {
eprintln!("{:#?}", e);
panic!("Wallet unlock Fail");
}
},
}
}
pub async fn sign_transaction(
&self,
transaction: TransactionIn,
pubkey: Vec<EOSPublicKey>,
) -> Result<TransactionInSigned> {
let mut pubkey_str: Vec<String> = vec![];
for k in pubkey {
pubkey_str.push(k.to_eos_string()?)
}
if self.chain_id.is_none() {
Err(ErrorKind::WalletMissingChainID.into())
} else {
let value =
serde_json::json![[transaction, pubkey_str, self.chain_id.as_ref().unwrap()]];
let res = self
.keos
.non_blocking_req("/v1/wallet/sign_transaction", value)
.await?;
let t: TransactionInSigned = serde_json::from_str(&res).unwrap();
Ok(t)
}
}
pub async fn sign_digest(&self, digest: &[u8], pubkey: &EOSPublicKey) -> Result<String> {
let digest_b = vec_u8_to_hex(digest)?;
let value = serde_json::json![[digest_b, pubkey.to_eos_string()?]];
let res = self
.keos
.non_blocking_req("/v1/wallet/sign_digest", value)
.await?;
let sig: String = serde_json::from_str(&res).unwrap();
Ok(sig)
}
}
#[allow(dead_code)]
pub fn get_wallet_pass() -> Result<String> {
use std::fs;
let pass = String::from(fs::read_to_string(".env")?.trim());
Ok(pass)
}
#[cfg(test)]
mod test {
use super::*;
use eosio_client_keys::hash::hash_sha256;
use eosio_client_keys::EOSSignature;
const KEOSD_HOST: &str = "http://127.0.0.1:3888";
#[tokio::test]
async fn wallet_list_test() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let _wallets = Wallet::create(keos).list().await?;
Ok(())
}
#[tokio::test]
async fn wallet_list_unlock() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let pass = get_wallet_pass()?;
let _wallets = Wallet::create(keos).unlock("default", &pass).await?;
Ok(())
}
#[tokio::test]
async fn wallet_list_keys() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let pass = get_wallet_pass()?;
let wallet = Wallet::create(keos);
let _wallets = wallet.unlock("default", &pass).await?;
let _keys = wallet.keys().await?;
Ok(())
}
#[tokio::test]
async fn wallet_list_private_keys() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let pass = get_wallet_pass()?;
let wallet = Wallet::create(keos);
let _res = wallet.unlock("default", &pass).await?;
let keys = wallet.private_keys("default", &pass).await?;
for k in keys {
assert_eq!(k.0.to_eos_string()?, k.1.to_public().to_eos_string()?);
}
Ok(())
}
#[tokio::test]
async fn wallet_sign_txn() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let pass = get_wallet_pass()?;
let wallet = Wallet::create_with_chain_id(keos, EOSIO_CHAIN_ID);
let _res = wallet.unlock("default", &pass).await?;
let t = TransactionIn::dummy();
let pubkey =
EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")?;
let ti: TransactionInSigned = wallet.sign_transaction(t, vec![pubkey]).await?;
let sigs = ti.signatures;
assert_eq!(sigs.len(), 1);
for sig in sigs {
let eos_sig: EOSSignature = EOSSignature::from_string(&sig)?;
if !eos_sig.is_canonical() {
eprintln!("{:#?}", eos_sig.to_eos_string());
}
assert!(eos_sig.is_canonical());
}
Ok(())
}
#[tokio::test]
async fn wallet_sign_digest() -> Result<()> {
let keos = EOSRPC::non_blocking(String::from(KEOSD_HOST)).await?;
let pass = get_wallet_pass()?;
let wallet = Wallet::create(keos);
let _res = wallet.unlock("default", &pass).await?;
let pubkey =
EOSPublicKey::from_eos_string("EOS7ctUUZhtCGHnxUnh4Rg5eethj3qNS5S9fijyLMKgRsBLh8eMBB")?;
let phrase: Vec<u8> = "Greg! The Stop sign".as_bytes().to_vec();
let hash = hash_sha256(&phrase);
let sig = wallet.sign_digest(&hash, &pubkey).await?;
let eos_sig: EOSSignature = EOSSignature::from_string(&sig)?;
eos_sig.verify_hash(&hash, &pubkey)?;
assert!(eos_sig.is_canonical());
Ok(())
}
}