CIP-0103 Provider
CIP-0103 is the Canton dApp Standard — the specification for how wallets and dApps communicate on the Canton Network. PartyLayer fully implements CIP-0103 with 10 methods, 4 events, and a typed error model.
Two Integration Paths
PartyLayer supports two ways to integrate:
- Adapter SDK (recommended) — Use
PartyLayerKit and React hooks. The SDK abstracts CIP-0103 behind a higher-level API. - Native CIP-0103 Provider — Work directly with the CIP-0103 provider interface. Useful for non-React apps or when you need raw CIP-0103 compliance.
Provider API
The CIP-0103 provider uses a JSON-RPC–style request() method:
interface CIP0103Provider {
request<T>(args: { method: string; params?: unknown }): Promise<T>;
on<T>(event: string, listener: (data: T) => void): CIP0103Provider;
emit<T>(event: string, ...args: T[]): boolean;
removeListener<T>(event: string, listener: (data: T) => void): CIP0103Provider;
}
10 Mandatory Methods
connect
Establish a connection to the wallet.
const result = await provider.request<CIP0103ConnectResult>({
method: 'connect',
});
// → { isConnected: true, isNetworkConnected: true }
disconnect
await provider.request({ method: 'disconnect' });
isConnected
const status = await provider.request<CIP0103ConnectResult>({
method: 'isConnected',
});
// → { isConnected: true/false }
status
Get full provider status including connection, provider info, network, and session.
const status = await provider.request<CIP0103StatusEvent>({
method: 'status',
});
// → { connection: {...}, provider: { id, version, providerType }, network?: {...}, session?: {...} }
getActiveNetwork
Get the active network in CAIP-2 format.
const network = await provider.request<CIP0103Network>({
method: 'getActiveNetwork',
});
// → { networkId: 'canton:da-mainnet', ledgerApi: '...', accessToken: '...' }
listAccounts
const accounts = await provider.request<CIP0103Account[]>({
method: 'listAccounts',
});
// → [{ primary: true, partyId: '...', status: 'allocated', ... }]
getPrimaryAccount
const account = await provider.request<CIP0103Account>({
method: 'getPrimaryAccount',
});
// → { primary: true, partyId: '...', publicKey: '...', status: 'allocated' }
signMessage
const result = await provider.request<{ signature: string }>({
method: 'signMessage',
params: { message: 'Hello Canton!' },
});
// → { signature: '0x...' }
prepareExecute
Prepare and submit a Daml command for execution.
await provider.request({
method: 'prepareExecute',
params: {
commands: [{ templateId: '...', choiceId: '...', argument: {...} }],
},
});
ledgerApi
Proxy requests to the Canton Ledger API through the wallet.
const result = await provider.request<CIP0103LedgerApiResponse>({
method: 'ledgerApi',
params: {
requestMethod: 'GET',
resource: '/v1/parties',
},
});
4 Events
statusChanged
Emitted when the provider status changes.
provider.on('statusChanged', (status: CIP0103StatusEvent) => {
console.log('Connection:', status.connection.isConnected);
console.log('Provider:', status.provider.id);
});
accountsChanged
provider.on('accountsChanged', (accounts: CIP0103Account[]) => {
console.log('Accounts:', accounts.map(a => a.partyId));
});
txChanged
Transaction lifecycle events (pending → signed → executed or failed).
provider.on('txChanged', (event: CIP0103TxChangedEvent) => {
switch (event.status) {
case 'pending':
console.log('TX pending:', event.commandId);
break;
case 'signed':
console.log('TX signed:', event.payload.signature);
break;
case 'executed':
console.log('TX executed:', event.payload.updateId);
break;
case 'failed':
console.log('TX failed:', event.commandId);
break;
}
});
connected
Emitted when an async connect completes.
provider.on('connected', (result: CIP0103ConnectResult) => {
console.log('Async connect completed:', result.isConnected);
});
Provider Bridge
Wrap your PartyLayerClient as a CIP-0103 provider using asProvider():
import { createPartyLayer } from '@partylayer/sdk';
const client = createPartyLayer({
network: 'mainnet',
app: { name: 'My dApp' },
});
// Bridge to CIP-0103
const provider = client.asProvider();
// Now use standard CIP-0103 methods
const result = await provider.request({ method: 'connect' });
const accounts = await provider.request({ method: 'listAccounts' });
💡 Tip
Use the bridge when you need to expose a CIP-0103 compliant interface to third-party libraries or tools that expect a raw CIP-0103 provider.
Provider Discovery
import {
discoverInjectedProviders,
waitForProvider,
isCIP0103Provider,
} from '@partylayer/provider';
// Scan window.canton.* for all injected providers
const providers = discoverInjectedProviders();
// → [{ id: 'console', provider: CIP0103Provider }, ...]
// Wait for a specific provider to appear (returns null if not found)
const discovered = await waitForProvider('nightly', 5000);
if (discovered) {
console.log('Found:', discovered.id, discovered.provider);
}
// Duck-type check
if (isCIP0103Provider(window.canton?.console)) {
console.log('Console wallet is CIP-0103 compliant');
}
Network Utilities (CAIP-2)
Convert between PartyLayer network IDs and CAIP-2 format:
import { toCAIP2Network, fromCAIP2Network, isValidCAIP2 } from '@partylayer/provider';
toCAIP2Network('mainnet'); // → { networkId: 'canton:da-mainnet' }
fromCAIP2Network('canton:da-testnet'); // → 'testnet'
isValidCAIP2('canton:da-mainnet'); // → true
isValidCAIP2('not-a-network'); // → false (no colon separator)
Error Model
CIP-0103 uses ProviderRpcError with EIP-1193 and EIP-1474 numeric error codes:
EIP-1193 Codes
// 4001 — User Rejected
// 4100 — Unauthorized
// 4200 — Unsupported Method
// 4900 — Disconnected
// 4901 — Chain Disconnected
EIP-1474 Codes
// -32700 — Parse Error
// -32600 — Invalid Request
// -32601 — Method Not Found
// -32602 — Invalid Params
// -32603 — Internal Error
// -32000 — Invalid Input
// -32003 — Transaction Rejected
// -32005 — Limit Exceeded
Error Mapping
Convert between PartyLayer errors and CIP-0103 RPC errors:
import { toProviderRpcError, toPartyLayerError } from '@partylayer/provider';
// PartyLayer → CIP-0103
const rpcError = toProviderRpcError(new UserRejectedError('connect'));
// → ProviderRpcError { code: 4001, message: 'User Rejected' }
// CIP-0103 → PartyLayer
const plError = toPartyLayerError(rpcError);
// → UserRejectedError { code: 'USER_REJECTED' }