|
| 1 | +// Copyright 2024 ChainSafe Systems |
| 2 | +// SPDX-License-Identifier: Apache-2.0, MIT |
| 3 | + |
| 4 | +use crate::error::Error; |
| 5 | +use wasm_bindgen::prelude::*; |
| 6 | +use zcash_address::ZcashAddress; |
| 7 | +use zcash_primitives::memo::MemoBytes; |
| 8 | + |
| 9 | +/// A [ZIP-321](https://zips.z.cash/zip-0321) transaction request |
| 10 | +/// |
| 11 | +/// These can be created from a "zcash:" URI string, or constructed from an array of payment requests and encoded as a uri string |
| 12 | +#[wasm_bindgen] |
| 13 | +pub struct TransactionRequest(zip321::TransactionRequest); |
| 14 | + |
| 15 | +#[wasm_bindgen] |
| 16 | +impl TransactionRequest { |
| 17 | + /// Construct a new transaction request from a list of payment requests |
| 18 | + #[wasm_bindgen(constructor)] |
| 19 | + pub fn new(payments: Vec<PaymentRequest>) -> Result<TransactionRequest, Error> { |
| 20 | + let payments = payments.into_iter().map(|p| p.0).collect(); |
| 21 | + Ok(TransactionRequest(zip321::TransactionRequest::new( |
| 22 | + payments, |
| 23 | + )?)) |
| 24 | + } |
| 25 | + |
| 26 | + /// Construct an empty transaction request |
| 27 | + pub fn empty() -> TransactionRequest { |
| 28 | + TransactionRequest(zip321::TransactionRequest::empty()) |
| 29 | + } |
| 30 | + |
| 31 | + /// Returns the list of payment requests that are part of this transaction request. |
| 32 | + pub fn payment_requests(&self) -> Vec<PaymentRequest> { |
| 33 | + // BTreeMap automatically stores keys in sorted order so we can do this |
| 34 | + self.0 |
| 35 | + .payments() |
| 36 | + .iter() |
| 37 | + .map(|(_index, p)| PaymentRequest(p.clone())) |
| 38 | + .collect() |
| 39 | + } |
| 40 | + |
| 41 | + /// Returns the total value of the payments in this transaction request, in zatoshis. |
| 42 | + pub fn total(&self) -> Result<u64, Error> { |
| 43 | + Ok(self.0.total()?.into()) |
| 44 | + } |
| 45 | + |
| 46 | + /// Decode a transaction request from a "zcash:" URI string. |
| 47 | + /// |
| 48 | + /// ## Example |
| 49 | + /// |
| 50 | + /// ```javascript |
| 51 | + /// let uri = "zcash:u1mcxxpa0wyyd3qpkl8rftsa6n7tkh9lv8u8j3zpd9f6qz37dqwur38w6tfl5rpv7m8g8mlca7nyn7qxr5qtjemjqehcttwpupz3fk76q8ft82yh4scnyxrxf2jgywgr5f9ttzh8ah8ljpmr8jzzypm2gdkcfxyh4ad93c889qv3l4pa748945c372ku7kdglu388zsjvrg9dskr0v9zj?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg&message=Thank%20you%20for%20your%20purchase" |
| 52 | + /// let request = TransactionRequest.from_uri(uri); |
| 53 | + /// request.total() == 1; // true |
| 54 | + /// request.payment_requests().length == 1; // true |
| 55 | + /// request.payment_requests()[0].recipient_address() == "u1mcxxpa0wyyd3qpk..."; // true |
| 56 | + /// ``` |
| 57 | + /// |
| 58 | + pub fn from_uri(uri: &str) -> Result<TransactionRequest, Error> { |
| 59 | + Ok(zip321::TransactionRequest::from_uri(uri).map(TransactionRequest)?) |
| 60 | + } |
| 61 | + |
| 62 | + /// Returns the URI representation of this transaction request. |
| 63 | + pub fn to_uri(&self) -> String { |
| 64 | + self.0.to_uri() |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +/// A ZIP-321 transaction request |
| 69 | +#[wasm_bindgen] |
| 70 | +pub struct PaymentRequest(zip321::Payment); |
| 71 | + |
| 72 | +#[wasm_bindgen] |
| 73 | +impl PaymentRequest { |
| 74 | + /// Construct a new payment request |
| 75 | + #[wasm_bindgen(constructor)] |
| 76 | + pub fn new( |
| 77 | + recipient_address: &str, |
| 78 | + amount: u64, |
| 79 | + memo: Option<Vec<u8>>, |
| 80 | + label: Option<String>, |
| 81 | + message: Option<String>, |
| 82 | + other_params: JsValue, |
| 83 | + ) -> Result<PaymentRequest, Error> { |
| 84 | + let address = ZcashAddress::try_from_encoded(recipient_address)?; |
| 85 | + let amount = amount.try_into()?; |
| 86 | + let memo = if let Some(memo_bytes) = memo { |
| 87 | + Some(MemoBytes::from_bytes(&memo_bytes)?) |
| 88 | + } else { |
| 89 | + None |
| 90 | + }; |
| 91 | + let other_params = serde_wasm_bindgen::from_value(other_params)?; |
| 92 | + |
| 93 | + if let Some(payment) = |
| 94 | + zip321::Payment::new(address, amount, memo, label, message, other_params) |
| 95 | + { |
| 96 | + Ok(PaymentRequest(payment)) |
| 97 | + } else { |
| 98 | + Err(Error::UnsupportedMemoRecipient) |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + /// Helper method to construct a simple payment request with no memo, label, message, or other parameters. |
| 103 | + pub fn simple_payment(recipient_address: &str, amount: u64) -> Result<PaymentRequest, Error> { |
| 104 | + let address = ZcashAddress::try_from_encoded(recipient_address)?; |
| 105 | + let amount = amount.try_into()?; |
| 106 | + Ok(PaymentRequest(zip321::Payment::without_memo( |
| 107 | + address, amount, |
| 108 | + ))) |
| 109 | + } |
| 110 | + |
| 111 | + /// Returns the payment address to which the payment should be sent. |
| 112 | + pub fn recipient_address(&self) -> String { |
| 113 | + self.0.recipient_address().encode() |
| 114 | + } |
| 115 | + |
| 116 | + /// Returns the value of the payment that is being requested, in zatoshis. |
| 117 | + pub fn amount(&self) -> u64 { |
| 118 | + self.0.amount().into() |
| 119 | + } |
| 120 | + |
| 121 | + /// Returns the memo that, if included, must be provided with the payment. |
| 122 | + pub fn memo(&self) -> Option<Vec<u8>> { |
| 123 | + self.0.memo().map(|m| m.as_array().to_vec()) |
| 124 | + } |
| 125 | + |
| 126 | + /// A human-readable label for this payment within the larger structure |
| 127 | + /// of the transaction request. |
| 128 | + /// |
| 129 | + /// This will not be part of any generated transactions and is just for display purposes. |
| 130 | + pub fn label(&self) -> Option<String> { |
| 131 | + self.0.label().cloned() |
| 132 | + } |
| 133 | + |
| 134 | + /// A human-readable message to be displayed to the user describing the |
| 135 | + /// purpose of this payment. |
| 136 | + /// |
| 137 | + /// This will not be part of any generated transactions and is just for display purposes. |
| 138 | + pub fn message(&self) -> Option<String> { |
| 139 | + self.0.message().cloned() |
| 140 | + } |
| 141 | + |
| 142 | + /// A list of other arbitrary key/value pairs associated with this payment. |
| 143 | + /// |
| 144 | + /// This will not be part of any generated transactions. How these are used is up to the wallet |
| 145 | + pub fn other_params(&self) -> JsValue { |
| 146 | + serde_wasm_bindgen::to_value(&self.0.other_params()).unwrap() |
| 147 | + } |
| 148 | +} |
0 commit comments