Canton Contracts
Working with Daml smart contracts through the SDK.
Overview
The SDK provides generic operations for any Daml template:
| Operation | Description |
|---|---|
| Query | Find contracts by template |
| Create | Create new contract instances |
| Exercise | Execute choices on contracts |
Prerequisites
Before using Canton features:
- DAR package deployed to the Canton participant
- User has party ID assigned by admin
- 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:Assetdef456...#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 Type | TypeScript | JSON |
|---|---|---|
Text | string | "hello" |
Int | number | 42 |
Decimal | string | "100.50" |
Bool | boolean | true |
Party | string | "alice::123..." |
Optional a | T | null | null or value |
[a] | T[] | [...] |
ContractId a | string | "00abc..." |
Next Steps
- SDK Reference - Complete API
- Transactions - Multi-chain signing
