Skip to main content

Overview

The deployContract action creator deploys a compiled WebAssembly (WASM) contract to a NEAR account. The contract must be compiled to WASM format before deployment.

Signature

deployContract(args: {
  wasmBytes: Uint8Array
} | {
  wasmBase64: string
}): DeployContractAction

Parameters

You must provide either wasmBytes OR wasmBase64 (not both):
wasmBytes
Uint8Array
The contract bytecode as a Uint8Array
wasmBase64
string
The contract bytecode as a base64-encoded string

Returns

A DeployContractAction object:
{
  actionType: 'DeployContract',
  wasmBytes: Uint8Array
}

Basic Example (Node.js)

import { deployContract } from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./path/to/contract.wasm');

const action = deployContract({ wasmBytes });

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    action
  }
});

Using Base64

import { deployContract } from '@near-api-ts/universal';

const wasmBase64 = 'AGFzbQEAAAABhYCAgAAA...'; // your base64 string

const action = deployContract({ wasmBase64 });

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    action
  }
});

Browser Example

import { deployContract } from '@near-api-ts/universal';

// From file input
const handleFileUpload = async (file: File) => {
  const wasmBytes = new Uint8Array(await file.arrayBuffer());
  
  const action = deployContract({ wasmBytes });
  
  await signer.executeTransaction({
    intent: {
      receiverAccountId: 'contract.near',
      action
    }
  });
};

// From fetch
const deployFromUrl = async (url: string) => {
  const response = await fetch(url);
  const wasmBytes = new Uint8Array(await response.arrayBuffer());
  
  const action = deployContract({ wasmBytes });
  
  await signer.executeTransaction({
    intent: {
      receiverAccountId: 'contract.near',
      action
    }
  });
};

Deploy and Initialize

Deploy a contract and call its initialization function:
import { 
  deployContract,
  functionCall,
  teraGas,
  near
} from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./contract.wasm');

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    actions: [
      deployContract({ wasmBytes }),
      functionCall({
        functionName: 'init',
        functionArgs: {
          owner_id: 'alice.near',
          metadata: {
            name: 'My Contract',
            version: '1.0.0'
          }
        },
        gasLimit: teraGas('50'),
        attachedDeposit: near('0')
      })
    ]
  }
});

Create Account and Deploy

Create a new account and deploy a contract to it:
import { 
  createAccount,
  transfer,
  deployContract,
  addFullAccessKey,
  near,
  randomEd25519KeyPair
} from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./contract.wasm');
const newKeyPair = randomEd25519KeyPair();

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'new-contract.alice.near',
    actions: [
      createAccount(),
      transfer({ amount: near('5') }),
      addFullAccessKey({ publicKey: newKeyPair.publicKey }),
      deployContract({ wasmBytes })
    ]
  }
});

Update Existing Contract

import { deployContract } from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./updated-contract.wasm');

const action = deployContract({ wasmBytes });

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    action
  }
});

console.log('Contract updated successfully');
Updating a contract replaces the code but preserves the state. Ensure your new contract is compatible with the existing state structure.

Deploy with Migration

import { 
  deployContract,
  functionCall,
  teraGas
} from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./contract-v2.wasm');

await signer.executeTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    actions: [
      deployContract({ wasmBytes }),
      functionCall({
        functionName: 'migrate',
        functionArgs: { version: 2 },
        gasLimit: teraGas('100')
      })
    ]
  }
});

Check Contract Size

import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./contract.wasm');
const sizeInKB = wasmBytes.length / 1024;

console.log(`Contract size: ${sizeInKB.toFixed(2)} KB`);

if (sizeInKB > 4000) {
  console.warn('Contract is very large. Consider optimization.');
}
NEAR has no strict size limit for contracts, but larger contracts cost more gas to deploy and may hit transaction gas limits (300 TGas). Most contracts should be under 1-2 MB.

Error Handling

import { deployContract, isNatError } from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

try {
  const wasmBytes = await readFile('./contract.wasm');
  
  const action = deployContract({ wasmBytes });
  
  await signer.executeTransaction({
    intent: {
      receiverAccountId: 'contract.near',
      action
    }
  });
  
  console.log('Contract deployed successfully');
} catch (error) {
  if (isNatError(error)) {
    if (error.kind === 'CreateAction.DeployContract.Args.InvalidSchema') {
      console.error('Invalid WASM format');
    } else if (error.kind === 'MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow') {
      console.error('Insufficient balance for deployment');
    }
  }
}

Safe Variant

import { safeDeployContract } from '@near-api-ts/universal';
import { readFile } from 'fs/promises';

const wasmBytes = await readFile('./contract.wasm');

const actionResult = safeDeployContract({ wasmBytes });

if (!actionResult.ok) {
  console.error('Invalid deploy action:', actionResult.error.kind);
  return;
}

const result = await signer.safeExecuteTransaction({
  intent: {
    receiverAccountId: 'contract.near',
    action: actionResult.value
  }
});

if (result.ok) {
  console.log('Deployed:', result.value.transactionHash);
} else {
  console.error('Deployment failed:', result.error.kind);
}

Estimating Deployment Cost

const wasmBytes = await readFile('./contract.wasm');
const sizeInBytes = wasmBytes.length;

// Rough estimate: ~1 TGas per 10KB of contract size
const estimatedGas = Math.ceil(sizeInBytes / 10240);
console.log(`Estimated gas needed: ~${estimatedGas} TGas`);

// Storage cost: ~100 bytes per NEAR token
const storageCost = sizeInBytes / 100000;
console.log(`Estimated storage cost: ~${storageCost.toFixed(4)} NEAR`);

Common Errors

  • CreateAction.DeployContract.Args.InvalidSchema - Invalid WASM format or missing required parameter
  • CreateAction.DeployContract.Internal - Internal error during action creation
  • MemorySigner.ExecuteTransaction.Rpc.Transaction.Signer.Balance.TooLow - Insufficient account balance
  • Transaction gas limit exceeded - Contract too large or not optimized

Best Practices

DO:
  • Compile contracts in release mode (--release)
  • Optimize WASM with wasm-opt before deployment
  • Test deployments on testnet first
  • Keep contracts small and modular
  • Version your contracts
  • Plan state migration for updates
DON’T:
  • Don’t deploy debug builds to production
  • Don’t update contracts without considering state compatibility
  • Don’t forget to initialize after deployment
  • Don’t deploy without testing thoroughly

Optimization Tips

# Compile contract in release mode
cargo build --target wasm32-unknown-unknown --release

# Optimize with wasm-opt (from binaryen)
wasm-opt -Oz -o optimized.wasm target/wasm32-unknown-unknown/release/contract.wasm

# Check size
ls -lh optimized.wasm

See Also