use js_export_macro::js_export;
use miden_client::Word as NativeWord;
use miden_client::asset::{
Asset as NativeAsset,
FungibleAsset as NativeFungibleAsset,
NonFungibleAsset as NativeNonFungibleAsset,
};
use super::account_id::AccountId;
use super::fungible_asset::FungibleAsset;
use super::non_fungible_asset::NonFungibleAsset;
use super::word::Word;
use crate::platform::{JsErr, from_str_err, js_u64_to_u64};
#[derive(Clone, Copy)]
#[js_export]
pub struct VaultAsset(NativeAsset);
#[js_export]
impl VaultAsset {
pub fn fungible(faucet_id: &AccountId, amount: JsU64) -> Result<VaultAsset, JsErr> {
NativeFungibleAsset::new(faucet_id.into(), js_u64_to_u64(amount))
.map(|asset| VaultAsset(asset.into()))
.map_err(|err| from_str_err(&format!("Failed to create fungible asset: {err}")))
}
#[js_export(js_name = "nonFungible")]
pub fn non_fungible(input: NonFungibleAssetInput) -> Result<VaultAsset, JsErr> {
let (key, value) = input.words()?;
NativeNonFungibleAsset::from_id_and_value_words(key, value)
.map(|asset| VaultAsset(asset.into()))
.map_err(|err| from_str_err(&format!("Failed to create non-fungible asset: {err}")))
}
#[js_export(js_name = "fromVaultEntry")]
pub fn from_vault_entry(key: &Word, value: &Word) -> Result<VaultAsset, JsErr> {
NativeAsset::from_id_and_value_words(key.into(), value.into())
.map(VaultAsset)
.map_err(|err| from_str_err(&format!("Failed to create asset: {err}")))
}
pub fn kind(&self) -> String {
if self.0.is_fungible() {
"fungible".into()
} else {
"nonFungible".into()
}
}
#[js_export(js_name = "faucetId")]
pub fn faucet_id(&self) -> AccountId {
self.0.faucet_id().into()
}
#[js_export(js_name = "vaultKey")]
pub fn vault_key(&self) -> Word {
self.0.to_id_word().into()
}
#[js_export(js_name = "intoWord")]
pub fn into_word(&self) -> Word {
self.0.to_value_word().into()
}
#[js_export(js_name = "asFungible")]
pub fn as_fungible(&self) -> Result<FungibleAsset, JsErr> {
self.0
.as_fungible()
.map(Into::into)
.ok_or_else(|| from_str_err("Asset is not fungible"))
}
#[js_export(js_name = "asNonFungible")]
pub fn as_non_fungible(&self) -> Result<NonFungibleAsset, JsErr> {
native_non_fungible_asset(&self.0)
.map(Into::into)
.ok_or_else(|| from_str_err("Asset is not non-fungible"))
}
}
pub(crate) fn native_non_fungible_asset(asset: &NativeAsset) -> Option<NativeNonFungibleAsset> {
NativeNonFungibleAsset::from_id_and_value(asset.id(), asset.to_value_word()).ok()
}
impl From<NativeAsset> for VaultAsset {
fn from(asset: NativeAsset) -> Self {
Self(asset)
}
}
impl_napi_from_value!(VaultAsset);
#[cfg(feature = "browser")]
mod browser {
use wasm_bindgen::prelude::*;
use super::{JsErr, NativeAsset, NativeWord, from_str_err};
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(typescript_type = "VaultAsset | FungibleAsset | NonFungibleAsset")]
pub type AssetInput;
#[wasm_bindgen(method, structural, catch, js_name = vaultKey)]
fn input_key(this: &AssetInput) -> Result<InputWord, JsValue>;
#[wasm_bindgen(method, structural, catch, js_name = intoWord)]
fn input_value(this: &AssetInput) -> Result<InputWord, JsValue>;
#[wasm_bindgen(typescript_type = "{ key: Word; value: Word }")]
pub type NonFungibleAssetInput;
#[wasm_bindgen(method, structural, getter, catch, js_name = key)]
fn key(this: &NonFungibleAssetInput) -> Result<InputWord, JsValue>;
#[wasm_bindgen(method, structural, getter, catch, js_name = value)]
fn value(this: &NonFungibleAssetInput) -> Result<InputWord, JsValue>;
#[wasm_bindgen(typescript_type = "Word")]
type InputWord;
#[wasm_bindgen(method, structural, catch, js_name = toHex)]
fn to_hex(this: &InputWord) -> Result<String, JsValue>;
}
impl InputWord {
fn native(&self) -> Result<NativeWord, JsErr> {
NativeWord::try_from(self.to_hex()?.as_str())
.map_err(|err| from_str_err(&format!("Invalid asset word: {err}")))
}
}
impl NonFungibleAssetInput {
pub(super) fn words(&self) -> Result<(NativeWord, NativeWord), JsErr> {
Ok((self.key()?.native()?, self.value()?.native()?))
}
}
impl AssetInput {
pub(crate) fn native(&self) -> Result<NativeAsset, JsErr> {
NativeAsset::from_id_and_value_words(
self.input_key()?.native()?,
self.input_value()?.native()?,
)
.map_err(|err| from_str_err(&format!("Invalid note asset: {err}")))
}
}
}
#[cfg(feature = "browser")]
pub use browser::{AssetInput, NonFungibleAssetInput};
#[cfg(feature = "nodejs")]
pub type AssetInput = napi::bindgen_prelude::Either3<VaultAsset, FungibleAsset, NonFungibleAsset>;
#[cfg(feature = "nodejs")]
mod node_validation {
use napi::bindgen_prelude::{ValidateNapiValue, sys};
use super::{FungibleAsset, NonFungibleAsset, VaultAsset};
macro_rules! validate_owned_asset {
($asset:ty) => {
impl ValidateNapiValue for $asset {
unsafe fn validate(
env: sys::napi_env,
value: sys::napi_value,
) -> napi::Result<sys::napi_value> {
unsafe { <&Self as ValidateNapiValue>::validate(env, value) }
}
}
};
}
validate_owned_asset!(VaultAsset);
validate_owned_asset!(FungibleAsset);
validate_owned_asset!(NonFungibleAsset);
}
#[cfg(feature = "nodejs")]
#[napi_derive::napi(object)]
pub struct NonFungibleAssetInput {
pub key: Word,
pub value: Word,
}
#[cfg(feature = "nodejs")]
impl NonFungibleAssetInput {
#[allow(clippy::unnecessary_wraps)]
fn words(&self) -> Result<(NativeWord, NativeWord), JsErr> {
Ok(((&self.key).into(), (&self.value).into()))
}
}
#[cfg_attr(feature = "nodejs", allow(clippy::unnecessary_wraps))]
pub(crate) fn native_asset(input: &AssetInput) -> Result<NativeAsset, JsErr> {
#[cfg(feature = "browser")]
{
input.native()
}
#[cfg(feature = "nodejs")]
{
use napi::bindgen_prelude::Either3;
Ok(match input {
Either3::A(asset) => asset.0,
Either3::B(asset) => asset.into(),
Either3::C(asset) => asset.into(),
})
}
}