diff --git a/docs/src/web-client/counter_contract_tutorial.md b/docs/src/web-client/counter_contract_tutorial.md index 718b10b..0c1a1c0 100644 --- a/docs/src/web-client/counter_contract_tutorial.md +++ b/docs/src/web-client/counter_contract_tutorial.md @@ -11,7 +11,7 @@ In this tutorial, we will interact with a counter contract already deployed on c Using a script, we will invoke the increment function within the counter contract to update the count. This tutorial provides a foundational understanding of interacting with custom smart contracts on Miden. -## What we'll cover +## What we'll cover - Interacting with a custom smart contract on Miden - Calling procedures in an account from a script @@ -96,7 +96,7 @@ export default function Home() { } ``` -## Step 3 — Incrementing the Count of the Counter Contract +## Step 3 — Incrementing the Count of the Counter Contract Create the file `lib/incrementCounterContract.ts` and add the following code. @@ -117,139 +117,117 @@ export async function incrementCounterContract(): Promise { // dynamic import → only in the browser, so WASM is loaded client‑side const { - Address, - AccountBuilder, - AccountComponent, - AccountStorageMode, AccountType, + Address, AuthSecretKey, + StorageMode, StorageSlot, - TransactionRequestBuilder, - WebClient, + MidenClient, } = await import('@miden-sdk/miden-sdk'); const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); - console.log('Current block number: ', (await client.syncState()).blockNum()); + const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); + console.log('Current block number: ', (await client.sync()).blockNum()); // Counter contract code in Miden Assembly const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end + use miden::protocol::active_account + use miden::protocol::native_account + use miden::core::word + use miden::core::sys + + const COUNTER_SLOT = word("miden::tutorials::counter") + + #! Inputs: [] + #! Outputs: [count] + pub proc get_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + exec.sys::truncate_stack + # => [count] + end + + #! Inputs: [] + #! Outputs: [] + pub proc increment_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + push.COUNTER_SLOT[0..2] exec.native_account::set_item + # => [] + + exec.sys::truncate_stack + # => [] + end `; - // Building the counter contract // Counter contract account id on testnet const counterContractId = Address.fromBech32( 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', ).accountId(); // Reading the public state of the counter contract from testnet, - // and importing it into the WebClient - let counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - await client.importAccountById(counterContractId); - await client.syncState(); - counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - throw new Error(`Account not found after import: ${counterContractId}`); - } - } + // and importing it into the client + const counterContractAccount = + await client.accounts.getOrImport(counterContractId); - const builder = client.createCodeBuilder(); const counterSlotName = 'miden::tutorials::counter'; - const counterStorageSlot = StorageSlot.emptyValue(counterSlotName); - const counterComponentCode = - builder.compileAccountComponentCode(counterContractCode); - const counterAccountComponent = AccountComponent.compile( - counterComponentCode, - [counterStorageSlot], - ).withSupportsAllTypes(); + // Compile the counter component + const counterAccountComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], + }); const walletSeed = new Uint8Array(32); crypto.getRandomValues(walletSeed); - const secretKey = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const authComponent = - AccountComponent.createAuthComponentFromSecretKey(secretKey); - - const accountBuilderResult = new AccountBuilder(walletSeed) - .accountType(AccountType.RegularAccountImmutableCode) - .storageMode(AccountStorageMode.public()) - .withAuthComponent(authComponent) - .withComponent(counterAccountComponent) - .build(); + const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed); - await client.addAccountSecretKeyToWebStore( - accountBuilderResult.account.id(), - secretKey, - ); - await client.newAccount(accountBuilderResult.account, false); - - await client.syncState(); - - const accountCodeLib = builder.buildLibrary( - 'external_contract::counter_contract', - counterContractCode, - ); + // Create the counter contract account + const account = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: walletSeed, + auth, + components: [counterAccountComponent], + }); - builder.linkDynamicLibrary(accountCodeLib); + await client.sync(); // Building the transaction script which will call the counter contract const txScriptCode = ` -use external_contract::counter_contract -begin -call.counter_contract::increment_count -end + use external_contract::counter_contract + begin + call.counter_contract::increment_count + end `; - const txScript = builder.compileTxScript(txScriptCode); - const txIncrementRequest = new TransactionRequestBuilder() - .withCustomScript(txScript) - .build(); + const script = await client.compile.txScript({ + code: txScriptCode, + libraries: [ + { + namespace: 'external_contract::counter_contract', + code: counterContractCode, + }, + ], + }); // Executing the transaction script against the counter contract - await client.submitNewTransaction( - counterContractAccount.id(), - txIncrementRequest, - ); + await client.transactions.execute({ + account: account.id(), + script, + }); // Sync state - await client.syncState(); + await client.sync(); // Logging the count of counter contract - const counter = await client.getAccount(counterContractAccount.id()); + const counter = await client.accounts.get(counterContractAccount.id()); // Here we get the first Word from storage of the counter contract // A word is comprised of 4 Felts, 2**64 - 2**32 + 1 @@ -343,6 +321,58 @@ This `NoAuth` component allows any user to interact with the smart contract with **Note**: _Adding the `account::incr_nonce` to a state changing procedure allows any user to call the procedure._ +### Compiling the account component + +Use `client.compile.component()` to compile MASM code and its storage slots into an `AccountComponent`. Each call creates a fresh compiler instance so compilations are fully independent: + +```ts +const counterAccountComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], +}); +``` + +### Creating the contract account + +Use `client.accounts.create()` with `type: AccountType.ImmutableContract` to build and register the contract. You must supply a `seed` (for deterministic ID derivation) and a raw `AuthSecretKey` — the client stores the key automatically: + +```ts +const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed); + +const account = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: walletSeed, + auth, + components: [counterAccountComponent], +}); +``` + +### Compiling and executing the custom script + +Use `client.compile.txScript()` to compile a transaction script. Pass any needed libraries inline — the client links them dynamically: + +```ts +const script = await client.compile.txScript({ + code: txScriptCode, + libraries: [ + { + namespace: 'external_contract::counter_contract', + code: counterContractCode, + }, + ], +}); +``` + +Then execute it with `client.transactions.execute()`: + +```ts +await client.transactions.execute({ + account: account.id(), + script, +}); +``` + ### Custom script This is the Miden assembly script that calls the `increment_count` procedure during the transaction. diff --git a/docs/src/web-client/create_deploy_tutorial.md b/docs/src/web-client/create_deploy_tutorial.md index 80666c2..4594437 100644 --- a/docs/src/web-client/create_deploy_tutorial.md +++ b/docs/src/web-client/create_deploy_tutorial.md @@ -88,6 +88,7 @@ react: { code: `// lib/react/createMintConsume.tsx 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet } from '@miden-sdk/react'; +import { StorageMode } from '@miden-sdk/miden-sdk'; function CreateMintConsumeInner() { .const { isReady } = useMiden(); @@ -123,16 +124,17 @@ export async function createMintConsume(): Promise { .} .// dynamic import → only in the browser, so WASM is loaded client‑side -.const { WebClient } = +.const { MidenClient, AccountType, StorageMode } = ..await import('@miden-sdk/miden-sdk'); .// Connect to Miden testnet RPC endpoint -.const nodeEndpoint = 'https://rpc.testnet.miden.io'; -.const client = await WebClient.createClient(nodeEndpoint); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); .// 1. Sync with the latest blockchain state .// This fetches the latest block header and state commitments -.const state = await client.syncState(); +.const state = await client.sync(); .console.log('Latest block number:', state.blockNum()); .// At this point, your client is connected and synchronized @@ -209,7 +211,7 @@ Back in your library file, extend the function: react: { code: `const run = async () => { .// 1. Create Alice's wallet (public, mutable) .console.log('Creating account for Alice…'); -.const alice = await createWallet({ storageMode: 'public' }); +.const alice = await createWallet({ storageMode: StorageMode.Public }); .console.log('Alice ID:', alice.id().toString()); };` }, typescript: { code: `// lib/createMintConsume.ts @@ -219,24 +221,22 @@ export async function createMintConsume(): Promise { ..return; .} -.const { WebClient, AccountStorageMode, AuthScheme } = await import( -.."@miden-sdk/miden-sdk" -.); +.const { MidenClient } = await import('@miden-sdk/miden-sdk'); -.const nodeEndpoint = 'https://rpc.testnet.miden.io'; -.const client = await WebClient.createClient(nodeEndpoint); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); .// 1. Sync with the latest blockchain state -.const state = await client.syncState(); +.const state = await client.sync(); .console.log('Latest block number:', state.blockNum()); .// 2. Create Alice's account .console.log('Creating account for Alice…'); -.const alice = await client.newWallet( -..AccountStorageMode.public(), // Public: account state is visible on-chain -..true, // Mutable: account code can be upgraded later -..AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 -.); +.const alice = await client.accounts.create({ +..type: AccountType.MutableWallet, // Mutable: account code can be upgraded later +..storage: StorageMode.Public, // Public: account state is visible on-chain +.}); .console.log('Alice ID:', alice.id().toString()); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -254,7 +254,7 @@ const faucet = await createFaucet({ .tokenSymbol: 'MID', // Token symbol (like ETH, BTC, etc.) .decimals: 8, // Decimals (8 means 1 MID = 100,000,000 base units) .maxSupply: BigInt(1_000_000), // Max supply: total tokens that can ever be minted -.storageMode: 'public', // Public: faucet operations are transparent +.storageMode: StorageMode.Public, // Public: faucet operations are transparent }); console.log('Faucet account ID:', faucet.id().toString()); @@ -262,14 +262,13 @@ console.log('Setup complete.');`}, typescript: { code:`// 3. Deploy a fungible faucet // A faucet is an account that can mint new tokens console.log('Creating faucet…'); -const faucetAccount = await client.newFaucet( -.AccountStorageMode.public(), // Public: faucet operations are transparent -.false, // Immutable: faucet rules cannot be changed -."MID", // Token symbol (like ETH, BTC, etc.) -.8, // Decimals (8 means 1 MID = 100,000,000 base units) -.BigInt(1_000_000), // Max supply: total tokens that can ever be minted -.AuthScheme.AuthRpoFalcon512 // Auth Scheme: RPO Falcon 512 -); +const faucetAccount = await client.accounts.create({ +.type: AccountType.FungibleFaucet, // Fungible faucet: can mint divisible tokens +.symbol: 'MID', // Token symbol (like ETH, BTC, etc.) +.decimals: 8, // Decimals (8 means 1 MID = 100,000,000 base units) +.maxSupply: BigInt(1_000_000), // Max supply: total tokens that can ever be minted +.storage: StorageMode.Public, // Public: faucet operations are transparent +}); console.log('Faucet account ID:', faucetAccount.id().toString()); console.log('Setup complete.');` }, @@ -300,6 +299,7 @@ Your final `lib/react/createMintConsume.tsx` (React) or `lib/createMintConsume.t react: { code: `'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet } from '@miden-sdk/react'; +import { StorageMode } from '@miden-sdk/miden-sdk'; function CreateMintConsumeInner() { .const { isReady } = useMiden(); @@ -309,7 +309,7 @@ function CreateMintConsumeInner() { .const run = async () => { ..// 1. Create Alice's wallet (public, mutable) ..console.log('Creating account for Alice…'); -..const alice = await createWallet({ storageMode: 'public' }); +..const alice = await createWallet({ storageMode: StorageMode.Public }); ..console.log('Alice ID:', alice.id().toString()); ..// 2. Deploy a fungible faucet @@ -318,7 +318,7 @@ function CreateMintConsumeInner() { ...tokenSymbol: 'MID', ...decimals: 8, ...maxSupply: BigInt(1_000_000), -...storageMode: 'public', +...storageMode: StorageMode.Public, ..}); ..console.log('Faucet ID:', faucet.id().toString()); @@ -349,35 +349,34 @@ export async function createMintConsume(): Promise { .} .// dynamic import → only in the browser, so WASM is loaded client‑side -.const { WebClient, AccountStorageMode, AuthScheme } = +.const { MidenClient, AccountType, StorageMode } = ..await import('@miden-sdk/miden-sdk'); -.const nodeEndpoint = 'https://rpc.testnet.miden.io'; -.const client = await WebClient.createClient(nodeEndpoint); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); .// 1. Sync with the latest blockchain state -.const state = await client.syncState(); +.const state = await client.sync(); .console.log('Latest block number:', state.blockNum()); .// 2. Create Alice's account .console.log('Creating account for Alice…'); -.const alice = await client.newWallet( -..AccountStorageMode.public(), -..true, -..AuthScheme.AuthRpoFalcon512, -.); +.const alice = await client.accounts.create({ +..type: AccountType.MutableWallet, +..storage: StorageMode.Public, +.}); .console.log('Alice ID:', alice.id().toString()); .// 3. Deploy a fungible faucet .console.log('Creating faucet…'); -.const faucet = await client.newFaucet( -..AccountStorageMode.public(), -..false, -..'MID', -..8, -..BigInt(1_000_000), -..AuthScheme.AuthRpoFalcon512, -.); +.const faucet = await client.accounts.create({ +..type: AccountType.FungibleFaucet, +..symbol: 'MID', +..decimals: 8, +..maxSupply: BigInt(1_000_000), +..storage: StorageMode.Public, +.}); .console.log('Faucet ID:', faucet.id().toString()); .console.log('Setup complete.'); diff --git a/docs/src/web-client/creating_multiple_notes_tutorial.md b/docs/src/web-client/creating_multiple_notes_tutorial.md index 08ed894..b9e2e77 100644 --- a/docs/src/web-client/creating_multiple_notes_tutorial.md +++ b/docs/src/web-client/creating_multiple_notes_tutorial.md @@ -39,7 +39,7 @@ _How does it work?_ When a user choses to use delegated proving, they send off a Anyone can run their own delegated prover server. If you are building a product on Miden, it may make sense to run your own delegated prover server for your users. To run your own delegated proving server, follow the instructions here: https://crates.io/crates/miden-proving-service -The code below uses `submitNewTransaction`, which handles proving via the network's delegated +The code below uses `client.transactions.submit()`, which handles proving via the network's delegated proving service. This means your browser never has to generate the full ZK proof locally. ## Step 1: Initialize your Next.js project @@ -143,6 +143,7 @@ mkdir -p lib react: { code: `'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useMultiSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function MultiSendInner() { .const { isReady } = useMiden(); @@ -179,23 +180,20 @@ export default function MultiSendWithDelegatedProver() { .if (typeof window === 'undefined') return console.warn('Run in browser'); .const { -..WebClient, -..AccountStorageMode, -..AuthScheme, -..Address, -..NoteType, -..Note, -..NoteAssets, +..MidenClient, +..AccountType, +..NoteVisibility, +..StorageMode, +..createP2IDNote, ..OutputNoteArray, -..NoteAttachment, -..FungibleAsset, ..TransactionRequestBuilder, -..OutputNote, .} = await import('@miden-sdk/miden-sdk'); -.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); -.console.log('Latest block:', (await client.syncState()).blockNum()); +.console.log('Latest block:', (await client.sync()).blockNum()); }` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -206,7 +204,7 @@ Add the code snippet below to the function. This code creates a wallet and fauce n.inputNoteRecord().id().toString()); +const noteIds = notes.map((n) => n.inputNoteRecord().id()); await consume({ accountId: aliceId, noteIds });`}, typescript: { code:`// ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); -const alice = await client.newWallet( -.AccountStorageMode.public(), -.true, -.AuthScheme.AuthRpoFalcon512, -); +const alice = await client.accounts.create({ +.type: AccountType.MutableWallet, +.storage: StorageMode.Public, +}); console.log('Alice account ID:', alice.id().toString()); // ── Creating new faucet ────────────────────────────────────────────────────── -const faucet = await client.newFaucet( -.AccountStorageMode.public(), -.false, -.'MID', -.8, -.BigInt(1_000_000), -.AuthScheme.AuthRpoFalcon512, -); +const faucet = await client.accounts.create({ +.type: AccountType.FungibleFaucet, +.symbol: 'MID', +.decimals: 8, +.maxSupply: BigInt(1_000_000), +.storage: StorageMode.Public, +}); console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -await client.submitNewTransaction( -.faucet.id(), -.client.newMintTransactionRequest( -..alice.id(), -..faucet.id(), -..NoteType.Public, -..BigInt(10_000), -.), -); +const mintTxId = await client.transactions.mint({ +.account: faucet, +.to: alice, +.amount: BigInt(10_000), +.type: NoteVisibility.Public, +}); console.log('waiting for settlement'); -await new Promise((r) => setTimeout(r, 7_000)); -await client.syncState(); +await client.transactions.waitFor(mintTxId); +await client.sync(); // ── consume the freshly minted notes ────────────────────────────────────────────── -const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -.rec.inputNoteRecord().toNote(), -); - -await client.submitNewTransaction( -.alice.id(), -.client.newConsumeTransactionRequest(noteList), -);` }, +const noteList = await client.notes.listAvailable({ account: alice }); +await client.transactions.consume({ +.account: alice, +.notes: noteList.map((n) => n.inputNoteRecord()), +});` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> ## Step 5 — Build and Create P2ID notes @@ -295,7 +285,7 @@ await sendMany({ ..{ to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) }, ..{ to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) }, .], -.noteType: 'public', +.noteType: NoteVisibility.Public, }); console.log('All notes created ✅');`}, @@ -306,25 +296,19 @@ const recipientAddresses = [ .'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', ]; -const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); - -const p2idNotes = recipientAddresses.map((addr) => { -.const receiverAccountId = Address.fromBech32(addr).accountId(); -.const note = Note.createP2IDNote( -..alice.id(), -..receiverAccountId, -..assets, -..NoteType.Public, -..new NoteAttachment(), -.); - -.return OutputNote.full(note); -}); +const p2idNotes = recipientAddresses.map((addr) => +.createP2IDNote({ +..from: alice, +..to: addr, +..assets: { token: faucet, amount: BigInt(100) }, +..type: NoteVisibility.Public, +.}), +); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── const builder = new TransactionRequestBuilder(); const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); -await client.submitNewTransaction(alice.id(), txRequest); +await client.transactions.submit(alice, txRequest); console.log('All notes created ✅');` }, }} reactFilename="lib/react/multiSendWithDelegatedProver.tsx" tsFilename="lib/multiSendWithDelegatedProver.ts" /> @@ -337,6 +321,7 @@ Your library file should now look like this: react: { code: `'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useMultiSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function MultiSendInner() { .const { isReady } = useMiden(); @@ -351,7 +336,7 @@ function MultiSendInner() { .const run = async () => { ..// 1. Create Alice's wallet ..console.log('Creating account for Alice…'); -..const alice = await createWallet({ storageMode: 'public' }); +..const alice = await createWallet({ storageMode: StorageMode.Public }); ..const aliceId = alice.id().toString(); ..console.log('Alice account ID:', aliceId); @@ -360,7 +345,7 @@ function MultiSendInner() { ...tokenSymbol: 'MID', ...decimals: 8, ...maxSupply: BigInt(1_000_000), -...storageMode: 'public', +...storageMode: StorageMode.Public, ..}); ..const faucetId = faucet.id().toString(); ..console.log('Faucet ID:', faucetId); @@ -370,7 +355,7 @@ function MultiSendInner() { ...faucetId, ...targetAccountId: aliceId, ...amount: BigInt(10_000), -...noteType: 'public', +...noteType: NoteVisibility.Public, ..}); ..console.log('Waiting for settlement…'); @@ -390,7 +375,7 @@ function MultiSendInner() { ....{ to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) }, ....{ to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) }, ...], -...noteType: 'public', +...noteType: NoteVisibility.Public, ..}); ..console.log('All notes created ✅'); @@ -423,68 +408,57 @@ export async function multiSendWithDelegatedProver(): Promise { .if (typeof window === 'undefined') return console.warn('Run in browser'); .const { -..WebClient, -..AccountStorageMode, -..AuthScheme, -..Address, -..NoteType, -..Note, -..NoteAssets, +..MidenClient, +..AccountType, +..NoteVisibility, +..StorageMode, +..createP2IDNote, ..OutputNoteArray, -..FungibleAsset, -..NoteAttachment, ..TransactionRequestBuilder, -..OutputNote, .} = await import('@miden-sdk/miden-sdk'); -.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); -.console.log('Latest block:', (await client.syncState()).blockNum()); +.console.log('Latest block:', (await client.sync()).blockNum()); .// ── Creating new account ────────────────────────────────────────────────────── .console.log('Creating account for Alice…'); -.const alice = await client.newWallet( -..AccountStorageMode.public(), -..true, -..AuthScheme.AuthRpoFalcon512, -.); +.const alice = await client.accounts.create({ +..type: AccountType.MutableWallet, +..storage: StorageMode.Public, +.}); .console.log('Alice account ID:', alice.id().toString()); .// ── Creating new faucet ────────────────────────────────────────────────────── -.const faucet = await client.newFaucet( -..AccountStorageMode.public(), -..false, -..'MID', -..8, -..BigInt(1_000_000), -..AuthScheme.AuthRpoFalcon512, -.); +.const faucet = await client.accounts.create({ +..type: AccountType.FungibleFaucet, +..symbol: 'MID', +..decimals: 8, +..maxSupply: BigInt(1_000_000), +..storage: StorageMode.Public, +.}); .console.log('Faucet ID:', faucet.id().toString()); .// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -.await client.submitNewTransaction( -..faucet.id(), -..client.newMintTransactionRequest( -...alice.id(), -...faucet.id(), -...NoteType.Public, -...BigInt(10_000), -..), -.); +.const mintTxId = await client.transactions.mint({ +..account: faucet, +..to: alice, +..amount: BigInt(10_000), +..type: NoteVisibility.Public, +.}); .console.log('waiting for settlement'); -.await new Promise((r) => setTimeout(r, 7_000)); -.await client.syncState(); +.await client.transactions.waitFor(mintTxId); +.await client.sync(); .// ── consume the freshly minted notes ────────────────────────────────────────────── -.const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -..rec.inputNoteRecord().toNote(), -.); - -.await client.submitNewTransaction( -..alice.id(), -..client.newConsumeTransactionRequest(noteList), -.); +.const noteList = await client.notes.listAvailable({ account: alice }); +.await client.transactions.consume({ +..account: alice, +..notes: noteList.map((n) => n.inputNoteRecord()), +.}); .// ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── .const recipientAddresses = [ @@ -493,25 +467,19 @@ export async function multiSendWithDelegatedProver(): Promise { ..'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', .]; -.const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); - -.const p2idNotes = recipientAddresses.map((addr) => { -..const receiverAccountId = Address.fromBech32(addr).accountId(); -..const note = Note.createP2IDNote( -...alice.id(), -...receiverAccountId, -...assets, -...NoteType.Public, -...new NoteAttachment(), -..); - -..return OutputNote.full(note); -.}); +.const p2idNotes = recipientAddresses.map((addr) => +..createP2IDNote({ +...from: alice, +...to: addr, +...assets: { token: faucet, amount: BigInt(100) }, +...type: NoteVisibility.Public, +..}), +.); .// ── create all P2ID notes ─────────────────────────────────────────────────────────────── .const builder = new TransactionRequestBuilder(); .const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); -.await client.submitNewTransaction(alice.id(), txRequest); +.await client.transactions.submit(alice, txRequest); .console.log('All notes created ✅'); }` }, diff --git a/docs/src/web-client/foreign_procedure_invocation_tutorial.md b/docs/src/web-client/foreign_procedure_invocation_tutorial.md index 1998a78..ed82799 100644 --- a/docs/src/web-client/foreign_procedure_invocation_tutorial.md +++ b/docs/src/web-client/foreign_procedure_invocation_tutorial.md @@ -132,23 +132,17 @@ export async function foreignProcedureInvocation(): Promise { // dynamic import → only in the browser, so WASM is loaded client‑side const { - AccountBuilder, - AccountComponent, - Address, AccountType, + Address, AuthSecretKey, + StorageMode, StorageSlot, - TransactionRequestBuilder, - ForeignAccount, - ForeignAccountArray, - AccountStorageRequirements, - WebClient, - AccountStorageMode, + MidenClient, } = await import('@miden-sdk/miden-sdk'); const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); - console.log('Current block number: ', (await client.syncState()).blockNum()); + const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); + console.log('Current block number: ', (await client.sync()).blockNum()); // ------------------------------------------------------------------------- // STEP 1: Create the Count Reader Contract @@ -169,7 +163,7 @@ export async function foreignProcedureInvocation(): Promise { pub proc copy_count exec.tx::execute_foreign_procedure # => [count] - + push.COUNT_READER_SLOT[0..2] # [slot_id_prefix, slot_id_suffix, count] @@ -187,63 +181,44 @@ export async function foreignProcedureInvocation(): Promise { const countReaderSlotName = 'miden::tutorials::count_reader'; const counterSlotName = 'miden::tutorials::counter'; - const builder = client.createCodeBuilder(); - const countReaderComponentCode = - builder.compileAccountComponentCode(countReaderCode); - const countReaderComponent = AccountComponent.compile( - countReaderComponentCode, - [StorageSlot.emptyValue(countReaderSlotName)], - ).withSupportsAllTypes(); + // Compile the count reader component + const countReaderComponent = await client.compile.component({ + code: countReaderCode, + slots: [StorageSlot.emptyValue(countReaderSlotName)], + }); const walletSeed = new Uint8Array(32); crypto.getRandomValues(walletSeed); - const secretKey = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const authComponent = - AccountComponent.createAuthComponentFromSecretKey(secretKey); + const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const countReaderContract = new AccountBuilder(walletSeed) - .accountType(AccountType.RegularAccountImmutableCode) - .storageMode(AccountStorageMode.public()) - .withAuthComponent(authComponent) - .withComponent(countReaderComponent) - .build(); - - await client.addAccountSecretKeyToWebStore( - countReaderContract.account.id(), - secretKey, - ); - await client.syncState(); - - // Create the count reader contract account (using available WebClient API) + // Create the count reader contract account console.log('Creating count reader contract account...'); - console.log( - 'Count reader contract ID:', - countReaderContract.account.id().toString(), - ); + const countReaderAccount = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: walletSeed, + auth, + components: [countReaderComponent], + }); + + console.log('Count reader contract ID:', countReaderAccount.id().toString()); - await client.newAccount(countReaderContract.account, false); + await client.sync(); // ------------------------------------------------------------------------- // STEP 2: Build & Get State of the Counter Contract // ------------------------------------------------------------------------- console.log('\n[STEP 2] Building counter contract from public state'); - // Define the Counter Contract account id from counter contract deploy (same as Rust) + // Define the Counter Contract account id from counter contract deploy const counterContractId = Address.fromBech32( 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', ).accountId(); // Import the counter contract - let counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - await client.importAccountById(counterContractId); - await client.syncState(); - counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - throw new Error(`Account not found after import: ${counterContractId}`); - } - } + const counterContractAccount = + await client.accounts.getOrImport(counterContractId); console.log( 'Account storage slot:', counterContractAccount.storage().getItem(counterSlotName)?.toHex(), @@ -292,18 +267,16 @@ export async function foreignProcedureInvocation(): Promise { end `; - // Create the counter contract component to get the procedure hash (following Rust pattern) - const counterContractComponentCode = - builder.compileAccountComponentCode(counterContractCode); - const counterContractComponent = AccountComponent.compile( - counterContractComponentCode, - [StorageSlot.emptyValue(counterSlotName)], - ).withSupportsAllTypes(); + // Compile the counter contract component to get the procedure hash + const counterContractComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], + }); const getCountProcHash = counterContractComponent.getProcedureHash('get_count'); - // Build the script that calls the count reader contract (exactly from reader_script.masm with replacements) + // Build the script that calls the count reader contract const fpiScriptCode = ` use external_contract::count_reader_contract use miden::core::sys @@ -327,44 +300,33 @@ export async function foreignProcedureInvocation(): Promise { end `; - // Create the library for the count reader contract - const countReaderLib = builder.buildLibrary( - 'external_contract::count_reader_contract', - countReaderCode, - ); - builder.linkDynamicLibrary(countReaderLib); - // Compile the transaction script with the count reader library - const txScript = builder.compileTxScript(fpiScriptCode); - - // foreign account - const storageRequirements = new AccountStorageRequirements(); - const foreignAccount = ForeignAccount.public( - counterContractId, - storageRequirements, - ); - - // Build a transaction request with the custom script - const txRequest = new TransactionRequestBuilder() - .withCustomScript(txScript) - .withForeignAccounts(new ForeignAccountArray([foreignAccount])) - .build(); - - // Execute the transaction on the count reader contract and send it to the network (following Rust pattern) - const txResult = await client.submitNewTransaction( - countReaderContract.account.id(), - txRequest, - ); + const script = await client.compile.txScript({ + code: fpiScriptCode, + libraries: [ + { + namespace: 'external_contract::count_reader_contract', + code: countReaderCode, + }, + ], + }); + + // Execute the transaction on the count reader contract and send it to the network + const txId = await client.transactions.execute({ + account: countReaderAccount.id(), + script, + foreignAccounts: [{ id: counterContractId }], + }); console.log( 'View transaction on MidenScan: https://testnet.midenscan.com/tx/' + - txResult.toHex(), + txId.toHex(), ); - await client.syncState(); + await client.sync(); - // Retrieve updated contract data to see the results (following Rust pattern) - const updatedCounterContract = await client.getAccount( + // Retrieve updated contract data to see the results + const updatedCounterContract = await client.accounts.get( counterContractAccount.id(), ); console.log( @@ -372,8 +334,8 @@ export async function foreignProcedureInvocation(): Promise { updatedCounterContract?.storage().getItem(counterSlotName)?.toHex(), ); - const updatedCountReaderContract = await client.getAccount( - countReaderContract.account.id(), + const updatedCountReaderContract = await client.accounts.get( + countReaderAccount.id(), ); console.log( 'count reader contract storage:', @@ -423,27 +385,6 @@ Count reader contract ID: 0x90128b4e27f34500000720bedaa49b Account storage slot: 0x0000000000000000000000000000000000000000000000001200000000000000 [STEP 3] Call counter contract with FPI from count reader contract -fpiScript - use external_contract::count_reader_contract - use miden::core::sys - - begin - push.0x92495ca54d519eb5e4ba22350f837904d3895e48d74d8079450f19574bb84cb6 - # => [GET_COUNT_HASH] - - push.297741160627968 - # => [account_id_suffix, GET_COUNT_HASH] - - push.12911083037950619392 - # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] - - call.count_reader_contract::copy_count - # => [] - - exec.sys::truncate_stack - # => [] - - end View transaction on MidenScan: https://testnet.midenscan.com/tx/0xffff3dc5454154d1ccf64c1ad170bdef2df471c714f6fe6ab542d060396b559f counter contract storage: 0x0000000000000000000000000000000000000000000000001200000000000000 count reader contract storage: 0x0000000000000000000000000000000000000000000000001200000000000000 @@ -532,38 +473,43 @@ This script: ### Getting Procedure Hashes -In the WebClient, we get the procedure hash using the [`getProcedureHash`](https://github.com/0xMiden/miden-tutorials/blob/7bfa1996979cbb221b8cab455596093535787784/web-client/lib/foreignProcedureInvocation.ts#L176) method: +Compile the counter contract component using `client.compile.component()` and call `getProcedureHash()` to obtain the hash needed by the FPI script: ```ts -let getCountProcHash = counterContractComponent.getProcedureHash('get_count'); +const counterContractComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], +}); + +const getCountProcHash = counterContractComponent.getProcedureHash('get_count'); ``` -### Foreign Accounts +### Compiling the Transaction Script with a Library -To execute foreign procedure calls, we need to specify the foreign account in our transaction request: +Use `client.compile.txScript()` and pass the count reader library inline. The library is linked dynamically so the script can call its procedures: ```ts -let foreignAccount = ForeignAccount.public( - counterContractId, - storageRequirements, -); - -let txRequest = new TransactionRequestBuilder() - .withCustomScript(txScript) - .withForeignAccounts(new ForeignAccountArray([foreignAccount])) - .build(); +const script = await client.compile.txScript({ + code: fpiScriptCode, + libraries: [ + { + namespace: 'external_contract::count_reader_contract', + code: countReaderCode, + }, + ], +}); ``` -### Account Component Libraries +### Foreign Accounts -We create a library for the count reader contract so our transaction script can call its procedures: +Pass the foreign account directly in the `execute()` call using the `foreignAccounts` option. The client creates the `ForeignAccount` and `AccountStorageRequirements` internally — no manual construction needed: ```ts -const countReaderLib = builder.buildLibrary( - 'external_contract::count_reader_contract', - countReaderCode, -); -builder.linkDynamicLibrary(countReaderLib); +const txId = await client.transactions.execute({ + account: countReaderAccount.id(), + script, + foreignAccounts: [{ id: counterContractId }], +}); ``` ## Summary diff --git a/docs/src/web-client/mint_consume_create_tutorial.md b/docs/src/web-client/mint_consume_create_tutorial.md index 81c03c7..7204035 100644 --- a/docs/src/web-client/mint_consume_create_tutorial.md +++ b/docs/src/web-client/mint_consume_create_tutorial.md @@ -45,56 +45,51 @@ const mintResult = await mint({ .faucetId, // Faucet account (who mints the tokens) .targetAccountId: aliceId, // Target account (who receives the tokens) .amount: BigInt(1000), // Amount to mint (in base units) -.noteType: 'public', // Note visibility (public = onchain) +.noteType: NoteVisibility.Public, // Note visibility (public = onchain) }); console.log('Mint tx:', mintResult.transactionId); // Wait for the mint transaction to be committed await waitForCommit(mintResult.transactionId);`}, typescript: { code:`// 4. Mint tokens from the faucet to Alice -await client.syncState(); +await client.sync(); console.log("Minting tokens to Alice..."); -const mintTxRequest = client.newMintTransactionRequest( -.alice.id(), // Target account (who receives the tokens) -.faucet.id(), // Faucet account (who mints the tokens) -.NoteType.Public, // Note visibility (public = onchain) -.BigInt(1000), // Amount to mint (in base units) -); - -await client.submitNewTransaction(faucet.id(), mintTxRequest); +const mintTxId = await client.transactions.mint({ +.account: faucet, // Faucet account (who mints the tokens) +.to: alice, // Target account (who receives the tokens) +.amount: BigInt(1000), // Amount to mint (in base units) +.type: NoteVisibility.Public, // Note visibility (public = onchain) +}); // Wait for the transaction to be processed -console.log("Waiting 10 seconds for transaction confirmation..."); -await new Promise((resolve) => setTimeout(resolve, 10000)); -await client.syncState();` }, +console.log("Waiting for transaction confirmation..."); +await client.transactions.waitFor(mintTxId); +await client.sync();` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> ### What's happening here? -1. **newMintTransactionRequest**: Creates a request to mint tokens 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). -2. **newTransaction**: Locally executes and proves the transaction. -3. **submitTransaction**: Sends the transaction to the network. -4. Wait 10 seconds for the transaction to be included in a block. +1. **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). +2. **client.transactions.waitFor()**: Polls until the transaction is committed on-chain. ## Step 2: Find consumable notes After minting, Alice has a note waiting for her but the tokens aren't in her account yet. -To identify notes that are ready to consume, the Miden WebClient provides the `getConsumableNotes` function: +To identify notes that are ready to consume, the MidenClient provides the `client.notes.listAvailable()` method: n.inputNoteRecord().id().toString()); -console.log('Consumable notes:', noteIds);` }, +const noteIds = notes.map((n) => n.inputNoteRecord().id()); +console.log('Consumable notes:', noteIds.length);` }, typescript: { code: `// 5. Find notes available for consumption -const mintedNotes = await client.getConsumableNotes(alice.id()); +const mintedNotes = await client.notes.listAvailable({ account: alice }); console.log(\`Found \${mintedNotes.length} note(s) to consume\`); -const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); console.log( .'Minted notes:', -.mintedNoteList.map((note) => note.id().toString()), +.mintedNotes.map((n) => n.inputNoteRecord().id().toString()), );` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -109,11 +104,12 @@ await consume({ accountId: aliceId, noteIds }); console.log('Notes consumed.');` }, typescript: { code: `// 6. Consume the notes to add tokens to Alice's balance console.log('Consuming minted notes...'); -const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); - -await client.submitNewTransaction(alice.id(), consumeTxRequest); +await client.transactions.consume({ +.account: alice, +.notes: mintedNotes.map((n) => n.inputNoteRecord()), +}); -await client.syncState(); +await client.sync(); console.log('Notes consumed.');` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -134,24 +130,20 @@ await send({ .to: bobAddress, .assetId: faucetId, .amount: BigInt(100), -.noteType: 'public', +.noteType: NoteVisibility.Public, }); console.log('Tokens sent successfully!');` }, typescript: { code: `// 7. Send tokens from Alice to Bob -const bobAccountId = Address.fromBech32( -.'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', -).accountId(); +const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; console.log("Sending tokens to Bob's account..."); -const sendTxRequest = client.newSendTransactionRequest( -.alice.id(), // Sender account ID -.bobAccountId, // Recipient account ID -.faucet.id(), // Asset ID (faucet that created the tokens) -.NoteType.Public, // Note visibility -.BigInt(100), // Amount to send -); - -await client.submitNewTransaction(alice.id(), sendTxRequest); +await client.transactions.send({ +.account: alice, // Sender account ID +.to: bobAddress, // Recipient (bech32 address) +.token: faucet, // Asset ID (faucet that created the tokens) +.amount: BigInt(100), // Amount to send +.type: NoteVisibility.Public, // Note visibility +}); console.log('Tokens sent successfully!');` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> @@ -172,6 +164,7 @@ Here's the complete `lib/react/createMintConsume.tsx` (React) or `lib/createMint react: { code: `'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function CreateMintConsumeInner() { .const { isReady } = useMiden(); @@ -186,7 +179,7 @@ function CreateMintConsumeInner() { .const run = async () => { ..// 1. Create Alice's wallet (public, mutable) ..console.log('Creating account for Alice…'); -..const alice = await createWallet({ storageMode: 'public' }); +..const alice = await createWallet({ storageMode: StorageMode.Public }); ..const aliceId = alice.id().toString(); ..console.log('Alice ID:', aliceId); @@ -196,7 +189,7 @@ function CreateMintConsumeInner() { ...tokenSymbol: 'MID', ...decimals: 8, ...maxSupply: BigInt(1_000_000), -...storageMode: 'public', +...storageMode: StorageMode.Public, ..}); ..const faucetId = faucet.id().toString(); ..console.log('Faucet ID:', faucetId); @@ -207,7 +200,7 @@ function CreateMintConsumeInner() { ...faucetId, ...targetAccountId: aliceId, ...amount: BigInt(1000), -...noteType: 'public', +...noteType: NoteVisibility.Public, ..}); ..console.log('Mint tx:', mintResult.transactionId); @@ -216,8 +209,8 @@ function CreateMintConsumeInner() { ..// 5. Wait for consumable notes to appear ..const notes = await waitForConsumableNotes({ accountId: aliceId }); -..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); -..console.log('Consumable notes:', noteIds); +..const noteIds = notes.map((n) => n.inputNoteRecord().id()); +..console.log('Consumable notes:', noteIds.length); ..// 6. Consume minted notes ..console.log('Consuming minted notes...'); @@ -232,7 +225,7 @@ function CreateMintConsumeInner() { ...to: bobAddress, ...assetId: faucetId, ...amount: BigInt(100), -...noteType: 'public', +...noteType: NoteVisibility.Public, ..}); ..console.log('Tokens sent successfully!'); .}; @@ -261,90 +254,78 @@ export async function createMintConsume(): Promise { .} .// dynamic import → only in the browser, so WASM is loaded client‑side -.const { WebClient, AccountStorageMode, AuthScheme, NoteType, Address } = -..await import('@miden-sdk/miden-sdk'); +.const { MidenClient, AccountType, NoteVisibility, StorageMode } = await import('@miden-sdk/miden-sdk'); -.const nodeEndpoint = 'https://rpc.testnet.miden.io'; -.const client = await WebClient.createClient(nodeEndpoint); +.const client = await MidenClient.create({ +..rpcUrl: 'https://rpc.testnet.miden.io', +.}); .// 1. Sync with the latest blockchain state -.const state = await client.syncState(); +.const state = await client.sync(); .console.log('Latest block number:', state.blockNum()); .// 2. Create Alice's account .console.log('Creating account for Alice…'); -.const aliceSeed = new Uint8Array(32); -.crypto.getRandomValues(aliceSeed); -.const alice = await client.newWallet( -..AccountStorageMode.public(), -..true, -..AuthScheme.AuthRpoFalcon512, -..aliceSeed, -.); +.const alice = await client.accounts.create({ +..type: AccountType.MutableWallet, +..storage: StorageMode.Public, +.}); .console.log('Alice ID:', alice.id().toString()); .// 3. Deploy a fungible faucet .console.log('Creating faucet…'); -.const faucet = await client.newFaucet( -..AccountStorageMode.public(), -..false, -..'MID', -..8, -..BigInt(1_000_000), -..AuthScheme.AuthRpoFalcon512, -.); +.const faucet = await client.accounts.create({ +..type: AccountType.FungibleFaucet, +..symbol: 'MID', +..decimals: 8, +..maxSupply: BigInt(1_000_000), +..storage: StorageMode.Public, +.}); .console.log('Faucet ID:', faucet.id().toString()); -.await client.syncState(); +.await client.sync(); .// 4. Mint tokens to Alice -.await client.syncState(); .console.log('Minting tokens to Alice...'); -.const mintTxRequest = client.newMintTransactionRequest( -..alice.id(), -..faucet.id(), -..NoteType.Public, -..BigInt(1000), -.); - -.await client.submitNewTransaction(faucet.id(), mintTxRequest); +.const mintTxId = await client.transactions.mint({ +..account: faucet, +..to: alice, +..amount: BigInt(1000), +..type: NoteVisibility.Public, +.}); -.console.log('Waiting 10 seconds for transaction confirmation...'); -.await new Promise((resolve) => setTimeout(resolve, 10000)); -.await client.syncState(); +.console.log('Waiting for transaction confirmation...'); +.await client.transactions.waitFor(mintTxId); +.await client.sync(); .// 5. Fetch minted notes -.const mintedNotes = await client.getConsumableNotes(alice.id()); -.const mintedNoteList = mintedNotes.map((n) => n.inputNoteRecord().toNote()); +.const mintedNotes = await client.notes.listAvailable({ account: alice }); .console.log( ..'Minted notes:', -..mintedNoteList.map((note) => note.id().toString()), +..mintedNotes.map((n) => n.inputNoteRecord().id().toString()), .); .// 6. Consume minted notes .console.log('Consuming minted notes...'); -.const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); +.await client.transactions.consume({ +..account: alice, +..notes: mintedNotes.map((n) => n.inputNoteRecord()), +.}); -.await client.submitNewTransaction(alice.id(), consumeTxRequest); - -.await client.syncState(); +.await client.sync(); .console.log('Notes consumed.'); .// 7. Send tokens to Bob -.const bobAccountId = Address.fromBech32( -..'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', -.).accountId(); +.const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; .console.log("Sending tokens to Bob's account..."); -.const sendTxRequest = client.newSendTransactionRequest( -..alice.id(), -..bobAccountId, -..faucet.id(), -..NoteType.Public, -..BigInt(100), -.); - -.await client.submitNewTransaction(alice.id(), sendTxRequest); +.await client.transactions.send({ +..account: alice, +..to: bobAddress, +..token: faucet, +..amount: BigInt(100), +..type: NoteVisibility.Public, +.}); .console.log('Tokens sent successfully!'); }` }, }} reactFilename="lib/react/createMintConsume.tsx" tsFilename="lib/createMintConsume.ts" /> diff --git a/docs/src/web-client/react_wallet_tutorial.md b/docs/src/web-client/react_wallet_tutorial.md index 24bfc0b..7fc2a08 100644 --- a/docs/src/web-client/react_wallet_tutorial.md +++ b/docs/src/web-client/react_wallet_tutorial.md @@ -324,6 +324,7 @@ The `useSend()` hook provides a function to send tokens to other accounts. ```tsx import { useState, type ChangeEvent } from 'react'; import { useSend, parseAssetAmount } from '@miden-sdk/react'; +import { NoteVisibility } from '@miden-sdk/miden-sdk'; function SendForm({ accountId, @@ -336,7 +337,9 @@ function SendForm({ const [to, setTo] = useState(''); const [assetId, setAssetId] = useState(assets[0]?.assetId ?? ''); const [amount, setAmount] = useState(''); - const [noteType, setNoteType] = useState<'private' | 'public'>('private'); + const [noteType, setNoteType] = useState( + NoteVisibility.Private, + ); const selectedAsset = assets.find((asset) => asset.assetId === assetId); const selectedDecimals = selectedAsset?.decimals; @@ -462,6 +465,7 @@ import { useConsume, useSend, } from '@miden-sdk/react'; +import { NoteVisibility } from '@miden-sdk/miden-sdk'; const Panel = ({ title, children }: { title: string; children: ReactNode }) => (
@@ -507,7 +511,9 @@ function Wallet({ accountId }: { accountId: string }) { const [to, setTo] = useState(''); const [assetId, setAssetId] = useState(''); const [amount, setAmount] = useState(''); - const [noteType, setNoteType] = useState<'private' | 'public'>('private'); + const [noteType, setNoteType] = useState( + NoteVisibility.Private, + ); const defaultAssetId = assets[0]?.assetId; const selectedAsset = assets.find((asset) => asset.assetId === assetId); const selectedDecimals = selectedAsset?.decimals; diff --git a/docs/src/web-client/unauthenticated_note_how_to.md b/docs/src/web-client/unauthenticated_note_how_to.md index c574355..bb0f7bf 100644 --- a/docs/src/web-client/unauthenticated_note_how_to.md +++ b/docs/src/web-client/unauthenticated_note_how_to.md @@ -162,7 +162,8 @@ Copy and paste the following code into `lib/react/unauthenticatedNoteTransfer.ts { ..// 1. Create Alice and 5 wallets for the transfer chain ..console.log('Creating accounts…'); -..const alice = await createWallet({ storageMode: 'public' }); -..const aliceId = alice.id().toString(); -..console.log('Alice account ID:', aliceId); +..const alice = await createWallet({ storageMode: StorageMode.Public }); +..console.log('Alice account ID:', alice.id().toString()); -..const walletIds: string[] = []; +..const wallets = []; ..for (let i = 0; i < 5; i++) { -...const wallet = await createWallet({ storageMode: 'public' }); -...walletIds.push(wallet.id().toString()); -...console.log(\`Wallet \${i}:\`, walletIds[i]); +...const wallet = await createWallet({ storageMode: StorageMode.Public }); +...wallets.push(wallet); +...console.log(\`Wallet \${i}:\`, wallet.id().toString()); ..} ..// 2. Deploy a fungible faucet @@ -193,43 +193,46 @@ function UnauthenticatedNoteTransferInner() { ...tokenSymbol: 'MID', ...decimals: 8, ...maxSupply: BigInt(1_000_000), -...storageMode: 'public', +...storageMode: StorageMode.Public, ..}); -..const faucetId = faucet.id().toString(); -..console.log('Faucet ID:', faucetId); +..console.log('Faucet ID:', faucet.id().toString()); ..// 3. Mint 10,000 MID to Alice ..const mintResult = await mint({ -...faucetId, -...targetAccountId: aliceId, +...faucetId: faucet, +...targetAccountId: alice, ...amount: BigInt(10_000), -...noteType: 'public', +...noteType: NoteVisibility.Public, ..}); ..console.log('Waiting for settlement…'); ..await waitForCommit(mintResult.transactionId); ..// 4. Consume the freshly minted notes -..const notes = await waitForConsumableNotes({ accountId: aliceId }); -..const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); -..await consume({ accountId: aliceId, noteIds }); +..const notes = await waitForConsumableNotes({ accountId: alice }); +..const noteIds = notes.map((n) => n.inputNoteRecord().id()); +..await consume({ accountId: alice, noteIds }); ..// 5. Create the unauthenticated note transfer chain: ..// Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 ..console.log('Starting unauthenticated transfer chain…'); -..const results = await transferChain({ -...from: aliceId, -...recipients: walletIds, -...assetId: faucetId, -...amount: BigInt(50), -...noteType: 'public', -..}); - -..results.forEach((r, i) => { +..let currentSender = alice; +..for (let i = 0; i < wallets.length; i++) { +...const wallet = wallets[i]; +...const { note } = await send({ +....from: currentSender, +....to: wallet, +....assetId: faucet, +....amount: BigInt(50), +....noteType: NoteVisibility.Public, +....authenticated: false, +...}); +...const result = await consume({ accountId: wallet, noteIds: [note] }); ...console.log( -....\`Transfer \${i + 1}: https://testnet.midenscan.com/tx/\${r.consumeTransactionId}\`, +....\`Transfer \${i + 1}: https://testnet.midenscan.com/tx/\${result.transactionId}\`, ...); -..}); +...currentSender = wallet; +..} ..console.log('Asset transfer chain completed ✅'); .}; @@ -261,174 +264,95 @@ export async function unauthenticatedNoteTransfer(): Promise { .if (typeof window === 'undefined') return console.warn('Run in browser'); .const { -..WebClient, -..AccountStorageMode, -..AuthScheme, -..NoteType, -..TransactionProver, -..Note, -..NoteAssets, -..OutputNoteArray, -..FungibleAsset, -..NoteAndArgsArray, -..NoteAndArgs, -..NoteAttachment, -..TransactionRequestBuilder, -..OutputNote, +..MidenClient, +..AccountType, +..NoteVisibility, +..StorageMode, .} = await import('@miden-sdk/miden-sdk'); -.const client = await WebClient.createClient('https://rpc.testnet.miden.io'); -.const prover = TransactionProver.newLocalProver(); +.const client = await MidenClient.create({ +..rpcUrl: 'local', +..proverUrl: 'local', +.}); -.console.log('Latest block:', (await client.syncState()).blockNum()); - -.// ── Creating new account ────────────────────────────────────────────────────── -.console.log('Creating accounts'); +.console.log('Latest block:', (await client.sync()).blockNum()); +.// ── Creating accounts ────────────────────────────────────────────────────── .console.log('Creating account for Alice…'); -.const alice = await client.newWallet( -..AccountStorageMode.public(), -..true, -..AuthScheme.AuthRpoFalcon512, -.); +.const alice = await client.accounts.create({ +..type: AccountType.MutableWallet, +..storage: StorageMode.Public, +.}); .console.log('Alice account ID:', alice.id().toString()); .const wallets = []; .for (let i = 0; i < 5; i++) { -..const wallet = await client.newWallet( -...AccountStorageMode.public(), -...true, -...AuthScheme.AuthRpoFalcon512, -..); +..const wallet = await client.accounts.create({ +...type: AccountType.MutableWallet, +...storage: StorageMode.Public, +..}); ..wallets.push(wallet); ..console.log('wallet ', i.toString(), wallet.id().toString()); .} .// ── Creating new faucet ────────────────────────────────────────────────────── -.const faucet = await client.newFaucet( -..AccountStorageMode.public(), -..false, -..'MID', -..8, -..BigInt(1_000_000), -..AuthScheme.AuthRpoFalcon512, -.); +.const faucet = await client.accounts.create({ +..type: AccountType.FungibleFaucet, +..symbol: 'MID', +..decimals: 8, +..maxSupply: BigInt(1_000_000), +..storage: StorageMode.Public, +.}); .console.log('Faucet ID:', faucet.id().toString()); -.// ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── -.{ -..const txResult = await client.executeTransaction( -...faucet.id(), -...client.newMintTransactionRequest( -....alice.id(), -....faucet.id(), -....NoteType.Public, -....BigInt(10_000), -...), -..); -..const proven = await client.proveTransaction(txResult, prover); -..const submissionHeight = await client.submitProvenTransaction( -...proven, -...txResult, -..); -..await client.applyTransaction(txResult, submissionHeight); -.} +.// ── Mint 10,000 MID to Alice ────────────────────────────────────────────────────── +.const mintTxId = await client.transactions.mint({ +..account: faucet, +..to: alice, +..amount: BigInt(10_000), +..type: NoteVisibility.Public, +.}); .console.log('Waiting for settlement'); -.await new Promise((r) => setTimeout(r, 7_000)); -.await client.syncState(); +.await client.transactions.waitFor(mintTxId); +.await client.sync(); .// ── Consume the freshly minted note ────────────────────────────────────────────── -.const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => -..rec.inputNoteRecord().toNote(), -.); - -.{ -..const txResult = await client.executeTransaction( -...alice.id(), -...client.newConsumeTransactionRequest(noteList), -..); -..const proven = await client.proveTransaction(txResult, prover); -..const submissionHeight = await client.submitProvenTransaction( -...proven, -...txResult, -..); -..await client.applyTransaction(txResult, submissionHeight); -..await client.syncState(); -.} +.const noteList = await client.notes.listAvailable({ account: alice }); +.await client.transactions.consume({ +..account: alice, +..notes: noteList.map((n) => n.inputNoteRecord()), +.}); +.await client.sync(); .// ── Create unauthenticated note transfer chain ───────────────────────────────────────────── .// Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 .for (let i = 0; i < wallets.length; i++) { ..console.log(\`\\nUnauthenticated tx \${i + 1}\`); -..// Determine sender and receiver for this iteration ..const sender = i === 0 ? alice : wallets[i - 1]; ..const receiver = wallets[i]; ..console.log('Sender:', sender.id().toString()); ..console.log('Receiver:', receiver.id().toString()); -..const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(50))]); -..const p2idNote = Note.createP2IDNote( -...sender.id(), -...receiver.id(), -...assets, -...NoteType.Public, -...new NoteAttachment(), -..); - -..const outputP2ID = OutputNote.full(p2idNote); - -..console.log('Creating P2ID note...'); -..{ -...const builder = new TransactionRequestBuilder(); -...const request = builder.withOwnOutputNotes(new OutputNoteArray([outputP2ID])).build(); -...const txResult = await client.executeTransaction( -....sender.id(), -....request, -...); -...const proven = await client.proveTransaction(txResult, prover); -...const submissionHeight = await client.submitProvenTransaction( -....proven, -....txResult, -...); -...await client.applyTransaction(txResult, submissionHeight); -..} - -..console.log('Consuming P2ID note...'); - -..const noteIdAndArgs = new NoteAndArgs(p2idNote, null); - -..const consumeBuilder = new TransactionRequestBuilder(); -..const consumeRequest = consumeBuilder.withInputNotes(new NoteAndArgsArray([noteIdAndArgs])).build(); - -..{ -...const txResult = await client.executeTransaction( -....receiver.id(), -....consumeRequest, -...); -...const proven = await client.proveTransaction(txResult, prover); -...const submissionHeight = await client.submitProvenTransaction( -....proven, -....txResult, -...); -...const txExecutionResult = await client.applyTransaction( -....txResult, -....submissionHeight, -...); - -...const txId = txExecutionResult -....executedTransaction() -....id() -....toHex() -....toString(); +..const { note } = await client.transactions.send({ +...account: sender, +...to: receiver, +...token: faucet, +...amount: BigInt(50), +...type: NoteVisibility.Public, +...authenticated: false, +..}); -...console.log( -....\`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${txId}\`, -...); -..} +..const consumeTxId = await client.transactions.consume({ +...account: receiver, +...notes: [note], +..}); +..console.log( +...\`Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/\${consumeTxId.toHex()}\`, +..); .} .console.log('Asset transfer chain completed ✅'); diff --git a/web-client/lib/createMintConsume.ts b/web-client/lib/createMintConsume.ts index a7612fd..86b0b95 100644 --- a/web-client/lib/createMintConsume.ts +++ b/web-client/lib/createMintConsume.ts @@ -6,96 +6,76 @@ export async function createMintConsume(): Promise { } // dynamic import → only in the browser, so WASM is loaded client‑side - const { - WebClient, - AccountStorageMode, - AuthScheme, - NoteType, - Address, - } = await import('@miden-sdk/miden-sdk'); + const { MidenClient, AccountType, NoteVisibility, StorageMode } = await import('@miden-sdk/miden-sdk'); - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); + const client = await MidenClient.create({ + rpcUrl: 'http://localhost:57291', + }); // 1. Sync with the latest blockchain state - const state = await client.syncState(); + const state = await client.sync(); console.log('Latest block number:', state.blockNum()); // 2. Create Alice's account console.log('Creating account for Alice…'); - const aliceSeed = new Uint8Array(32); - crypto.getRandomValues(aliceSeed); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - aliceSeed, - ); + const alice = await client.accounts.create({ + type: AccountType.MutableWallet, + storage: StorageMode.Public, + }); console.log('Alice ID:', alice.id().toString()); // 3. Deploy a fungible faucet console.log('Creating faucet…'); - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); + const faucet = await client.accounts.create({ + type: AccountType.FungibleFaucet, + symbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storage: StorageMode.Public, + }); console.log('Faucet ID:', faucet.id().toString()); - await client.syncState(); + await client.sync(); // 4. Mint tokens to Alice - await client.syncState(); - console.log('Minting tokens to Alice...'); - const mintTxRequest = client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(1000), - ); - - await client.submitNewTransaction(faucet.id(), mintTxRequest); + const mintTxId = await client.transactions.mint({ + account: faucet, + to: alice, + amount: BigInt(1000), + type: NoteVisibility.Public, + }); - console.log('Waiting 10 seconds for transaction confirmation...'); - await new Promise((resolve) => setTimeout(resolve, 10000)); - await client.syncState(); + console.log('Waiting for transaction confirmation...'); + await client.transactions.waitFor(mintTxId); + await client.sync(); // 5. Fetch minted notes - const mintedNotes = await client.getConsumableNotes(alice.id()); - const mintedNoteList = mintedNotes.map((n) => - n.inputNoteRecord().toNote(), - ); + const mintedNotes = await client.notes.listAvailable({ account: alice }); console.log( 'Minted notes:', - mintedNoteList.map((note) => note.id().toString()), + mintedNotes.map((n) => n.inputNoteRecord().id().toString()), ); // 6. Consume minted notes console.log('Consuming minted notes...'); - const consumeTxRequest = client.newConsumeTransactionRequest(mintedNoteList); - - await client.submitNewTransaction(alice.id(), consumeTxRequest); + await client.transactions.consume({ + account: alice, + notes: mintedNotes.map((n) => n.inputNoteRecord()), + }); - await client.syncState(); + await client.sync(); console.log('Notes consumed.'); // 7. Send tokens to Bob - const bobAccountId = Address.fromBech32( - 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph', - ).accountId(); + const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; console.log("Sending tokens to Bob's account..."); - const sendTxRequest = client.newSendTransactionRequest( - alice.id(), - bobAccountId, - faucet.id(), - NoteType.Public, - BigInt(100), - ); - - await client.submitNewTransaction(alice.id(), sendTxRequest); + await client.transactions.send({ + account: alice, + to: bobAddress, + token: faucet, + amount: BigInt(100), + type: NoteVisibility.Public, + }); console.log('Tokens sent successfully!'); } diff --git a/web-client/lib/foreignProcedureInvocation.ts b/web-client/lib/foreignProcedureInvocation.ts index 79c2eda..47ded5b 100644 --- a/web-client/lib/foreignProcedureInvocation.ts +++ b/web-client/lib/foreignProcedureInvocation.ts @@ -5,32 +5,48 @@ export async function foreignProcedureInvocation(): Promise { return; } - // dynamic import → only in the browser, so WASM is loaded client‑side - const { - AccountBuilder, - AccountComponent, - Address, - AccountType, - AuthSecretKey, - StorageSlot, - TransactionRequestBuilder, - ForeignAccount, - ForeignAccountArray, - AccountStorageRequirements, - WebClient, - AccountStorageMode, - } = await import('@miden-sdk/miden-sdk'); - - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); - console.log('Current block number: ', (await client.syncState()).blockNum()); + const { AccountType, AuthSecretKey, StorageMode, StorageSlot, MidenClient } = + await import('@miden-sdk/miden-sdk'); - // ------------------------------------------------------------------------- - // STEP 1: Create the Count Reader Contract - // ------------------------------------------------------------------------- - console.log('\n[STEP 1] Creating count reader contract.'); + const nodeEndpoint = 'http://localhost:57291'; + const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); + console.log('Current block number: ', (await client.sync()).blockNum()); + + const counterContractCode = ` + use miden::protocol::active_account + use miden::protocol::native_account + use miden::core::word + use miden::core::sys + + const COUNTER_SLOT = word("miden::tutorials::counter") + + #! Inputs: [] + #! Outputs: [count] + pub proc get_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + exec.sys::truncate_stack + # => [count] + end + + #! Inputs: [] + #! Outputs: [] + pub proc increment_count + push.COUNTER_SLOT[0..2] exec.active_account::get_item + # => [count] + + add.1 + # => [count+1] + + push.COUNTER_SLOT[0..2] exec.native_account::set_item + # => [] + + exec.sys::truncate_stack + # => [] + end +`; - // Count reader contract code in Miden Assembly (exactly from count_reader.masm) const countReaderCode = ` use miden::protocol::active_account use miden::protocol::native_account @@ -44,7 +60,7 @@ export async function foreignProcedureInvocation(): Promise { pub proc copy_count exec.tx::execute_foreign_procedure # => [count] - + push.COUNT_READER_SLOT[0..2] # [slot_id_prefix, slot_id_suffix, count] @@ -59,71 +75,76 @@ export async function foreignProcedureInvocation(): Promise { end `; - const countReaderSlotName = 'miden::tutorials::count_reader'; const counterSlotName = 'miden::tutorials::counter'; + const countReaderSlotName = 'miden::tutorials::count_reader'; - const builder = client.createCodeBuilder(); - const countReaderComponentCode = - builder.compileAccountComponentCode(countReaderCode); - const countReaderComponent = AccountComponent.compile( - countReaderComponentCode, - [StorageSlot.emptyValue(countReaderSlotName)], - ).withSupportsAllTypes(); - - const walletSeed = new Uint8Array(32); - crypto.getRandomValues(walletSeed); - - const secretKey = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const authComponent = AccountComponent.createAuthComponentFromSecretKey( - secretKey, - ); + // ------------------------------------------------------------------------- + // STEP 1: Deploy the Counter Contract + // ------------------------------------------------------------------------- + console.log('\n[STEP 1] Deploying counter contract.'); + + const counterComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], + }); + + const counterSeed = new Uint8Array(32); + crypto.getRandomValues(counterSeed); + const counterAuth = AuthSecretKey.rpoFalconWithRNG(counterSeed); + + const counterAccount = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: counterSeed, + auth: counterAuth, + components: [counterComponent], + }); + + // Deploy the counter to the node by executing a transaction on it + const deployScript = await client.compile.txScript({ + code: ` + use external_contract::counter_contract + begin + call.counter_contract::increment_count + end + `, + libraries: [{ namespace: 'external_contract::counter_contract', code: counterContractCode }], + }); + + // Wait for the deploy transaction to be committed to a block + // before using it as a foreign account in FPI + await client.transactions.execute({ + account: counterAccount, + script: deployScript, + waitForConfirmation: true, + }); + await client.sync(); + console.log('Counter contract ID:', counterAccount.id().toString()); - const countReaderContract = new AccountBuilder(walletSeed) - .accountType(AccountType.RegularAccountImmutableCode) - .storageMode(AccountStorageMode.public()) - .withAuthComponent(authComponent) - .withComponent(countReaderComponent) - .build(); + // ------------------------------------------------------------------------- + // STEP 2: Create the Count Reader Contract + // ------------------------------------------------------------------------- + console.log('\n[STEP 2] Creating count reader contract.'); - await client.addAccountSecretKeyToWebStore( - countReaderContract.account.id(), - secretKey, - ); - await client.syncState(); + const countReaderComponent = await client.compile.component({ + code: countReaderCode, + slots: [StorageSlot.emptyValue(countReaderSlotName)], + }); - // Create the count reader contract account (using available WebClient API) - console.log('Creating count reader contract account...'); - console.log( - 'Count reader contract ID:', - countReaderContract.account.id().toString(), - ); + const readerSeed = new Uint8Array(32); + crypto.getRandomValues(readerSeed); + const readerAuth = AuthSecretKey.rpoFalconWithRNG(readerSeed); - await client.newAccount(countReaderContract.account, false); + const countReaderAccount = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: readerSeed, + auth: readerAuth, + components: [countReaderComponent], + }); - // ------------------------------------------------------------------------- - // STEP 2: Build & Get State of the Counter Contract - // ------------------------------------------------------------------------- - console.log('\n[STEP 2] Building counter contract from public state'); - - // Define the Counter Contract account id from counter contract deploy (same as Rust) - const counterContractId = Address.fromBech32( - 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Import the counter contract - let counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - await client.importAccountById(counterContractId); - await client.syncState(); - counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - throw new Error(`Account not found after import: ${counterContractId}`); - } - } - console.log( - 'Account storage slot:', - counterContractAccount.storage().getItem(counterSlotName)?.toHex(), - ); + await client.sync(); + console.log('Count reader contract ID:', countReaderAccount.id().toString()); // ------------------------------------------------------------------------- // STEP 3: Call the Counter Contract via Foreign Procedure Invocation (FPI) @@ -132,54 +153,8 @@ export async function foreignProcedureInvocation(): Promise { '\n[STEP 3] Call counter contract with FPI from count reader contract', ); - // Counter contract code (exactly from counter.masm) - const counterContractCode = ` - use miden::protocol::active_account - use miden::protocol::native_account - use miden::core::word - use miden::core::sys - - const COUNTER_SLOT = word("miden::tutorials::counter") - - #! Inputs: [] - #! Outputs: [count] - pub proc get_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - exec.sys::truncate_stack - # => [count] - end - - #! Inputs: [] - #! Outputs: [] - pub proc increment_count - push.COUNTER_SLOT[0..2] exec.active_account::get_item - # => [count] - - add.1 - # => [count+1] - - push.COUNTER_SLOT[0..2] exec.native_account::set_item - # => [] - - exec.sys::truncate_stack - # => [] - end -`; - - // Create the counter contract component to get the procedure hash (following Rust pattern) - const counterContractComponentCode = - builder.compileAccountComponentCode(counterContractCode); - const counterContractComponent = AccountComponent.compile( - counterContractComponentCode, - [StorageSlot.emptyValue(counterSlotName)], - ).withSupportsAllTypes(); + const getCountProcHash = counterComponent.getProcedureHash('get_count'); - const getCountProcHash = - counterContractComponent.getProcedureHash('get_count'); - - // Build the script that calls the count reader contract (exactly from reader_script.masm with replacements) const fpiScriptCode = ` use external_contract::count_reader_contract use miden::core::sys @@ -188,10 +163,10 @@ export async function foreignProcedureInvocation(): Promise { push.${getCountProcHash} # => [GET_COUNT_HASH] - push.${counterContractAccount.id().suffix()} + push.${counterAccount.id().suffix()} # => [account_id_suffix, GET_COUNT_HASH] - push.${counterContractAccount.id().prefix()} + push.${counterAccount.id().prefix()} # => [account_id_prefix, account_id_suffix, GET_COUNT_HASH] call.count_reader_contract::copy_count @@ -203,63 +178,26 @@ export async function foreignProcedureInvocation(): Promise { end `; - // Create the library for the count reader contract - const countReaderLib = builder.buildLibrary( - 'external_contract::count_reader_contract', - countReaderCode, - ); - builder.linkDynamicLibrary(countReaderLib); + const script = await client.compile.txScript({ + code: fpiScriptCode, + libraries: [{ namespace: 'external_contract::count_reader_contract', code: countReaderCode }], + }); - // Compile the transaction script with the count reader library - const txScript = builder.compileTxScript(fpiScriptCode); + await client.transactions.execute({ + account: countReaderAccount, + script, + foreignAccounts: [counterAccount], + }); - // foreign account - const storageRequirements = new AccountStorageRequirements(); - const foreignAccount = ForeignAccount.public( - counterContractId, - storageRequirements, - ); - - // Build a transaction request with the custom script - const txRequest = new TransactionRequestBuilder() - .withCustomScript(txScript) - .withForeignAccounts(new ForeignAccountArray([foreignAccount])) - .build(); + await client.sync(); - // Execute the transaction on the count reader contract and send it to the network (following Rust pattern) - const txResult = await client.submitNewTransaction( - countReaderContract.account.id(), - txRequest, + const updatedCountReaderContract = await client.accounts.get( + countReaderAccount, ); - - console.log( - 'View transaction on MidenScan: https://testnet.midenscan.com/tx/' + - txResult.toHex(), - ); - - await client.syncState(); - - // Retrieve updated contract data to see the results (following Rust pattern) - const updatedCounterContract = await client.getAccount( - counterContractAccount.id(), - ); - console.log( - 'counter contract storage:', - updatedCounterContract?.storage().getItem(counterSlotName)?.toHex(), - ); - - const updatedCountReaderContract = await client.getAccount( - countReaderContract.account.id(), - ); - console.log( - 'count reader contract storage:', - updatedCountReaderContract?.storage().getItem(countReaderSlotName)?.toHex(), - ); - - // Log the count value copied via FPI const countReaderStorage = updatedCountReaderContract ?.storage() .getItem(countReaderSlotName); + if (countReaderStorage) { const countValue = Number( BigInt( diff --git a/web-client/lib/incrementCounterContract.ts b/web-client/lib/incrementCounterContract.ts index 1ed7b7e..8c36252 100644 --- a/web-client/lib/incrementCounterContract.ts +++ b/web-client/lib/incrementCounterContract.ts @@ -5,24 +5,13 @@ export async function incrementCounterContract(): Promise { return; } - // dynamic import → only in the browser, so WASM is loaded client‑side - const { - Address, - AccountBuilder, - AccountComponent, - AccountStorageMode, - AccountType, - AuthSecretKey, - StorageSlot, - TransactionRequestBuilder, - WebClient, - } = await import('@miden-sdk/miden-sdk'); - - const nodeEndpoint = 'https://rpc.testnet.miden.io'; - const client = await WebClient.createClient(nodeEndpoint); - console.log('Current block number: ', (await client.syncState()).blockNum()); - - // Counter contract code in Miden Assembly + const { AccountType, AuthSecretKey, StorageMode, StorageSlot, MidenClient } = + await import('@miden-sdk/miden-sdk'); + + const nodeEndpoint = 'http://localhost:57291'; + const client = await MidenClient.create({ rpcUrl: nodeEndpoint }); + console.log('Current block number: ', (await client.sync()).blockNum()); + const counterContractCode = ` use miden::protocol::active_account use miden::protocol::native_account @@ -58,65 +47,27 @@ export async function incrementCounterContract(): Promise { end `; - // Building the counter contract - // Counter contract account id on testnet - const counterContractId = Address.fromBech32( - 'mtst1arjemrxne8lj5qz4mg9c8mtyxg954483', - ).accountId(); - - // Reading the public state of the counter contract from testnet, - // and importing it into the WebClient - let counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - await client.importAccountById(counterContractId); - await client.syncState(); - counterContractAccount = await client.getAccount(counterContractId); - if (!counterContractAccount) { - throw new Error(`Account not found after import: ${counterContractId}`); - } - } - - const builder = client.createCodeBuilder(); const counterSlotName = 'miden::tutorials::counter'; - const counterStorageSlot = StorageSlot.emptyValue(counterSlotName); - const counterComponentCode = - builder.compileAccountComponentCode(counterContractCode); - const counterAccountComponent = AccountComponent.compile( - counterComponentCode, - [counterStorageSlot], - ).withSupportsAllTypes(); + const counterAccountComponent = await client.compile.component({ + code: counterContractCode, + slots: [StorageSlot.emptyValue(counterSlotName)], + }); const walletSeed = new Uint8Array(32); crypto.getRandomValues(walletSeed); + const auth = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const secretKey = AuthSecretKey.rpoFalconWithRNG(walletSeed); - const authComponent = - AccountComponent.createAuthComponentFromSecretKey(secretKey); - - const accountBuilderResult = new AccountBuilder(walletSeed) - .accountType(AccountType.RegularAccountImmutableCode) - .storageMode(AccountStorageMode.public()) - .withAuthComponent(authComponent) - .withComponent(counterAccountComponent) - .build(); - - await client.addAccountSecretKeyToWebStore( - accountBuilderResult.account.id(), - secretKey, - ); - await client.newAccount(accountBuilderResult.account, false); + const account = await client.accounts.create({ + type: AccountType.ImmutableContract, + storage: StorageMode.Public, + seed: walletSeed, + auth, + components: [counterAccountComponent], + }); - await client.syncState(); + await client.sync(); - const accountCodeLib = builder.buildLibrary( - 'external_contract::counter_contract', - counterContractCode, - ); - - builder.linkDynamicLibrary(accountCodeLib); - - // Building the transaction script which will call the counter contract const txScriptCode = ` use external_contract::counter_contract begin @@ -124,31 +75,24 @@ export async function incrementCounterContract(): Promise { end `; - const txScript = builder.compileTxScript(txScriptCode); - const txIncrementRequest = new TransactionRequestBuilder() - .withCustomScript(txScript) - .build(); + const script = await client.compile.txScript({ + code: txScriptCode, + libraries: [{ namespace: 'external_contract::counter_contract', code: counterContractCode }], + }); - // Executing the transaction script against the counter contract - await client.submitNewTransaction( - counterContractAccount.id(), - txIncrementRequest, - ); + await client.transactions.execute({ + account, + script, + }); - // Sync state - await client.syncState(); + await client.sync(); - // Logging the count of counter contract - const counter = await client.getAccount(counterContractAccount.id()); + console.log('Counter contract ID:', account.id().toString()); - // Here we get the first Word from storage of the counter contract - // A word is comprised of 4 Felts, 2**64 - 2**32 + 1 + const counter = await client.accounts.get(account); const count = counter?.storage().getItem(counterSlotName); - - // Converting the Word represented as a hex to a single integer value const counterValue = Number( BigInt('0x' + count!.toHex().slice(-16).match(/../g)!.reverse().join('')), ); - console.log('Count: ', counterValue); } diff --git a/web-client/lib/mintTestnetToAddress.ts b/web-client/lib/mintTestnetToAddress.ts index 6ed1823..90ddaf1 100644 --- a/web-client/lib/mintTestnetToAddress.ts +++ b/web-client/lib/mintTestnetToAddress.ts @@ -8,31 +8,31 @@ export async function mintTestnetToAddress(): Promise { } const { - WebClient, - AccountStorageMode, - AuthScheme, + MidenClient, + AccountType, + NoteVisibility, + StorageMode, Address, - NoteType, - TransactionProver, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); + const client = await MidenClient.create({ + rpcUrl: 'local', + proverUrl: 'local', + }); - console.log('Latest block:', (await client.syncState()).blockNum()); + console.log('Latest block:', (await client.sync()).blockNum()); // ── Create a faucet ──────────────────────────────────────────────────────── console.log('Creating faucet...'); - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); + const faucet = await client.accounts.create({ + type: AccountType.FungibleFaucet, + symbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storage: StorageMode.Public, + }); console.log('Faucet ID:', faucet.id().toString()); - await client.syncState(); + await client.sync(); // ── Mint to recipient ─────────────────────────────────────────────────────── const recipientAddress = @@ -41,30 +41,17 @@ export async function mintTestnetToAddress(): Promise { console.log('Recipient account ID:', recipientAccountId.toString()); console.log('Minting 100 MIDEN tokens...'); - const txResult = await client.executeTransaction( - faucet.id(), - client.newMintTransactionRequest( - recipientAccountId, - faucet.id(), - NoteType.Public, - BigInt(100), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - const txExecutionResult = await client.applyTransaction( - txResult, - submissionHeight, - ); + const mintTxId = await client.transactions.mint({ + account: faucet, + to: recipientAccountId, + amount: BigInt(100), + type: NoteVisibility.Public, + }); console.log('Waiting for settlement...'); - await new Promise((resolve) => setTimeout(resolve, 7_000)); - await client.syncState(); + await client.transactions.waitFor(mintTxId); + await client.sync(); - const txId = txExecutionResult.executedTransaction().id().toHex().toString(); - console.log('Mint tx id:', txId); + console.log('Mint tx id:', mintTxId.toHex()); console.log('Mint complete.'); } diff --git a/web-client/lib/multiSendWithDelegatedProver.ts b/web-client/lib/multiSendWithDelegatedProver.ts index 08a2bad..a6912ac 100644 --- a/web-client/lib/multiSendWithDelegatedProver.ts +++ b/web-client/lib/multiSendWithDelegatedProver.ts @@ -9,68 +9,57 @@ export async function multiSendWithDelegatedProver(): Promise { if (typeof window === 'undefined') return console.warn('Run in browser'); const { - WebClient, - AccountStorageMode, - AuthScheme, - Address, - NoteType, - Note, - NoteAssets, + MidenClient, + AccountType, + NoteVisibility, + StorageMode, + createP2IDNote, OutputNoteArray, - FungibleAsset, - NoteAttachment, TransactionRequestBuilder, - OutputNote, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); + const client = await MidenClient.create({ + rpcUrl: 'http://localhost:57291', + }); - console.log('Latest block:', (await client.syncState()).blockNum()); + console.log('Latest block:', (await client.sync()).blockNum()); // ── Creating new account ────────────────────────────────────────────────────── console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); + const alice = await client.accounts.create({ + type: AccountType.MutableWallet, + storage: StorageMode.Public, + }); console.log('Alice account ID:', alice.id().toString()); // ── Creating new faucet ────────────────────────────────────────────────────── - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); + const faucet = await client.accounts.create({ + type: AccountType.FungibleFaucet, + symbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storage: StorageMode.Public, + }); console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── - await client.submitNewTransaction( - faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); + const mintTxId = await client.transactions.mint({ + account: faucet, + to: alice, + amount: BigInt(10_000), + type: NoteVisibility.Public, + }); console.log('waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); + await client.transactions.waitFor(mintTxId); + await client.sync(); // ── consume the freshly minted notes ────────────────────────────────────────────── - const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => - rec.inputNoteRecord().toNote(), - ); - - await client.submitNewTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); + const noteList = await client.notes.listAvailable({ account: alice }); + await client.transactions.consume({ + account: alice, + notes: noteList.map((n) => n.inputNoteRecord()), + }); // ── build 3 P2ID notes (100 MID each) ───────────────────────────────────────────── const recipientAddresses = [ @@ -79,25 +68,19 @@ export async function multiSendWithDelegatedProver(): Promise { 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', ]; - const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(100))]); - - const p2idNotes = recipientAddresses.map((addr) => { - const receiverAccountId = Address.fromBech32(addr).accountId(); - const note = Note.createP2IDNote( - alice.id(), - receiverAccountId, - assets, - NoteType.Public, - new NoteAttachment(), - ); - - return OutputNote.full(note); - }); + const p2idNotes = recipientAddresses.map((addr) => + createP2IDNote({ + from: alice, + to: addr, + assets: { token: faucet, amount: BigInt(100) }, + type: NoteVisibility.Public, + }), + ); // ── create all P2ID notes ─────────────────────────────────────────────────────────────── const builder = new TransactionRequestBuilder(); const txRequest = builder.withOwnOutputNotes(new OutputNoteArray(p2idNotes)).build(); - await client.submitNewTransaction(alice.id(), txRequest); + await client.transactions.submit(alice, txRequest); console.log('All notes created ✅'); } diff --git a/web-client/lib/react/createMintConsume.tsx b/web-client/lib/react/createMintConsume.tsx index 2977512..3c27946 100644 --- a/web-client/lib/react/createMintConsume.tsx +++ b/web-client/lib/react/createMintConsume.tsx @@ -5,6 +5,7 @@ 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function CreateMintConsumeInner() { const { isReady } = useMiden(); @@ -19,9 +20,8 @@ function CreateMintConsumeInner() { const run = async () => { // 1. Create Alice's wallet (public, mutable) console.log('Creating account for Alice…'); - const alice = await createWallet({ storageMode: 'public' }); - const aliceId = alice.id().toString(); - console.log('Alice ID:', aliceId); + const alice = await createWallet({ storageMode: StorageMode.Public }); + console.log('Alice ID:', alice.id().toString()); // 2. Deploy a fungible faucet console.log('Creating faucet…'); @@ -29,18 +29,17 @@ function CreateMintConsumeInner() { tokenSymbol: 'MID', decimals: 8, maxSupply: BigInt(1_000_000), - storageMode: 'public', + storageMode: StorageMode.Public, }); - const faucetId = faucet.id().toString(); - console.log('Faucet ID:', faucetId); + console.log('Faucet ID:', faucet.id().toString()); // 3. Mint 1000 tokens to Alice console.log('Minting tokens to Alice...'); const mintResult = await mint({ - faucetId, - targetAccountId: aliceId, + faucetId: faucet, + targetAccountId: alice, amount: BigInt(1000), - noteType: 'public', + noteType: NoteVisibility.Public, }); console.log('Mint tx:', mintResult.transactionId); @@ -48,24 +47,24 @@ function CreateMintConsumeInner() { await waitForCommit(mintResult.transactionId); // 5. Wait for consumable notes to appear - const notes = await waitForConsumableNotes({ accountId: aliceId }); - const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); - console.log('Consumable notes:', noteIds); + const notes = await waitForConsumableNotes({ accountId: alice }); + const noteIds = notes.map((n) => n.inputNoteRecord().id()); + console.log('Consumable notes:', noteIds.length); // 6. Consume minted notes console.log('Consuming minted notes...'); - await consume({ accountId: aliceId, noteIds }); + await consume({ accountId: alice, noteIds }); console.log('Notes consumed.'); // 7. Send 100 tokens to Bob const bobAddress = 'mtst1apve54rq8ux0jqqqqrkh5y0r0y8cwza6_qruqqypuyph'; console.log("Sending tokens to Bob's account..."); await send({ - from: aliceId, + from: alice, to: bobAddress, - assetId: faucetId, + assetId: faucet, amount: BigInt(100), - noteType: 'public', + noteType: NoteVisibility.Public, }); console.log('Tokens sent successfully!'); }; diff --git a/web-client/lib/react/multiSendWithDelegatedProver.tsx b/web-client/lib/react/multiSendWithDelegatedProver.tsx index 4226acb..f6c1667 100644 --- a/web-client/lib/react/multiSendWithDelegatedProver.tsx +++ b/web-client/lib/react/multiSendWithDelegatedProver.tsx @@ -5,6 +5,7 @@ 'use client'; import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useMultiSend, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function MultiSendInner() { const { isReady } = useMiden(); @@ -19,46 +20,44 @@ function MultiSendInner() { const run = async () => { // 1. Create Alice's wallet console.log('Creating account for Alice…'); - const alice = await createWallet({ storageMode: 'public' }); - const aliceId = alice.id().toString(); - console.log('Alice account ID:', aliceId); + const alice = await createWallet({ storageMode: StorageMode.Public }); + console.log('Alice account ID:', alice.id().toString()); // 2. Deploy a fungible faucet const faucet = await createFaucet({ tokenSymbol: 'MID', decimals: 8, maxSupply: BigInt(1_000_000), - storageMode: 'public', + storageMode: StorageMode.Public, }); - const faucetId = faucet.id().toString(); - console.log('Faucet ID:', faucetId); + console.log('Faucet ID:', faucet.id().toString()); // 3. Mint 10,000 MID to Alice const mintResult = await mint({ - faucetId, - targetAccountId: aliceId, + faucetId: faucet, + targetAccountId: alice, amount: BigInt(10_000), - noteType: 'public', + noteType: NoteVisibility.Public, }); console.log('Waiting for settlement…'); await waitForCommit(mintResult.transactionId); // 4. Consume the freshly minted notes - const notes = await waitForConsumableNotes({ accountId: aliceId }); - const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); - await consume({ accountId: aliceId, noteIds }); + const notes = await waitForConsumableNotes({ accountId: alice }); + const noteIds = notes.map((n) => n.inputNoteRecord().id()); + await consume({ accountId: alice, noteIds }); // 5. Send 100 MID to three recipients in a single transaction await sendMany({ - from: aliceId, - assetId: faucetId, + from: alice, + assetId: faucet, recipients: [ { to: 'mtst1aqezqc90x7dkzypr9m5fmlpp85w6cl04', amount: BigInt(100) }, { to: 'mtst1apjg2ul76wrkxyr5qlcnczaskypa4ljn', amount: BigInt(100) }, { to: 'mtst1arpee6y9cm8t7ypn33pc8fzj6gkzz7kd', amount: BigInt(100) }, ], - noteType: 'public', + noteType: NoteVisibility.Public, }); console.log('All notes created ✅'); diff --git a/web-client/lib/react/unauthenticatedNoteTransfer.tsx b/web-client/lib/react/unauthenticatedNoteTransfer.tsx index e3c28db..ada2b87 100644 --- a/web-client/lib/react/unauthenticatedNoteTransfer.tsx +++ b/web-client/lib/react/unauthenticatedNoteTransfer.tsx @@ -4,7 +4,8 @@ // lib/unauthenticatedNoteTransfer.ts is used for Playwright tests instead. 'use client'; -import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useInternalTransfer, useWaitForCommit, useWaitForNotes } from '@miden-sdk/react'; +import { MidenProvider, useMiden, useCreateWallet, useCreateFaucet, useMint, useConsume, useSend, useWaitForCommit, useWaitForNotes, type Account } from '@miden-sdk/react'; +import { NoteVisibility, StorageMode } from '@miden-sdk/miden-sdk'; function UnauthenticatedNoteTransferInner() { const { isReady } = useMiden(); @@ -12,22 +13,21 @@ function UnauthenticatedNoteTransferInner() { const { createFaucet } = useCreateFaucet(); const { mint } = useMint(); const { consume } = useConsume(); - const { transferChain } = useInternalTransfer(); + const { send } = useSend(); const { waitForCommit } = useWaitForCommit(); const { waitForConsumableNotes } = useWaitForNotes(); const run = async () => { // 1. Create Alice and 5 wallets for the transfer chain console.log('Creating accounts…'); - const alice = await createWallet({ storageMode: 'public' }); - const aliceId = alice.id().toString(); - console.log('Alice account ID:', aliceId); + const alice = await createWallet({ storageMode: StorageMode.Public }); + console.log('Alice account ID:', alice.id().toString()); - const walletIds: string[] = []; + const wallets: Account[] = []; for (let i = 0; i < 5; i++) { - const wallet = await createWallet({ storageMode: 'public' }); - walletIds.push(wallet.id().toString()); - console.log(`Wallet ${i}:`, walletIds[i]); + const wallet = await createWallet({ storageMode: StorageMode.Public }); + wallets.push(wallet); + console.log(`Wallet ${i}:`, wallet.id().toString()); } // 2. Deploy a fungible faucet @@ -35,43 +35,48 @@ function UnauthenticatedNoteTransferInner() { tokenSymbol: 'MID', decimals: 8, maxSupply: BigInt(1_000_000), - storageMode: 'public', + storageMode: StorageMode.Public, }); - const faucetId = faucet.id().toString(); - console.log('Faucet ID:', faucetId); + console.log('Faucet ID:', faucet.id().toString()); // 3. Mint 10,000 MID to Alice const mintResult = await mint({ - faucetId, - targetAccountId: aliceId, + faucetId: faucet, + targetAccountId: alice, amount: BigInt(10_000), - noteType: 'public', + noteType: NoteVisibility.Public, }); console.log('Waiting for settlement…'); await waitForCommit(mintResult.transactionId); // 4. Consume the freshly minted notes - const notes = await waitForConsumableNotes({ accountId: aliceId }); - const noteIds = notes.map((n) => n.inputNoteRecord().id().toString()); - await consume({ accountId: aliceId, noteIds }); + const notes = await waitForConsumableNotes({ accountId: alice }); + const noteIds = notes.map((n) => n.inputNoteRecord().id()); + await consume({ accountId: alice, noteIds }); // 5. Create the unauthenticated note transfer chain: // Alice → Wallet 0 → Wallet 1 → Wallet 2 → Wallet 3 → Wallet 4 console.log('Starting unauthenticated transfer chain…'); - const results = await transferChain({ - from: aliceId, - recipients: walletIds, - assetId: faucetId, - amount: BigInt(50), - noteType: 'public', - }); + let currentSender: Account = alice; + for (let i = 0; i < wallets.length; i++) { + const wallet = wallets[i]; + const { note } = await send({ + from: currentSender, + to: wallet, + assetId: faucet, + amount: BigInt(50), + noteType: NoteVisibility.Public, + authenticated: false, + }); - results.forEach((r, i) => { + const result = await consume({ accountId: wallet, noteIds: [note!] }); console.log( - `Transfer ${i + 1}: https://testnet.midenscan.com/tx/${r.consumeTransactionId}`, + `Transfer ${i + 1}: https://testnet.midenscan.com/tx/${result.transactionId}`, ); - }); + + currentSender = wallet; + } console.log('Asset transfer chain completed ✅'); }; diff --git a/web-client/lib/unauthenticatedNoteTransfer.ts b/web-client/lib/unauthenticatedNoteTransfer.ts index fb7fdc9..fc05b41 100644 --- a/web-client/lib/unauthenticatedNoteTransfer.ts +++ b/web-client/lib/unauthenticatedNoteTransfer.ts @@ -9,173 +9,97 @@ export async function unauthenticatedNoteTransfer(): Promise { if (typeof window === 'undefined') return console.warn('Run in browser'); const { - WebClient, - AccountStorageMode, - AuthScheme, - NoteType, - TransactionProver, - Note, - NoteAssets, - OutputNoteArray, - FungibleAsset, - NoteAndArgsArray, - NoteAndArgs, - NoteAttachment, - TransactionRequestBuilder, - OutputNote, + MidenClient, + AccountType, + NoteVisibility, + StorageMode, } = await import('@miden-sdk/miden-sdk'); - const client = await WebClient.createClient('https://rpc.testnet.miden.io'); - const prover = TransactionProver.newLocalProver(); + const client = await MidenClient.create({ + rpcUrl: 'local', + proverUrl: 'local', + }); - console.log('Latest block:', (await client.syncState()).blockNum()); + console.log('Latest block:', (await client.sync()).blockNum()); // ── Creating new account ────────────────────────────────────────────────────── console.log('Creating accounts'); console.log('Creating account for Alice…'); - const alice = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); + const alice = await client.accounts.create({ + type: AccountType.MutableWallet, + storage: StorageMode.Public, + }); console.log('Alice account ID:', alice.id().toString()); const wallets = []; for (let i = 0; i < 5; i++) { - const wallet = await client.newWallet( - AccountStorageMode.public(), - true, - AuthScheme.AuthRpoFalcon512, - ); + const wallet = await client.accounts.create({ + type: AccountType.MutableWallet, + storage: StorageMode.Public, + }); wallets.push(wallet); console.log('wallet ', i.toString(), wallet.id().toString()); } // ── Creating new faucet ────────────────────────────────────────────────────── - const faucet = await client.newFaucet( - AccountStorageMode.public(), - false, - 'MID', - 8, - BigInt(1_000_000), - AuthScheme.AuthRpoFalcon512, - ); + const faucet = await client.accounts.create({ + type: AccountType.FungibleFaucet, + symbol: 'MID', + decimals: 8, + maxSupply: BigInt(1_000_000), + storage: StorageMode.Public, + }); console.log('Faucet ID:', faucet.id().toString()); // ── mint 10 000 MID to Alice ────────────────────────────────────────────────────── - { - const txResult = await client.executeTransaction( - faucet.id(), - client.newMintTransactionRequest( - alice.id(), - faucet.id(), - NoteType.Public, - BigInt(10_000), - ), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } + const mintTxId = await client.transactions.mint({ + account: faucet, + to: alice, + amount: BigInt(10_000), + type: NoteVisibility.Public, + }); console.log('Waiting for settlement'); - await new Promise((r) => setTimeout(r, 7_000)); - await client.syncState(); + await client.transactions.waitFor(mintTxId); + await client.sync(); // ── Consume the freshly minted note ────────────────────────────────────────────── - const noteList = (await client.getConsumableNotes(alice.id())).map((rec) => - rec.inputNoteRecord().toNote(), - ); - - { - const txResult = await client.executeTransaction( - alice.id(), - client.newConsumeTransactionRequest(noteList), - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - await client.syncState(); - } + const noteList = await client.notes.listAvailable({ account: alice }); + await client.transactions.consume({ + account: alice, + notes: noteList.map((n) => n.inputNoteRecord()), + }); + await client.sync(); // ── Create unauthenticated note transfer chain ───────────────────────────────────────────── // Alice → wallet 1 → wallet 2 → wallet 3 → wallet 4 for (let i = 0; i < wallets.length; i++) { console.log(`\nUnauthenticated tx ${i + 1}`); - // Determine sender and receiver for this iteration const sender = i === 0 ? alice : wallets[i - 1]; const receiver = wallets[i]; console.log('Sender:', sender.id().toString()); console.log('Receiver:', receiver.id().toString()); - const assets = new NoteAssets([new FungibleAsset(faucet.id(), BigInt(50))]); - const p2idNote = Note.createP2IDNote( - sender.id(), - receiver.id(), - assets, - NoteType.Public, - new NoteAttachment(), + const { note } = await client.transactions.send({ + account: sender, + to: receiver, + token: faucet, + amount: BigInt(50), + type: NoteVisibility.Public, + authenticated: false, + }); + + const consumeTxId = await client.transactions.consume({ + account: receiver, + notes: [note], + }); + + console.log( + `Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/${consumeTxId.toHex()}`, ); - - const outputP2ID = OutputNote.full(p2idNote); - - console.log('Creating P2ID note...'); - { - const builder = new TransactionRequestBuilder(); - const request = builder.withOwnOutputNotes(new OutputNoteArray([outputP2ID])).build(); - const txResult = await client.executeTransaction( - sender.id(), - request, - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - await client.applyTransaction(txResult, submissionHeight); - } - - console.log('Consuming P2ID note...'); - - const noteIdAndArgs = new NoteAndArgs(p2idNote, null); - - const consumeBuilder = new TransactionRequestBuilder(); - const consumeRequest = consumeBuilder.withInputNotes(new NoteAndArgsArray([noteIdAndArgs])).build(); - - { - const txResult = await client.executeTransaction( - receiver.id(), - consumeRequest, - ); - const proven = await client.proveTransaction(txResult, prover); - const submissionHeight = await client.submitProvenTransaction( - proven, - txResult, - ); - const txExecutionResult = await client.applyTransaction( - txResult, - submissionHeight, - ); - - const txId = txExecutionResult - .executedTransaction() - .id() - .toHex() - .toString(); - - console.log( - `Consumed Note Tx on MidenScan: https://testnet.midenscan.com/tx/${txId}`, - ); - } } console.log('Asset transfer chain completed ✅'); diff --git a/web-client/package.json b/web-client/package.json index 2a78ff2..d37dad3 100644 --- a/web-client/package.json +++ b/web-client/package.json @@ -9,8 +9,8 @@ "lint": "next lint" }, "dependencies": { - "@miden-sdk/miden-sdk": "0.13.0", - "@miden-sdk/react": "0.13.2", + "@miden-sdk/miden-sdk": "file:../../miden-client/crates/web-client", + "@miden-sdk/react": "file:../../miden-client/packages/react-sdk", "next": "15.3.2", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/web-client/playwright.config.ts b/web-client/playwright.config.ts index ad95c84..0d5850a 100644 --- a/web-client/playwright.config.ts +++ b/web-client/playwright.config.ts @@ -12,6 +12,9 @@ export default defineConfig({ use: { baseURL: "http://localhost:3000", headless: true, + launchOptions: { + args: ["--disable-web-security"], + }, }, webServer: { command: "yarn dev", diff --git a/web-client/yarn.lock b/web-client/yarn.lock index 5c08f48..27d528c 100644 --- a/web-client/yarn.lock +++ b/web-client/yarn.lock @@ -234,15 +234,18 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@miden-sdk/miden-sdk@0.13.0": +"@miden-sdk/miden-sdk@file:../../miden-client/crates/web-client": version "0.13.0" - resolved "https://registry.yarnpkg.com/@miden-sdk/miden-sdk/-/miden-sdk-0.13.0.tgz#df7f639f90931d279761a62bb513374366d755e4" - integrity sha512-N0qUCZW9Dvk3Oqj37IrGmm0b0v3Nq5qHsX3BtQIzZIwDXKXKPBxy/0lO40oCwDtwI8AfriZQyMLbJR81Fo4Vpg== dependencies: "@rollup/plugin-typescript" "^12.3.0" dexie "^4.0.1" glob "^11.0.0" +"@miden-sdk/react@file:../../miden-client/packages/react-sdk": + version "0.13.2" + dependencies: + zustand "^5.0.0" + "@napi-rs/wasm-runtime@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.7.tgz#dcfea99a75f06209a235f3d941e3460a51e9b14c" @@ -1039,3 +1042,8 @@ wrap-ansi@^8.1.0: ansi-styles "^6.1.0" string-width "^5.0.1" strip-ansi "^7.0.1" + +zustand@^5.0.0: + version "5.0.11" + resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494" + integrity sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==