Sovereign Wallet

Multisig Coordination Dashboard

Signing Room®.io

Coordinator Flow

Simulate the wallet generating a PSBT and passing it directly to the widget.

Guest Flow

Simulate a user clicking a deep-link from a co-signer to join an existing room.

Audit Events

Waiting for interaction...

1. Installation

Install via package manager to use with modern build tools like Vite, Webpack, or Angular:

# npm npm install @signing-room/embed # yarn yarn add @signing-room/embed

Or drop the Web Component bundle directly into your HTML via CDN:

<!-- Import from jsDelivr CDN --> <script type="module" src="https://cdn.jsdelivr.net/npm/@signing-room/embed/index.js"></script>

2. HTML Attributes

You can render the component directly in your DOM. It requires zero framework dependencies.

<!-- Example Usage --> <signing-room network="signet" hide-header="true"></signing-room>
Attribute Description
network Sets the Bitcoin network. Options: bitcoin, testnet, signet. (Default: bitcoin)
hide-header If true, hides the top "SigningRoom.io" header and network badges for a cleaner white-label embed.
relay-endpoint (Optional) Overrides the default relay server URL.
view (Optional) Set to inject to load into a specific flow context.
room-id (Optional) The public ID of an existing room to join automatically.
decryption-key (Optional) The private decryption key required to unlock the room.

3. JavaScript API (Coordinator Injection)

If your application already holds the Unsigned PSBT in memory, you can inject it directly into the component to instantly create a new room without file uploads. This is typically done by programmatically creating the element.

// 1. Create the element programmatically const widget = document.createElement('signing-room'); // 2. Set necessary attributes widget.setAttribute('network', 'signet'); widget.setAttribute('view', 'inject'); // 3. Mount it to your DOM document.getElementById('container').appendChild(widget); // 4. Inject the PSBT (Wait slightly for the DOM to flush) setTimeout(() => { const base64Psbt = "cHNidP8BAFICAAAA..."; widget.loadPsbt(base64Psbt); }, 50);

4. Webhook Events (Output)

The <signing-room> web component emits standardized DOM CustomEvent objects whenever significant actions, security workflows, or lifecycle changes occur. Host applications can capture these events by attaching standard event listeners to the widget DOM element. All event data is passed inside the event.detail property.

Universal Context: Every event payload implicitly includes a BaseEventContext object, ensuring your host application always knows who triggered the event, and in which room.

Listening to Events (Example)

const widget = document.querySelector('signing-room'); widget.addEventListener('destinationVerified', (event) => { const data = event.detail; console.log(`[${data.role.toUpperCase()} : ${data.sessionId}] [Room: ${data.roomId}] Compliance: ${data.type} address ${data.address} verified: ${data.isVerified}`); });

Core Lifecycle & Security

roomCreated: Fired immediately after the Coordinator successfully initializes a new signing room.

export interface RoomCreatedPayload extends BaseEventContext { roomId: string; network: 'bitcoin' | 'testnet' | 'signet'; }

transactionFinalized: Fired when the Coordinator successfully aggregates all required signatures and finalizes the transaction.

export interface TransactionFinalizedPayload extends BaseEventContext { txId: string; // The final SHA256 transaction ID txHex: string; // The broadcast-ready raw hex auditPdfUri: string; // Base64 Data URI of the generated PDF auditLogCsv: string; // Formatted CSV string of events settlementCsv: string; // Formatted CSV string of financial data roomState: RoomState; // The complete raw state (minus internal keys) }

roomStateChanged: Fired when the Coordinator changes the overarching access or lifecycle state of the room.

export interface RoomStateChangedPayload extends BaseEventContext { state: 'locked' | 'unlocked' | 'closed'; }

participantPresence: Fired when a user successfully connects to or disconnects from the active WebSocket session.

export interface ParticipantPresencePayload extends BaseEventContext { action: 'joined' | 'left'; participantId: string; participantRole: 'coordinator' | 'guest'; displayName?: string; }

destinationVerified: Fired when an administrator approves or revokes a specific input or output address on the strict whitelist.

export interface DestinationVerifiedPayload extends BaseEventContext { type: 'inputs' | 'outputs'; address: string | 'batch'; isVerified: boolean; }

Security Alerts

securityAlert: Fired when suspicious activity (like failed decryption attempts) is detected.

export interface SecurityAlertPayload extends BaseEventContext { alertType: 'access_denied'; severity: 'low' | 'medium' | 'high'; message: string; }

Privacy & UI Telemetry

privacyToggled: Fired whenever the user interacts with the OpSec blur/reveal mechanics.

export interface PrivacyToggledPayload extends BaseEventContext { section: 'transaction-overview' | 'transaction-proposal' | 'transaction-details' | 'signers' | 'all'; state: 'blurred' | 'reveal-all' | 'reveal-section' | 'hidden'; }

modalViewed: Fired when the user opens a specific modal or dialog within the widget.

export interface ModalViewedPayload extends BaseEventContext { modalName: string; context?: string; }

transactionViewChanged: Fired when the user toggles between viewing the Inputs and Outputs.

export interface TransactionViewChangedPayload extends BaseEventContext { view: 'inputs' | 'outputs'; }

Hardware & Cryptographic Imports

signatureReceived: Fired when a new signature is successfully merged into the transaction over the network.

export interface SignatureReceivedPayload extends BaseEventContext { fingerprint: string; // The hardware fingerprint that provided the signature signerLabel?: string; // The label assigned to this hardware by the Coordinator signerSessionId?: string; // The session ID of the user submitting (if known) signerName?: string; // The display name of the user submitting (if known) }

fountainFormatChanged: Fired when the user switches the Air-Gapped animated QR code format.

export interface FountainFormatChangedPayload extends BaseEventContext { format: 'ur' | 'bbqr'; }

fountainStateChanged: Fired when the user reveals or hides the Air-Gapped animated PSBT QR code.

export interface FountainStateChangedPayload extends BaseEventContext { isRevealed: boolean; format: 'ur' | 'bbqr'; }

psbtImported: Fired when a participant successfully injects a signed PSBT into the room.

export interface PsbtImportedPayload extends BaseEventContext { method: 'scan' | 'upload'; }

qrStateChanged: Fired when the user configures or reveals the secure Room Entry QR code.

export interface QrStateChangedPayload extends BaseEventContext { includesKey: boolean; isRevealed: boolean; }

User Actions & Data Management

dataCopied: Fired when a user copies sensitive configuration data to their clipboard.

export interface DataCopiedPayload extends BaseEventContext { dataType: 'room-id' | 'session-id' | 'decryption-key' | 'admin-token' | 'share-link' | 'share-link-full' | 'final-hex'; }

downloadTriggered: Fired when the user exports files or data from the room.

export interface DownloadTriggeredPayload extends BaseEventContext { fileType: 'audit-log' | 'csv' | 'unsigned-psbt' | 'qr-code-image'; }

roomRenamed: Fired when the Coordinator changes the display name of the current session.

export interface RoomRenamedPayload extends BaseEventContext { newName: string; }

participantLabelled: Fired when a user sets their own display name, or when the Coordinator applies a label to a specific hardware signer.

export interface ParticipantLabelledPayload extends BaseEventContext { target: 'self' | 'participant' | 'signer'; label: string; fingerprint?: string; participantId?: string; }

Base Interfaces

export interface BaseEventContext { roomId: string | null; sessionId: string | null; role: 'coordinator' | 'guest' | 'unknown'; network: 'bitcoin' | 'testnet' | 'signet' | null; timestamp: number; } export interface AuditEntry { timestamp: number; event: string; detail?: string; encryptedDetail?: string; user: string; } export interface RoomState { protocolVersion: string; roomId: string; roomName: string; network: 'bitcoin' | 'testnet' | 'signet'; psbt: string; signatures: string[]; finalTxHex?: string; finalTxId?: string; connectedCount: number; createdAt: number; expiresAt: number; isLocked: boolean; auditLog: AuditEntry[]; signerLabels: Record<string, string>; whitelist: string[]; participants?: Record<string, { id: string; role: string; encryptedDisplayName?: string; displayName?: string; }>; }

5. Error Handling Events

Listen for the signingError CustomEvent to catch parsing issues, network mismatches, or room expiration events.

widget.addEventListener('signingError', (e) => { console.error("Room Failed:", e.detail.message); // e.detail.code === 'PSBT_INVALID' });