@sigilry/dapp
@sigilry/dapp is the core package for CIP-103-compliant dApp ↔ wallet extension communication on Canton Network. It provides the CIP-103 provider interface (SpliceProvider), JSON-RPC client and server, transport abstractions, and Zod schemas generated from the CIP-103 OpenRPC spec.
Install
Section titled “Install”yarn add @sigilry/dappProvider usage (dApp)
Section titled “Provider usage (dApp)”The wallet extension injects a CIP-103 provider at window.canton. The object shape (request, on, removeListener) is the EIP-1193 pattern that CIP-103 adopts; method names, params, results, and error codes are fixed by the CIP-103 spec.
async function connect() { const status = await window.canton.request({ method: "status" }); if (!status.connection.isConnected) { await window.canton.request({ method: "connect" }); }
const accounts = await window.canton.request({ method: "listAccounts" }); console.log(accounts); return accounts;}
const accounts = await window.canton.request({ method: "listAccounts" });console.log(accounts);Typed client usage (custom transport)
Section titled “Typed client usage (custom transport)”import { createCantonClient, WindowTransport } from "@sigilry/dapp";
async function getStatus() { const transport = new WindowTransport(window, { timeout: 30000 }); const client = createCantonClient(transport); return await client.status();}
getStatus().then(console.log);Extension server usage
Section titled “Extension server usage”import { createCantonServer, createStubHandlers, isSpliceMessageEvent, jsonRpcResponse, WalletEvent,} from "@sigilry/dapp";
const server = createCantonServer({ ...createStubHandlers(), status: async () => ({ provider: { id: "send-extension", providerType: "browser" }, connection: { isConnected: true, isNetworkConnected: true }, }), connect: async () => ({ isConnected: true, isNetworkConnected: true, }), disconnect: async () => null, getActiveNetwork: async () => ({ networkId: "canton:localnet" }), listAccounts: async () => [], getPrimaryAccount: async () => ({ primary: true, partyId: "alice::1220abc123", status: "allocated", hint: "alice", publicKey: "ed25519:abc123", namespace: "1220abc123", networkId: "canton:localnet", signingProviderId: "passkey", }), prepareExecute: async () => null, prepareExecuteAndWait: async () => ({ tx: { status: "executed", commandId: "cmd-123", payload: { updateId: "update-1", completionOffset: 1 }, }, }), signMessage: async () => ({ signature: "base64-signature" }), ledgerApi: async () => ({ events: [] }),});
window.addEventListener("message", async (event) => { if (!isSpliceMessageEvent(event)) return; if (event.data.type !== WalletEvent.SPLICE_WALLET_REQUEST) return;
const response = await server.handleRequest(event.data.request.method, event.data.request.params);
window.postMessage( { type: WalletEvent.SPLICE_WALLET_RESPONSE, response: jsonRpcResponse(event.data.request.id, response), }, "*", );});Provider discovery
Section titled “Provider discovery”When more than one Canton wallet may be present, dApps should discover providers instead of reaching for window.canton directly. The discovery primitives live on the @sigilry/dapp/discovery subpath — they are intentionally not re-exported from the package root. See Provider Discovery for the model and fallback semantics.
import { createDiscoveryStore } from "@sigilry/dapp/discovery";
const store = createDiscoveryStore();
const unsubscribe = store.subscribe( (wallets) => { // wallets: readonly DiscoveredWallet[] for (const w of wallets) console.log(w.info.rdns, w.info.name); }, { emitImmediately: true },);
// Build a live provider bound to a chosen wallet's transport target.const provider = store.findProvider({ rdns: "it.send.connect" })?.getProvider();
// Teardown removes the window listeners.store.destroy();A wallet extension announces itself instead:
import { announceProvider } from "@sigilry/dapp/discovery";
announceProvider({ id: "send-extension", name: "Send Connect", icon: "data:image/svg+xml;base64,...", target: "send-connect", rdns: "it.send.connect", uuid: crypto.randomUUID(),});Discovery subpath exports: requestProviders, announceProvider, createDiscoveryStore, createProvider, and the DiscoveredWallet, SpliceProviderInfo, SpliceAnnounceDetail, DiscoveryStore, and TransportOptions types. Pass a TransportOptions to wallet.getProvider(opts?) to tune the announced wallet’s transport (e.g. a longer request timeout).
Push events
Section titled “Push events”A SpliceProvider delivers the CIP-103 push events through the EIP-1193-style .on() API — the §4.2.2 sync events (accountsChanged, statusChanged, txChanged) plus the connected login event. WindowTransport carries notifications inbound on the same channel as requests (a notification is an id-less frame; the JSON-RPC id is the direction discriminant), and SpliceProviderBase auto-wires them to the provider’s emitter — so a provider from createProvider (or the injected window.canton) emits with no extra setup.
const onStatus = (status) => { /* ongoing status changes: network, session, disconnect */};provider.on("statusChanged", onStatus);provider.on("connected", (status) => { /* login-flow completion (login event, not §4.2.2) */});provider.on("accountsChanged", (accounts) => { /* active account / session changes */});provider.on("txChanged", (tx) => { /* transaction updates */});
// Unsubscribe:provider.removeListener("statusChanged", onStatus);On the wallet (server) side, emit notifications with notify(event, payload, target?) on the transport. Frames whose target does not match a provider’s configured target are filtered out, so multi-wallet pages never cross-deliver events. See CIP-103 Conformance for the normative event semantics.
Key exports
Section titled “Key exports”CANTON_DAPP_API_VERSIONSpliceProviderBase,SpliceProviderWindowTransportcreateCantonClient,createCantonServer,createStubHandlersWalletEvent,isSpliceMessage,isSpliceMessageEventjsonRpcRequest,jsonRpcResponseRpcErrorCode,rpcError,RpcClientError- Discovery (subpath
@sigilry/dapp/discovery):requestProviders,announceProvider,createDiscoveryStore,createProvider, and theTransportOptionstype
Generated schemas
Section titled “Generated schemas”Zod schemas are generated from OpenRPC specifications and exposed under @sigilry/dapp/schemas:
import { StatusEventSchema, JsPrepareSubmissionRequestSchema, type StatusEvent,} from "@sigilry/dapp/schemas";
const status = StatusEventSchema.parse(data);Regenerate schemas after spec updates:
yarn workspace @sigilry/dapp codegen