Contract deployment
Deploy smart contracts to the Stacks blockchain
Overview
Contract deployment creates new smart contracts on the blockchain. Stacks.js provides tools to compile, deploy, and verify Clarity contracts programmatically. Deployments can be simple single contracts or complex multi-contract systems with dependencies.
Basic contract deployment
Deploy a simple smart contract:
import {makeContractDeploy,broadcastTransaction,AnchorMode} from '@stacks/transactions';import { StacksTestnet } from '@stacks/network';import { readFileSync } from 'fs';async function deployContract() {const network = new StacksTestnet();// Read contract source codeconst contractSource = readFileSync('./contracts/my-contract.clar', 'utf-8');const txOptions = {contractName: 'my-contract',codeBody: contractSource,senderKey: 'your-private-key',network,anchorMode: AnchorMode.Any,fee: 10000, // Higher fee for deployment};const transaction = await makeContractDeploy(txOptions);const broadcastResponse = await broadcastTransaction(transaction, network);console.log('Contract deployed!');console.log('Transaction ID:', broadcastResponse.txid);console.log('Contract address:', `${senderAddress}.${txOptions.contractName}`);}
Deploying with initialization
Deploy contracts that require initialization:
// Contract with configurable constantsconst contractWithConfig = `(define-constant contract-owner tx-sender)(define-data-var token-name (string-ascii 32) "${tokenName}")(define-data-var token-symbol (string-ascii 10) "${tokenSymbol}")(define-data-var token-decimals uint u${decimals})(define-public (get-name)(ok (var-get token-name)))`;async function deployConfigurableContract(tokenName: string,tokenSymbol: string,decimals: number) {// Inject configuration into contractconst contractSource = contractWithConfig.replace('${tokenName}', tokenName).replace('${tokenSymbol}', tokenSymbol).replace('${decimals}', decimals.toString());const txOptions = {contractName: `${tokenSymbol.toLowerCase()}-token`,codeBody: contractSource,senderKey: 'your-private-key',network: new StacksTestnet(),anchorMode: AnchorMode.Any,};const transaction = await makeContractDeploy(txOptions);return broadcastTransaction(transaction, network);}
Multi-contract deployment
Deploy multiple related contracts:
async function deployContractSystem() {const network = new StacksTestnet();const deployerKey = 'your-private-key';// Step 1: Deploy core contractconst coreContract = readFileSync('./contracts/core.clar', 'utf-8');const coreTx = await makeContractDeploy({contractName: 'core',codeBody: coreContract,senderKey: deployerKey,network,anchorMode: AnchorMode.Any,});const coreResult = await broadcastTransaction(coreTx, network);await waitForConfirmation(coreResult.txid, network);// Step 2: Deploy token contract that depends on coreconst tokenContract = readFileSync('./contracts/token.clar', 'utf-8');const tokenTx = await makeContractDeploy({contractName: 'token',codeBody: tokenContract,senderKey: deployerKey,network,anchorMode: AnchorMode.Any,nonce: 1, // Increment nonce for sequential deployment});const tokenResult = await broadcastTransaction(tokenTx, network);await waitForConfirmation(tokenResult.txid, network);// Step 3: Deploy governance contractconst govContract = readFileSync('./contracts/governance.clar', 'utf-8');const govTx = await makeContractDeploy({contractName: 'governance',codeBody: govContract,senderKey: deployerKey,network,anchorMode: AnchorMode.Any,nonce: 2,});const govResult = await broadcastTransaction(govTx, network);return {core: coreResult.txid,token: tokenResult.txid,governance: govResult.txid,};}
Contract verification
Verify deployed contract matches source:
import { cvToJSON, deserializeCV } from '@stacks/transactions';async function verifyDeployedContract(contractAddress: string,contractName: string,expectedSource: string,network: StacksNetwork) {// Fetch deployed contract infoconst response = await fetch(`${network.coreApiUrl}/v2/contracts/source/${contractAddress}/${contractName}`);const contractInfo = await response.json();// Compare source codeif (contractInfo.source !== expectedSource) {throw new Error('Contract source mismatch!');}// Verify contract interfaceconst interfaceResponse = await fetch(`${network.coreApiUrl}/v2/contracts/interface/${contractAddress}/${contractName}`);const contractInterface = await interfaceResponse.json();return {verified: true,source: contractInfo.source,interface: contractInterface,deploymentTx: contractInfo.tx_id,};}
Deployment with post-conditions
Add safety constraints to deployment:
import {makeContractDeploy,makeStandardSTXPostCondition,FungibleConditionCode} from '@stacks/transactions';async function deployWithConstraints() {const deploymentCost = 50000; // Estimated deployment cost// Ensure deployment doesn't exceed expected costconst postConditions = [makeStandardSTXPostCondition(senderAddress,FungibleConditionCode.LessEqual,deploymentCost),];const txOptions = {contractName: 'expensive-contract',codeBody: contractSource,senderKey: 'your-private-key',network: new StacksTestnet(),postConditions,anchorMode: AnchorMode.Any,fee: deploymentCost,};const transaction = await makeContractDeploy(txOptions);return broadcastTransaction(transaction, network);}
Deployment patterns
Common deployment patterns and utilities:
class ContractDeployer {constructor(private network: StacksNetwork,private deployerKey: string) {}async deployFromFile(contractPath: string,contractName: string,options: Partial<DeployOptions> = {}) {const contractSource = readFileSync(contractPath, 'utf-8');// Validate contract syntaxawait this.validateContract(contractSource);// Estimate deployment costconst estimatedFee = await this.estimateDeploymentFee(contractSource);const txOptions = {contractName,codeBody: contractSource,senderKey: this.deployerKey,network: this.network,fee: estimatedFee,anchorMode: AnchorMode.Any,...options,};const transaction = await makeContractDeploy(txOptions);const result = await broadcastTransaction(transaction, this.network);// Wait for confirmationawait this.waitForDeployment(result.txid);return {txId: result.txid,contractId: `${this.getDeployerAddress()}.${contractName}`,};}private async validateContract(source: string) {// Basic validation - check for common issuesif (!source.includes('define-')) {throw new Error('Invalid contract: No definitions found');}// Check parentheses balanceconst openParens = (source.match(/\(/g) || []).length;const closeParens = (source.match(/\)/g) || []).length;if (openParens !== closeParens) {throw new Error('Invalid contract: Unbalanced parentheses');}}private async estimateDeploymentFee(source: string): Promise<number> {// Rough estimation based on contract sizeconst baseRate = 100; // microSTX per byteconst contractSize = new TextEncoder().encode(source).length;const estimatedFee = contractSize * baseRate;// Add buffer for safetyreturn Math.ceil(estimatedFee * 1.2);}private getDeployerAddress(): string {// Derive address from private key// Implementation depends on your key managementreturn 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM';}private async waitForDeployment(txId: string) {// Implementation similar to waitForConfirmation}}
Upgradeable contracts
Deploy contracts with upgrade mechanisms:
// Version 1 contractconst v1Contract = `(define-data-var contract-version uint u1)(define-data-var migration-contract (optional principal) none)(define-public (set-migration-contract (new-contract principal))(begin(asserts! (is-eq tx-sender contract-owner) (err u401))(var-set migration-contract (some new-contract))(ok true)))(define-read-only (get-migration-contract)(var-get migration-contract))`;// Deploy V2 and migrateasync function deployUpgrade() {// Deploy V2 contractconst v2Result = await deployContract('contract-v2', v2Source);// Set migration in V1const migrationTx = await makeContractCall({contractAddress: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM',contractName: 'contract-v1',functionName: 'set-migration-contract',functionArgs: [contractPrincipalCV('ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM', 'contract-v2')],senderKey: 'your-private-key',network: new StacksTestnet(),anchorMode: AnchorMode.Any,});return broadcastTransaction(migrationTx, network);}
Testing deployments
Test contracts before mainnet deployment:
async function testDeployment() {const testnet = new StacksTestnet();const mocknet = new StacksMocknet();// Test on local mocknet firsttry {const mockResult = await deployContract('test-contract', source, mocknet);console.log('Mocknet deployment successful:', mockResult.txId);// Run integration testsawait runContractTests(mockResult.contractId, mocknet);// If tests pass, deploy to testnetconst testnetResult = await deployContract('test-contract', source, testnet);console.log('Testnet deployment successful:', testnetResult.txId);} catch (error) {console.error('Deployment test failed:', error);throw error;}}
Deployment checklist
Pre-deployment verification steps:
interface DeploymentChecklist {syntaxValid: boolean;testsPass: boolean;gasEstimated: boolean;securityAudited: boolean;postConditionsSet: boolean;}async function preDeploymentCheck(contractSource: string): Promise<DeploymentChecklist> {const checklist: DeploymentChecklist = {syntaxValid: false,testsPass: false,gasEstimated: false,securityAudited: false,postConditionsSet: false,};// Validate syntaxtry {await validateContract(contractSource);checklist.syntaxValid = true;} catch (error) {console.error('Syntax validation failed:', error);}// Run teststry {await runContractTests(contractSource);checklist.testsPass = true;} catch (error) {console.error('Tests failed:', error);}// Estimate gasconst gasEstimate = await estimateDeploymentFee(contractSource);checklist.gasEstimated = gasEstimate > 0;// Manual checkschecklist.securityAudited = await promptUser('Has the contract been audited?');checklist.postConditionsSet = contractSource.includes('post-condition');return checklist;}
Best practices
- Test thoroughly: Deploy to testnet before mainnet
- Estimate fees accurately: Deployment is more expensive than regular transactions
- Use clear naming: Contract names are permanent and global
- Include documentation: Add comments explaining contract functionality
- Plan for upgrades: Consider upgrade patterns from the start
Common issues
Deployment failures
// Handle common deployment errorstry {const result = await broadcastTransaction(deployTx, network);} catch (error: any) {if (error.message.includes('ContractAlreadyExists')) {console.error('Contract name already taken');} else if (error.message.includes('CodeTooLarge')) {console.error('Contract exceeds size limit');} else if (error.message.includes('BadContractSyntax')) {console.error('Contract has syntax errors');}}