Files
me.elcaju/rust/src/api/token.rs
T

162 lines
4.2 KiB
Rust

use std::{
str::FromStr,
sync::{Arc, RwLock},
};
use bc_ur::{prelude::CBOR, MultipartDecoder, MultipartEncoder, UR};
use cdk::nuts::Token as CdkToken;
use cdk_common::{KeySetInfo, Proofs};
use flutter_rust_bridge::frb;
use super::error::Error;
pub struct Token {
pub encoded: String,
pub raw: Option<Vec<u8>>,
pub amount: u64,
pub mint_url: String,
}
impl Token {
#[frb(sync)]
pub fn parse(encoded: &str) -> Result<Self, Error> {
let encoded = encoded.strip_prefix("cashu:").unwrap_or(encoded);
let token = CdkToken::from_str(encoded)?;
Token::try_from(token)
}
#[frb(sync)]
pub fn from_raw_bytes(raw: Vec<u8>) -> Result<Self, Error> {
let token = CdkToken::try_from(&raw)?;
Token::try_from(token)
}
pub(crate) fn proofs(&self, mint_keysets: &[KeySetInfo]) -> Result<Proofs, Error> {
let token: CdkToken = self.try_into()?;
Ok(token.proofs(mint_keysets)?)
}
}
impl std::fmt::Display for Token {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.encoded)
}
}
impl FromStr for Token {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Error> {
Token::parse(s)
}
}
impl TryFrom<CdkToken> for Token {
type Error = Error;
fn try_from(token: CdkToken) -> Result<Self, Error> {
match token {
CdkToken::TokenV3(token_v3) => Ok(Token {
encoded: token_v3.to_string(),
raw: None,
amount: token_v3.value()?.into(),
mint_url: token_v3
.mint_urls()
.first()
.ok_or(Error::InvalidInput)?
.to_string(),
}),
CdkToken::TokenV4(token_v4) => Ok(Token {
encoded: token_v4.to_string(),
raw: Some(token_v4.to_raw_bytes()?),
amount: token_v4.value()?.into(),
mint_url: token_v4.mint_url.to_string(),
}),
}
}
}
impl TryInto<CdkToken> for Token {
type Error = Error;
fn try_into(self) -> Result<CdkToken, Error> {
Ok(CdkToken::from_str(&self.encoded)?)
}
}
impl TryInto<CdkToken> for &Token {
type Error = Error;
fn try_into(self) -> Result<CdkToken, Error> {
Ok(CdkToken::from_str(&self.encoded)?)
}
}
// === QR Multiframe (bc-ur) ===
pub struct TokenDecoder {
decoder: Arc<RwLock<MultipartDecoder>>,
}
impl TokenDecoder {
#[frb(sync)]
pub fn new() -> Self {
Self {
decoder: Arc::new(RwLock::new(MultipartDecoder::default())),
}
}
#[frb(sync)]
pub fn is_complete(&self) -> bool {
self.decoder
.read()
.map(|decoder| decoder.is_complete())
.unwrap_or(false)
}
#[frb(sync)]
pub fn receive(&self, input: String) -> Result<(), Error> {
let mut decoder = self
.decoder
.write()
.map_err(|_| Error::Ur("Token decoder lock poisoned".to_string()))?;
decoder.receive(&input)?;
Ok(())
}
#[frb(sync)]
pub fn value(&self) -> Result<Option<Token>, Error> {
let decoder = self
.decoder
.read()
.map_err(|_| Error::Ur("Token decoder lock poisoned".to_string()))?;
let ur = decoder.message()?;
match ur {
Some(ur) => {
let bytes = ur
.cbor()
.try_byte_string()
.map_err(|e| Error::Ur(format!("Failed to decode CBOR byte string: {}", e)))?;
let utf8_encoded = String::from_utf8(bytes)?;
let token = Token::from_str(&utf8_encoded)?;
Ok(Some(token))
}
None => Ok(None),
}
}
}
#[frb(sync)]
pub fn encode_qr_token(
token: &Token,
max_fragment_length: Option<usize>,
) -> Result<Vec<String>, Error> {
let ur = UR::new("", CBOR::to_byte_string(&token.encoded))?;
let mut encoder = MultipartEncoder::new(&ur, max_fragment_length.unwrap_or(150))?;
let mut parts = Vec::new();
for _ in 0..encoder.parts_count() {
parts.push(encoder.next_part()?);
}
Ok(parts)
}