Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 128 additions & 98 deletions docs/src/web-client/counter_contract_tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.

Expand All @@ -117,139 +117,117 @@ export async function incrementCounterContract(): Promise<void> {

// 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
Expand Down Expand Up @@ -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.
Expand Down
Loading