meteor-canton-sdk
v0.1.1
Published
SDK for dApps to connect to Meteor Canton Wallet
Readme
Meteor Canton SDK (meteor-canton-sdk)
A comprehensive, developer-friendly SDK for decentralized applications (dApps) to securely interact with the Meteor Canton Wallet.
This SDK utilizes a JSON-RPC over postMessage architecture, providing a seamless popup-based user experience for wallet connection and transaction approval. It enables dApps to authenticate users, query accounts and token holdings, and submit both standard transfers and raw Daml commands directly to the Canton network via the Meteor Wallet.
Table of Contents
- Installation
- Quick Start
- Architecture & Lifecycle
- API Reference
- TypeScript Interfaces
- Error Handling & Edge Cases
- React Integration Guide
- Advanced: Raw Daml Commands
- Local Development & Testing DApp
Installation
npm install meteor-canton-sdk
# or
bun add meteor-canton-sdk
# or
yarn add meteor-canton-sdkQuick Start
import { meteor } from 'meteor-canton-sdk';
// 1. Initialize the SDK (Call once on app startup)
meteor.init({
network: 'testnet', // 'testnet' | 'mainnet'
appName: 'My Awesome DApp',
});
// 2. Connect the wallet (Triggers a popup for user approval)
const { partyId, publicKey, token } = await meteor.connect();
console.log(`Connected Party: ${partyId}`);
// 3. Fetch user's token holdings
const tokens = await meteor.listTokens();
// 4. Initiate a token transfer (Triggers popup for signature/approval)
if (tokens.length > 0) {
const token = tokens[0].interfaceViewValue;
const result = await meteor.transfer({
receiverPartyId: 'recipient::party-id',
amount: '10.5',
instrumentId: token.instrumentId.id,
instrumentAdmin: token.instrumentId.admin,
memo: 'Payment for services',
});
console.log('Transfer Result:', result);
}
// 5. Disconnect when finished
await meteor.disconnect();Architecture & Lifecycle
The SDK communicates with the Meteor Canton Wallet through two channels: a popup window (for interactive operations) and direct HTTP calls (for read-only queries).
┌──────────────────────────┐
│ Your DApp │
│ meteor-canton-sdk │
└───┬──────────────────┬───┘
│ │
│ postMessage │ HTTP (REST)
│ (JSON-RPC) │ with JWT
▼ ▼
┌──────────┐ ┌─────────────────┐
│ Wallet │ │ Wallet Backend │
│ Popup │──▶│ /api/wallet/* │
│ │ └────────┬────────┘
│ • connect│ HTTP (REST)│ gRPC/HTTP
│ • approve│ ▼
│ • sign │ ┌─────────────────┐
└──────────┘ │ Canton Network │
└─────────────────┘Popup channel — used for interactive operations that require user approval:
connect(),disconnect(),status()listAccounts()transfer(),prepareExecuteAndWait()
Direct HTTP channel — used for read-only queries (no popup needed):
listTokens()→GET /wallet/tokenspendingTokens()→GET /wallet/pending-tokens
Lifecycle
- Connection:
connect()opens a popup to the wallet's/dapp-connectpage. The SDK polls withSPLICE_WALLET_EXT_READYuntil the popup repliesSPLICE_WALLET_EXT_ACKto establish a secure JSON-RPC bridge. On success, the SDK receives a JWT token, party ID, and account list. - Read-only queries:
listTokens()andpendingTokens()call the wallet backend directly via HTTP using the JWT obtained during connection. No popup interaction is needed. - Transactions: State-mutating actions (e.g.,
transfer,prepareExecuteAndWait) send a request to the popup for the user to review and approve. After approval, results are returned viapostMessage. - Auto-recovery: If the popup is closed between calls, the SDK will automatically reopen it when a new popup-based operation is initiated.
- Disconnect:
disconnect()sends a disconnect RPC, closes the popup, and clears all state (JWT, partyId, accounts).
API Reference
The SDK exports a singleton instance named meteor.
Core State Properties
After a successful connection, the meteor instance exposes the following synchronous properties:
| Property | Type | Description |
| -------- | ---- | ----------- |
| meteor.partyId | string | The primary connected Canton party ID. |
| meteor.token | string | The active JWT auth token used for backend API queries. |
| meteor.isConnected | boolean | true if a wallet connection is currently active. |
Initialization & Connection
meteor.init(config: MeteorConfig): void
Must be called before any other methods to configure network endpoints. Sets the URL for the popup and backend REST calls.
Network Defaults:
devnet: Wallethttp://localhost:8081/dapp-connect, APIhttp://localhost:3000/api(Note: there is no public devnet wallet)testnet: Wallethttps://testnet.canton.meteorwallet.app/dapp-connect, APIhttps://testnet.canton.meteorwallet.app/apimainnet: Wallethttps://app.canton.meteorwallet.app/dapp-connect, APIhttps://app.canton.meteorwallet.app/api
meteor.connect(): Promise<ConnectResult>
Opens the wallet popup, requests user approval, and retrieves primary account details and an authentication token. Note: Must be triggered by a user gesture (e.g., button click) to bypass browser popup blockers.
Returns: A Promise that resolves to a ConnectResult object.
meteor.disconnect(): Promise<void>
Sends a disconnect signal to the wallet, closes the active popup, and wipes the local session state (including the JWT, cached partyId, and accounts).
meteor.status(): Promise<StatusResult>
Queries the underlying wallet connection and network synchronization status via JSON-RPC.
Returns: A Promise that resolves to a StatusResult.
Queries
(These methods require an active connection and utilize the fetched JWT to query the wallet backend directly).
meteor.listAccounts(): Promise<Account[]>
Returns all accounts (parties) associated with the connected wallet. The first account or the one marked primary is typically used as the default sender.
meteor.listTokens(): Promise<TokenHolding[]>
Fetches all confirmed token balances owned by the connected party.
meteor.pendingTokens(): Promise<TokenHolding[]>
Fetches in-flight (pending) token holdings that have not yet been fully finalized on the ledger.
Transactions & Commands
(These methods will automatically refocus or reopen the wallet popup for user review and signature).
meteor.transfer(params: TransferParams): Promise<TransferResult>
Initiates a standard token transfer to another party. Validates the parameters client-side before sending the RPC request.
Validation Rules:
receiverPartyId,amount,instrumentId, andinstrumentAdminare strictly required.amountmust be a positive numeric string.
meteor.prepareExecuteAndWait(params: ExecuteParams): Promise<ExecuteResult>
Submits raw Daml commands directly to the ledger. Opens the wallet popup for the user to review and sign the execution request. See Advanced: Raw Daml Commands.
TypeScript Interfaces
The SDK exports all its types. You can import them directly:
import type { MeteorConfig, TransferParams, Account, TokenHolding } from 'meteor-canton-sdk';Core Configuration & Connection
export type MeteorNetwork = 'testnet' | 'mainnet' | 'devnet';
export interface MeteorConfig {
network: MeteorNetwork;
appName?: string;
popupFeatures?: string; // Default: 'width=420,height=700'
}
export interface ConnectResult {
partyId: string;
publicKey: string;
token: string;
}Entities
export interface Account {
primary: boolean;
partyId: string;
publicKey: string;
status: string;
hint: string;
namespace: string;
networkId: string;
signingProviderId: string;
}
export interface TokenHolding {
interfaceViewValue: {
owner: string;
amount: string;
instrumentId: { id: string; admin: string };
lock: unknown;
};
createdAt: string;
utxoCount: number;
contractIds: string[];
}Transactions
export interface TransferParams {
receiverPartyId: string;
amount: string;
instrumentId: string;
instrumentAdmin: string;
memo?: string;
expiryDate?: string;
senderPartyId?: string; // Defaults to meteor.partyId
}
export interface TransferResult {
tx: {
status: string;
commandId: string;
payload: { updateId: string; completionOffset: number };
};
}
export interface ExecuteParams {
commands: unknown;
commandId?: string;
}
export interface ExecuteResult {
tx: {
status: string;
commandId: string;
payload: { updateId: string; completionOffset: number };
};
}
export interface StatusResult {
provider: { id: string; providerType: string };
connection: {
isConnected: boolean;
isNetworkConnected: boolean;
};
}Error Handling & Edge Cases
Popup Blockers
Because meteor.connect() relies on window.open, browsers will block it if it's not triggered directly by a user interaction.
Error: Error: Failed to open wallet popup — check your popup blocker
Solution: Always call meteor.connect() inside an onClick or similar event handler.
Authentication State
If you attempt to call listTokens(), transfer(), or prepareExecuteAndWait() without connecting first, the SDK will throw:
Error: Error: Not connected. Call connect() first. or Error: No auth token. Call connect() first.
Wallet Initialization Timeout
If the popup opens but the wallet application fails to load or handshake within 15 seconds, the SDK will reject the connection:
Error: Error: Wallet failed to initialize
RPC Timeouts
Transactions and status requests that rely on user approval or wallet processing have a 120-second timeout.
Error: Error: RPC "<method>" timed out
React Integration Guide
The ideal way to integrate meteor-canton-sdk into a React application is via @tanstack/react-query, which handles caching, loading states, and refetching elegantly.
1. Global Setup
Initialize the SDK once at the root of your application.
import { useEffect } from 'react';
import { meteor } from 'meteor-canton-sdk';
export function App() {
useEffect(() => {
meteor.init({ network: 'testnet', appName: 'My React DApp' });
}, []);
return <WalletConnector />;
}2. Connect & List Tokens
import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { meteor } from 'meteor-canton-sdk';
function WalletConnector() {
const [connected, setConnected] = useState(meteor.isConnected);
const handleConnect = async () => {
try {
await meteor.connect();
setConnected(true);
} catch (error) {
console.error("Connection failed", error);
}
};
const { data: tokens, isLoading } = useQuery({
queryKey: ['tokens', meteor.partyId],
queryFn: () => meteor.listTokens(),
enabled: connected, // Only fetch if connected
});
if (!connected) return <button onClick={handleConnect}>Connect Wallet</button>;
return (
<div>
<p>Welcome, {meteor.partyId}</p>
{isLoading ? <p>Loading tokens...</p> : (
<ul>
{tokens?.map(t => (
<li key={`${t.interfaceViewValue.instrumentId.id}-${t.interfaceViewValue.instrumentId.admin}`}>
{t.interfaceViewValue.instrumentId.id}: {t.interfaceViewValue.amount}
</li>
))}
</ul>
)}
</div>
);
}Advanced: Raw Daml Commands
For complex smart contract interactions beyond standard transfers, use meteor.prepareExecuteAndWait(). This accepts a JSON array of standard Daml ExerciseCommand or CreateCommand structures.
const result = await meteor.prepareExecuteAndWait({
commands: [
{
ExerciseCommand: {
templateId: '#splice-api-token-transfer-instruction-v1:Splice.Api.Token.TransferInstructionV1:TransferFactory',
contractId: 'your-target-contract-id',
choice: 'TransferFactory_Transfer',
choiceArgument: {
// Arguments matching your Daml choice structure
},
},
},
],
// commandId: 'optional-custom-id' // Auto-generated if omitted
});
console.log('Transaction Status:', result.tx.status);