@bsv/p2p
TypeScript icon, indicating that this package has built-in type declarations

1.1.6 • Public • Published

BSV Peer-to-Peer Messaging & Payment Tools

@bsv/p2p 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.

Table of Contents

  1. Introduction
  2. Installation
  3. Overview
  4. Quick Start Examples
  5. API Reference
  6. Contributing
  7. License

1. Introduction

The @bsv/p2p library provides two main tools for peer-to-peer interaction:

  1. 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.
  2. 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.


2. Installation

npm install @bsv/p2p

The package exports both MessageBoxClient and PeerPayClient. You can import them individually in your JavaScript/TypeScript applications.


2.1. Running Integration Tests

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

  1. 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)
  1. 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.
  1. 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

3. Overview

3.1. MessageBoxClient Overview

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.).

Key Features

  1. Secure by default using Auth libraries for signing/verification.
  2. Real-time or delayed delivery with sockets or HTTP.
  3. Easy integration with higher-level protocols and services.

Important: Initialization Requirement

Important:
Starting with version @bsv/p2p@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.


3.2. PeerPayClient Overview

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.

Key Features

  1. Deterministic derivation of payment information using the SPV-compliant BRC-29 protocol.
  2. Secure transaction passing using the MessageBoxClient infrastructure.
  3. Live or offline support for receiving payments.
  4. Easy acceptance/refunds with built-in methods.

4. Quick Start Examples

Below are two condensed examples: one for basic messaging (MessageBoxClient) and another for peer-to-peer payments (PeerPayClient).

4.1. Using MessageBoxClient

const { WalletClient } = require('@bsv/sdk')
const { MessageBoxClient } = require('@bsv/p2p')

// 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)
  }
})

4.2. Using PeerPayClient

import { WalletClient } from '@bsv/sdk'
import { PeerPayClient } from '@bsv/p2p'

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. API Reference

### 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


Interface: EncryptedMessage

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


Interface: ListMessagesParams

Defines the structure of a request to list messages.

Example

{
  messageBox: "payment_inbox"
}
export interface ListMessagesParams {
    messageBox: string;
}

Links: API, Interfaces, Classes


Interface: MessageBoxClientOptions

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


Interface: PeerMessage

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


Interface: SendMessageParams

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


Interface: SendMessageResponse

Server response structure for successful message delivery.

Returned by both sendMessage and sendLiveMessage.

export interface SendMessageResponse {
    status: string;
    messageId: string;
}

Links: API, Interfaces, Classes


Classes
Logger
MessageBoxClient

Links: API, Interfaces, Classes


Class: Logger
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


Class: MessageBoxClient

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).

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



5.2. PeerPayClient API

import { PeerPayClient } from '@bsv/p2p'

Constructor

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.

sendPayment({ recipient, amount })

await peerPay.sendPayment({
  recipient: '0277a2b...',
  amount: 10000
})
  • Sends a payment using HTTP.
  • Internally derives a public key for the recipient and builds a transaction.

sendLivePayment({ recipient, amount })

await peerPay.sendLivePayment({
  recipient: '0277a2b...',
  amount: 15000
})
  • Sends a payment using WebSockets, falling back to HTTP if needed.

listenForLivePayments({ onPayment })

await peerPay.listenForLivePayments({
  onPayment: (payment) => {
    console.log('New live payment:', payment)
  }
})
  • Subscribes to live payments in the payment_inbox.
  • Invokes onPayment callback with an IncomingPayment object:
interface IncomingPayment {
  messageId: number;
  sender: string;
  token: {
    customInstructions: {
      derivationPrefix: string;
      derivationSuffix: string;
    };
    transaction: any; // typically your BSV transaction format
    amount: number;
  };
}

acceptPayment(payment)

await peerPay.acceptPayment(payment)
  • Accepts (and "internalizes") the payment into your wallet.
  • Acknowledges the message, removing it from the inbox.

rejectPayment(payment)

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.

listIncomingPayments()

const payments = await peerPay.listIncomingPayments()
  • Lists all incoming payments in the payment_inbox.
  • Returns an array of IncomingPayment objects.

6. Contributing

  1. Clone this repository.
  2. Install dependencies with npm install.
  3. Make your changes, write tests, and open a PR.

We welcome bug reports, feature requests, and community contributions!


7. License

This code is licensed under the Open BSV License. See LICENSE for details.

Package Sidebar

Install

npm i @bsv/p2p

Weekly Downloads

26

Version

1.1.6

License

SEE LICENSE IN LICENSE.txt

Unpacked Size

1.18 MB

Total Files

104

Last publish

Collaborators

  • dkellen
  • braydude
  • tyeverett