Skip to content

Canton Contracts

Working with Daml smart contracts through the SDK.


Overview

The SDK provides generic operations for any Daml template:

OperationDescription
QueryFind contracts by template
CreateCreate new contract instances
ExerciseExecute choices on contracts

Prerequisites

Before using Canton features:

  1. DAR package deployed to the Canton participant
  2. User has party ID assigned by admin
  3. Package ID known for template references

Check Canton Access

typescript
const user = await sdk.getUser();

if (!user?.partyId) {
  throw new Error('Canton access required');
}

console.log('Party ID:', user.partyId);

Query Contracts

Find contracts visible to the user:

typescript
interface Position {
  owner: string;
  poolId: string;
  shares: string;
}

const TEMPLATE = 'abc123...#DeFi:Position';

// Query all positions
const positions = await sdk.cantonQuery<Position>({
  templateId: TEMPLATE,
});

// Query with filter
const myPositions = await sdk.cantonQuery<Position>({
  templateId: TEMPLATE,
  filter: { owner: user.partyId },
});

// Access typed payload
myPositions.forEach(pos => {
  console.log(`Pool: ${pos.payload.poolId}`);
  console.log(`Shares: ${pos.payload.shares}`);
});

Response Structure

typescript
interface CantonContract<T> {
  contractId: string;
  templateId: string;
  payload: T;
  createdAt?: string;
  signatories?: string[];
  observers?: string[];
}

Create Contracts

Create new contract instances:

typescript
const result = await sdk.cantonCreate({
  templateId: 'abc123...#Orders:LimitOrder',
  payload: {
    trader: user.partyId,
    inputToken: 'ETH',
    outputToken: 'USDC',
    inputAmount: '1.0',
    minOutput: '3000.0',
    expiry: new Date(Date.now() + 86400000).toISOString(),
  },
});

console.log('Created:', result.contractId);

Exercise Choices

Execute a choice on a contract:

typescript
// Simple exercise
await sdk.cantonExercise({
  contractId: order.contractId,
  templateId: 'abc123...#Orders:LimitOrder',
  choice: 'Cancel',
  argument: {},
});

// Exercise with arguments and typed result
interface FillResult {
  outputAmount: string;
  filledAt: string;
}

const result = await sdk.cantonExercise<FillResult>({
  contractId: order.contractId,
  templateId: 'abc123...#Orders:LimitOrder',
  choice: 'Fill',
  argument: {
    filler: fillerPartyId,
    fillAmount: '1.0',
  },
});

console.log('Received:', result.exerciseResult.outputAmount);

Template ID Format

Template IDs follow this format:

{packageId}#{moduleName}:{templateName}

Examples:

  • abc123...#Main:Asset
  • def456...#Finance.Orders:LimitOrder

Store as Constants

typescript
const PACKAGE_ID = import.meta.env.VITE_PACKAGE_ID;

const TEMPLATES = {
  POSITION: `${PACKAGE_ID}#DeFi:Position`,
  ORDER: `${PACKAGE_ID}#DeFi:Order`,
  POOL: `${PACKAGE_ID}#DeFi:Pool`,
};

Complete Example

typescript
const PACKAGE_ID = 'abc123...';
const POSITION_TEMPLATE = `${PACKAGE_ID}#DeFi:Position`;

interface PositionPayload {
  owner: string;
  poolId: string;
  shares: string;
}

// Get user positions
async function getPositions() {
  const user = await sdk.getUser();

  return sdk.cantonQuery<PositionPayload>({
    templateId: POSITION_TEMPLATE,
    filter: { owner: user.partyId },
  });
}

// Add to position
async function addLiquidity(poolId: string, amount: string) {
  const user = await sdk.getUser();

  const result = await sdk.cantonCreate({
    templateId: POSITION_TEMPLATE,
    payload: {
      owner: user.partyId,
      poolId,
      shares: amount,
    },
  });

  await sdk.refresh();
  return result.contractId;
}

// Withdraw from position
async function withdraw(contractId: string, amount: string) {
  const result = await sdk.cantonExercise<{ withdrawn: string }>({
    contractId,
    templateId: POSITION_TEMPLATE,
    choice: 'Withdraw',
    argument: { amount },
  });

  await sdk.refresh();
  return result.exerciseResult.withdrawn;
}

Package Distribution

If your app uses custom Daml templates, provide an install endpoint:

/api/package Endpoint

typescript
export const onRequestGet = async (context) => {
  return Response.json({
    name: 'My DeFi Protocol',
    packageId: context.env.PACKAGE_ID,
    darUrl: `${new URL(context.request.url).origin}/api/package/download`,
    templates: [
      'DeFi:Position',
      'DeFi:Order',
      'DeFi:Pool',
    ],
    version: '1.0.0',
  });
};

/api/package/download Endpoint

typescript
export const onRequestGet = async (context) => {
  const dar = await context.env.ASSETS.fetch(
    new URL('/packages/protocol.dar', context.request.url)
  );

  return new Response(dar.body, {
    headers: {
      'Content-Type': 'application/octet-stream',
      'Content-Disposition': 'attachment; filename="protocol.dar"',
    },
  });
};

Wallet admins can then install your package with one click.


Error Handling

typescript
try {
  await sdk.cantonCreate({...});
} catch (error) {
  if (error.message.includes('Template not found')) {
    showError('Package not installed. Contact admin.');
  } else if (error.message.includes('authorization')) {
    showError('Not authorized for this operation.');
  } else if (error.message.includes('validation')) {
    showError('Invalid data provided.');
  } else {
    showError(`Canton error: ${error.message}`);
  }
}

Type Mapping

Daml TypeTypeScriptJSON
Textstring"hello"
Intnumber42
Decimalstring"100.50"
Boolbooleantrue
Partystring"alice::123..."
Optional aT | nullnull or value
[a]T[][...]
ContractId astring"00abc..."

Next Steps

Enterprise-grade multi-chain wallet infrastructure.