Wallets & Adapters
PartyLayer includes 5 built-in wallet adapters and supports custom adapters for any Canton wallet. Wallets are discovered through the registry and CIP-0103 native provider detection.
Built-in Wallets
| Wallet | Transport | Detection | Auto-registered |
|---|
| Console Wallet | PostMessage / QR / Deep Link | Extension + mobile | Yes |
| 5N Loop | QR Code / Popup | Mobile / Web app | Yes |
| Cantor8 (C8) | Deep Link | Browser extension | Yes |
| Nightly | Injected | window.nightly.canton | Yes |
| Bron | OAuth | Enterprise config | No |
ℹ️ Note
All wallets except
Bron are auto-registered when using
PartyLayerKit. Bron requires explicit configuration because it needs an OAuth client ID.
Capability Matrix
Not every wallet supports every operation. Check this matrix before building features that depend on specific capabilities.
| Wallet | connect | signMessage | signTransaction | submitTransaction | ledgerApi | restore |
|---|
| Console | supported | supported | supported | supported | full | supported |
| 5N Loop | supported | supported | none | supported | limited | supported |
| Cantor8 | supported | supported | supported | none | none | supported |
| Nightly | supported | supported | none | supported | full | supported |
| Bron | supported | supported | supported | none | full | supported |
Capability Notes
- Loop / Nightly — signTransaction: both combine signing and submission into a single step. Use
submitTransaction directly instead of the separate sign-then-submit pattern. Calling signTransaction on either wallet throws CapabilityNotSupportedError pointing you at this fix. - Loop — ledgerApi (limited): Supports
POST /v2/state/acs, GET /v2/state/acs/active-contracts, POST /v2/commands/submit, and POST /v2/commands/submit-and-wait. Other endpoints are not available — for full Ledger API access, use Console, Nightly, or Bron. - Cantor8: Mobile-only deep link transport. Supports
signMessage and signTransaction via the deep link flow, but does not expose submitTransaction or ledgerApi. - Bron — submitTransaction: Bron is a remote signer (OAuth) — it signs commands but does not submit them directly to the ledger. Pair
signTransaction with your own participant submission, or call ledgerApi against /v2/commands/submit-and-wait and let Bron sign the pre-built command. Requires explicit OAuth configuration via BronAdapter with a clientId. - Session restore (all five wallets): every adapter declares the
restore capability and implements a matching restore() method. On page reload, the SDK decrypts the persisted session and hands it to the adapter, which re-establishes the provider if it can. See Advanced → Session Persistence for per-wallet behavior and edge cases.
Adding Bron (Enterprise)
import { PartyLayerKit } from '@partylayer/react';
import { getBuiltinAdapters } from '@partylayer/sdk';
import { BronAdapter } from '@partylayer/adapter-bron';
<PartyLayerKit
network="mainnet"
appName="My dApp"
adapters={[
...getBuiltinAdapters(),
new BronAdapter({
clientId: 'your-oauth-client-id',
// Optional: custom OAuth redirect URI
// redirectUri: 'https://my-app.com/callback',
}),
]}
>
Wallet Discovery
PartyLayer discovers wallets through two mechanisms:
1. Registry Discovery
On initialization, the SDK fetches the wallet registry from registry.partylayer.xyz. The registry contains verified wallet metadata — names, icons, capabilities, install hints, and supported networks. Registry entries are cryptographically signed.
If the registry is unreachable, the SDK gracefully falls back to adapter-only discovery, ensuring your dApp still works offline.
2. CIP-0103 Native Detection
The SDK scans window.canton.* for CIP-0103 compliant providers injected by wallet extensions. Native wallets appear first in the wallet list with a "Native" badge.
// What the SDK does internally:
import { discoverInjectedProviders } from '@partylayer/provider';
const providers = discoverInjectedProviders();
// → [{ id: 'console', provider: CIP0103Provider }, ...]
getBuiltinAdapters
Returns all auto-registered adapters (Console, Loop, Cantor8, Nightly):
import { getBuiltinAdapters } from '@partylayer/sdk';
const adapters = getBuiltinAdapters();
// → [ConsoleAdapter, LoopAdapter, Cantor8Adapter, NightlyAdapter]
Creating a Custom Adapter
To support a new wallet, implement the WalletAdapter interface:
import type {
WalletAdapter, AdapterContext, AdapterDetectResult,
AdapterConnectResult, Session, SignMessageParams,
SignTransactionParams, SubmitTransactionParams,
} from '@partylayer/core';
class MyWalletAdapter implements WalletAdapter {
readonly walletId = 'my-wallet' as WalletId;
readonly name = 'My Custom Wallet';
getCapabilities() {
return ['connect', 'disconnect', 'signMessage', 'signTransaction'];
}
async detectInstalled(): Promise<AdapterDetectResult> {
const installed = typeof window !== 'undefined' && !!window.myWallet;
return { installed, reason: installed ? undefined : 'Extension not found' };
}
async connect(ctx: AdapterContext, opts?: { timeoutMs?: number }): Promise<AdapterConnectResult> {
const result = await window.myWallet.connect({
appName: ctx.appName,
network: ctx.network,
});
return {
partyId: result.partyId,
session: { metadata: result.metadata },
capabilities: this.getCapabilities(),
};
}
async disconnect(ctx: AdapterContext, session: Session): Promise<void> {
await window.myWallet.disconnect();
}
async signMessage(ctx: AdapterContext, session: Session, params: SignMessageParams) {
const sig = await window.myWallet.sign(params.message);
return {
signature: sig,
partyId: session.partyId,
message: params.message,
};
}
async signTransaction(ctx: AdapterContext, session: Session, params: SignTransactionParams) {
const result = await window.myWallet.signTx(params.tx);
return {
signedTx: result.signedPayload,
transactionHash: result.txHash,
partyId: session.partyId,
};
}
}
Registering at Runtime
Register adapters at runtime using the client's registerAdapter method:
import { createPartyLayer } from '@partylayer/sdk';
const client = createPartyLayer({ network: 'mainnet', app: { name: 'My dApp' } });
// Register your custom adapter
client.registerAdapter(new MyWalletAdapter());
// Now it appears in listWallets()
const wallets = await client.listWallets();
WalletAdapter Interface
| Prop | Type | Default | Description |
|---|
| walletId | readonly WalletId | — | Unique wallet identifier. |
| name | readonly string | — | Human-readable wallet name. |
| getCapabilities() | () => CapabilityKey[] | — | Return supported capabilities. |
| detectInstalled() | () => Promise<AdapterDetectResult> | — | Check if wallet is installed. Returns { installed, reason? }. |
| connect() | (ctx, opts?) => Promise<AdapterConnectResult> | — | Establish wallet connection. Returns { partyId, session, capabilities }. |
| disconnect() | (ctx, session) => Promise<void> | — | Close connection. Required. |
| restore?() | (ctx, persisted) => Promise<Session | null> | optional | Restore a persisted session. |
| signMessage?() | (ctx, session, params) => Promise<SignedMessage> | optional | Sign arbitrary message. |
| signTransaction?() | (ctx, session, params) => Promise<SignedTransaction> | optional | Sign transaction. |
| submitTransaction?() | (ctx, session, params) => Promise<TxReceipt> | optional | Sign and submit transaction. |
| ledgerApi?() | (ctx, session, params) => Promise<LedgerApiResult> | optional | Proxy a JSON Ledger API request. |