The Canister Plugin provides comprehensive canister management capabilities for the ICP Agent Kit. It enables creating, deploying, managing, and interacting with canisters on the Internet Computer Protocol.
The Canister Plugin requires the @dfinity/ic-management package and integrates with the IC Management Canister for all operations.
The Canister Plugin now features improved TypeScript support with strict type checking, better error handling, and full compatibility with the latest @dfinity packages.

Features

Lifecycle Management

Create, start, stop, and delete canisters

Code Deployment

Install, upgrade, and uninstall WASM modules

Settings Control

Manage controllers, allocations, and thresholds

Cycles Monitoring

Track cycles balance and burn rates

Quick Start

import { ICPAgent } from 'icp-agent-kit';

const agent = new ICPAgent({
  network: 'local', // or 'mainnet'
});

await agent.initialize();
const canister = agent.canisterPlugin;

// Create a new canister
const { canisterId } = await canister.create({
  cycles: '5T', // 5 trillion cycles
});

console.log(`Created: ${canisterId.toText()}`);

Core Methods

Creating Canisters

The create method creates a new canister with specified settings:
const { canisterId } = await canister.create({
  settings: {
    controllers: [principal],          // Canister controllers
    computeAllocation: 10,            // 0-100 (percentage)
    memoryAllocation: 2_147_483_648n, // Bytes (2GB)
    freezingThreshold: 2_592_000n,    // Seconds (30 days)
  },
  cycles: '10T', // String or bigint
});

Deploying Code

Deploy WASM modules to canisters:
// Basic deployment
const result = await canister.deploy({
  wasmModule: new Uint8Array(wasmBytes),
  installArgs: new TextEncoder().encode('init args'),
});

console.log(`Deployed with hash: ${result.moduleHash}`);

// Install to existing canister
await canister.installCode({
  canisterId,
  wasmModule: wasmBytes,
  mode: 'install', // or 'upgrade', 'reinstall'
  arg: initArgs,
});
The plugin automatically handles large WASM modules (>2MB) using chunked upload.

Managing Lifecycle

Control canister execution state:
// Stop a running canister
await canister.stop(canisterId);

// Start a stopped canister  
await canister.start(canisterId);

// Delete and recover cycles
await canister.delete(canisterId);

// Uninstall code from a canister (keeps the canister but removes code)
await canister.uninstallCode(canisterId);

// Check if a canister exists
const exists = await canister.exists(canisterId);
console.log(`Canister exists: ${exists}`);

// Check if you're a controller
const isController = await canister.isController(canisterId);
console.log(`You are ${isController ? 'a' : 'not a'} controller`);

Querying Status

Get detailed canister information:
// Get comprehensive status
const status = await canister.getStatus(canisterId);
console.log(`
  Status: ${status.status}
  Cycles: ${canister.formatCycles(status.cycles)}
  Memory: ${status.memorySize} bytes
  Controllers: ${status.settings.controllers.join(', ')}
`);

// Get just cycles balance
const cycles = await canister.getCycles(canisterId);
console.log(`Balance: ${canister.formatCycles(cycles)}`);

Managing Controllers

Add or remove canister controllers:
// Add a new controller
await canister.addController(canisterId, newPrincipal);

// Remove a controller (cannot remove last one)
await canister.removeController(canisterId, oldPrincipal);

// Update multiple controllers at once
await canister.updateSettings(canisterId, {
  controllers: [principal1, principal2, principal3],
});

Advanced Features

Canister Upgrades

Upgrade canisters while preserving state:
await canister.upgrade({
  canisterId,
  wasmModule: newWasmBytes,
  upgradeArgs: new TextEncoder().encode('upgrade data'),
  mode: 'upgrade', // Preserves state
});

Batch Operations

Execute multiple operations efficiently:
const results = await canister.executeBatch([
  {
    type: 'deploy',
    params: { wasmModule: wasm1 },
    id: 'canister-1',
  },
  {
    type: 'deploy', 
    params: { wasmModule: wasm2 },
    id: 'canister-2',
  },
]);

Interacting with Deployed Canisters

The Canister Plugin provides powerful methods to interact with any deployed canister on the Internet Computer, whether it’s your own or a third-party canister.

Understanding IDL Factories

To interact with a canister, you need its Interface Description Language (IDL) factory. This TypeScript code describes the canister’s methods and types:
// Example IDL factory for a simple greeting canister
export const idlFactory = ({ IDL }) => {
  return IDL.Service({
    'greet': IDL.Func([IDL.Text], [IDL.Text], ['query']),
    'setGreeting': IDL.Func([IDL.Text], [], []),
  });
};
IDL factories are typically generated from .did files using dfx generate or the didc tool.

Making Query Calls

Query calls are fast, read-only operations that don’t modify canister state:
// Import your canister's IDL factory
import { idlFactory } from './your-canister.idl';

// Make a query call
const greeting = await canister.query({
  canisterId,
  methodName: 'greet',
  args: ['World'],
  idlFactory,
});

console.log(greeting); // "Hello, World!"

// Query with certificate verification (more secure but slower)
const verifiedBalance = await canister.query({
  canisterId,
  methodName: 'getBalance',
  args: [accountId],
  idlFactory,
  verifyCertificate: true,
});

Making Update Calls

Update calls modify canister state and go through consensus (2-4 seconds):
// Make an update call
await canister.call({
  canisterId,
  methodName: 'transfer',
  args: [recipientPrincipal, amount],
  idlFactory,
});

// Update call with complex arguments
const result = await canister.call({
  canisterId,
  methodName: 'createProfile',
  args: [{
    name: 'Alice',
    age: 30,
    preferences: ['coding', 'coffee'],
  }],
  idlFactory,
});

Working with Common Canisters

Here are examples for interacting with popular ICP canisters:

ICP Ledger Canister

import { idlFactory as ledgerIdl } from '@dfinity/ledger-icp';

// Check ICP balance
const balance = await canister.query({
  canisterId: 'ryjl3-tyaaa-aaaaa-aaaba-cai', // ICP Ledger
  methodName: 'account_balance',
  args: [{ account: accountIdentifier }],
  idlFactory: ledgerIdl,
});

ICRC-1 Token Canisters

// Get token balance
const tokenBalance = await canister.query({
  canisterId: tokenCanisterId,
  methodName: 'icrc1_balance_of',
  args: [{ owner: principal, subaccount: [] }],
  idlFactory: icrc1IdlFactory,
});

// Transfer tokens
const transferResult = await canister.call({
  canisterId: tokenCanisterId,
  methodName: 'icrc1_transfer',
  args: [{
    to: { owner: recipientPrincipal, subaccount: [] },
    amount: 1000000n, // Amount in token units
    fee: [],
    memo: [],
    created_at_time: [],
  }],
  idlFactory: icrc1IdlFactory,
});

Actor Caching and Performance

The plugin automatically caches actors for better performance:
// First call creates and caches the actor
await canister.query({ canisterId, methodName: 'method1', args: [], idlFactory });

// Subsequent calls reuse the cached actor (faster)
await canister.query({ canisterId, methodName: 'method2', args: [], idlFactory });

// Clear cache if needed (e.g., after canister upgrade)
canister.clearActorCache(canisterId);

Creating Typed Actors

For better type safety, you can create typed actors directly:
// Define your canister interface
interface MyCanister {
  greet: (name: string) => Promise<string>;
  setGreeting: (greeting: string) => Promise<void>;
  getStats: () => Promise<{ calls: bigint; users: number }>;
}

// Create a typed actor
const actor = canister.createActor<MyCanister>(
  canisterId,
  idlFactory
);

// Now you have full type safety
const greeting = await actor.greet('World');
const stats = await actor.getStats();
This approach provides:
  • Full TypeScript type checking
  • Better IDE autocomplete
  • Compile-time error detection
  • Direct method calls without strings

Error Handling for Canister Calls

Handle common errors when interacting with canisters:
try {
  const result = await canister.call({
    canisterId,
    methodName: 'riskyOperation',
    args: [param],
    idlFactory,
  });
} catch (error) {
  if (error.message.includes('trapped')) {
    console.error('Canister trapped:', error.message);
  } else if (error.code === 'METHOD_NOT_FOUND') {
    console.error('Method does not exist on canister');
  } else if (error.message.includes('out of cycles')) {
    console.error('Canister is out of cycles');
  }
}

Complete Interaction Example

// Deploy and interact with a hello world canister
import { idlFactory } from './hello-world/hello.idl';

// Deploy the canister
const { canisterId } = await canister.deploy({
  wasmModule: helloWorldWasm,
});

// Set a greeting (update call)
await canister.call({
  canisterId,
  methodName: 'setLastGreeted',
  args: ['ICP Developer'],
  idlFactory,
});

// Get the greeting (query call)
const lastGreeted = await canister.query({
  canisterId,
  methodName: 'getLastGreeted',
  args: [],
  idlFactory,
});

// Check metrics
const metrics = canister.getMetrics(canisterId);
if (metrics) {
  console.log(`
    Update calls: ${metrics.updateCalls}
    Query calls: ${metrics.queryCalls}
    Average response time: ${metrics.avgResponseTime}ms
    Error rate: ${(metrics.errorRate * 100).toFixed(2)}%
  `);
}
For a complete working example, see the hello-world-interaction example.

Snapshots

Create and manage canister snapshots:
// Take a snapshot
const snapshot = await canister.takeSnapshot(canisterId);
console.log(`Snapshot created with ID: ${snapshot.id}`);

// List all snapshots
const snapshots = await canister.listSnapshots(canisterId);
snapshots.forEach(snap => {
  console.log(`Snapshot ${snap.id} taken at ${snap.taken_at_timestamp}`);
});

// Restore from snapshot
await canister.loadSnapshot(canisterId, snapshot.id);

Canister Logs

Fetch canister execution logs:
// Get canister logs
const logs = await canister.fetchLogs(canisterId);
console.log(`Retrieved ${logs.canister_log_records.length} log entries`);

Utility Functions

The plugin provides helpful utilities:
// Format cycles for display
canister.formatCycles(5_000_000_000_000n); // "5T"

// Parse cycles from string
canister.parseCycles('1.5T'); // 1_500_000_000_000n

// Format canister settings
const formatted = canister.formatSettings(settings);

Error Handling

The plugin provides specific error types:
try {
  await canister.create({ cycles: '100M' }); // Too few
} catch (error) {
  if (error.code === 'INSUFFICIENT_CYCLES') {
    console.log('Need at least 1T cycles');
  }
}
Common error codes:
  • CANISTER_NOT_FOUND - Canister doesn’t exist
  • INSUFFICIENT_CYCLES - Not enough cycles
  • UNAUTHORIZED - Not a controller
  • INVALID_WASM - Invalid WASM module

Best Practices

Example: Complete Canister Lifecycle

// 1. Create canister
const { canisterId } = await canister.create({
  settings: {
    controllers: [myPrincipal],
    freezingThreshold: 2_592_000n, // 30 days
  },
  cycles: '5T',
});

// 2. Deploy code
await canister.installCode({
  canisterId,
  wasmModule: myWasm,
  mode: 'install',
});

// 3. Monitor status
const status = await canister.getStatus(canisterId);
console.log(`Cycles: ${canister.formatCycles(status.cycles)}`);

// 4. Upgrade code
await canister.upgrade({
  canisterId,
  wasmModule: newWasm,
  mode: 'upgrade',
});

// 5. Clean up
await canister.delete(canisterId);

Next Steps