Bläddra i källkod

`bindings/cashu-js` nut00 and nu01

thesimplekid 1 år sedan
förälder
incheckning
e830cbf0b7

+ 2 - 1
Cargo.toml

@@ -5,7 +5,8 @@ members = [
     "crates/cashu-sdk",
     "bindings/uniffi-bindgen",
     "bindings/cashu-ffi",
-    "bindings/cashu-sdk-ffi"
+    "bindings/cashu-sdk-ffi",
+    "bindings/cashu-js"
 ]
 resolver = "2"
 

+ 22 - 0
bindings/cashu-js/Cargo.toml

@@ -0,0 +1,22 @@
+[package]
+name = "cashu-js"
+version = "0.1.0"
+edition = "2021"
+description = "Cashu rust implementation, for JavaScript"
+authors = ["thesimplekid <tsk@thesimplekid.com>"]
+publish = false
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+[lib]
+crate-type = ["lib", "cdylib"]
+
+[dependencies]
+cashu = { path = "../../crates/cashu"}
+js-sys = "0.3.64"
+serde-wasm-bindgen = "0.6.0"
+serde_json.workspace = true
+wasm-bindgen = "0.2.87"
+wasm-bindgen-futures = "0.4.37"
+
+[package.metadata.wasm-pack.profile.release]
+wasm-opt = true

+ 12 - 0
bindings/cashu-js/src/error.rs

@@ -0,0 +1,12 @@
+use wasm_bindgen::JsValue;
+
+pub type Result<T, E = JsValue> = std::result::Result<T, E>;
+
+/// Helper to replace the `E` to `Error` to `napi::Error` conversion.
+#[inline]
+pub fn into_err<E>(error: E) -> JsValue
+where
+    E: std::error::Error,
+{
+    JsValue::from_str(&error.to_string())
+}

+ 3 - 0
bindings/cashu-js/src/lib.rs

@@ -0,0 +1,3 @@
+pub mod error;
+mod nuts;
+pub mod types;

+ 3 - 0
bindings/cashu-js/src/nuts/mod.rs

@@ -0,0 +1,3 @@
+mod nut00;
+mod nut01;
+mod nut02;

+ 51 - 0
bindings/cashu-js/src/nuts/nut00/blinded_message.rs

@@ -0,0 +1,51 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut00::BlindedMessage;
+use wasm_bindgen::prelude::*;
+
+use crate::nuts::nut01::JsPublicKey;
+use crate::types::amount::JsAmount;
+
+#[wasm_bindgen(js_name = BlindedMessage)]
+pub struct JsBlindedMessage {
+    inner: BlindedMessage,
+}
+
+impl Deref for JsBlindedMessage {
+    type Target = BlindedMessage;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<BlindedMessage> for JsBlindedMessage {
+    fn from(inner: BlindedMessage) -> JsBlindedMessage {
+        JsBlindedMessage { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = BlindedMessage)]
+impl JsBlindedMessage {
+    #[allow(clippy::new_without_default)]
+    #[wasm_bindgen(constructor)]
+    pub fn new(amount: JsAmount, b: JsPublicKey) -> Self {
+        Self {
+            inner: BlindedMessage {
+                amount: amount.deref().clone(),
+                b: b.deref().clone(),
+            },
+        }
+    }
+
+    /// Amount
+    #[wasm_bindgen(getter)]
+    pub fn amount(&self) -> JsAmount {
+        self.inner.amount.into()
+    }
+
+    /// B
+    #[wasm_bindgen(getter)]
+    pub fn b(&self) -> JsPublicKey {
+        self.inner.b.clone().into()
+    }
+}

+ 39 - 0
bindings/cashu-js/src/nuts/nut00/blinded_messages.rs

@@ -0,0 +1,39 @@
+use std::ops::Deref;
+
+use wasm_bindgen::prelude::*;
+
+use cashu::nuts::nut00::wallet::BlindedMessages;
+
+use crate::error::{into_err, Result};
+use crate::types::JsAmount;
+
+#[wasm_bindgen(js_name = BlindedMessages)]
+pub struct JsBlindedMessages {
+    inner: BlindedMessages,
+}
+
+impl Deref for JsBlindedMessages {
+    type Target = BlindedMessages;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+#[wasm_bindgen(js_class = BlindedMessages)]
+impl JsBlindedMessages {
+    #[wasm_bindgen(js_name = random)]
+    pub fn random(amount: JsAmount) -> Result<JsBlindedMessages> {
+        Ok(JsBlindedMessages {
+            inner: BlindedMessages::random(*amount.deref()).map_err(into_err)?,
+        })
+    }
+
+    #[wasm_bindgen(js_name = blank)]
+    pub fn blank(fee_reserve: JsAmount) -> Result<JsBlindedMessages> {
+        Ok(JsBlindedMessages {
+            inner: BlindedMessages::blank(*fee_reserve.deref()).map_err(into_err)?,
+        })
+    }
+
+    // TODO: Gettters
+}

+ 33 - 0
bindings/cashu-js/src/nuts/nut00/blinded_signature.rs

@@ -0,0 +1,33 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut00::BlindedSignature;
+use wasm_bindgen::prelude::*;
+
+use crate::{nuts::nut01::JsPublicKey, nuts::nut02::JsId, types::JsAmount};
+
+#[wasm_bindgen(js_name = BlindedSignature)]
+pub struct JsBlindedSignature {
+    inner: BlindedSignature,
+}
+
+impl Deref for JsBlindedSignature {
+    type Target = BlindedSignature;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+#[wasm_bindgen(js_class = BlindedSignature)]
+impl JsBlindedSignature {
+    #[allow(clippy::new_without_default)]
+    #[wasm_bindgen(constructor)]
+    pub fn new(id: JsId, amount: JsAmount, c: JsPublicKey) -> Self {
+        Self {
+            inner: BlindedSignature {
+                id: id.deref().clone(),
+                amount: amount.deref().clone(),
+                c: c.deref().clone(),
+            },
+        }
+    }
+}

+ 10 - 0
bindings/cashu-js/src/nuts/nut00/mod.rs

@@ -0,0 +1,10 @@
+mod blinded_message;
+mod blinded_messages;
+mod blinded_signature;
+mod proof;
+mod token;
+
+pub use blinded_message::JsBlindedMessage;
+pub use blinded_messages::JsBlindedMessages;
+pub use blinded_signature::JsBlindedSignature;
+pub use proof::JsProof;

+ 63 - 0
bindings/cashu-js/src/nuts/nut00/proof.rs

@@ -0,0 +1,63 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut00::Proof;
+use wasm_bindgen::prelude::*;
+
+use crate::{nuts::nut01::JsPublicKey, nuts::nut02::JsId, types::JsAmount, types::JsSecret};
+
+#[wasm_bindgen(js_name = Token)]
+pub struct JsProof {
+    inner: Proof,
+}
+
+impl Deref for JsProof {
+    type Target = Proof;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Proof> for JsProof {
+    fn from(inner: Proof) -> JsProof {
+        JsProof { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Proof)]
+impl JsProof {
+    #[wasm_bindgen(constructor)]
+    pub fn new(amount: JsAmount, secret: JsSecret, c: JsPublicKey, id: Option<JsId>) -> JsProof {
+        Self {
+            inner: Proof {
+                amount: amount.deref().clone(),
+                secret: secret.deref().clone(),
+                c: c.deref().clone(),
+                id: id.map(|i| *i.deref()),
+            },
+        }
+    }
+
+    /// Amount
+    #[wasm_bindgen(getter)]
+    pub fn amount(&self) -> JsAmount {
+        self.inner.amount.into()
+    }
+
+    /// Secret
+    #[wasm_bindgen(getter)]
+    pub fn secret(&self) -> JsSecret {
+        self.inner.secret.clone().into()
+    }
+
+    /// C
+    #[wasm_bindgen(getter)]
+    pub fn c(&self) -> JsPublicKey {
+        self.inner.c.clone().into()
+    }
+
+    /// Id
+    #[wasm_bindgen(getter)]
+    pub fn id(&self) -> Option<JsId> {
+        self.inner.id.map(|id| id.into())
+    }
+}

+ 59 - 0
bindings/cashu-js/src/nuts/nut00/token.rs

@@ -0,0 +1,59 @@
+use std::{ops::Deref, str::FromStr};
+
+use cashu::{nuts::nut00::wallet::Token, url::UncheckedUrl};
+use wasm_bindgen::prelude::*;
+
+use crate::error::{into_err, Result};
+
+#[wasm_bindgen(js_name = Token)]
+pub struct JsToken {
+    inner: Token,
+}
+
+impl Deref for JsToken {
+    type Target = Token;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Token> for JsToken {
+    fn from(inner: Token) -> JsToken {
+        JsToken { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Token)]
+impl JsToken {
+    // TODO: Simply passing a string for proofs is not ideal
+    #[wasm_bindgen(constructor)]
+    pub fn new(mint: String, proofs: String, memo: Option<String>) -> Result<JsToken> {
+        let mint = UncheckedUrl::from_str(&mint).map_err(into_err)?;
+        let proofs = serde_json::from_str(&proofs).map_err(into_err)?;
+        Ok(Self {
+            inner: Token::new(mint, proofs, memo).map_err(into_err)?,
+        })
+    }
+
+    /// Memo
+    #[wasm_bindgen(getter)]
+    pub fn memo(&self) -> Option<String> {
+        self.inner.memo.clone()
+    }
+
+    /// From String
+    #[wasm_bindgen(js_name = fromString)]
+    pub fn from_string(token: String) -> Result<JsToken> {
+        Ok(JsToken {
+            inner: Token::from_str(&token).map_err(into_err)?,
+        })
+    }
+
+    /// As String
+    #[wasm_bindgen(js_name = asString)]
+    pub fn as_string(&self) -> Result<String> {
+        Ok(self.inner.convert_to_string().map_err(into_err)?)
+    }
+
+    // TODO: Getter mint proofs
+}

+ 47 - 0
bindings/cashu-js/src/nuts/nut01/key_pair.rs

@@ -0,0 +1,47 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut01::mint::KeyPair;
+use wasm_bindgen::prelude::*;
+
+use super::{JsPublicKey, JsSecretKey};
+
+#[wasm_bindgen(js_name = KeyPair)]
+pub struct JsKeyPair {
+    inner: KeyPair,
+}
+
+impl Deref for JsKeyPair {
+    type Target = KeyPair;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<KeyPair> for JsKeyPair {
+    fn from(inner: KeyPair) -> JsKeyPair {
+        JsKeyPair { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = KeyPair)]
+impl JsKeyPair {
+    /// From Hex
+    #[wasm_bindgen(js_name = fromSecretKey)]
+    pub fn from_secret_key(secret_key: JsSecretKey) -> JsKeyPair {
+        Self {
+            inner: KeyPair::from_secret_key(secret_key.deref().clone()),
+        }
+    }
+
+    /// Secret Key
+    #[wasm_bindgen(getter)]
+    pub fn secret_key(&self) -> JsSecretKey {
+        self.inner.secret_key.clone().into()
+    }
+
+    /// Public Key
+    #[wasm_bindgen(getter)]
+    pub fn public_key(&self) -> JsPublicKey {
+        self.inner.public_key.clone().into()
+    }
+}

+ 54 - 0
bindings/cashu-js/src/nuts/nut01/keys.rs

@@ -0,0 +1,54 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut01::Keys;
+use wasm_bindgen::prelude::*;
+
+use crate::{
+    error::{into_err, Result},
+    types::JsAmount,
+};
+
+use super::JsPublicKey;
+
+#[wasm_bindgen(js_name = Keys)]
+pub struct JsKeys {
+    inner: Keys,
+}
+
+impl Deref for JsKeys {
+    type Target = Keys;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Keys> for JsKeys {
+    fn from(inner: Keys) -> JsKeys {
+        JsKeys { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Keys)]
+impl JsKeys {
+    /// From Hex
+    #[wasm_bindgen(constructor)]
+    pub fn new(keys: String) -> Result<JsKeys> {
+        let keys = serde_json::from_str(&keys).map_err(into_err)?;
+
+        Ok(JsKeys {
+            inner: Keys::new(keys),
+        })
+    }
+
+    /// Keys
+    #[wasm_bindgen(js_name = keys)]
+    pub fn keys(&self) -> Result<String> {
+        Ok(serde_json::to_string(&self.inner.keys()).map_err(into_err)?)
+    }
+
+    /// Amount Key
+    #[wasm_bindgen(js_name = amountKey)]
+    pub fn amount_key(&self, amount: JsAmount) -> Option<JsPublicKey> {
+        self.inner.amount_key(*amount.deref()).map(|k| k.into())
+    }
+}

+ 9 - 0
bindings/cashu-js/src/nuts/nut01/mod.rs

@@ -0,0 +1,9 @@
+mod key_pair;
+mod keys;
+mod public_key;
+mod secret_key;
+
+pub use key_pair::JsKeyPair;
+pub use keys::JsKeys;
+pub use public_key::JsPublicKey;
+pub use secret_key::JsSecretKey;

+ 41 - 0
bindings/cashu-js/src/nuts/nut01/public_key.rs

@@ -0,0 +1,41 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut01::PublicKey;
+use wasm_bindgen::prelude::*;
+
+use crate::error::{into_err, Result};
+
+#[wasm_bindgen(js_name = PublicKey)]
+pub struct JsPublicKey {
+    inner: PublicKey,
+}
+
+impl Deref for JsPublicKey {
+    type Target = PublicKey;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<PublicKey> for JsPublicKey {
+    fn from(inner: PublicKey) -> JsPublicKey {
+        JsPublicKey { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = PublicKey)]
+impl JsPublicKey {
+    /// From Hex
+    #[wasm_bindgen(js_name = fromHex)]
+    pub fn from_hex(hex: String) -> Result<JsPublicKey> {
+        Ok(Self {
+            inner: PublicKey::from_hex(hex).map_err(into_err)?,
+        })
+    }
+
+    /// To Hex
+    #[wasm_bindgen(js_name = toHex)]
+    pub fn to_hex(&self) -> String {
+        self.inner.to_hex()
+    }
+}

+ 31 - 0
bindings/cashu-js/src/nuts/nut01/secret_key.rs

@@ -0,0 +1,31 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut01::SecretKey;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen(js_name = PublicKey)]
+pub struct JsSecretKey {
+    inner: SecretKey,
+}
+
+impl Deref for JsSecretKey {
+    type Target = SecretKey;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<SecretKey> for JsSecretKey {
+    fn from(inner: SecretKey) -> JsSecretKey {
+        JsSecretKey { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = SecretKey)]
+impl JsSecretKey {
+    /// To Hex
+    #[wasm_bindgen(js_name = toHex)]
+    pub fn to_hex(&self) -> String {
+        self.inner.to_hex()
+    }
+}

+ 41 - 0
bindings/cashu-js/src/nuts/nut02/id.rs

@@ -0,0 +1,41 @@
+use std::ops::Deref;
+
+use cashu::nuts::nut02::Id;
+use wasm_bindgen::prelude::*;
+
+use crate::error::{into_err, Result};
+
+#[wasm_bindgen(js_name = Id)]
+pub struct JsId {
+    inner: Id,
+}
+
+impl Deref for JsId {
+    type Target = Id;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Id> for JsId {
+    fn from(inner: Id) -> JsId {
+        JsId { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Id)]
+impl JsId {
+    /// Try From Base 64 String
+    #[wasm_bindgen(js_name = tryFromBase64)]
+    pub fn try_from_base64(id: String) -> Result<JsId> {
+        Ok(JsId {
+            inner: Id::try_from_base64(&id).map_err(into_err)?,
+        })
+    }
+
+    /// As String
+    #[wasm_bindgen(js_name = asString)]
+    pub fn as_string(&self) -> String {
+        self.inner.to_string()
+    }
+}

+ 3 - 0
bindings/cashu-js/src/nuts/nut02/mod.rs

@@ -0,0 +1,3 @@
+pub mod id;
+
+pub use id::JsId;

+ 68 - 0
bindings/cashu-js/src/types/amount.rs

@@ -0,0 +1,68 @@
+use std::ops::Deref;
+
+use cashu::Amount;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen(js_name = Amount)]
+pub struct JsAmount {
+    inner: Amount,
+}
+
+impl Deref for JsAmount {
+    type Target = Amount;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Amount> for JsAmount {
+    fn from(inner: Amount) -> JsAmount {
+        JsAmount { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Amount)]
+impl JsAmount {
+    #[wasm_bindgen(constructor)]
+    pub fn new(sats: u64) -> Self {
+        Self {
+            inner: Amount::from_sat(sats),
+        }
+    }
+
+    /// From Sats
+    #[wasm_bindgen(js_name = fromSat)]
+    pub fn from_sat(sats: u64) -> Self {
+        Self {
+            inner: Amount::from_sat(sats),
+        }
+    }
+
+    /// From Msats
+    #[wasm_bindgen(js_name = fromMSat)]
+    pub fn from_msat(msats: u64) -> Self {
+        Self {
+            inner: Amount::from_msat(msats),
+        }
+    }
+
+    /// Get as sats
+    #[wasm_bindgen(js_name = toSat)]
+    pub fn to_sat(&self) -> u64 {
+        self.inner.to_sat()
+    }
+
+    /// Get as msats
+    #[wasm_bindgen(js_name = toMSat)]
+    pub fn to_msat(&self) -> u64 {
+        self.inner.to_msat()
+    }
+
+    /// Split amount returns sat vec of sats
+    // REVIEW: https://github.com/rustwasm/wasm-bindgen/issues/111
+    #[wasm_bindgen(js_name = split)]
+    pub fn split(&self) -> Vec<u64> {
+        let split = self.inner.split();
+        split.into_iter().map(|a| a.to_sat()).collect()
+    }
+}

+ 49 - 0
bindings/cashu-js/src/types/bolt11_invoice.rs

@@ -0,0 +1,49 @@
+use std::{ops::Deref, str::FromStr};
+
+use cashu::Bolt11Invoice;
+use wasm_bindgen::prelude::*;
+
+use crate::error::{into_err, Result};
+use crate::types::JsAmount;
+
+#[wasm_bindgen(js_name = Bolt11Invoice)]
+pub struct JsBolt11Invoice {
+    inner: Bolt11Invoice,
+}
+
+impl Deref for JsBolt11Invoice {
+    type Target = Bolt11Invoice;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Bolt11Invoice> for JsBolt11Invoice {
+    fn from(inner: Bolt11Invoice) -> JsBolt11Invoice {
+        JsBolt11Invoice { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Bolt11Invoice)]
+impl JsBolt11Invoice {
+    #[wasm_bindgen(constructor)]
+    pub fn new(invoice: String) -> Result<JsBolt11Invoice> {
+        Ok(JsBolt11Invoice {
+            inner: Bolt11Invoice::from_str(&invoice).map_err(into_err)?,
+        })
+    }
+
+    /// Amount
+    #[wasm_bindgen(getter)]
+    pub fn amount(&self) -> Option<JsAmount> {
+        self.inner
+            .amount_milli_satoshis()
+            .map(|a| JsAmount::from_msat(a))
+    }
+
+    /// Invoice as string
+    #[wasm_bindgen(js_name = asString)]
+    pub fn as_string(&self) -> String {
+        self.inner.to_string()
+    }
+}

+ 7 - 0
bindings/cashu-js/src/types/mod.rs

@@ -0,0 +1,7 @@
+pub mod amount;
+pub mod bolt11_invoice;
+pub mod secret;
+
+pub use amount::JsAmount;
+pub use bolt11_invoice::JsBolt11Invoice;
+pub use secret::JsSecret;

+ 1 - 0
bindings/cashu-js/src/types/proofs_status.rs

@@ -0,0 +1 @@
+

+ 38 - 0
bindings/cashu-js/src/types/secret.rs

@@ -0,0 +1,38 @@
+use std::ops::Deref;
+
+use cashu::secret::Secret;
+use wasm_bindgen::prelude::*;
+
+#[wasm_bindgen(js_name = Secret)]
+pub struct JsSecret {
+    inner: Secret,
+}
+
+impl Deref for JsSecret {
+    type Target = Secret;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl From<Secret> for JsSecret {
+    fn from(inner: Secret) -> JsSecret {
+        JsSecret { inner }
+    }
+}
+
+#[wasm_bindgen(js_class = Secret)]
+impl JsSecret {
+    #[wasm_bindgen(constructor)]
+    pub fn new() -> JsSecret {
+        Self {
+            inner: Secret::new(),
+        }
+    }
+
+    /// As Bytes
+    #[wasm_bindgen(js_name = asBytes)]
+    pub fn as_bytes(&self) -> Vec<u8> {
+        self.inner.as_bytes().to_vec()
+    }
+}

+ 2 - 0
crates/cashu/src/nuts/nut02.rs

@@ -18,6 +18,8 @@ pub enum Error {
     Length,
 }
 
+impl std::error::Error for Error {}
+
 impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {