Skip to content

Commit

Permalink
Merge pull request #39 from ChainSafe/willem/docs-update
Browse files Browse the repository at this point in the history
Update docs for current API with examples
  • Loading branch information
willemolding authored Oct 11, 2024
2 parents aa536e2 + dd447c0 commit d00cec4
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 25 deletions.
2 changes: 1 addition & 1 deletion packages/demo-wallet/src/App/Actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export async function triggerTransfer(
let proposal = await state.webWallet?.propose_transfer(state.activeAccount, toAddress, amount);
console.log(JSON.stringify(proposal.describe(), null, 2));

let txids = await state.webWallet.create_proposed_transactions(proposal, activeAccountSeedPhrase);
let txids = await state.webWallet.create_proposed_transactions(proposal, activeAccountSeedPhrase, 0);
console.log(JSON.stringify(txids, null, 2));

await state.webWallet.send_authorized_transactions(txids);
Expand Down
1 change: 1 addition & 0 deletions src/bindgen/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ impl From<Proposal> for zcash_client_backend::proposal::Proposal<FeeRule, NoteRe

#[wasm_bindgen]
impl Proposal {
/// Returns a JSON object with the details of the proposal.
pub fn describe(&self) -> JsValue {
serde_wasm_bindgen::to_value(&self.inner).unwrap()
}
Expand Down
167 changes: 144 additions & 23 deletions src/bindgen/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,64 @@ pub type NoteRef = <MemoryWalletDb<zcash_primitives::consensus::Network> as Inpu

/// # A Zcash wallet
///
/// This is the main entry point for interacting with this library.
/// For the most part you will only need to create and interact with a Wallet instance.
///
/// A wallet is a set of accounts that can be synchronized together with the blockchain.
/// Once synchronized these can be used to build transactions that spend notes
/// Once synchronized, the wallet can be used to propose, build and send transactions.
///
/// Create a new WebWallet with
/// ```javascript
/// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10);
/// ```
///
/// ## Adding Accounts
///
/// TODO
/// Accounts can be added by either importing a seed phrase or a Unified Full Viewing Key (UFVK).
/// If you do import via a UFVK it is important that you also have access to the Unified Spending Key (USK) for that account otherwise the wallet will not be able to create transactions.
///
/// When importing an account you can also specify the block height at which the account was created. This can significantly reduce the time it takes to sync the account as the wallet will only scan for transactions after this height.
/// Failing to provide a birthday height will result in extremely slow sync times as the wallet will need to scan the entire blockchain.
///
/// e.g.
/// ```javascript
/// const account_id = await wallet.create_account("...", 1, 2657762)
///
/// // OR
///
/// const account_id = await wallet.import_ufvk("...", 2657762)
/// ``
///
/// ## Synchronizing
///
/// A wallet can be syncced with the blockchain by feeding it blocks. The accounts currently managed by the wallet will be used to
/// scan the blocks and retrieve relevant transactions. The wallet itself keeps track of blocks it has seen and can be queried for
/// the suggest range of blocks that should be retrieved for it to process next.
/// The wallet can be synchronized with the blockchain by calling the `sync` method. This will fetch compact blocks from the connected lightwalletd instance and scan them for transactions.
/// The sync method uses a built-in strategy to determine which blocks is needs to download and scan in order to gain full knowledge of the balances for all accounts that are managed.
///
/// Syncing is a long running process and so is delegated to a WebWorker to prevent from blocking the main thread. It is safe to call other methods on the wallet during syncing although they may take
/// longer than usual while they wait for a write-lock to be released.
///
/// ```javascript
/// await wallet.sync();
/// ```
///
/// ## Transacting
///
/// Sending a transaction is a three step process: proposing, authorizing, and sending.
///
/// A transaction proposal is created by calling `propose_transfer` with the intended recipient and amount. This will create a proposal object that describes which notes will be spent in order to fulfil this request.
/// The proposal should be presented to the user for review before being authorized.
///
/// To authorize the transaction the caller must currently provide the seed phrase and account index of the account that will be used to sign the transaction. This method also perform the SNARK proving which is an expensive operation and performed in parallel by a series of WebWorkers.
/// Note: Handing the sensitive key material this way is not recommended for production applications. Upcoming changes to how proposals are authorized will allow separation of proof generation and signing but currently these are coupled.
///
/// ## Building Transactions
/// Finally, A transaction can be sent to the network by calling `send_authorized_transactions` with the list of transaction IDs that were generated by the authorization step.
///
/// TODO
/// The full flow looks like
/// ```javascript
/// const proposal = wallet.propose_transfer(1, "...", 100000000);
/// const authorized_txns = wallet.create_proposed_transactions(proposal, "...", 1);
/// await wallet.send_authorized_transactions(authorized_txns);
/// ```
///
#[wasm_bindgen]
#[derive(Clone)]
Expand Down Expand Up @@ -70,7 +112,19 @@ impl WebWallet {

#[wasm_bindgen]
impl WebWallet {
/// Create a new instance of a Zcash wallet for a given network
/// Create a new instance of a Zcash wallet for a given network. Only one instance should be created per page.
///
/// # Arguments
///
/// * `network` - Must be one of "main" or "test"
/// * `lightwalletd_url` - Url of the lightwalletd instance to connect to (e.g. https://zcash-mainnet.chainsafe.dev)
/// * `min_confirmations` - Number of confirmations required before a transaction is considered final
///
/// # Examples
///
/// ```javascript
/// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10);
/// ```
#[wasm_bindgen(constructor)]
pub fn new(
network: &str,
Expand All @@ -92,13 +146,20 @@ impl WebWallet {
})
}

/// Add a new account to the wallet
/// Add a new account to the wallet using a given seed phrase
///
/// # Arguments
/// seed_phrase - mnemonic phrase to initialise the wallet
/// account_hd_index - The HD derivation index to use. Can be any integer
/// birthday_height - The block height at which the account was created, optionally None and the current height is used
///
/// * `seed_phrase` - 24 word mnemonic seed phrase
/// * `account_hd_index` - [ZIP32](https://zips.z.cash/zip-0032) hierarchical deterministic index of the account
/// * `birthday_height` - Block height at which the account was created. The sync logic will assume no funds are send or received prior to this height which can VERY significantly reduce sync time
///
/// # Examples
///
/// ```javascript
/// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10);
/// const account_id = await wallet.create_account("...", 1, 2657762)
/// ```
pub async fn create_account(
&self,
seed_phrase: &str,
Expand All @@ -112,6 +173,19 @@ impl WebWallet {
.map(|id| *id)
}

/// Add a new account to the wallet by directly importing a Unified Full Viewing Key (UFVK)
///
/// # Arguments
///
/// * `key` - [ZIP316](https://zips.z.cash/zip-0316) encoded UFVK
/// * `birthday_height` - Block height at which the account was created. The sync logic will assume no funds are send or received prior to this height which can VERY significantly reduce sync time
///
/// # Examples
///
/// ```javascript
/// const wallet = new WebWallet("main", "https://zcash-mainnet.chainsafe.dev", 10);
/// const account_id = await wallet.import_ufvk("...", 2657762)
/// ```
pub async fn import_ufvk(&self, key: &str, birthday_height: Option<u32>) -> Result<u32, Error> {
let ufvk = UnifiedFullViewingKey::decode(&self.inner.network, key)
.map_err(Error::KeyParseError)?;
Expand All @@ -122,11 +196,12 @@ impl WebWallet {
.map(|id| *id)
}

pub async fn suggest_scan_ranges(&self) -> Result<Vec<BlockRange>, Error> {
self.inner.suggest_scan_ranges().await
}

/// Synchronize the wallet with the blockchain up to the tip using zcash_client_backend's algo
///
/// Start a background sync task which will fetch and scan blocks from the connected lighwalletd server
///
/// IMPORTANT: This will spawn a new webworker which will handle the sync task. The sync task will continue to run in the background until the sync process is complete.
/// During this time the main thread will not block but certain wallet methods may temporarily block while the wallet is being written to during the sync.
///
pub async fn sync(&self) -> Result<(), Error> {
assert!(!thread::is_web_worker_thread());

Expand Down Expand Up @@ -154,9 +229,25 @@ impl WebWallet {
Ok(self.inner.get_wallet_summary().await?.map(Into::into))
}

/// Create a new transaction proposal to send funds to a given address
///
/// Not this does NOT sign, generate a proof, or send the transaction. It will only craft the proposal which designates how notes from this account can be spent to realize the requested transfer.
///
/// Create a transaction proposal to send funds from the wallet to a given address.
/// # Arguments
///
/// * `account_id` - The ID of the account in this wallet to send funds from
/// * `to_address` - [ZIP316](https://zips.z.cash/zip-0316) encoded address to send funds to
/// * `value` - Amount to send in Zatoshis (1 ZEC = 100_000_000 Zatoshis)
///
/// # Returns
///
/// A proposal object which can be inspected and later used to generate a valid transaction
///
/// # Examples
///
/// ```javascript
/// const proposal = await wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000);
/// ```
pub async fn propose_transfer(
&self,
account_id: u32,
Expand All @@ -171,26 +262,54 @@ impl WebWallet {
Ok(proposal.into())
}

/// Generate a valid Zcash transaction from a given proposal
///
/// Perform the proving and signing required to create one or more transaction from the proposal.
/// Created transactions are stored in the wallet database and a list of the IDs is returned
/// # Arguments
///
/// * `proposal` - A proposal object generated by `propose_transfer`
/// * `seed_phrase` - 24 word mnemonic seed phrase. This MUST correspond to the accountID used when creating the proposal.
/// * `account_hd_index` - [ZIP32](https://zips.z.cash/zip-0032) hierarchical deterministic index of the account. This MUST correspond to the accountID used when creating the proposal.
///
/// # Returns
///
/// A list of transaction IDs which can be used to track the status of the transaction on the network.
/// The transactions themselves are stored within the wallet
///
/// # Examples
///
/// ```javascript
/// const proposal = await wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000);
/// const authorized_txns = await wallet.create_proposed_transactions(proposal, "...", 1);
/// ```
pub async fn create_proposed_transactions(
&self,
proposal: Proposal,
seed_phrase: &str,
account_hd_index: u32,
) -> Result<JsValue, Error> {
let usk = usk_from_seed_str(seed_phrase, 0, &self.inner.network)?;
let usk = usk_from_seed_str(seed_phrase, account_hd_index, &self.inner.network)?;
let txids = self
.inner
.create_proposed_transactions(proposal.into(), &usk)
.await?;
Ok(serde_wasm_bindgen::to_value(&txids).unwrap())
}

/// Send a list of authorized transactions to the network to be included in the blockchain
///
/// Send a list of transactions to the network via the lightwalletd instance this wallet is connected to
/// These will be sent via the connected lightwalletd instance
///
/// # Arguments
///
/// * `txids` - A list of transaction IDs (typically generated by `create_proposed_transactions`)
///
/// # Examples
///
/// ```javascript
/// const proposal = wallet.propose_transfer(1, "u18rakpts0de589sx9dkamcjms3apruqqax9k2s6e7zjxx9vv5kc67pks2trg9d3nrgd5acu8w8arzjjuepakjx38dyxl6ahd948w0mhdt9jxqsntan6px3ysz80s04a87pheg2mqvlzpehrgup7568nfd6ez23xd69ley7802dfvplnfn7c07vlyumcnfjul4pvv630ac336rjhjyak5", 100000000);
/// const authorized_txns = wallet.create_proposed_transactions(proposal, "...", 1);
/// await wallet.send_authorized_transactions(authorized_txns);
/// ```
pub async fn send_authorized_transactions(&self, txids: JsValue) -> Result<(), Error> {
let txids: NonEmpty<TxId> = serde_wasm_bindgen::from_value(txids).unwrap();
self.inner.send_authorized_transactions(&txids).await
Expand All @@ -200,7 +319,9 @@ impl WebWallet {
// lightwalletd gRPC methods
///////////////////////////////////////////////////////////////////////////////////////

/// Forwards a call to lightwalletd to retrieve the height of the latest block in the chain
///
/// Get the hightest known block height from the connected lightwalletd instance
///
pub async fn get_latest_block(&self) -> Result<u64, Error> {
self.client()
.get_latest_block(ChainSpec {})
Expand Down
2 changes: 1 addition & 1 deletion src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fn setup_tracing() {
}

#[wasm_bindgen(start)]
pub fn start() {
fn start() {
set_panic_hook();
setup_tracing();
}

0 comments on commit d00cec4

Please sign in to comment.