bindings/cashu-sdk: add ffi bindings for mint and wallet

This commit is contained in:
thesimplekid
2023-09-01 11:27:33 +01:00
parent 8940afac9e
commit 0beca645b8
26 changed files with 558 additions and 72 deletions

View File

@@ -1,4 +1,4 @@
[[language]]
name = "rust"
config = { cargo = { features = [ "blocking", "wallet" ] } }
config = { cargo = { features = [ "blocking", "wallet", "mint" ] } }

View File

@@ -11,7 +11,7 @@ crate-type = ["lib", "cdylib", "staticlib"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cashu = { path = "../../crates/cashu", featues = ["wallet", "mint"] }
cashu = { path = "../../crates/cashu", features = ["wallet", "mint"] }
url = { workspace = true }
uniffi = { workspace = true }

View File

@@ -6,56 +6,6 @@ init:
cargo install cbindgen
cargo install cargo-ndk
kotlin:
$(JUST) ffi-kotlin-clean
$(JUST) ffi-kotlin-generate
ffi-kotlin-clean:
find ./ffi/kotlin/jniLibs -name libcashu_sdk_ffi.so -type f -delete
ffi-kotlin-generate:
cargo run -p uniffi-bindgen generate src/cashu.udl --language kotlin --no-format -o ffi/kotlin
android: aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
aarch64-linux-android:
cargo ndk -t aarch64-linux-android -o ffi/kotlin/jniLibs build --release
armv7-linux-androideabi:
$(JUST) ffi-ndk-build TARGET=armv7-linux-androideabi
i686-linux-android:
$(JUST) ffi-ndk-build TARGET=i686-linux-android
x86_64-linux-android:
$(JUST) ffi-ndk-build TARGET=x86_64-linux-android
ffi-ndk-build:
cargo ndk -t $(TARGET) -o ffi/kotlin/jniLibs build --release
bindings-android:
$(JUST) bindings-android-clean
$(JUST) bindings-android-copy
cd bindings-android && ./gradlew assemble
$(JUST) bindings-android-package
bindings-android-clean:
rm -rf bindings-android/lib/src/main/jniLibs
rm -rf bindings-android/lib/src/main/kotlin
bindings-android-copy:
cp -r ffi/kotlin/jniLibs bindings-android/lib/src/main
cp -r ffi/kotlin/cashu bindings-android/lib/src/main/kotlin/
bindings-android-package:
mkdir -p ffi/android
cp bindings-android/lib/build/outputs/aar/lib-release.aar ffi/android
publish-android:
cd bindings-android && ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
python:
rm -rf dist
pip install -r bindings-python/requirements.txt

View File

@@ -59,3 +59,15 @@ impl BlindedMessages {
.collect()
}
}
impl From<cashu::nuts::nut00::wallet::BlindedMessages> for BlindedMessages {
fn from(inner: cashu::nuts::nut00::wallet::BlindedMessages) -> BlindedMessages {
BlindedMessages { inner }
}
}
impl From<BlindedMessages> for cashu::nuts::nut00::wallet::BlindedMessages {
fn from(blinded_messages: BlindedMessages) -> cashu::nuts::nut00::wallet::BlindedMessages {
blinded_messages.inner
}
}

View File

@@ -32,14 +32,12 @@ impl MintProofs {
}
pub fn proofs(&self) -> Vec<Arc<Proof>> {
let proofs = self
.inner
self.inner
.proofs
.clone()
.into_iter()
.map(|p| Arc::new(p.into()))
.collect();
proofs
.collect()
}
}

View File

@@ -8,7 +8,7 @@ use crate::MintProofs;
use crate::Proof;
pub struct Token {
token: TokenSdk,
inner: TokenSdk,
}
impl Token {
@@ -16,12 +16,12 @@ impl Token {
let mint = url::Url::from_str(&mint)?;
let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect();
Ok(Self {
token: TokenSdk::new(mint, proofs, memo)?,
inner: TokenSdk::new(mint, proofs, memo)?,
})
}
pub fn token(&self) -> Vec<Arc<MintProofs>> {
self.token
self.inner
.token
.clone()
.into_iter()
@@ -30,16 +30,22 @@ impl Token {
}
pub fn memo(&self) -> Option<String> {
self.token.memo.clone()
self.inner.memo.clone()
}
pub fn from_string(token: String) -> Result<Self> {
Ok(Self {
token: TokenSdk::from_str(&token)?,
inner: TokenSdk::from_str(&token)?,
})
}
pub fn as_string(&self) -> Result<String> {
Ok(self.token.convert_to_string()?)
Ok(self.inner.convert_to_string()?)
}
}
impl From<cashu::nuts::nut00::wallet::Token> for Token {
fn from(inner: cashu::nuts::nut00::wallet::Token) -> Token {
Token { inner }
}
}

View File

@@ -53,3 +53,9 @@ impl KeySetResponse {
self.inner.clone().keysets.into_iter().collect()
}
}
impl From<cashu::nuts::nut02::Response> for KeySetResponse {
fn from(inner: Response) -> KeySetResponse {
KeySetResponse { inner }
}
}

View File

@@ -25,3 +25,11 @@ impl RequestMintResponse {
self.inner.hash.to_string()
}
}
impl From<cashu::nuts::nut03::RequestMintResponse> for RequestMintResponse {
fn from(mint_response: cashu::nuts::nut03::RequestMintResponse) -> RequestMintResponse {
RequestMintResponse {
inner: mint_response,
}
}
}

View File

@@ -56,3 +56,9 @@ impl PostMintResponse {
.collect()
}
}
impl From<cashu::nuts::nut04::PostMintResponse> for PostMintResponse {
fn from(inner: cashu::nuts::nut04::PostMintResponse) -> PostMintResponse {
PostMintResponse { inner }
}
}

View File

@@ -46,6 +46,18 @@ impl CheckFeesResponse {
}
}
impl From<cashu::nuts::nut05::CheckFeesResponse> for CheckFeesResponse {
fn from(inner: cashu::nuts::nut05::CheckFeesResponse) -> CheckFeesResponse {
Self { inner }
}
}
impl From<CheckFeesResponse> for cashu::nuts::nut05::CheckFeesResponse {
fn from(res: CheckFeesResponse) -> cashu::nuts::nut05::CheckFeesResponse {
res.inner
}
}
pub struct MeltRequest {
inner: MeltRequestSdk,
}

View File

@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::{ops::Deref, sync::Arc};
use cashu::nuts::nut06::{SplitRequest as SplitRequestSdk, SplitResponse as SplitResponseSdk};
@@ -8,6 +8,13 @@ pub struct SplitRequest {
inner: SplitRequestSdk,
}
impl Deref for SplitRequest {
type Target = SplitRequestSdk;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl SplitRequest {
pub fn new(proofs: Vec<Arc<Proof>>, outputs: Vec<Arc<BlindedMessage>>) -> Self {
let proofs = proofs.into_iter().map(|p| p.as_ref().into()).collect();
@@ -79,3 +86,9 @@ impl SplitResponse {
self.inner.promises_amount().map(|a| Arc::new(a.into()))
}
}
impl From<cashu::nuts::nut06::SplitResponse> for SplitResponse {
fn from(inner: cashu::nuts::nut06::SplitResponse) -> SplitResponse {
SplitResponse { inner }
}
}

View File

@@ -49,3 +49,9 @@ impl CheckSpendableResponse {
self.inner.pending.clone()
}
}
impl From<cashu::nuts::nut07::CheckSpendableResponse> for CheckSpendableResponse {
fn from(inner: cashu::nuts::nut07::CheckSpendableResponse) -> CheckSpendableResponse {
CheckSpendableResponse { inner }
}
}

View File

@@ -85,3 +85,15 @@ impl MeltResponse {
.map(|change| change.into_iter().map(|bs| Arc::new(bs.into())).collect())
}
}
impl From<cashu::nuts::nut08::MeltResponse> for MeltResponse {
fn from(inner: cashu::nuts::nut08::MeltResponse) -> MeltResponse {
MeltResponse { inner }
}
}
impl From<MeltResponse> for cashu::nuts::nut08::MeltResponse {
fn from(res: MeltResponse) -> cashu::nuts::nut08::MeltResponse {
res.inner
}
}

View File

@@ -74,3 +74,9 @@ impl MintInfo {
}
}
}
impl From<cashu::nuts::nut09::MintInfo> for MintInfo {
fn from(inner: cashu::nuts::nut09::MintInfo) -> MintInfo {
MintInfo { inner }
}
}

View File

@@ -13,5 +13,4 @@ src/cashu-sdk/*.so
*.whl
build/
testing-setup-py-simple-example.py

View File

@@ -0,0 +1,3 @@
import cashu_sdk;
help(cashu_sdk)

View File

@@ -7,6 +7,14 @@ interface CashuError {
Generic(string err);
};
interface Bolt11Invoice {
[Throws=CashuError]
constructor(string bolt11);
string as_string();
Amount? amount();
};
interface Amount {
u64 to_sat();
u64 to_msat();
@@ -216,4 +224,73 @@ enum InvoiceStatus {
"Paid",
"Expired",
"InFlight"
};
// Cashu Sdk
[Error]
interface CashuSdkError {
Generic(string err);
};
interface SendProofs {
constructor(sequence<Proof> change_proofs, sequence<Proof> send_proofs);
sequence<Proof> send_proofs();
sequence<Proof> change_proofs();
};
interface Melted {
constructor(boolean paid, string? preimage, sequence<Proof>? change);
string? preimage();
boolean paid();
sequence<Proof>? change();
};
interface Client {
[Throws=CashuSdkError]
constructor(string mint_url);
[Throws=CashuSdkError]
Keys get_keys();
[Throws=CashuSdkError]
KeySetResponse get_keysets();
[Throws=CashuSdkError]
RequestMintResponse request_mint(Amount amount);
[Throws=CashuSdkError]
PostMintResponse mint(BlindedMessages blinded_messages, string hash);
[Throws=CashuSdkError]
CheckFeesResponse check_fees(Bolt11Invoice invoice);
[Throws=CashuSdkError]
MeltResponse melt(sequence<Proof> proofs, Bolt11Invoice invoice, sequence<BlindedMessage>? outputs);
[Throws=CashuSdkError]
SplitResponse split(SplitRequest split_request);
[Throws=CashuSdkError]
CheckSpendableResponse check_spendable(sequence<MintProof> proofs);
[Throws=CashuSdkError]
MintInfo get_info();
};
interface Wallet {
constructor(Client client, Keys mint_keys);
// TODO: ProofStatus type
// [Throws=CashuSdkError]
// ProofsStatus check_proofs_spent(sequence<MintProof> proofs);
[Throws=CashuSdkError]
RequestMintResponse request_mint(Amount amount);
[Throws=CashuSdkError]
Token mint_token(Amount amount, string hash);
[Throws=CashuSdkError]
sequence<Proof> mint(Amount amount, string hash);
[Throws=CashuSdkError]
Amount check_fee(Bolt11Invoice invoice);
[Throws=CashuSdkError]
sequence<Proof> receive(string encoded_token);
[Throws=CashuSdkError]
sequence<Proof> process_split_response(BlindedMessages blinded_messages, sequence<BlindedSignature> promises);
[Throws=CashuSdkError]
SendProofs send(Amount amount, sequence<Proof> proofs);
[Throws=CashuSdkError]
Melted melt(Bolt11Invoice invoice, sequence<Proof> proofs, Amount fee_reserve);
[Throws=CashuSdkError]
string proof_to_token(sequence<Proof> proof, string? memo);
};

View File

@@ -0,0 +1,105 @@
use std::ops::Deref;
use std::sync::Arc;
use cashu_ffi::{
BlindedMessage, BlindedMessages, Bolt11Invoice, CheckFeesResponse, CheckSpendableResponse,
KeySetResponse, MeltResponse, MintInfo, MintProof, PostMintResponse, Proof,
RequestMintResponse, SplitRequest, SplitResponse,
};
use cashu_sdk::client::blocking::Client as ClientSdk;
use crate::error::Result;
use crate::{Amount, Keys};
pub struct Client {
inner: ClientSdk,
}
impl Deref for Client {
type Target = ClientSdk;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl Client {
pub fn new(mint_url: String) -> Result<Self> {
Ok(Self {
inner: ClientSdk::new(&mint_url)?,
})
}
pub fn get_keys(&self) -> Result<Arc<Keys>> {
Ok(Arc::new(self.inner.get_keys()?.into()))
}
pub fn get_keysets(&self) -> Result<Arc<KeySetResponse>> {
Ok(Arc::new(self.inner.get_keysets()?.into()))
}
pub fn request_mint(&self, amount: Arc<Amount>) -> Result<Arc<RequestMintResponse>> {
Ok(Arc::new(
self.inner.request_mint(*amount.as_ref().deref())?.into(),
))
}
pub fn mint(
&self,
blinded_messages: Arc<BlindedMessages>,
hash: String,
) -> Result<Arc<PostMintResponse>> {
Ok(Arc::new(
self.inner
.mint(blinded_messages.as_ref().deref().clone(), &hash)?
.into(),
))
}
pub fn check_fees(&self, invoice: Arc<Bolt11Invoice>) -> Result<Arc<CheckFeesResponse>> {
Ok(Arc::new(
self.inner
.check_fees(invoice.as_ref().deref().clone())?
.into(),
))
}
pub fn melt(
&self,
proofs: Vec<Arc<Proof>>,
invoice: Arc<Bolt11Invoice>,
outputs: Option<Vec<Arc<BlindedMessage>>>,
) -> Result<Arc<MeltResponse>> {
Ok(Arc::new(
self.inner
.melt(
proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
invoice.as_ref().deref().clone(),
outputs.map(|bs| bs.iter().map(|b| b.as_ref().deref().clone()).collect()),
)?
.into(),
))
}
pub fn split(&self, split_request: Arc<SplitRequest>) -> Result<Arc<SplitResponse>> {
Ok(Arc::new(
self.inner
.split(split_request.as_ref().deref().clone())?
.into(),
))
}
pub fn check_spendable(
&self,
proofs: Vec<Arc<MintProof>>,
) -> Result<Arc<CheckSpendableResponse>> {
Ok(Arc::new(
self.inner
.check_spendable(&proofs.iter().map(|p| p.as_ref().deref().clone()).collect())?
.into(),
))
}
pub fn get_info(&self) -> Result<Arc<MintInfo>> {
Ok(Arc::new(self.inner.get_info()?.into()))
}
}

View File

@@ -0,0 +1,32 @@
use std::fmt;
pub type Result<T, E = CashuSdkError> = std::result::Result<T, E>;
#[derive(Debug)]
pub enum CashuSdkError {
Generic { err: String },
}
impl fmt::Display for CashuSdkError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Generic { err } => write!(f, "{err}"),
}
}
}
impl From<cashu_sdk::client::Error> for CashuSdkError {
fn from(err: cashu_sdk::client::Error) -> CashuSdkError {
Self::Generic {
err: err.to_string(),
}
}
}
impl From<cashu_sdk::wallet::Error> for CashuSdkError {
fn from(err: cashu_sdk::wallet::Error) -> CashuSdkError {
Self::Generic {
err: err.to_string(),
}
}
}

View File

@@ -1,12 +1,23 @@
mod client;
mod error;
mod types;
mod wallet;
mod ffi {
pub use cashu_ffi::{
Amount, BlindedMessage, BlindedMessages, BlindedSignature, CashuError, CheckFeesRequest,
CheckFeesResponse, CheckSpendableRequest, CheckSpendableResponse, InvoiceStatus, KeyPair,
KeySet, KeySetResponse, Keys, MeltRequest, MeltResponse, MintInfo, MintProof, MintProofs,
MintRequest, MintVersion, Nut05MeltRequest, Nut05MeltResponse, PostMintResponse, Proof,
PublicKey, RequestMintResponse, SecretKey, SplitRequest, SplitResponse, Token,
Amount, BlindedMessage, BlindedMessages, BlindedSignature, Bolt11Invoice, CashuError,
CheckFeesRequest, CheckFeesResponse, CheckSpendableRequest, CheckSpendableResponse,
InvoiceStatus, KeyPair, KeySet, KeySetResponse, Keys, MeltRequest, MeltResponse, MintInfo,
MintProof, MintProofs, MintRequest, MintVersion, Nut05MeltRequest, Nut05MeltResponse,
PostMintResponse, Proof, PublicKey, RequestMintResponse, SecretKey, SplitRequest,
SplitResponse, Token,
};
pub use crate::client::Client;
pub use crate::error::CashuSdkError;
pub use crate::types::{Melted, SendProofs};
pub use crate::wallet::Wallet;
// UDL
uniffi::include_scaffolding!("cashu_sdk");
}

View File

@@ -0,0 +1,42 @@
use std::{ops::Deref, sync::Arc};
use cashu_sdk::types::Melted as MeltedSdk;
use cashu_ffi::Proof;
pub struct Melted {
inner: MeltedSdk,
}
impl Melted {
pub fn new(paid: bool, preimage: Option<String>, change: Option<Vec<Arc<Proof>>>) -> Self {
Self {
inner: MeltedSdk {
paid,
preimage,
change: change.map(|c| c.iter().map(|p| p.as_ref().deref().clone()).collect()),
},
}
}
pub fn preimage(&self) -> Option<String> {
self.inner.preimage.clone()
}
pub fn paid(&self) -> bool {
self.inner.paid
}
pub fn change(&self) -> Option<Vec<Arc<Proof>>> {
self.inner
.change
.clone()
.map(|c| c.into_iter().map(|p| Arc::new(p.into())).collect())
}
}
impl From<cashu_sdk::types::Melted> for Melted {
fn from(inner: cashu_sdk::types::Melted) -> Melted {
Melted { inner }
}
}

View File

@@ -0,0 +1,5 @@
pub mod melted;
pub mod send_proofs;
pub use melted::Melted;
pub use send_proofs::SendProofs;

View File

@@ -0,0 +1,50 @@
use std::{ops::Deref, sync::Arc};
use cashu_sdk::types::SendProofs as SendProofSdk;
use cashu_ffi::Proof;
pub struct SendProofs {
inner: SendProofSdk,
}
impl SendProofs {
pub fn new(change_proofs: Vec<Arc<Proof>>, send_proofs: Vec<Arc<Proof>>) -> Self {
Self {
inner: SendProofSdk {
change_proofs: change_proofs
.iter()
.map(|p| p.as_ref().deref().clone())
.collect(),
send_proofs: send_proofs
.iter()
.map(|p| p.as_ref().deref().clone())
.collect(),
},
}
}
pub fn send_proofs(&self) -> Vec<Arc<Proof>> {
self.inner
.send_proofs
.clone()
.into_iter()
.map(|p| Arc::new(p.into()))
.collect()
}
pub fn change_proofs(&self) -> Vec<Arc<Proof>> {
self.inner
.change_proofs
.clone()
.into_iter()
.map(|p| Arc::new(p.into()))
.collect()
}
}
impl From<cashu_sdk::types::SendProofs> for SendProofs {
fn from(inner: cashu_sdk::types::SendProofs) -> SendProofs {
SendProofs { inner }
}
}

View File

@@ -0,0 +1,127 @@
use std::ops::Deref;
use std::sync::Arc;
use cashu_ffi::{
BlindedMessages, BlindedSignature, Bolt11Invoice, Proof, RequestMintResponse, Token,
};
use cashu_sdk::types::ProofsStatus;
use cashu_sdk::wallet::Wallet as WalletSdk;
use crate::{
client::Client,
error::Result,
types::{Melted, SendProofs},
Amount, Keys, MintProof,
};
pub struct Wallet {
inner: WalletSdk,
}
impl Wallet {
pub fn new(client: Arc<Client>, mint_keys: Arc<Keys>) -> Self {
Self {
inner: WalletSdk::new(
client.as_ref().deref().clone(),
mint_keys.as_ref().deref().clone(),
),
}
}
pub fn check_proofs_spent(&self, proofs: Vec<Arc<MintProof>>) -> Result<Arc<ProofsStatus>> {
Ok(Arc::new(self.inner.check_proofs_spent(
&proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
)?))
}
pub fn request_mint(&self, amount: Arc<Amount>) -> Result<Arc<RequestMintResponse>> {
Ok(Arc::new(
self.inner.request_mint(*amount.as_ref().deref())?.into(),
))
}
pub fn mint_token(&self, amount: Arc<Amount>, hash: String) -> Result<Arc<Token>> {
Ok(Arc::new(
self.inner
.mint_token(*amount.as_ref().deref(), &hash)?
.into(),
))
}
pub fn mint(&self, amount: Arc<Amount>, hash: String) -> Result<Vec<Arc<Proof>>> {
Ok(self
.inner
.mint(*amount.as_ref().deref(), &hash)?
.into_iter()
.map(|p| Arc::new(p.into()))
.collect())
}
pub fn check_fee(&self, invoice: Arc<Bolt11Invoice>) -> Result<Arc<Amount>> {
Ok(Arc::new(
self.inner
.check_fee(invoice.as_ref().deref().clone())?
.into(),
))
}
pub fn receive(&self, encoded_token: String) -> Result<Vec<Arc<Proof>>> {
Ok(self
.inner
.receive(&encoded_token)?
.into_iter()
.map(|p| Arc::new(p.into()))
.collect())
}
pub fn process_split_response(
&self,
blinded_messages: Arc<BlindedMessages>,
promises: Vec<Arc<BlindedSignature>>,
) -> Result<Vec<Arc<Proof>>> {
Ok(self
.inner
.process_split_response(
blinded_messages.as_ref().deref().clone(),
promises.iter().map(|p| p.as_ref().deref().into()).collect(),
)?
.into_iter()
.map(|p| Arc::new(p.into()))
.collect())
}
pub fn send(&self, amount: Arc<Amount>, proofs: Vec<Arc<Proof>>) -> Result<Arc<SendProofs>> {
Ok(Arc::new(
self.inner
.send(
*amount.as_ref().deref(),
proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
)?
.into(),
))
}
pub fn melt(
&self,
invoice: Arc<Bolt11Invoice>,
proofs: Vec<Arc<Proof>>,
fee_reserve: Arc<Amount>,
) -> Result<Arc<Melted>> {
Ok(Arc::new(
self.inner
.melt(
invoice.as_ref().deref().clone(),
proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
*fee_reserve.as_ref().deref(),
)?
.into(),
))
}
pub fn proof_to_token(&self, proofs: Vec<Arc<Proof>>, memo: Option<String>) -> Result<String> {
Ok(self.inner.proofs_to_token(
proofs.iter().map(|p| p.as_ref().deref().clone()).collect(),
memo,
)?)
}
}

View File

@@ -44,10 +44,10 @@ impl Client {
pub fn mint(
&self,
blinded_mssages: BlindedMessages,
blinded_messages: BlindedMessages,
hash: &str,
) -> Result<PostMintResponse, Error> {
RUNTIME.block_on(async { self.client.mint(blinded_mssages, hash).await })
RUNTIME.block_on(async { self.client.mint(blinded_messages, hash).await })
}
pub fn check_fees(&self, invoice: Bolt11Invoice) -> Result<CheckFeesResponse, Error> {

View File

@@ -88,7 +88,7 @@ impl Keys {
self.0.get(&amount.to_sat()).cloned()
}
/// As seralized hashmap
/// As serialized hashmap
pub fn as_hashmap(&self) -> HashMap<u64, String> {
self.0
.iter()