@bsv/message-box-client is a toolkit for peer-to-peer messaging and payments on the BSV blockchain. It leverages a server-side store-and-forward system for message delivery (via MessageBoxClient
) and also includes a higher-level peer-to-peer payment flow (via PeerPayClient
). Both functionalities build on BRC-103 for mutual authentication and identity key management, allowing secure and authenticated exchanges of data and BSV.
The @bsv/message-box-client library provides two main tools for peer-to-peer interaction:
- MessageBoxClient – A store-and-forward messaging system backed by a "message box" server. It supports authenticated sending, listing, and acknowledging (deleting) messages with a mutual-auth approach.
- PeerPayClient – A higher-level payment client built on top of
MessageBoxClient
, enabling real-time, peer-to-peer Bitcoin payments on the BSV blockchain.
Both clients use the BRC-103-based authentication model. By integrating with a WalletClient, they can sign and verify messages, ensuring only authorized parties can send and receive.
npm install @bsv/message-box-client
The package exports both MessageBoxClient
and PeerPayClient
. You can import them individually in your JavaScript/TypeScript applications.
The P2P integration tests verify live communication between the MessageBoxClient and a running instance of the MessageBox Server. To run them, make sure the following are set up:
Prerequisites
- MessageBox Server Running Locally You must have a local instance of the MessageBox Server running. Follow these steps inside the messagebox-server project:
git clone https://github.com/bitcoin-sv/messagebox-server.git
cd messagebox-server
cp .env.example .env
npm install
npm run start # Starts LARS (overlay service on http://localhost:8080)
npm run dev # Starts MessageBox Server (HTTP API on http://localhost:5001)
- Ensure Environment Configuration Your .env file in the MessageBox Server must have:
NODE_ENV=development
BSV_NETWORK=local
ENABLE_WEBSOCKETS=true
and valid values for:
- SERVER_PRIVATE_KEY
- WALLET_STORAGE_URL
- MONGO_URI and MONGO_DB
- (Optional) If using MySQL locally, ensure KNEX_DB_CONNECTION is properly set.
- Wallet Storage Running (optional) If your .env points to a local wallet-storage instance, make sure it is also running.
Running the Tests Once the MessageBox Server and overlay service are running:
npm run test:integration
This will execute all integration tests under src/tests/integration/, including HTTP, WebSocket, encryption, and overlay scenarios.
Notes
- If the server is not running, tests will fail with network or timeout errors.
- Integration tests use the local network preset (networkPreset: 'local') and assume the default MessageBox API endpoints (e.g., http://localhost:8080).
- Some tests may require clearing the database manually between runs if data conflicts occur.
- Unit tests (non-integration) still run by default with npm test. Integration tests are separated.
Quick Summary
- npm run test → unit tests only
- npm run test:integration → integration tests against a running MessageBox server
MessageBoxClient
uses a store-and-forward architecture for Peer-to-Peer messages:
- Store-and-forward: Messages are posted to a MessageBoxServer under a named "message box" (like an inbox).
- Ephemeral storage: Once the recipient acknowledges the messages, they are removed from the server.
- Mutual authentication: Ensures only authorized peers can read or post messages, using AuthFetch and AuthSocketClient.
- Flexible transport: Supports both WebSockets (for live, push-style delivery) and HTTP (for polling or fallback).
- Extensible: Can be the foundation for more advanced workflows (e.g., token-based messaging, invoice/ticket systems, etc.).
- Secure by default using Auth libraries for signing/verification.
- Real-time or delayed delivery with sockets or HTTP.
- Easy integration with higher-level protocols and services.
Important:
Starting with version @bsv/message-box-client@1.2.0
, MessageBoxClient
automatically initializes itself if needed.
- If you provide a host during construction, the client will auto-initialize on first use.
- If no host is provided, the client will anoint a host from the overlay network automatically during initialization.
You may still manually call await init()
if you want to control when initialization happens.
Example:
const client = new MessageBoxClient({ walletClient, host: 'https://messagebox.babbage.systems' })
// Manual init (optional but supported)
await client.init()
// Or just start sending — init will auto-run if needed
await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
Example:
const client = new MessageBoxClient({ walletClient, host: 'https://messagebox.babbage.systems' })
await client.init() // Must always call init() before using the client
await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
The init()
method will:
-
Anoint your messagebox host onto the overlay network if necessary.
-
Initialize the client’s internal identity and network information.
-
Ensure that your client can receive messages securely from peers.
PeerPayClient
builds on MessageBoxClient
to enable peer-to-peer Bitcoin payments:
- Secure Payment Delivery: Utilizes the same store-and-forward or live WebSocket approach for delivering payment instructions.
- Derivation & Signing: Creates a unique output script for each payment, derived from sender + recipient keys.
- Live or Delayed: Works with web sockets for immediate notifications, or HTTP for an asynchronous flow.
- Wallet Integration: Accept or reject incoming payments. If accepted, the payment is “internalized” into your BRC-100 compatible wallet automatically.
- Deterministic derivation of payment information using the SPV-compliant BRC-29 protocol.
- Secure transaction passing using the
MessageBoxClient
infrastructure. - Live or offline support for receiving payments.
- Easy acceptance/refunds with built-in methods.
Below are two condensed examples: one for basic messaging (MessageBoxClient) and another for peer-to-peer payments (PeerPayClient).
const { WalletClient } = require('@bsv/sdk')
const { MessageBoxClient } = require('@bsv/message-box-client')
// Example identity key of the recipient (public key in hex)
const johnSmithKey = '022600d2ef37d123fdcac7d25d7a464ada7acd3fb65a0daf85412140ee20884311'
async function main() {
// 1) Create your WalletClient (this obtains your identity key)
const myWallet = new WalletClient()
// 2) Create a MessageBoxClient, pointing to a MessageBoxServer
const msgBoxClient = new MessageBoxClient({
host: 'https://messagebox.babbage.systems',
walletClient: myWallet
})
// 3) (Optional) Initialize the client manually
await msgBoxClient.init()
// (Optional) Initialize a WebSocket connection (for real-time listening)
await msgBoxClient.initializeConnection()
// 4) Send a message to John's "demo_inbox"
await msgBoxClient.sendMessage({
recipient: johnSmithKey,
messageBox: 'demo_inbox',
body: 'Hello John! This is a test message.'
})
// 5) List messages in "demo_inbox"
const messages = await msgBoxClient.listMessages({ messageBox: 'demo_inbox' })
console.log(messages[0].body) // --> "Hello John! This is a test message."
// 6) Acknowledge (and delete) them from the server
await msgBoxClient.acknowledgeMessage({
messageIds: messages.map(msg => msg.messageId.toString())
})
}
main().catch(console.error)
Note:
- If you do not manually call
await init()
, the client will automatically initialize itself when you use it. - You can still call
await init()
manually if you want explicit control. init()
ensures your identity is properly registered and discoverable via overlay advertisement if necessary.
Listening for Live Messages
If you want push-style message notifications instead of polling:
await msgBoxClient.listenForLiveMessages({
messageBox: 'demo_inbox',
onMessage: (msg) => {
console.log('Received live message in "demo_inbox":', msg.body)
}
})
import { WalletClient } from '@bsv/sdk'
import { PeerPayClient } from '@bsv/message-box-client'
async function paymentDemo() {
// 1) Create your wallet instance
const wallet = new WalletClient()
// 2) Create a PeerPayClient
const peerPay = new PeerPayClient({
walletClient: wallet
})
// 3) (Optional) Listen for incoming payments
await peerPay.listenForLivePayments({
onPayment: async (payment) => {
console.log('Received payment:', payment)
// Accept it into the wallet
await peerPay.acceptPayment(payment)
}
})
// 4) Send a payment of 50,000 sats to the recipient
await peerPay.sendLivePayment({
recipient: '0277a2b...e3f4', // recipient's public key
amount: 50000
})
}
paymentDemo().catch(console.error)
Note: sendLivePayment
will try WebSocket first and fall back to HTTP if unavailable.
### 5.1. MessageBoxClient API
<!--#region ts2md-api-merged-here-->
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes)
##### Interfaces
| |
| --- |
| [AcknowledgeMessageParams](#interface-acknowledgemessageparams) |
| [EncryptedMessage](#interface-encryptedmessage) |
| [ListMessagesParams](#interface-listmessagesparams) |
| [MessageBoxClientOptions](#interface-messageboxclientoptions) |
| [PeerMessage](#interface-peermessage) |
| [SendMessageParams](#interface-sendmessageparams) |
| [SendMessageResponse](#interface-sendmessageresponse) |
Links: [API](#api), [Interfaces](#interfaces), [Classes](#classes)
---
###### Interface: AcknowledgeMessageParams
Defines the structure of a request to acknowledge messages.
Example
```ts
{
messageIds: ["abc123", "def456"]
}
export interface AcknowledgeMessageParams {
messageIds: string[];
}
Links: API, Interfaces, Classes
Encapsulates an AES-256-GCM encrypted message body.
Used when transmitting encrypted payloads to the MessageBox server.
export interface EncryptedMessage {
encryptedMessage: Base64String;
}
Links: API, Interfaces, Classes
Defines the structure of a request to list messages.
Example
{
messageBox: "payment_inbox"
}
export interface ListMessagesParams {
messageBox: string;
}
Links: API, Interfaces, Classes
Configuration options for initializing a MessageBoxClient.
export interface MessageBoxClientOptions {
walletClient?: WalletClient;
host?: string;
enableLogging?: boolean;
networkPreset?: "local" | "mainnet" | "testnet";
}
Interface MessageBoxClientOptions Details
####### Property enableLogging
If true, enables detailed logging to the console.
enableLogging?: boolean
####### Property host
Base URL of the MessageBox server.
host?: string
####### Property networkPreset
Overlay network preset for routing resolution.
networkPreset?: "local" | "mainnet" | "testnet"
####### Property walletClient
Wallet instance used for auth, identity, and encryption. If not provided, a new WalletClient will be created.
walletClient?: WalletClient
Links: API, Interfaces, Classes
Represents a decrypted message received from a MessageBox. Includes metadata such as sender identity, timestamps, and optional acknowledgment status.
Used in both HTTP and WebSocket message retrieval responses.
export interface PeerMessage {
messageId: string;
body: string;
sender: string;
created_at: string;
updated_at: string;
acknowledged?: boolean;
}
Links: API, Interfaces, Classes
Parameters required to send a message. Message content may be a string or object, and encryption is enabled by default.
Example
{
recipient: "03abc...",
messageBox: "payment_inbox",
body: { type: "ping" },
skipEncryption: false
}
export interface SendMessageParams {
recipient: string;
messageBox: string;
body: string | object;
messageId?: string;
skipEncryption?: boolean;
}
Links: API, Interfaces, Classes
Server response structure for successful message delivery.
Returned by both sendMessage
and sendLiveMessage
.
export interface SendMessageResponse {
status: string;
messageId: string;
}
Links: API, Interfaces, Classes
Logger |
MessageBoxClient |
Links: API, Interfaces, Classes
export class Logger {
static enable(): void
static disable(): void
static log(...args: unknown[]): void
static warn(...args: unknown[]): void
static error(...args: unknown[]): void
}
Links: API, Interfaces, Classes
Example
const client = new MessageBoxClient({ walletClient, enableLogging: true })
// Manual init is optional — client will auto-initialize if needed
await client.init()
await client.sendMessage({ recipient, messageBox: 'payment_inbox', body: 'Hello world' })
export class MessageBoxClient {
public readonly authFetch: AuthFetch;
constructor(options: MessageBoxClientOptions = {})
async init(targetHost: string = this.host): Promise<void>
public getJoinedRooms(): Set<string>
public async getIdentityKey(): Promise<string>
public get testSocket(): ReturnType<typeof AuthSocketClient> | undefined
async initializeConnection(): Promise<void>
async resolveHostForRecipient(identityKey: string): Promise<string>
async joinRoom(messageBox: string): Promise<void>
async listenForLiveMessages({ onMessage, messageBox }: {
onMessage: (message: PeerMessage) => void;
messageBox: string;
}): Promise<void>
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption }: SendMessageParams): Promise<SendMessageResponse>
async leaveRoom(messageBox: string): Promise<void>
async disconnectWebSocket(): Promise<void>
async sendMessage(message: SendMessageParams, overrideHost?: string): Promise<SendMessageResponse>
async anointHost(host: string): Promise<{
txid: string;
}>
async listMessages({ messageBox }: ListMessagesParams): Promise<PeerMessage[]>
async acknowledgeMessage({ messageIds }: AcknowledgeMessageParams): Promise<string>
}
See also: AcknowledgeMessageParams, ListMessagesParams, MessageBoxClientOptions, PeerMessage, SendMessageParams, SendMessageResponse
Class MessageBoxClient Details
####### Constructor
constructor(options: MessageBoxClientOptions = {})
See also: MessageBoxClientOptions
Argument Details
- options
- Initialization options for the MessageBoxClient.
Example
const client = new MessageBoxClient({
host: 'https://messagebox.example',
walletClient,
enableLogging: true,
networkPreset: 'testnet'
})
await client.init()
####### Method acknowledgeMessage
async acknowledgeMessage({ messageIds }: AcknowledgeMessageParams): Promise<string>
See also: AcknowledgeMessageParams
Returns
- A string indicating the result, typically
'success'
.
Argument Details
- params
- An object containing an array of message IDs to acknowledge.
Throws
If the message ID array is missing or empty, or if the request to the server fails.
Example
await client.acknowledgeMessage({ messageIds: ['msg123', 'msg456'] })
####### Method anointHost
async anointHost(host: string): Promise<{
txid: string;
}>
Returns
- The transaction ID of the advertisement broadcast to the overlay network.
Argument Details
- host
- The full URL of the server you want to designate as your MessageBox host (e.g., "https://mybox.com").
Throws
If the URL is invalid, the PushDrop creation fails, or the overlay broadcast does not succeed.
Example
const { txid } = await client.anointHost('https://my-messagebox.io')
####### Method disconnectWebSocket
async disconnectWebSocket(): Promise<void>
Returns
Resolves when the WebSocket connection is successfully closed.
Example
await client.disconnectWebSocket()
####### Method getIdentityKey
public async getIdentityKey(): Promise<string>
Returns
The identity public key of the user
####### Method getJoinedRooms
public getJoinedRooms(): Set<string>
Returns
A set of currently joined WebSocket room IDs
####### Method init
async init(targetHost: string = this.host): Promise<void>
Argument Details
- targetHost
- Optional host to set or override the default host.
Throws
If no valid host is provided, or anointing fails.
Example
const client = new MessageBoxClient({ host: 'https://mybox.example', walletClient })
await client.init()
await client.sendMessage({ recipient, messageBox: 'inbox', body: 'Hello' })
####### Method initializeConnection
async initializeConnection(): Promise<void>
Throws
If the identity key is unavailable or authentication fails
Example
const mb = new MessageBoxClient({ walletClient })
await mb.initializeConnection()
// WebSocket is now ready for use
####### Method joinRoom
async joinRoom(messageBox: string): Promise<void>
Argument Details
- messageBox
- The name of the WebSocket room to join (e.g., "payment_inbox").
Example
await client.joinRoom('payment_inbox')
// Now listening for real-time messages in room '028d...-payment_inbox'
####### Method leaveRoom
async leaveRoom(messageBox: string): Promise<void>
Argument Details
- messageBox
- The name of the WebSocket room to leave (e.g.,
payment_inbox
).
- The name of the WebSocket room to leave (e.g.,
Example
await client.leaveRoom('payment_inbox')
####### Method listMessages
async listMessages({ messageBox }: ListMessagesParams): Promise<PeerMessage[]>
See also: ListMessagesParams, PeerMessage
Returns
- Returns an array of decrypted
PeerMessage
objects.
Argument Details
- params
- Contains the name of the messageBox to read from.
Throws
If no messageBox is specified, the request fails, or the server returns an error.
Example
const messages = await client.listMessages({ messageBox: 'inbox' })
messages.forEach(msg => console.log(msg.sender, msg.body))
####### Method listenForLiveMessages
async listenForLiveMessages({ onMessage, messageBox }: {
onMessage: (message: PeerMessage) => void;
messageBox: string;
}): Promise<void>
See also: PeerMessage
Argument Details
- params
- Configuration for the live message listener.
Example
await client.listenForLiveMessages({
messageBox: 'payment_inbox',
onMessage: (msg) => console.log('Received live message:', msg)
})
####### Method resolveHostForRecipient
async resolveHostForRecipient(identityKey: string): Promise<string>
Returns
- A fully qualified host URL for the recipient's MessageBox server.
Argument Details
- identityKey
- The public identity key of the intended recipient.
Example
const host = await resolveHostForRecipient('028d...') // → returns either overlay host or this.host
####### Method sendLiveMessage
async sendLiveMessage({ recipient, messageBox, body, messageId, skipEncryption }: SendMessageParams): Promise<SendMessageResponse>
See also: SendMessageParams, SendMessageResponse
Returns
A success response with the generated messageId.
Argument Details
- param0
- The message parameters including recipient, box name, body, and options.
Throws
If message validation fails, HMAC generation fails, or both WebSocket and HTTP fail to deliver.
Example
await client.sendLiveMessage({
recipient: '028d...',
messageBox: 'payment_inbox',
body: { amount: 1000 }
})
####### Method sendMessage
async sendMessage(message: SendMessageParams, overrideHost?: string): Promise<SendMessageResponse>
See also: SendMessageParams, SendMessageResponse
Returns
- Resolves with
{ status, messageId }
on success.
Argument Details
- message
- Contains recipient, messageBox name, message body, optional messageId, and skipEncryption flag.
- overrideHost
- Optional host to override overlay resolution (useful for testing or private routing).
Throws
If validation, encryption, HMAC, or network request fails.
Example
await client.sendMessage({
recipient: '03abc...',
messageBox: 'notifications',
body: { type: 'ping' }
})
Links: API, Interfaces, Classes
import { PeerPayClient } from '@bsv/message-box-client'
new PeerPayClient({
walletClient: WalletClient,
messageBoxHost?: string,
enableLogging?: boolean
})
- walletClient: (Required) Your identity/signing wallet.
- messageBoxHost: (Optional) Base URL of the MessageBoxServer. Defaults to
https://messagebox.babbage.systems
. - enableLogging: (Optional) Enables verbose debug output.
await peerPay.sendPayment({
recipient: '0277a2b...',
amount: 10000
})
- Sends a payment using HTTP.
- Internally derives a public key for the recipient and builds a transaction.
await peerPay.sendLivePayment({
recipient: '0277a2b...',
amount: 15000
})
- Sends a payment using WebSockets, falling back to HTTP if needed.
await peerPay.listenForLivePayments({
onPayment: (payment) => {
console.log('New live payment:', payment)
}
})
- Subscribes to live payments in the
payment_inbox
. - Invokes
onPayment
callback with anIncomingPayment
object:
interface IncomingPayment {
messageId: number;
sender: string;
token: {
customInstructions: {
derivationPrefix: string;
derivationSuffix: string;
};
transaction: any; // typically your BSV transaction format
amount: number;
};
}
await peerPay.acceptPayment(payment)
- Accepts (and "internalizes") the payment into your wallet.
- Acknowledges the message, removing it from the inbox.
await peerPay.rejectPayment(payment)
- Rejects the payment, returning a refund to the sender (minus a small fee, e.g. 1000 sats).
- If the amount is too small to refund, the payment is simply acknowledged and dropped.
const payments = await peerPay.listIncomingPayments()
- Lists all incoming payments in the
payment_inbox
. - Returns an array of
IncomingPayment
objects.
- Clone this repository.
- Install dependencies with
npm install
. - Make your changes, write tests, and open a PR.
We welcome bug reports, feature requests, and community contributions!
This code is licensed under the Open BSV License. See LICENSE for details.