Skip to main content
Version: 8.6.0

Deploy

Overview

In Starknet, deploying a smart contract involves two distinct phases:

  1. Declaring the contract class - Publishing the contract's logic and code on the network
  2. Deploying a contract instance - Creating a specific instance with its own storage and address

This two-phase deployment model is unique to Starknet and offers several advantages:

  • Cost efficiency: Multiple instances can reuse the same declared class
  • Storage optimization: Contract code is stored once per class
  • Upgradability: New instances can be deployed from existing classes

Contract Creation Process

Important Concepts
  • Contract Class: Contains the logic and code (identified by Class Hash)
  • Contract Instance: Contains the state/storage (identified by Contract Address)
  • Fees: Both declaration and deployment incur fees, paid by the declaring account

Deployment Methods Overview

Starknet.js provides two main approaches for contract deployment:

MethodUse CaseDeclare + DeployDeploy OnlyABI Handling
Contract.factory()Quick, streamlined deploymentsAuto-fetch or manual
Account methodsFine-grained controlManual

Choose the method that best fits your use case:

  • Contract.factory(): Best for most use cases, especially when deploying from existing class hashes
  • Account methods: Use when you need maximum control over the deployment process

Using Contract.factory() Static Method

The Contract.factory() static method provides a streamlined approach for both declaring and deploying contracts, or deploying from existing class hashes. It handles the entire deployment lifecycle: compiling constructor calldata, declaring the contract class, deploying an instance, and returning a ready-to-use Contract object.

This approach is useful when you need to:

  • Deploy contracts with automatic declaration and deployment
  • Deploy from existing class hashes with automatic ABI fetching
  • Ensure constructor argument validation against the contract ABI
  • Get a ready-to-use Contract instance immediately after deployment

Declare and Deploy Mode

Use this mode when you need to declare a new contract class and deploy an instance:

import { Contract } from 'starknet';

// Declare and deploy in one step
const myContract = await Contract.factory({
contract: compiledSierra, // Compiled Sierra contract
casm: compiledCasm, // Compiled CASM file
account: myAccount, // Deploying account
constructorCalldata: {
name: 'MyToken',
symbol: 'MTK',
decimals: 18,
initialSupply: 1000n * 10n ** 18n,
recipient: myAccount.address,
},
});

console.log('Contract deployed at:', myContract.address);
console.log('Class hash:', myContract.classHash);

// Contract is immediately ready to use
const balance = await myContract.balanceOf(myAccount.address);

Deploy-Only Mode

When you have an existing class hash (e.g., standard contracts like ERC20), you can deploy directly without declaring:

Deploy with ABI Fetched from Network

// ABI will be automatically fetched from the network
const myContract = await Contract.factory({
classHash: '0x1234...', // Existing class hash
account: myAccount,
constructorCalldata: {
name: 'MyToken',
symbol: 'MTK',
decimals: 18,
initialSupply: 1000n * 10n ** 18n,
recipient: myAccount.address,
},
});

// Contract is ready with automatically fetched ABI
const totalSupply = await myContract.totalSupply();

Deploy with Provided ABI (Faster)

// Provide ABI to skip network fetch
const myContract = await Contract.factory({
classHash: '0x1234...', // Existing class hash
abi: contractAbi, // Your contract ABI
account: myAccount,
constructorCalldata: {
name: 'MyToken',
symbol: 'MTK',
decimals: 18,
initialSupply: 1000n * 10n ** 18n,
recipient: myAccount.address,
},
});

Advanced Options

The factory method supports all deployment parameters:

const myContract = await Contract.factory({
classHash: '0x1234...',
abi: contractAbi, // Optional: will fetch from network if not provided
account: myAccount,
constructorCalldata: {
// Your constructor parameters
},
salt: '0xabcd...', // Optional: custom salt for address generation
unique: false, // Optional: set to false for predictable addresses
parseRequest: true, // Optional: enable/disable request parsing (default: true)
});

The factory method supports all deployment parameters:

const myContract = await Contract.factory({
classHash: '0x1234...',
abi: contractAbi, // Optional: will fetch from network if not provided
account: myAccount,
constructorCalldata: {
// Your constructor parameters
},
salt: '0xabcd...', // Optional: custom salt for address generation
unique: false, // Optional: set to false for predictable addresses
parseRequest: true, // Optional: enable/disable request parsing (default: true)
});

Error Handling

try {
const myContract = await Contract.factory({
classHash: '0x1234...',
account: myAccount,
constructorCalldata: constructorParams,
});
console.log('Deployed at:', myContract.address);
} catch (error) {
if (error.message.includes('Class hash not declared')) {
console.error('Class hash does not exist on the network');
} else if (error.message.includes('ABI not found')) {
console.error('Could not fetch ABI from network, provide it manually');
} else if (error.message.includes('Insufficient funds')) {
console.error('Account has insufficient funds for deployment');
} else {
console.error('Deployment failed:', error.message);
}
}
Recommended Approach

Contract.factory() is the recommended way to deploy and create contract instances in Starknet.js. It provides a streamlined API that handles both declaration and deployment, with automatic ABI fetching when deploying from existing class hashes.

Using Account Methods Directly

If you need more fine-grained control over the deployment process, you can use the Account methods directly.

Quick Start: declareAndDeploy()

The fastest way to get your contract on Starknet is using the declareAndDeploy() method, which handles both phases in one transaction.

import {
RpcProvider,
Account,
Contract,
json,
stark,
uint256,
shortString,
type RawCalldata,
type Calldata,
} from 'starknet';

// Assuming provider and account are already set up (see Account guide)
// Load compiled contract files
const compiledSierra = json.parse(
fs.readFileSync('./compiledContracts/test.contract_class.json').toString('ascii')
);
const compiledCasm = json.parse(
fs.readFileSync('./compiledContracts/test.compiled_contract_class.json').toString('ascii')
);

// 3. Declare & Deploy
const response = await myAccount.declareAndDeploy({
contract: compiledSierra,
casm: compiledCasm,
});

// 4. Create Contract Instance
const myContract = new Contract({
abi: compiledSierra.abi,
address: response.deploy.contract_address,
providerOrAccount: myProvider,
});

console.log('Contract Class Hash:', response.declare.class_hash);
console.log('Contract Address:', myContract.address);

Deploying from Existing Classes

If you want to deploy a new instance of an already declared contract class (e.g., a standard ERC20), use deployContract():

// 1. Setup (provider & account setup same as above)

// 2. Deploy using existing class hash
const existingClassHash = '0xff0378becffa6ad51c67ac968948dbbd110b8a8550397cf17866afebc6c17d';
const deployResponse = await myAccount.deployContract({
classHash: existingClassHash,
});

// 3. Wait for deployment
await myProvider.waitForTransaction(deployResponse.transaction_hash);

// 4. Get contract ABI and create instance
const { abi } = await myProvider.getClassByHash(existingClassHash);
if (!abi) throw new Error('Contract ABI not found');

const myContract = new Contract({
abi,
address: deployResponse.contract_address,
providerOrAccount: myProvider,
});
Simplified Alternative

For a more streamlined approach, consider using Contract.factory() which handles ABI fetching automatically:

const myContract = await Contract.factory({
classHash: existingClassHash,
account: myAccount,
// ABI will be fetched automatically from the network
});

Working with Constructors

Many contracts require constructor arguments during deployment. Here are the recommended ways to handle constructor parameters:

This method provides type safety and automatic validation against the contract's ABI:

// Example constructor with multiple parameter types
const myArray: RawCalldata = ['0x0a', 24, 36n];
const contractCallData = new CallData(compiledContractSierra.abi);

const constructorParams = contractCallData.compile('constructor', {
name: 'MyToken',
symbol: 'MTK',
decimals: 18,
initialSupply: 1000n * 10n ** 18n,
array: myArray,
});

const deployResponse = await myAccount.deployContract({
classHash: contractClassHash,
constructorCalldata: constructorParams,
});

2. Using CallData.compile (Simple Cases)

For straightforward constructors, you can use the simpler CallData.compile:

// Named parameters
const constructorParams = CallData.compile({
name: 'MyToken',
symbol: 'MTK',
decimals: 18,
});

// OR array format
const constructorParams = CallData.compile(['MyToken', 'MTK', 18]);
danger

Unlike myCalldata.compile, even the named parameters must match their order in the ABI since CallData.compile doesn't have access to the ABI to verify and enforce its constraints.

String Handling

For Cairo 2.4.0+, you can pass strings directly. For older versions, use:

shortString.splitLongString('Your long string here');

Declaring New Contract Classes

To only declare a new contract class without deployment, use declare():

const declareResponse = await myAccount.declare({
contract: compiledSierra,
casm: compiledCasm,
});

await myProvider.waitForTransaction(declareResponse.transaction_hash);
console.log('Class Hash:', declareResponse.class_hash);
Avoiding Duplicate Declarations

Use declareIfNot() to prevent errors when declaring an already existing contract class:

const declareResponse = await myAccount.declareIfNot({
contract: compiledSierra,
casm: compiledCasm,
});

Best Practices

  1. Always wait for transactions: Use myProvider.waitForTransaction() after deployments
  2. Error handling: Implement proper try-catch blocks for network issues
  3. Gas estimation: Consider estimating fees before deployment
  4. Contract verification: Verify your contract works as intended on Devnet and/or Testnet before deploying to Mainnet