Skip to main content
Version: v3.0.0-nightly.20251231

Deploying a Token Contract

In this guide, we will retrieving the local network and deploy a pre-written token contract to it using Aztec.js. Check out the source code. We will then use Aztec.js to interact with this contract and transfer tokens.

Before starting, make sure to be running Aztec local network at version 3.0.0-nightly.20251231. Check out the guide for info about that.

Set up the project

First, create a new directory for your project and initialize it with yarn:

mkdir token-tutorial
cd token-tutorial
yarn init -y

Next, add the TypeScript dependencies:

yarn add typescript @types/node tsx
tip

Never heard of tsx? Well, it will just run typescript with reasonable defaults. Pretty cool for a small example like this one. You may want to tune in your own project's tsconfig.json later!

Let's also import the Aztec dependencies for this tutorial:

yarn add @aztec/aztec.js@3.0.0-nightly.20251231 @aztec/accounts@3.0.0-nightly.20251231 @aztec/noir-contracts.js@3.0.0-nightly.20251231 @aztec/test-wallet@3.0.0-nightly.20251231

Aztec.js assumes your project is using ESM, so make sure you add "type": "module" to package.json. You probably also want at least a start script. For example:

{
"type": "module",
"scripts": {
"start": "tsx index.ts"
}
}

Connecting to the local network

Now let's connect to the Aztec local network and set up test accounts.

Step 1: Start the Aztec Local Network

In a separate terminal, run:

aztec start --local-network

Keep this terminal running throughout the tutorial.

Step 2: Create the index.ts file

Create an index.ts file in the root of your project with the following code. This connects to the local network and imports test accounts (Alice and Bob):

setup
import { createAztecNodeClient } from "@aztec/aztec.js/node";
import { TestWallet } from "@aztec/test-wallet/server";
import { getInitialTestAccountsData } from "@aztec/accounts/testing";

const nodeUrl = "http://localhost:8080";
const node = createAztecNodeClient(nodeUrl);
const wallet = await TestWallet.create(node);

const [alice, bob] = await getInitialTestAccountsData();
await wallet.createSchnorrAccount(alice.secret, alice.salt);
await wallet.createSchnorrAccount(bob.secret, bob.salt);
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L1-L13

Step 3: Verify the script runs

Run the script to make sure everything is set up correctly:

yarn start

If there are no errors, you're ready to continue. For more details on connecting to the local network, see this guide.

Deploy the token contract

Now that we have our accounts loaded, let's deploy a pre-compiled token contract from the Aztec library. You can find the full code for the contract here (GitHub link).

Add the following to index.ts to import the contract and deploy it with Alice as the admin:

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

const token = await TokenContract.deploy(
wallet,
alice.address,
"TokenName",
"TKN",
18
)
.send({ from: alice.address })
.deployed();
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L15-L27

Mint and transfer

Let's go ahead and have Alice mint herself some tokens, in private:

mint
await token.methods
.mint_to_private(alice.address, 100)
.send({ from: alice.address })
.wait();
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L29-L34

Let's check both Alice's and Bob's balances now:

check_balances
let aliceBalance = await token.methods
.balance_of_private(alice.address)
.simulate({ from: alice.address });
console.log(`Alice's balance: ${aliceBalance}`);
let bobBalance = await token.methods
.balance_of_private(bob.address)
.simulate({ from: bob.address });
console.log(`Bob's balance: ${bobBalance}`);
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L36-L45

Alice should have 100 tokens, while Bob has none yet.

Great! Let's have Alice transfer some tokens to Bob, also in private:

transfer
await token.methods
.transfer(bob.address, 10)
.send({ from: alice.address })
.wait();
bobBalance = await token.methods
.balance_of_private(bob.address)
.simulate({ from: bob.address });
console.log(`Bob's balance: ${bobBalance}`);
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L47-L56

Bob should now see 10 tokens in his balance.

Other cool things

Say that Alice is nice and wants to set Bob as a minter. Even though it's a public function, it can be called in a similar way:

set_minter
await token.methods
.set_minter(bob.address, true)
.send({ from: alice.address })
.wait();
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L58-L63

Bob is now the minter, so he can mint some tokens to himself:

bob_mints
await token.methods
.mint_to_private(bob.address, 100)
.send({ from: bob.address })
.wait();
bobBalance = await token.methods
.balance_of_private(bob.address)
.simulate({ from: bob.address });
console.log(`Bob's balance: ${bobBalance}`);
Source code: docs/examples/ts/aztecjs_getting_started/index.ts#L65-L74
info

Have a look at the contract source. Notice is that the mint_to_private function we used above actually starts a partial note. This allows the total balance to increase while keeping the recipient private! How cool is that?