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.

Layout Tip: The widget is fully responsive but requires a parent container with a minimum height of 600px to properly display the signer dashboard.

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'); widget.setAttribute('relay-endpoint', 'https://signingroom.io'); // 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)

Listen for the transactionFinalized CustomEvent to capture the fully signed transaction, raw state, and generated audit logs.

widget.addEventListener('transactionFinalized', (e) => { const data = e.detail; console.log("Fully Signed Hex:", data.txHex); console.log("Transaction ID:", data.txId); // Full room state (participants, labels, whitelist, etc.) console.log("Room State Object:", data.roomState); // Compliance & Audit Data console.log("Audit Log (CSV):\n", data.auditLogCsv); console.log("Settlement Data (CSV):\n", data.settlementCsv); console.log("PDF Document Data URI:", data.auditPdfUri); });

Event Payload Structure:

/* TypeScript Interfaces for e.detail */ 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'; // Transaction Data psbt: string; signatures: string[]; finalTxHex?: string; finalTxId?: string; // Metadata connectedCount: number; createdAt: number; expiresAt: number; isLocked: boolean; // Governance auditLog: AuditEntry[]; signerLabels: Record<string, string>; whitelist: string[]; participants?: Record<string, { id: string; role: string; encryptedDisplayName?: string; displayName?: string; }>; } export interface TransactionFinalizedPayload { 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) }

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' });