PartyLayerDocs
Try Demo

Vanilla JS

Use PartyLayer without React by working directly with the PartyLayerClient. This is the foundation the React hooks are built on.

Creating a Client

typescript
import { createPartyLayer } from '@partylayer/sdk';

const client = createPartyLayer({
  network: 'mainnet',
  app: {
    name: 'My dApp',
    origin: window.location.origin,
  },
});

Configuration Options

PropTypeDefaultDescription
network"devnet" | "testnet" | "mainnet"Canton network to connect to.
app.namestringYour application name, shown during wallet connection.
app.originstringwindow.location.originOrigin URL for session binding.
registryUrlstring"https://registry.partylayer.xyz"Wallet registry URL.
channel"stable" | "beta""stable"Registry channel.
storageStorageAdapterlocalStorageCustom storage adapter for session persistence.
cryptoCryptoAdapterCustom crypto adapter.
registryPublicKeysstring[]Public keys for registry signature verification.
adapters(WalletAdapter | AdapterClass)[]Built-in adaptersCustom adapter list.
telemetryTelemetryConfig | TelemetryAdapterTelemetry configuration (opt-in).
loggerLoggerAdapterCustom logger adapter.

Wallet Management

listWallets

List all available wallets from the registry and registered adapters. Resilient — falls back to adapter-only list if the registry is unreachable.

typescript
const wallets = await client.listWallets();

for (const wallet of wallets) {
  console.log(wallet.name, wallet.walletId);
  console.log('  Capabilities:', wallet.capabilities);
  console.log('  Networks:', wallet.networks);
}

// With filter
const signingWallets = await client.listWallets({
  requiredCapabilities: ['signTransaction'],
  includeExperimental: false,
});

registerAdapter

Register a custom wallet adapter at runtime.

typescript
import { BronAdapter } from '@partylayer/adapter-bron';

client.registerAdapter(new BronAdapter({ clientId: 'your-client-id' }));

Session Management

connect

Connect to a wallet. Returns a Session on success.

typescript
// Connect to a specific wallet
const session = await client.connect({ walletId: 'console' });
console.log('Connected:', session.partyId);
console.log('Session ID:', session.sessionId);

// Let the user choose (when using with a UI)
const session = await client.connect();

disconnect

Disconnect the active session.

typescript
await client.disconnect();

getActiveSession

Get the current active session, if any.

typescript
const session = await client.getActiveSession();

if (session) {
  console.log('Active session:', session.walletId, session.partyId);
} else {
  console.log('No active session');
}

Signing Operations

signMessage

typescript
const signed = await client.signMessage({
  message: 'Hello Canton!',
  nonce: crypto.randomUUID(),
  domain: 'my-dapp.example.com',
});

console.log('Signature:', signed.signature);
console.log('Signed by:', signed.partyId);

signTransaction

typescript
const signed = await client.signTransaction({
  tx: {
    templateId: 'MyModule:MyTemplate',
    choiceId: 'MyChoice',
    argument: { amount: '100', recipient: 'party::...' },
  },
});

console.log('TX Hash:', signed.transactionHash);
console.log('Signed TX:', signed.signedTx);

submitTransaction

typescript
const receipt = await client.submitTransaction({
  signedTx: signedPayload,  // Pass the signed transaction from signTransaction()
});

console.log('TX Hash:', receipt.transactionHash);
console.log('Command ID:', receipt.commandId);
console.log('Submitted at:', new Date(receipt.submittedAt));

Ledger API

Proxy requests to the Canton Ledger API through the connected wallet. Returns raw JSON responses that must be parsed with JSON.parse().

typescript
// Query active contracts (wallet balances)
const result = await client.ledgerApi({
  requestMethod: 'POST',
  resource: '/v2/state/acs',
  body: JSON.stringify({
    filter: {
      filtersByParty: {
        [session.partyId]: {
          inclusive: {
            templateFilters: [{ templateId: 'Splice.Amulet:Amulet' }],
          },
        },
      },
    },
  }),
});

const { activeContracts = [] } = JSON.parse(result.response);
console.log('Contracts found:', activeContracts.length);

// Submit a command directly
const submitResult = await client.ledgerApi({
  requestMethod: 'POST',
  resource: '/v2/commands/submit-and-wait',
  body: JSON.stringify(commandPayload),
});

Params: { requestMethod: "GET" | "POST" | "PUT" | "DELETE", resource: string, body?: string }

Returns: { response: string } — raw JSON from the Canton Ledger API.

ℹ️ Note
Not all wallets support ledgerApi. Cantor8 does not support it at all. Loop supports a limited set of endpoints (ACS queries and command submission). See Capability Matrix and Wallet Balances for details.

Events

Subscribe to SDK events with on. The method returns an unsubscribe function.

typescript
// Session events
const unsub1 = client.on('session:connected', (event) => {
  console.log('Connected:', event.session.partyId);
});

const unsub2 = client.on('session:disconnected', (event) => {
  console.log('Disconnected:', event.sessionId);
});

const unsub3 = client.on('session:expired', (event) => {
  console.log('Session expired:', event.sessionId);
});

// Transaction status
const unsub4 = client.on('tx:status', (event) => {
  console.log('TX', event.txId, '→', event.status);
  // status: 'pending' | 'submitted' | 'committed' | 'rejected' | 'failed'
});

// Registry updates
const unsub5 = client.on('registry:status', (event) => {
  console.log('Registry:', event.status.source, event.status.verified);
});

// Errors
const unsub6 = client.on('error', (event) => {
  console.error('SDK error:', event.error.message);
});

// Unsubscribe when done
unsub1();
unsub2();

CIP-0103 Provider Bridge

Get a CIP-0103 compliant provider wrapping the client:

typescript
const provider = client.asProvider();

// Use CIP-0103 standard methods
const result = await provider.request({ method: 'connect' });
const accounts = await provider.request({ method: 'listAccounts' });

// Subscribe to CIP-0103 events
provider.on('statusChanged', (status) => {
  console.log('Provider status:', status);
});

See CIP-0103 Provider for the complete provider API.

Cleanup

typescript
// Always destroy the client when done
// Flushes telemetry, removes event listeners, cleans up resources
client.destroy();
⚠️ Warning
Always call client.destroy() when your application unmounts or the client is no longer needed. This prevents memory leaks and ensures telemetry data is flushed.

Complete Example

vanilla-wallet-app.ts
import { createPartyLayer } from '@partylayer/sdk';

async function main() {
  // 1. Create client
  const client = createPartyLayer({
    network: 'mainnet',
    app: { name: 'My Vanilla dApp' },
  });

  // 2. Listen for events
  client.on('session:connected', (e) => {
    console.log('Connected to', e.session.walletId);
  });

  client.on('error', (e) => {
    console.error('Error:', e.error.message);
  });

  // 3. List available wallets
  const wallets = await client.listWallets();
  console.log('Available wallets:', wallets.map(w => w.name));

  // 4. Connect
  const session = await client.connect({ walletId: 'console' });
  console.log('Party ID:', session.partyId);

  // 5. Sign a message
  const signed = await client.signMessage({
    message: 'Verify ownership',
    nonce: crypto.randomUUID(),
  });
  console.log('Signature:', signed.signature);

  // 6. Disconnect
  await client.disconnect();

  // 7. Cleanup
  client.destroy();
}

main().catch(console.error);
PreviousReact HooksNextWallets & Adapters