Shared WebSocket protocol types and SIWS (Sign-In With Solana) helpers for PlexChat — the wire contract spoken by Metaplex Agent Registry agents and their chat UIs.
This package is the single source of truth for the wire format, so the agent server and the chat client can't drift apart.
pnpm add @metaplex-foundation/plexchat
# or
npm install @metaplex-foundation/plexchat
# or
yarn add @metaplex-foundation/plexchatRequires Node.js 20+ on the server. The package is pure ESM and runs
unmodified in evergreen browsers — no node:* imports, no Buffer
dependencies.
Everything is exported from the package root — there are no subpath specifiers:
import {
buildSiwsMessage,
verifySiwsSignature,
type ClientMessage,
type ServerMessage,
type DebugMessage,
} from '@metaplex-foundation/plexchat';Discriminated unions covering every PlexChat WebSocket frame. Narrow with
a single switch on msg.type:
ClientMessage—auth_response,message,tx_result,tx_error, and the owner-onlyallowlist_list/allowlist_add/allowlist_removeadmin messages.ServerMessage—connected,auth_challenge,authenticated,auth_error,message,typing,transaction,error,allowlist_state,allowlist_error, and thedebug:*trace events.- Helper aliases:
SiwsNetwork,AuthMode,ServerAuthErrorCode,ServerErrorCode,ServerAllowlistErrorCode,DebugFinishReason,DebugMessage.
buildSiwsMessage(params)— builds the canonical Sign-In With Solana message. Both the server (during nonce issuance and signature verification) and the chat UI (to feed the wallet for signing) MUST call this function. The bytes need to be byte-identical on both sides for verification to succeed, so do not reimplement the format.verifySiwsSignature({ message, signatureBase58, publicKeyBase58 })— Ed25519 verification viatweetnacl. Returnsfalseon any decode, length, or signature failure. Never throws.
import {
buildSiwsMessage,
verifySiwsSignature,
type ClientAuthResponse,
type ServerAuthChallenge,
} from '@metaplex-foundation/plexchat';
const challenge: Omit<ServerAuthChallenge, 'type'> = {
agentName: 'MyAgent',
agentAsset: 'AsSeT1111111111111111111111111111111111111',
network: 'solana-mainnet',
nonce: crypto.randomUUID(),
issuedAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 5 * 60_000).toISOString(),
authMode: 'open',
};
// Send `{ type: 'auth_challenge', ...challenge }` to the client, then
// later when the client replies with `auth_response`:
function verifyAuthResponse(msg: ClientAuthResponse): boolean {
const expected = buildSiwsMessage(challenge);
if (msg.message !== expected) return false;
return verifySiwsSignature({
message: expected,
signatureBase58: msg.signature,
publicKeyBase58: msg.publicKey,
});
}import {
buildSiwsMessage,
type ServerAuthChallenge,
} from '@metaplex-foundation/plexchat';
function siwsBytesFor(challenge: ServerAuthChallenge): Uint8Array {
const message = buildSiwsMessage({
agentName: challenge.agentName,
agentAsset: challenge.agentAsset,
network: challenge.network,
nonce: challenge.nonce,
issuedAt: challenge.issuedAt,
expiresAt: challenge.expiresAt,
});
return new TextEncoder().encode(message);
}
// Pass the bytes to your wallet adapter's `signMessage(bytes)`, then
// send `{ type: 'auth_response', publicKey, signature, message }` back.The wire protocol is a single coherent contract. Semantic versioning applies to the TypeScript surface:
- Major — breaking change to a message shape, removal of a variant, or rename of a literal-union member.
- Minor — additive: new optional field, new optional message
variant, new literal-union member (e.g. a new
ServerErrorCode). - Patch — docs, tests, or internal refactors with no surface change.