use celestia_proto::cosmos::tx::v1beta1::SignDoc;
use celestia_types::consts::appconsts::JsAppVersion;
use celestia_types::Blob;
use js_sys::{BigInt, Function, Promise, Uint8Array};
use k256::ecdsa::signature::Error as SignatureError;
use k256::ecdsa::{Signature, VerifyingKey};
use prost::Message;
use tendermint_proto::google::protobuf::Any;
use tonic_web_wasm_client::Client;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use crate::grpc::{JsAuthParams, JsBaseAccount, JsCoin};
use crate::tx::{DocSigner, IntoAny, JsTxConfig, JsTxInfo};
use crate::utils::make_object;
use crate::{Result, TxClient};
#[wasm_bindgen(js_name = "TxClient")]
pub struct JsClient {
client: TxClient<Client, JsSigner>,
}
#[wasm_bindgen(js_class = "TxClient")]
impl JsClient {
#[wasm_bindgen(constructor)]
pub async fn new(
url: String,
bech32_address: String,
pubkey: Uint8Array,
signer_fn: JsSignerFn,
) -> Result<JsClient> {
let signer = JsSigner { signer_fn };
let address = bech32_address.parse()?;
let pubkey = VerifyingKey::try_from(pubkey.to_vec().as_slice())?;
let client = TxClient::with_grpcweb_url(url, &address, pubkey, signer).await?;
Ok(Self { client })
}
#[wasm_bindgen(js_name = lastSeenGasPrice)]
pub fn last_seen_gas_price(&self) -> f64 {
self.client.last_seen_gas_price()
}
#[wasm_bindgen(js_name = chainId, getter)]
pub fn chain_id(&self) -> String {
self.client.chain_id().to_string()
}
#[wasm_bindgen(js_name = appVersion, getter)]
pub fn app_version(&self) -> JsAppVersion {
self.client.app_version().into()
}
#[wasm_bindgen(js_name = submitBlobs)]
pub async fn submit_blobs(
&self,
blobs: Vec<Blob>,
tx_config: Option<JsTxConfig>,
) -> Result<JsTxInfo> {
let tx_config = tx_config.map(Into::into).unwrap_or_default();
let tx = self.client.submit_blobs(&blobs, tx_config).await?;
Ok(tx.into())
}
#[wasm_bindgen(js_name = submitMessage)]
pub async fn submit_message(
&self,
message: JsAny,
tx_config: Option<JsTxConfig>,
) -> Result<JsTxInfo> {
let tx_config = tx_config.map(Into::into).unwrap_or_default();
let tx = self.client.submit_message(message, tx_config).await?;
Ok(tx.into())
}
#[wasm_bindgen(js_name = getAuthParams)]
pub async fn get_auth_params(&self) -> Result<JsAuthParams> {
self.client.get_auth_params().await.map(Into::into)
}
#[wasm_bindgen(js_name = getAccount)]
pub async fn get_account(&self, account: String) -> Result<JsBaseAccount> {
self.client
.get_account(&account.parse()?)
.await
.map(Into::into)
}
#[wasm_bindgen(js_name = getAccounts)]
pub async fn get_accounts(&self) -> Result<Vec<JsBaseAccount>> {
self.client
.get_accounts()
.await
.map(|accs| accs.into_iter().map(Into::into).collect())
}
#[wasm_bindgen(js_name = getBalance)]
pub async fn get_balance(&self, address: String, denom: String) -> Result<JsCoin> {
self.client
.get_balance(&address.parse()?, denom)
.await
.map(Into::into)
}
#[wasm_bindgen(js_name = getAllBalances)]
pub async fn get_all_balances(&self, address: String) -> Result<Vec<JsCoin>> {
self.client
.get_all_balances(&address.parse()?)
.await
.map(|coins| coins.into_iter().map(Into::into).collect())
}
#[wasm_bindgen(js_name = getSpendableBalances)]
pub async fn get_spendable_balances(&self, address: String) -> Result<Vec<JsCoin>> {
self.client
.get_spendable_balances(&address.parse()?)
.await
.map(|coins| coins.into_iter().map(Into::into).collect())
}
#[wasm_bindgen(js_name = getTotalSupply)]
pub async fn get_total_supply(&self) -> Result<Vec<JsCoin>> {
self.client
.get_total_supply()
.await
.map(|coins| coins.into_iter().map(Into::into).collect())
}
}
#[wasm_bindgen(js_name = protoEncodeSignDoc)]
pub fn proto_encode_sign_doc(sign_doc: JsSignDoc) -> Vec<u8> {
SignDoc::from(sign_doc).encode_to_vec()
}
pub struct JsSigner {
signer_fn: JsSignerFn,
}
impl DocSigner for JsSigner {
async fn try_sign(&self, doc: SignDoc) -> Result<Signature, SignatureError> {
let msg = JsSignDoc::from(doc);
let mut res = self.signer_fn.call1(&JsValue::null(), &msg).map_err(|e| {
let err = format!("Error calling signer fn: {e:?}");
SignatureError::from_source(err)
})?;
if res.has_type::<Promise>() {
let promise = res.unchecked_into::<Promise>();
res = JsFuture::from(promise).await.map_err(|e| {
let err = format!("Error awaiting signer promise: {e:?}");
SignatureError::from_source(err)
})?
}
let sig = res.dyn_into::<Uint8Array>().map_err(|orig| {
let err = format!(
"Signature must be Uint8Array, found: {}",
orig.js_typeof().as_string().expect("typeof returns string")
);
SignatureError::from_source(err)
})?;
Signature::from_slice(&sig.to_vec()).map_err(SignatureError::from_source)
}
}
#[wasm_bindgen(typescript_custom_section)]
const _: &str = "
/**
* A payload to be signed
*/
export interface SignDoc {
bodyBytes: Uint8Array;
authInfoBytes: Uint8Array;
chainId: string;
accountNumber: bigint;
}
/**
* A function that produces a signature of a payload
*/
export type SignerFn = ((arg: SignDoc) => Uint8Array) | ((arg: SignDoc) => Promise<Uint8Array>);
";
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Function, typescript_type = "SignerFn")]
pub type JsSignerFn;
#[wasm_bindgen(typescript_type = "SignDoc")]
pub type JsSignDoc;
#[wasm_bindgen(method, getter, js_name = bodyBytes)]
pub fn body_bytes(this: &JsSignDoc) -> Vec<u8>;
#[wasm_bindgen(method, getter, js_name = authInfoBytes)]
pub fn auth_info_bytes(this: &JsSignDoc) -> Vec<u8>;
#[wasm_bindgen(method, getter, js_name = chainId)]
pub fn chain_id(this: &JsSignDoc) -> String;
#[wasm_bindgen(method, getter, js_name = accountNumber)]
pub fn account_number(this: &JsSignDoc) -> u64;
}
impl From<JsSignDoc> for SignDoc {
fn from(value: JsSignDoc) -> SignDoc {
SignDoc {
body_bytes: value.body_bytes(),
auth_info_bytes: value.auth_info_bytes(),
chain_id: value.chain_id(),
account_number: value.account_number(),
}
}
}
impl From<SignDoc> for JsSignDoc {
fn from(value: SignDoc) -> JsSignDoc {
let obj = make_object!(
"bodyBytes" => Uint8Array::from(value.body_bytes.as_ref()),
"authInfoBytes" => Uint8Array::from(value.auth_info_bytes.as_ref()),
"chainId" => value.chain_id.into(),
"accountNumber" => BigInt::from(value.account_number)
);
obj.unchecked_into()
}
}
#[wasm_bindgen(typescript_custom_section)]
const _: &str = "
/**
* Protobuf Any type
*/
export interface ProtoAny {
typeUrl: string;
value: Uint8Array;
}
";
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "ProtoAny")]
pub type JsAny;
#[wasm_bindgen(method, getter, js_name = typeUrl)]
pub fn type_url(this: &JsAny) -> String;
#[wasm_bindgen(method, getter, js_name = value)]
pub fn value(this: &JsAny) -> Vec<u8>;
}
impl IntoAny for JsAny {
fn into_any(self) -> Any {
Any {
type_url: self.type_url(),
value: self.value(),
}
}
}