Quickstart
Getting Started
Working with the SDK
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}
@demox-labs/miden-sdk
dynamically to ensure the worker and WASM are initialized properly.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
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.
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:
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:
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
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.
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:
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