Wallets & Adapters
PartyLayer includes 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 | Networks | Opt-in | Adapter |
|---|
| Console Wallet | devnet, testnet, mainnet | No | @partylayer/adapter-console |
| 5N Loop | devnet, mainnet | No | @partylayer/adapter-loop |
| Cantor8 | devnet, mainnet | No | @partylayer/adapter-cantor8 |
| Bron | devnet, testnet, mainnet | Yes | @partylayer/adapter-bron |
| Nightly | devnet, testnet, mainnet | No | @partylayer/adapter-nightly |
| Send | mainnet | No | @partylayer/adapter-send |
| Walley | mainnet, testnet, devnet | No | @k2flabs/walley-dapp-sdk |
| WalletConnect | devnet, testnet, mainnet | Yes | @partylayer/adapter-walletconnect |
ℹ️ Note
Wallets marked
Opt-in (
Bron,
WalletConnect) require explicit configuration, register them via
config.adapters (Bron needs an OAuth client ID; WalletConnect needs the optional
@walletconnect/* peers). The rest are auto-registered when using
PartyLayerKit.
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 |
| Send | supported | supported | none | supported | full | supported |
| Walley | supported | supported | none | supported | none | supported |
| WalletConnect | supported | supported | none | supported | full | supported |
Capability Notes
- Loop / Nightly / Send, signTransaction: these wallets combine signing and submission into a single step. Use
submitTransaction directly instead of the separate sign-then-submit pattern. Calling signTransaction on any of them throws CapabilityNotSupportedError pointing you at this fix. - Send, passkey signing & namespace guard: Send injects at the bare
window.canton slot (the same slot any splice-wallet-kernel-compatible extension would use). The Send adapter verifies the running provider's kernel.id before forwarding any RPC, so installing Send next to a Console-class wallet never claims the wrong provider. Send is currently mainnet-only and signs every transaction via WebAuthn-PRF (Touch ID / Face ID), so the user sees a passkey prompt rather than an extension popup. - 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 auth and api config. - 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({
auth: {
clientId: 'your-oauth-client-id',
redirectUri: 'https://my-app.com/callback',
authorizationUrl: 'https://auth.bron.example/authorize',
tokenUrl: 'https://auth.bron.example/token',
},
api: {
baseUrl: 'https://api.bron.example',
getAccessToken: async () => getStoredAccessToken(),
},
}),
]}
>
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. |