PartyLayerDocs
Try Demo

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

WalletTransportDetectionAuto-registered
Console WalletPostMessage / QR / Deep LinkExtension + mobileYes
5N LoopQR Code / PopupMobile / Web appYes
Cantor8 (C8)Deep LinkBrowser extensionYes
NightlyInjectedwindow.nightly.cantonYes
BronOAuthEnterprise configNo
ℹ️ 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.

WalletconnectsignMessagesignTransactionsubmitTransactionledgerApirestore
Consolesupportedsupportedsupportedsupportedfullsupported
5N Loopsupportedsupportednonesupportedlimitedsupported
Cantor8supportedsupportedsupportednonenonesupported
Nightlysupportedsupportednonesupportedfullsupported
Bronsupportedsupportedsupportednonefullsupported

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)

tsx
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.

typescript
// 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):

typescript
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:

typescript
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:

typescript
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

PropTypeDefaultDescription
walletIdreadonly WalletIdUnique wallet identifier.
namereadonly stringHuman-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>optionalRestore a persisted session.
signMessage?()(ctx, session, params) => Promise<SignedMessage>optionalSign arbitrary message.
signTransaction?()(ctx, session, params) => Promise<SignedTransaction>optionalSign transaction.
submitTransaction?()(ctx, session, params) => Promise<TxReceipt>optionalSign and submit transaction.
ledgerApi?()(ctx, session, params) => Promise<LedgerApiResult>optionalProxy a JSON Ledger API request.
PreviousVanilla JSNextCIP-0103 Provider