For the complete documentation index, see llms.txt.
Skip to main content
Version: Tierra (v6.0.0)

Using Multiple Wallets

This guide shows you how to run two wallets in one script, for example to test an app with two users. Each wallet has its own PXE (Private eXecution Environment), so it starts with no knowledge of the other wallet's accounts, contracts or notes. You give it that knowledge with registerSender and registerContract.

Prerequisites​

Create two wallets​

Create one EmbeddedWallet per user. Each one runs its own PXE with its own key store and note database:

create_wallets
import { createAztecNodeClient, waitForNode } from "@aztec-labs/aztec.js/node";
import { EmbeddedWallet } from "@aztec-labs/wallets/embedded";

const nodeUrl = process.env.AZTEC_NODE_URL ?? "http://localhost:8080";
const node = createAztecNodeClient(nodeUrl);
await waitForNode(node);

// Each wallet runs its own PXE, with its own keys, notes and registered contracts.
const aliceWallet = await EmbeddedWallet.create(node, { ephemeral: true });
const bobWallet = await EmbeddedWallet.create(node, { ephemeral: true });
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L1-L12

Then add one account to each wallet. This example uses the local network's funded test accounts, so neither account needs to be deployed:

load_accounts
import { getInitialTestAccountsData } from "@aztec-labs/accounts/testing";

const [aliceData, bobData] = await getInitialTestAccountsData();
const alice = await aliceWallet.createSchnorrInitializerlessAccount(
aliceData.secret,
aliceData.salt,
aliceData.signingKey,
);
const bob = await bobWallet.createSchnorrInitializerlessAccount(
bobData.secret,
bobData.salt,
bobData.signingKey,
);
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L14-L28

On another network, create and deploy each account in its own wallet as shown in Creating Accounts.

Register the sender​

A recipient's PXE finds its notes by computing tags from a secret it shares with each sender it knows (see Note discovery). A wallet treats its own accounts as known senders, but Bob's wallet has never heard of Alice. Register her before you expect notes from her:

register_sender
await bobWallet.registerSender(alice.address, "alice");
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L30-L32

Without this call, Bob's private balance reads as zero even after Alice sends him tokens.

Send private and public tokens​

Alice deploys a token from her wallet and mints to Bob, once privately and once publicly. Her wallet does not need Bob's account: it encrypts the private note to Bob's address.

deploy_and_mint
import { TokenContract } from "@aztec-labs/noir-contracts.js/Token";

const { contract: aliceToken } = await TokenContract.deploy(
aliceWallet,
alice.address,
"TokenName",
"TKN",
18,
).send({ from: alice.address });

await aliceToken.methods
.mint_to_private(bob.address, 100)
.send({ from: alice.address });
await aliceToken.methods
.mint_to_public(bob.address, 100)
.send({ from: alice.address });
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L34-L51

Register the contract​

Bob's wallet must know the contract before it can call it. Fetch the deployed instance from the node and register it with the contract's artifact:

register_contract
const tokenInstance = await node.getContract(aliceToken.address);
if (!tokenInstance) {
throw new Error(`Token ${aliceToken.address} is not deployed`);
}
await bobWallet.registerContract(tokenInstance, TokenContract.artifact);
const bobToken = await TokenContract.at(aliceToken.address, bobWallet);
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L53-L60

Read the balances​

Bob's wallet now syncs the notes Alice tagged for him and reads both balances:

read_balances
const { result: privateBalance } = await bobToken.methods
.balance_of_private(bob.address)
.simulate({ from: bob.address });
const { result: publicBalance } = await bobToken.methods
.balance_of_public(bob.address)
.simulate({ from: bob.address });
console.log(`Bob's private balance: ${privateBalance}`);
console.log(`Bob's public balance: ${publicBalance}`);
Source code: docs/lines/v6/examples/ts/aztecjs_multiple_wallets/index.ts#L62-L71

Expected output:

Bob's private balance: 100
Bob's public balance: 100

Troubleshooting​

  • The private balance is zero: the recipient's wallet has not registered the sender. By default the wallet uses the transaction's sender for tags; a contract can set another one with with_sender.
  • the contract is not registered: call registerContract in each wallet that calls or reads the contract, not only in the wallet that deployed it.

Next steps​