0

Quickstart

The Miden Typescript SDK is the easiest way to interact with the Miden blockchain. The SDK handles everything from account creation, creating and consuming notes, signing and sending transactions. The quickstart covers client interactions such as creating accounts, creating tokens, sending tokens and consuming notes.

Getting Started

Install the sdk via a package manager
npm install @demox-labs/miden-sdk

Working with the SDK

The web client uses WASM bindings with the Rust client and the IndexedDB for storage of things like account headers, block headers, notes etc. in a complex way to interact with the Miden blockchain. The client does not run on the main thread but rather on a worker thread which offloads the computationally heavy tasks. For more detail on this you can read web-client-methods-worker.js
The web SDK abstracts away the underlying complexity, so you can get started quickly with a straightforward interface. Here is how you can begin:
interact.ts
1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2 3async function fnThatUsesClient(){ 4 // dynamic import the sdk as this runs the wasm worker 5 const { WebClient } = await import("@demox-labs/miden-sdk"); 6 const client = await WebClient.createClient(RPC_ENDPOINT); 7 8 // now you can interact with the client 9 // for example finding the latest block number you can do 10 const syncSummary = await client.syncState(); 11 console.log(syncSummary.blockNum()) 12 13 // terminate the client when done 14 client.terminate(); 15}
A few points to keep in mind while using the client:
  • Always import @demox-labs/miden-sdk dynamically to ensure the worker and WASM are initialized properly.
  • Terminate the client when you are done with your interactions. This is important as the garbage collector will not terminate the worker thread automatically.
  • Whenever client.submitTransaction is called, the local prover is used. This may not be suitable for browser environments with limited resources. Consider using a remote prover like done in the examples below.

Accounts

Accounts on Miden are a complex entity but for user-facing apps they are nothing more than a simple address. Like Ethereum EOAs, they are capable of holding assets but can also store data and execute custom code.

The web SDK provides a simple interface to manage these accounts, creating accounts and fetching accounts:

1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2 3async function createAccount() { 4 // imports and init the client 5 const { 6 WebClient, 7 AccountStorageMode 8 } = await import("@demox-labs/miden-sdk"); 9 const client = await WebClient.createClient(RPC_ENDPOINT); 10 11 // create a new private account 12 const newAccount = await client.newWallet( 13 AccountStorageMode.private(), 14 true // account is mutable i.e it can change its state 15 ) 16 17 console.log("New account created with address:", newAccount.id().toBech32()); 18 19 // you can store the account in local storage to fetch it 20 // later when we need to retreive the account for things like balances etc. 21 localStorage.setItem("account_id", newAccount.id().toBech32()); 22 23 // terminate the client when done 24 client.terminate(); 25}

Tokens

Tokens or assets are digital units of value that can be transferred between accounts. On Miden, token transfers are handled through notes. Notes are pretty much similar to currencies like euros, dollars: in every transaction, you either spend dollars (sending notes) or receive dollars (receiving notes). Also, new bills/notes can be issued to you from the banks (minting). Additionally, the assets can be fungible, for example ERC20 tokens, or non-fungible like NFTs.

In the following sections you will see how easy it is to work with faucets, assets, creating notes, and consuming notes through the SDK.

Minting Tokens

The minting of tokens is handled by a special type of account called a faucet account. These faucets create notes that can be consumed by the receiver. The faucet ID or the account ID for the faucet account can be thought of as the token address for the asset. The code for creating faucet accounts and fetching faucet accounts via the SDK:

1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2 3async function createFaucet() { 4 // imports and init the client 5 const { 6 WebClient, 7 AccountStorageMode 8 } = await import("@demox-labs/miden-sdk"); 9 const client = await WebClient.createClient(RPC_ENDPOINT); 10 11 // create a new asset and the faucet account 12 const fungibleFaucetAccount = await client.newFaucet( 13 AccountStorageMode.public(), // public faucet 14 false, // immutable 15 "MID", // token symbol 16 8, // decimals 17 BigInt(1_000_000), // max supply 18 ); 19 const fungibleFaucetId = fungibleFaucetAccount.id().toBech32(); 20 console.log("Fungible faucet created with address:", fungibleFaucetId); 21 22 await client.syncState(); // sync the state 23 // you can store the faucet account in local storage 24 // to fetch it later when we need to retreive the faucet id 25 localStorage.setItem("faucet_id", fungibleFaucetAccount.id().toBech32()); 26 27 // terminate the client when done 28 client.terminate(); 29}

Consuming Notes

The consuming of public notes is pretty straightforward, but for private notes you would have to get the serialized NoteFile via the client.exportNote(noteId) method and send those bytes via some communication channel (for example our app uses WebRTC) and then import them via client.importNote(noteBytes). The code below consumes all the notes found by the client, both public and private:

consume.ts
1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2export const TX_PROVER_ENDPOINT = 'https://tx-prover.testnet.miden.io'; 3 4async function consumeTokens() { 5 // run setup to get the client, sender, receiver and fungibleFaucetAccount 6 const { 7 WebClient, 8 AccountId, 9 TransactionProver, 10 } = await import("@demox-labs/miden-sdk"); 11 const client = await WebClient.createClient(RPC_ENDPOINT); 12 const prover = TransactionProver.newRemoteProver(TX_PROVER_ENDPOINT); 13 14 // fetch the account from local storage 15 const accountId = AccountId.fromBech32(localStorage.getItem("account_id")!); 16 const consumableNotes = await client.getConsumableNotes(); 17 18 // if no consumable notes are found, return 19 if (consumableNotes.length === 0) { 20 console.log("No pending balance to consume"); 21 return; 22 } 23 24 // get all the noteIds and create a consume transaction request 25 const noteIds = consumableNotes.map( 26 (note: any) => note.inputNoteRecord().id().toString() 27 ); 28 const consumeTxRequest = client.newConsumeTransactionRequest(noteIds) 29 30 const txResult = await client.newTransaction(accountId, consumeTxRequest) 31 const txId = txResult.executedTransaction().id().toHex() 32 await client.submitTransaction(txResult, prover) 33 34 console.log("Consumed notes successfully with transaction id:", txId); 35 36 // terminate the client when done 37 client.terminate(); 38}

Sending Tokens

Sending tokens is pretty simple in the SDK:

send.ts
1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2export const TX_PROVER_ENDPOINT = 'https://tx-prover.testnet.miden.io'; 3 4async function sendTokens( 5 to: string, 6 amount: bigint, // assuming amount is in base denom 7 isPrivate: boolean = false, 8) { 9 // imports and init the client and prover 10 const { 11 WebClient, 12 AccountId, 13 TransactionProver, 14 NoteType 15 } = await import("@demox-labs/miden-sdk"); 16 const client = await WebClient.createClient(RPC_ENDPOINT); 17 const prover = TransactionProver.newRemoteProver(TX_PROVER_ENDPOINT); 18 19 // setup send transaction params 20 const noteType = isPrivate ? NoteType.Private : NoteType.Public; 21 const faucet_id = AccountId.fromBech32( 22 localStorage.getItem("faucet_id")! 23 ); 24 const accountId = AccountId.fromBech32( 25 localStorage.getItem("account_id")! 26 ); 27 const toAccountId = to.startsWith("0x") ? 28 AccountId.fromHex(to) : 29 AccountId.fromBech32(to); 30 31 const sendTxRequest = client.newSendTransactionRequest( 32 accountId, 33 toAccountId, 34 FAUCET_ID, 35 noteType, 36 amount 37 ) 38 39 const txResult = await client.newTransaction(accountId, sendTxRequest); 40 const txId = txResult.executedTransaction().id().toHex() 41 await client.submitTransaction(txResult, prover); 42 43 console.log("Send transaction submitted with id:", txId); 44 45 // terminate the client when done 46 client.terminate(); 47}

You're all set!

With these examples, you should be able to get started building powerful applications on Miden using the TypeScript SDK. For more advanced topics and detailed documentation, check out the Miden book. The section below covers some of the primitives used in this browser wallet.

Concepts

For greater detail on Miden protocol primitives you can refer to the Protocol Section of the Miden Book.

Unauthenticated Notes

Unauthenticated Notes allow consumption of Notes that are not fully committed yet. This means that both the creation and consumption of notes can happen in the same block, allowing for sub-block time settlements. There has to be a side channel for communication of note bytes generated by your application, which is quite simple to set up and is a good tradeoff for the speedup that you get.

mint.ts
1const RPC_ENDPOINT = "https://rpc.testnet.miden.io:443"; // testnet RPC 2 3async function unauthNotesExample( 4 to: string, 5 from: string, 6 faucetId: string, 7 amount: bigint, // assuming amount is in base denom 8 isPrivate: boolean = false, 9) { 10 // imports and init the client 11 const { 12 WebClient, 13 AccountId, 14 Note, 15 NoteType, 16 NoteExecutionMode, 17 NoteAssets, 18 NoteAndArgsArray, 19 NoteAndArgs, 20 OutputNote, 21 FungibleAsset, 22 OutputNotesArray, 23 TransactionProver, 24 TransactionRequestBuilder, 25 Word, 26 Felt 27 } = await import("@demox-labs/miden-sdk"); 28 const client = await WebClient.createClient(RPC_ENDPOINT); 29 const prover = TransactionProver.newRemoteProver(TX_PROVER_ENDPOINT); 30 31 // make AccountId from bech32 32 const toAccountId = AccountId.fromBech32(to); 33 const fromAccountId = AccountId.fromBech32(from); 34 const faucetAccountId = AccountId.fromBech32(faucetId); 35 36 const noteType = isPrivate ? NoteType.Private : NoteType.Public; 37 const noteAssets = new NoteAssets([ 38 new FungibleAsset(faucetAccountId, amount) 39 ]) 40 const randomNums = crypto.getRandomValues(new Uint32Array(4)); 41 const serialNum = Word.newFromFelts([ 42 new Felt(BigInt(randomNums[0])), 43 new Felt(BigInt(randomNums[1])), 44 new Felt(BigInt(randomNums[2])), 45 new Felt(BigInt(randomNums[3])) 46 ]); 47 const p2idNote = Note.createP2IDNote( 48 accountId, 49 toAccountId, 50 noteAssets, 51 noteType, 52 serialNum, 53 new Felt(BigInt(0)) 54 ); 55 56 const outputP2ID = OutputNote.full(p2idNote); 57 let sendTxRequest = new TransactionRequestBuilder() 58 .withOwnOutputNotes(new OutputNotesArray([outputP2ID])) 59 .build() 60 61 await client.submitTransaction(txResult, prover); 62 63 // now you can send the note via a side channel 64 // example: this wallet sends it via webrtc 65 // consmuption of unauthenticated notes 66 67 const noteAndArgs = new NoteAndArgs(p2idNote, null); 68 69 const consumeRequest = new TransactionRequestBuilder() 70 .withUnauthenticatedInputNotes(new NoteAndArgsArray([noteAndArgs])) 71 .build(); 72 73 const consumeTransaction = await client.newTransaction( 74 receiver.id(), 75 consumeRequest, 76 ); 77 78 await client.submitTransaction(consumeTransaction, prover); 79 80 // terminate the client when done 81 client.terminate(); 82}

Indexing Transactions

Indexing transactions is a way to get the transaction data from the blockchain. This is useful for things like displaying transaction history, getting account balances etc. The RPC provides the functionality to get updates for interested accounts. You can do this in Rust via:

indexer.rs
1 2use miden_client::rpc::{TonicRpcClient, Endpoint}; 3use std::collections::BTreeSet; 4 5 6async fn index_transactions() { 7 let endpoint = Endpoint::testnet(); 8 let rpc: TonicRpcClient = TonicRpcClient::new(&endpoint, 100_000); 9 10 // create a vector of account ids to be tracked 11 let accounts_to_be_tracked = vec![]; 12 13 // add note tags to be tracked 14 let btree_set = BtreeSet::new(); 15 16 let mut last_sync_block = 0; 17 18 loop { 19 // the sync result returns the 20 // `SyncStateInfo` struct which has the transactions and note inclusions 21 let sync_reuslt = rpc 22 .sync_state( 23 last_sync_block.into(), 24 &accounts_to_be_tracked, 25 &btree_set, 26 ) 27 .await 28 .expect("Sync failed"); 29 last_sync_block = sync_result.chain_tip.as_u32(); 30 31 // update the db here 32 // update_db_with_sync_result(sync_result).await; 33 34 println!("Transactions {:?}", sync_result.transactions); 35 println!("Notes {:?}", sync_result.note_inclusions); 36 } 37 38} 39

Use Case

In the quickstart, you learned how to mint, send, and receive tokens. While these may seem like basic operations, combining them with Unauthenticated Notes enables sub-block time payment settlement. As the protocol becomes faster, this will allow for near-instant payment settlements, resulting in smoother user experiences in scenarios such as:
  • Microtransactions or micropayments which are useful for in-game purchases, pay for content like TV show episodes, and other scenarios where small payments are frequent like tipping.
  • Enable x402 in a verifiable privacy-preserving way or pay-per-API call usage which is pretty useful in things like AI credits etc.
  • This one goes without saying but instant global privacy-preserving verifiable cheap payments can be made possible.