Mint, Consume, and Create Notes
Using the Miden client in TypeScript to mint, consume, and transfer assets
Follow the network and fee setup
and copy the shared support files imported by the complete example.
For React snippets, initialize authScheme with await tutorialAuthScheme()
as shown in the complete example.
Overview
In the previous tutorial, we set up the foundation - creating Alice's wallet and deploying a faucet. Now we'll put these to use by minting and transferring assets.
What we'll cover
- Minting assets from a faucet
- Consuming notes to fund an account
- Sending tokens to other users
Prerequisites
This tutorial builds directly on the previous one. Make sure you have:
- Completed the "Creating Accounts and Deploying Faucets" tutorial
- Your Next.js app with the Miden client set up
Understanding Notes in Miden
Before we start coding, it's important to understand notes:
- Minting a note from a faucet does not automatically add the tokens to your account balance. It creates a note addressed to you.
- You must consume a note to add its tokens to your account balance.
- Until consumed, tokens exist in the note but aren't in your account yet.
Step 1: Mint tokens from the faucet
Let's mint some tokens for Alice. When we mint from a faucet, it creates a note containing the specified amount of tokens targeted to Alice's account.
Add the operations below after funding Alice and the faucet in createMintConsume. Import NoteVisibility and the shared support functions shown in the complete example. For React, also initialize useMint, useConsume and useSend, and obtain committed, waitForTokenNotes and assertBalance from useTutorialSupport.
// 3. Mint 1000 tokens to Alice
console.log('Minting tokens to Alice...');
const mintResult = await mint({
faucetId: faucet, // Faucet account (who mints the tokens)
targetAccountId: alice, // Target account (who receives the tokens)
amount: BigInt(1000), // Amount to mint (in base units)
noteType: NoteVisibility.Public, // Note visibility (public = onchain)
});
console.log('Mint tx:', mintResult.transactionId);
// Wait for the mint transaction to be committed
await committed(mintResult.transactionId);
What's happening here?
- client.transactions.mint(): Creates, proves, and submits a mint transaction to Alice. Note that this is only possible to submit transactions on the faucets' behalf if the user controls the faucet (i.e. its keys are stored in the client).
- client.transactions.waitFor(): Polls until the transaction is committed on-chain.
Step 2: Consume minted notes
After minting, Alice has a note waiting for her but the tokens aren't in her account yet. We need to consume the note to add its assets to her account balance.
Select the tutorial notes before consuming them: v0.16 also exposes globally consumable TX_FEE notes. The shared consumeAllFeeAware TypeScript helper excludes those fee notes. In React, waitForTokenNotes selects committed notes containing our faucet’s token and returns the InputNoteRecord values accepted by useConsume.
// 4. Wait for committed tutorial-token notes, then consume them
const notes = await waitForTokenNotes(alice, faucet);
console.log('Consumable notes:', notes.length);
console.log('Consuming minted notes...');
const consumed = await consume({ accountId: alice.id().toString(), notes });
await committed(consumed.transactionId);
await assertBalance(alice, faucet, BigInt(1000));
console.log('Notes consumed.');
Step 3: Sending tokens to other accounts
After consuming the notes, Alice has tokens in her wallet. Now, she wants to send tokens to her friends. She has two options: create a separate transaction for each transfer or batch multiple notes in a single transaction.
The standard asset transfer note on Miden is the P2ID note (Pay-to-Id). There is also the P2IDE (Pay-to-Id Extended) variant which allows for both timelocking the note (target can only spend the note after a certain block height) and for the note to be reclaimable (the creator of the note can reclaim the note after a certain block height).
Now that Alice has tokens in her account, she can send some to Bob:
// 7. Create Bob and send him 100 tokens
const bob = await createWallet({ storageMode: StorageMode.Public, authScheme });
const bobAddress = bob.id().toString();
console.log("Sending tokens to Bob's account...");
const sent = await send({
from: alice,
to: bobAddress,
assetId: faucet,
amount: BigInt(100),
noteType: NoteVisibility.Public,
});
await committed(sent.txId);
await assertBalance(alice, faucet, BigInt(900));
console.log('Tokens sent successfully!');
Understanding P2ID notes
The transaction creates a P2ID (Pay-to-ID) note:
- It's the standard way to transfer assets in Miden
- The note is "locked" to Bob's account ID, i.e. only Bob can consume this note to receive the tokens
- Public notes are visible onchain; private notes would need to be shared offchain (e.g. via a private channel)
Summary
Here's the complete lib/react/createMintConsume.tsx (React) or lib/createMintConsume.ts (TypeScript):
'use client';
import {
MidenProvider,
useMiden,
useCreateWallet,
useCreateFaucet,
useMint,
useConsume,
useSend,
} from '@miden-sdk/react/lazy';
import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk/lazy';
import { tutorialNetwork } from '../feeSupport';
import {
TutorialButton,
tutorialAuthScheme,
useTutorialSupport,
} from './tutorialSupport';
function CreateMintConsumeInner() {
const { sync } = useMiden();
const { createWallet } = useCreateWallet();
const { createFaucet } = useCreateFaucet();
const { mint } = useMint();
const { consume } = useConsume();
const { send } = useSend();
const {
fundAccount,
committed,
waitForTokenNotes,
waitForNote,
assertBalance,
} = useTutorialSupport();
const run = async () => {
console.log('Synchronizing before creating accounts…');
await sync();
console.log('Creating Alice with useCreateWallet…');
const authScheme = await tutorialAuthScheme();
// Native fee tokens and the tutorial's MID token are separate assets.
const alice = await createWallet({
storageMode: StorageMode.Public,
authScheme,
});
console.log('Alice ID:', alice.id().toString());
await fundAccount(alice);
// v0.16 faucets include BasicWallet, so they can receive fee funding.
const faucet = await createFaucet({
tokenSymbol: 'MID',
decimals: 8,
maxSupply: BigInt(1_000_000),
storageMode: StorageMode.Public,
authScheme,
});
console.log('Faucet ID:', faucet.id().toString());
await fundAccount(faucet);
await sync();
const minted = await mint({
faucetId: faucet,
targetAccountId: alice,
amount: BigInt(1000),
noteType: NoteVisibility.Public,
});
await committed(minted.transactionId);
const notes = await waitForTokenNotes(alice, faucet);
const consumed = await consume({ accountId: alice.id().toString(), notes });
await committed(consumed.transactionId);
await assertBalance(alice, faucet, BigInt(1000));
const bob = await createWallet({
storageMode: StorageMode.Public,
authScheme,
});
const sent = await send({
from: alice,
to: bob,
assetId: faucet,
amount: BigInt(100),
noteType: NoteVisibility.Public,
returnNote: true,
});
await committed(sent.txId);
if (!sent.note) throw new Error('Send did not return its output note');
await waitForNote(sent.note.id().toString());
await assertBalance(alice, faucet, BigInt(900));
console.log('Tokens sent successfully!');
};
return (
<TutorialButton
name="createMintConsume"
label="Run: Create, Mint, Consume & Send"
run={run}
/>
);
}
export default function CreateMintConsume() {
return (
<MidenProvider
config={{
rpcUrl: tutorialNetwork(),
prover: 'local',
autoSyncInterval: 0,
}}
>
<CreateMintConsumeInner />
</MidenProvider>
);
}
Let's run the function again. Reload the page and click "Start".
The output will look like this (account IDs and block number vary with live testnet state):
Latest block number: <testnet block>
Creating account for Alice…
Alice ID: <testnet_account_id>
Creating faucet…
Faucet ID: <testnet_account_id>
Minting tokens to Alice...
Waiting for transaction confirmation...
Consuming minted notes...
Notes consumed.
Sending tokens to Bob's account...
Tokens sent successfully!
Resetting the MidenClientDB
The Miden webclient stores account and note data in IndexedDB. Stop or terminate the tutorial client and close other tabs using its store before resetting it. This deletes local account data and keys, so use it only for disposable tutorial accounts. The following browser-console snippet deletes the default testnet MidenClientDB_mtst store after the deletion request completes; change name if you configured a different store.
(async () => {
const name = 'MidenClientDB_mtst';
await new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(name);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
request.onblocked = () =>
reject(new Error('Close clients and tabs using this store, then retry.'));
});
console.log(`Deleted database: ${name}`);
})();
What's next?
You've now learned the complete note lifecycle in Miden:
- Minting - Creating new tokens from a faucet (issued in notes)
- Consuming - Adding tokens from notes to an account
- Transferring - Sending tokens to other accounts
In the next tutorials, we'll explore:
- Creating multiple notes in a single transaction
- Delegated proving