CIP-0103: the Canton dApp Standard, implemented
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.
For which wallets declare CIP-0103 native support, with the evidence recorded for each, see the Canton wallet directory.
Two Integration Paths
PartyLayer supports two ways to integrate:
- Adapter SDK (recommended), Use
PartyLayerKitand 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:
10 Mandatory Methods
connect
Establish a connection to the wallet.
disconnect
isConnected
status
Get full provider status including connection, provider info, network, and session.
getActiveNetwork
Get the active network in CAIP-2 format.
listAccounts
getPrimaryAccount
signMessage
prepareExecute
Prepare and submit a Daml command for execution.
ledgerApi
Proxy requests to the Canton Ledger API through the wallet.
4 Events
statusChanged
Emitted when the provider status changes.
accountsChanged
txChanged
Transaction lifecycle events (pending → signed → executed or failed).
connected
Emitted when an async connect completes.
Where the upstream client and this SDK diverge
This section exists because we are probably the only people positioned to notice. We wrote the conformance suite and took wallets through it, so the gaps below come from comparing three things that should agree and do not entirely: the specification's method table, the type declarations shipped by @canton-network/core-wallet-dapp-rpc-client (read at version 1.11.0), and this SDK.
A documented divergence is more useful than a false consensus. None of these is a defect in anyone's code; they are places where the standard leaves room and implementations took different readings.
1. prepareExecuteAndWait is in the client, not in the spec's method table
The upstream RpcTypes map declares prepareExecuteAndWait as a first-class key returning { tx: TxChangedExecutedEvent }. The specification's synchronous method table does not list it: there, prepareExecute returns void and the result arrives as a txChanged event.
This SDK uses it. The WalletConnect and Send adapters both call it, because a single call that returns the executed transaction is far easier to build a UI around than a void call plus an event subscription. Our CIP0103_METHODS constant nevertheless lists ten names and omits it, so the constant tracks the spec while the adapters track the client. That is a real inconsistency on our side, not a reading of the standard.
2. messageSignature exists upstream and not here
Upstream declares a messageSignature key with three payload shapes, pending, signed and failed, mirroring the transaction lifecycle for message signing. It appears in neither the spec's event table nor this SDK. If you need to track a signature request through a multi-step flow, the upstream client can express it and our hooks cannot.
3. Events and methods share one map upstream
Upstream RpcTypes has fourteen keys. Ten are the spec's methods, and accountsChanged and txChanged sit alongside them as keys of the same map, each typed as a function returning its event payload. The spec lists those two as events, not methods.
This is our interpretive choice: we split them, exposing methods through request() and events through on(), because a dApp subscribes to an event and calls a method, and collapsing both into one map makes that distinction invisible at the call site. The upstream shape is a faithful description of the transport, where everything is a JSON-RPC exchange. Neither is wrong; they describe different layers.
4. statusChanged is a spec event with no upstream key
The spec lists statusChanged as an event carrying StatusEvent. Upstream has no such key: it exposes status as a method returning StatusEvent and leaves the change notification out of the typed map. We implement the event, following the spec.
5. connected only exists in the asynchronous variant
Our CIP0103_EVENTS constant lists four events, and the fourth, connected, comes from the spec's asynchronous dApp API, where connect returns a userUrl and the wallet emits connected once login completes. A purely synchronous provider never emits it.
Our choice, and a debatable one: the constant does not distinguish sync from async, so it over-declares for a synchronous wallet. We kept one list because a dApp does not know in advance which variant a wallet implements, and a subscription to an event that never fires is harmless. The cost is that the constant is not a conformance target for a synchronous provider.
6. completionOffset is dropped
Upstream's executed-transaction payload is { updateId, completionOffset }. Our TxReceipt has no field for an offset, so the SDK reads updateId and discards completionOffset. If you need the offset, reach for ledgerApi rather than the receipt.
@canton-network/core-wallet-dapp-rpc-client@1.11.0, which the standard's authors ship and nobody here wrote. Semantics come from the specification. Our own constants are corroboration, not the source: a page about a standard we did not author should not cite our implementation as evidence about the standard.Implementing CIP-0103 in a dApp
You have two options and they differ in how much of the standard you touch. Use the SDK and you never write a request() call; use the provider directly and you own the whole surface.
The SDK route, which is what most applications want: Quick Start has the working version. The provider route, when you are building something the SDK does not cover:
Two things worth doing that the standard does not force you to do. Check isNetworkConnected as well as isConnected, because they are separate flags with separate reason strings and a wallet that is open but has no network will satisfy the first and fail every subsequent call. And handle userUrl on the connect result even if you only expect synchronous wallets, since its presence is the only signal that you are talking to an asynchronous one.
Implementing CIP-0103 in a wallet
The obligation is narrower than it looks. A wallet must answer the ten methods, emit the three synchronous events, and use the error codes below. Everything else is transport.
- Answer every method, including ones you do not support. A method you cannot honour returns error
4200(Unsupported Method) or-32004. Silence, or a rejection with no code, is what breaks dApps: the caller cannot tell refusal from failure. - Announce yourself. Provider discovery is how a dApp finds you without a per-wallet adapter. See Generic bridge for the two discovery paths and what each requires.
- Emit txChanged for every phase you reach. The lifecycle is
pending,signed,executed,failed. A wallet that only emits the terminal state leaves a dApp unable to show progress, which is where the pressure to poll comes from. - If you are a remote or server-side wallet, implement the async variant. Return a
userUrlfromconnectandprepareExecute, and emitconnectedafter login. That is what the variant is for, and it avoids pretending a multi-step flow is synchronous.
Verifying compliance
We ship the conformance suite we used to take wallets through this standard. It runs against any CIP-0103 provider, not only ours, and it checks interface shape, that all ten mandatory methods are handled, event subscription, error shape, and lifecycle.
Results are grouped as interface, method, event, error and lifecycle, so a failure tells you which obligation you missed rather than only that something is wrong. What it does not do is tell you whether your wallet is correct: it checks that the interface is honoured, not that a signature is valid or a transaction reached the ledger.
Provider Bridge
Wrap your PartyLayerClient as a CIP-0103 provider using asProvider():
Provider Discovery
Network Utilities (CAIP-2)
Convert between PartyLayer network IDs and CAIP-2 format:
Error Model
CIP-0103 uses ProviderRpcError with EIP-1193 and EIP-1474 numeric error codes:
EIP-1193 Codes
EIP-1474 Codes
Error Mapping
Convert between PartyLayer errors and CIP-0103 RPC errors: