Skip to main content
Version: Devnet (v3.0.0-devnet.20251212)

Deploying Contracts

This guide shows you how to deploy compiled contracts to Aztec using the generated TypeScript interfaces.

Overview

Deploying a contract to Aztec involves publishing the contract class (the bytecode) and creating a contract instance at a specific address. The generated TypeScript classes handle this process through an API: you call deploy() with constructor arguments, send() with transaction options, and deployed() to wait for completion. The contract address is deterministically computed from the contract class, constructor arguments, salt, and deployer address.

Prerequisites

  • Compiled contract artifacts (see How to Compile)
  • Running Aztec local network
  • Funded wallet for deployment fees
  • TypeScript project set up

Generate TypeScript bindings

Compile and generate code

# Compile the contract
aztec compile

# Generate TypeScript interface
aztec codegen ./target/my_contract-MyContract.json -o src/artifacts
info

The codegen command creates a TypeScript class with typed methods for deployment and interaction. This provides type safety and autocompletion in your IDE.

Deploy a contract

Step 1: Import and connect

import { MyContract } from "./artifacts/MyContract";
About wallets and accounts

In the examples below, wallet refers to a Wallet instance that manages keys and signs transactions. The from option in send() specifies which account pays for the transaction. This account must be registered in the wallet and have sufficient fee juice. On a local network, test accounts are pre-funded; on testnet, you typically use sponsored fees.

Step 2: Deploy the contract

How you deploy depends on how you pay for it. When paying using an account's fee juice (like a test account on the local network):

// Deploy with constructor arguments
const contract = await MyContract.deploy(
wallet,
constructorArg1,
constructorArg2
)
.send({ from: testAccount.address })
.deployed();

On testnet, you likely won't have funds in testAccount to pay for fee juice. Instead, pay fees using the Sponsored Fee Payment Contract method:

const contract = await MyContract.deploy(
wallet,
constructorArg1,
constructorArg2
)
.send({ from: alice.address, fee: { paymentMethod: sponsoredPaymentMethod } })
.deployed();

Here's a complete example:

const owner = defaultAccountAddress;
const contract = await StatefulTestContract.deploy(wallet, owner, 42)
.send({ from: defaultAccountAddress })
.deployed();

Use deployment options

Deploy with custom salt

By default, the deployment's salt is random, but you can specify it (for example, if you want to get a deterministic address):

import { Fr } from "@aztec/aztec.js/fields";

const salt = Fr.random();

const contract = await MyContract.deploy(wallet, arg1, arg2)
.send({
from: testAccount.address,
contractAddressSalt: salt,
})
.deployed();

Deploy universally

Deploy to the same address across networks by setting universalDeploy: true:

const opts = { universalDeploy: true, from: defaultAccountAddress };
const contract = await StatefulTestContract.deploy(wallet, owner, 42)
.send(opts)
.deployed();
info

Universal deployment excludes the sender from address computation, allowing the same address on any network with the same salt.

Skip initialization

Deploy without running the constructor:

const contract = await MyContract.deploy(wallet)
.send({
from: testAccount.address,
skipInitialization: true,
})
.deployed();

// Initialize later
await contract.methods
.initialize(arg1, arg2)
.send({ from: testAccount.address })
.wait();

Calculate deployment address

Get address before deployment

import { Fr } from "@aztec/aztec.js/fields";

const salt = Fr.random();

// Calculate address without deploying
const deployMethod = MyContract.deploy(wallet, arg1, arg2);
const instance = await deployMethod.getInstance({ contractAddressSalt: salt });
const address = instance.address;

console.log(`Contract will deploy at: ${address}`);
warning

This is an advanced pattern. For most use cases, deploy the contract directly and get the address from the deployed instance.

Monitor deployment progress

Track deployment transaction

const deployTx = MyContract.deploy(wallet, arg1, arg2).send({
from: testAccount.address,
});

// Get transaction hash immediately
const txHash = await deployTx.getTxHash();
console.log(`Deployment tx: ${txHash}`);

// Wait for the transaction to be mined
const receipt = await deployTx.wait();
console.log(`Deployed in block ${receipt.blockNumber}`);

// Get the deployed contract instance
const contract = await deployTx.deployed();
console.log(`Contract address: ${contract.address}`);

Deploy multiple contracts

Deploy a token contract

Here's an example deploying a TokenContract with constructor arguments for admin, name, symbol, and decimals:

const owner = defaultAccountAddress;
const token = await TokenContract.deploy(wallet, owner, "TOKEN", "TKN", 18)
.send({ from: defaultAccountAddress })
.deployed();

Deploy contracts with dependencies

When one contract depends on another, deploy them sequentially and pass the first contract's address:

// Deploy first contract
const token = await TokenContract.deploy(
wallet,
admin.address,
"MyToken",
"MTK",
18
)
.send({ from: admin.address })
.deployed();

// Deploy second contract with reference to first
const vault = await VaultContract.deploy(wallet, token.address)
.send({ from: admin.address })
.deployed();

Deploy contracts in parallel

// Start all deployments simultaneously
const deployments = [
Contract1.deploy(wallet, arg1).send({ from: deployer.address }),
Contract2.deploy(wallet, arg2).send({ from: deployer.address }),
Contract3.deploy(wallet, arg3).send({ from: deployer.address }),
];

// Wait for all to complete
const receipts = await Promise.all(deployments.map((d) => d.wait()));

// Get deployed contract instances
const contracts = await Promise.all(deployments.map((d) => d.deployed()));
Parallel deployment considerations

Parallel deployment is faster, but transactions from the same account share a nonce sequence. The wallet handles nonce assignment automatically, but if one deployment fails, subsequent deployments may also fail due to nonce gaps. For reliable parallel deployments:

  • Use separate accounts for each deployment, or
  • Handle failures gracefully and retry with fresh nonces
  • Consider using BatchCall to bundle multiple operations into a single transaction (see below)

Deploy with BatchCall

Use BatchCall to bundle a deployment with other calls into a single transaction. This is useful when you need to deploy a contract and immediately call methods on it:

import { BatchCall } from "@aztec/aztec.js";

const owner = defaultAccountAddress;
// Create a contract instance and make the PXE aware of it
const deployMethod = StatefulTestContract.deploy(wallet, owner, 42);
const contract = await deployMethod.register();

// Batch deployment and a public call into the same transaction
const publicCall = contract.methods.increment_public_value(owner, 84);
await new BatchCall(wallet, [deployMethod, publicCall])
.send({ from: defaultAccountAddress })
.wait();

Verify deployment

Check contract registration

Query the wallet to verify the contract was deployed and its class is published:

const metadata = await wallet.getContractMetadata(contract.address);
const isPublished = (
await wallet.getContractClassMetadata(
metadata.contractInstance!.currentContractClassId
)
).isContractClassPubliclyRegistered;

The getContractMetadata method returns:

  • contractInstance - The contract instance details (if found)
  • isContractInitialized - Whether the constructor has been called
  • isContractPublished - Whether the contract class is publicly registered

Verify contract is callable

try {
// Try calling a view function
const result = await contract.methods
.get_version()
.simulate({ from: testAccount.address });
console.log("Contract is callable, version:", result);
} catch (error) {
console.error("Contract not accessible:", error.message);
}

Register deployed contracts

Add existing contract to wallet

If a contract was deployed by another account:

const contract = await MyContract.at(contractAddress, wallet);

// Register the contract with the wallet
// The registerContract method takes positional parameters:
// - instance: ContractInstanceWithAddress (required)
// - artifact: ContractArtifact (optional)
// - secretKey: Fr (optional)
await wallet.registerContract(contract.instance, MyContract.artifact);
warning

You need the exact deployment parameters (salt, initialization hash, etc.) to correctly register an externally deployed contract. If you don't have access to the contract instance, you can reconstruct it:

import { getContractInstanceFromInstantiationParams } from "@aztec/stdlib/contract";
import { PublicKeys } from "@aztec/stdlib/keys";

const instance = await getContractInstanceFromInstantiationParams(
MyContract.artifact,
{
publicKeys: PublicKeys.default(),
constructorArtifact: "constructor", // or the initializer function name
constructorArgs: [arg1, arg2],
deployer: deployerAddress,
salt: deploymentSalt,
}
);

await wallet.registerContract(instance, MyContract.artifact);

Next steps