SessionKit docs

API reference

Everything @blaclabs/sessionkit exports, in both halves — browser and server.

Browser

connect(options)

Generates a session key pair, has your server sign the request, renders the BLAC Code, and resolves once the user approves in BLAC Wallet. On the CDN build it is BLACSession.connect.

OptionTypeMeaning
scopes Scope[] What you're asking for. identity is added automatically, and each sign:* scope adds its address scope. Required.
sign (input) => Promise Posts { kemPubKey, scopes } to your backend and returns its answer, { fields, sig, sigKid? }. The browser checks that the fields match its own key, origin and scopes before it shows a code. Required.
mount HTMLElement Where to draw the code. A canvas is created inside it. Omit to render the code yourself.
origin string Defaults to the page's origin. Must match your well-known document and what your server signs.
relayUrl string Defaults to the BLAC relay. Change it only for local testing.
render { color, background } Code colours. Keep the strokes light on a dark card.
signal AbortSignal Cancel the pending sign-in (user closed your modal).
onApproved ({ sas, origin }) => void Fires once the wallet approved. On a sign session, show sas next to the code: the user compares the four digits with the ones in the wallet.
onHandoff () => void The page runs inside BLAC Wallet's browser and the code was handed to it. Show "approve in BLAC Wallet" instead of a code.
onHandoffDeclined () => void The wallet declined the hand-off, or refused the code.
onDiagnostic (code) => void Fires for every frame the signing channel refused, with the reason only. Safe to log.

A decline in the wallet sends nothing, so the promise keeps waiting until the code expires or your signal fires.

The session object

{
  origin: "https://your-domain.com",
  grantedScopes: ["identity", "address:solana"],
  grantedChains: [],     // exact chains granted for signing; empty for a login
  grantedMethods: [],    // granted signing methods; empty for a login
  user: {
    id: "…",               // pairwise, stable, unique to your origin
    pubKey: "…",           // Ed25519 public key
    proof: "…",            // signature binding this login to this session
    username?: "…",        // iff profile:username granted
    avatar?: "…",          // iff profile:avatar granted (base64)
    avatarMimeType?: "…",
    solanaAddress?: "…",   // iff address:solana granted and its proof checked out
    evmAddress?: "…",      // iff address:evm granted and its proof checked out
    favoriteMarkets?: […]  // iff favorites:read granted
  },
  verified: { … },       // the verified view of the approval
  raw: { … },            // forward this to your backend to verify
  sas?: "1234",          // sign sessions only; the wallet shows the same digits
  isConnected: false,    // true while the signing channel is open

  request(input),        // ask the wallet to sign
  activity(),            // tell the wallet the user is here (once a minute at most)
  disconnect(),          // end the session on both sides
  onEnd(listener),       // called once when the session ends; returns an unsubscribe
  solanaWallet(options), // Wallet Standard wallet + Phantom-shaped provider
  ethereumProvider(options) // EIP-1193 provider
}

A field for a scope that was not granted holds undefined. Compare against undefined rather than testing whether the key exists — the user may approve some scopes and not others.

A login without a sign:* scope is one exchange: nothing stays open, isConnected is false, and request rejects with 4900.

session.request(input)

await session.request({
  chain: "solana",                 // or "eip155:1", "eip155:137", …
  method: "solana_signMessage",
  params: { message: base64Message }
});
ScopeMethods
sign:solana solana_signMessage, solana_signTransaction, solana_signAllTransactions, solana_signAndSendTransaction
sign:evm personal_sign, eth_signTypedData_v4, eth_sendTransaction

The wallet fills nonce, gas and fees itself, and the user approves every request in the app. A request that fails rejects with a SignRequestError whose code follows EIP-1193:

CodeMeaning
4001The user rejected the request.
4100The chain, method or account was not granted.
4200Unsupported method.
4900The session has ended, or never had a signing channel.
4902Unsupported chain.
-32602Invalid parameters.
-32005Rate limited. Too many requests at once.
-32603Internal error.

Adapters

session.solanaWallet({ register }) returns a Wallet Standard wallet and a Phantom-shaped provider. Pass register: true so wallet-adapter dapps list "BLAC Wallet".

session.ethereumProvider({ rpcUrls, defaultChainId, announce, injectGlobal }) returns an EIP-1193 provider. rpcUrls is required, keyed by chain id: the wallet never proxies reads. announce: true announces it via EIP-6963, and injectGlobal: true assigns window.ethereum when it is unset.

Errors from connect

connect rejects with a SessionError carrying a code you can branch on:

CodeMeaning
invalid_scope You requested a scope outside the third-party catalog.
signing_failed Your sign callback threw or returned no fields and sig, or your server signed a different key, topic, origin or audience, an expiry outside the window, or wider scopes than asked. Check your backend config first.
relay_unavailable The relay could not be reached. Offer a retry.
expired Nobody approved in time. Generate a new code.
sas_mismatch The four digits did not match on a sign session. Treat as hostile and start over.
aborted Your AbortSignal fired.

An approval that fails verification never rejects the promise. Anyone who saw the code can post junk to it, so SessionKit ignores anything that does not verify and keeps waiting.

Rendering the code yourself

Skip mount and draw it where you like. The pairing topic is in the fields your server returned, so read it in your sign callback. Geometry is on a 440×440 logical canvas.

import { connect, renderBlacCode, blacCodeGeometry } from "@blaclabs/sessionkit";

const session = await connect({
  scopes,
  sign: async (input) => {
    const signed = await signWithYourBackend(input);
    renderBlacCode(canvas, signed.fields.pairingTopic, { color: "#fff" });
    // or build your own SVG / WebGL / print output:
    // const { data, anchors } = blacCodeGeometry(signed.fields.pairingTopic);
    return signed;
  }
});

Server

Imported from @blaclabs/sessionkit/server. Node only — never import this from browser code.

createSessionSigner(config)

ConfigTypeMeaning
privateKeyPem string Your Ed25519 domain key, from npx @blaclabs/sessionkit init. Required.
origin string Your origin, exactly as served. Signed into every code; never read from the request. Required.
allowedScopes Scope[] The most a code may ask for. Requests are clipped to it, never widened. Required.
kid string Key id, when your well-known document publishes more than one key.
appId string Reverse-DNS client id. Defaults to the origin's hostname.
expirySeconds number Code lifetime. Default 120, capped at 180.

Returns a signer. Call signer.sign(body) with the browser's { kemPubKey, scopes }; it returns { fields, sig, sigKid? } for your endpoint to send back. Unknown or disallowed scopes are dropped silently, so a page cannot probe your policy. Store fields.pairingTopic with the browser session that asked.

signSessionRequest(config, input)

One-shot form of createSessionSigner(config).sign(input).

verifyApproval({ approval, origin, pairingTopic })

Verifies a completed login. Pass session.raw as approval and the pairing topic you stored when you signed, consumed once. Optional maxAgeMs (default five minutes) and maxSkewMs (default two minutes) bound the wallet's clock.

Returns { userId, origin, pairingTopic, issuedAt, grantedScopes, grantedChains, grantedMethods }, plus username, avatar, avatarMimeType, solanaAddress, evmAddress and favoriteMarkets when granted, and actionTopic and sessionExpiresAt on a sign session. Throws ApprovalError on the first check that fails. No network call — the proof is self-contained.

Exports

import {
  connect, SessionError, SignRequestError, ApprovalError,
  renderBlacCode, blacCodeGeometry, encodeBlacCodePayload,
  THIRD_PARTY_SCOPES, ERROR_CODES
} from "@blaclabs/sessionkit";

import type {
  Scope, Chain, SignMethod, Session, ConnectOptions, ApprovedUser,
  SignRequest, SignRequestInput, EthereumProviderOptions, SolanaWalletOptions
} from "@blaclabs/sessionkit";

import {
  createSessionSigner, signSessionRequest, verifyApproval
} from "@blaclabs/sessionkit/server";