npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bsv/message-box-client

v2.6.1

Published

A client for P2P messaging and payments

Downloads

4,235

Readme

@bsv/message-box-client

Authenticated, optionally encrypted store-and-forward messaging for BSV applications. The package supports browser and Node.js consumers, HTTP polling, live authenticated WebSockets, overlay-based host discovery, peer payments, token-settlement adapters, permissions, quotes, and push-device registration.

Install

npm install @bsv/message-box-client @bsv/sdk

@bsv/sdk is a required peer dependency. The package supports Node.js 22 and newer and publishes module-correct ESM and CommonJS entry points.

Basic messaging

import { MessageBoxClient } from '@bsv/message-box-client'
import { WalletClient } from '@bsv/sdk'

const wallet = new WalletClient()
const messages = new MessageBoxClient({
  walletClient: wallet,
  host: 'https://message-box-us-1.bsvb.tech'
})

const recipient = '025706528f0f6894b2ba505007267ccff1133e004452a1f6b72ac716f246216366'

await messages.sendMessage({
  recipient,
  messageBox: 'general_inbox',
  body: { text: 'Hello' }
})

const pending = await messages.listMessages({
  messageBox: 'general_inbox'
})

await messages.acknowledgeMessage({
  messageIds: pending.map(message => message.messageId)
})

TerraTestNet clients select the isolated overlay network and provide the dedicated TTN Message Box deployment explicitly:

const ttnMessages = new MessageBoxClient({
  walletClient: wallet,
  host: 'https://messagebox.ttn.example',
  networkPreset: 'teratestnet'
})

The explicit host is required until the dedicated TTN Message Box service has been deployed. This prevents TTN clients from silently using a testnet service.

listMessages() preserves its historical fetch-all behavior by following bounded server pages. Limit aggregate client memory when appropriate:

const firstTwoPages = await messages.listMessages({
  messageBox: 'general_inbox',
  pageSize: 250,
  maxPages: 2
})

const nextThousand = await messages.listMessages({
  messageBox: 'general_inbox',
  offset: 1000,
  limit: 1000
})

The client uses AuthFetch for BRC-105 challenges. It intentionally does not add a second Message Box-specific cost-approval mechanism because BRC-100 wallet permissions already govern payment authorization.

The UMD bundle includes SDK 2.5.0 support for the optional x-bsv-payment-known-txids response header; module consumers can enable it by using SDK 2.5.0 or later. This SDK extension lets recipients advertise already validated payment ancestors so compatible wallets can omit them from payment BEEF. It is not a standardized BRC-105 header. Existing compatible SDK peers remain supported, and services that omit the header retain existing payment behavior. No consumer migration is required.

Explicit init() is optional. Public methods initialize the wallet identity when needed:

await messages.init()

Client diagnostics, including errors, are disabled by default. Set enableLogging: true only when fixed lifecycle events are useful to a trusted local operator. Diagnostics intentionally omit message bodies and IDs, identity keys, hosts and query parameters, wallet exceptions, settlement artifacts, transactions, payment amounts, device tokens, and server response objects. Applications should record their own correlation identifier when they need to connect a fixed client event to an application request; do not reintroduce wallet or message data into console logs.

Encryption is enabled by default through the wallet protocol. Plaintext is an explicit interoperability choice:

await messages.sendMessage({
  recipient,
  messageBox: 'public_payloads',
  body: 'This payload is intentionally plaintext.',
  skipEncryption: true
})

The server authenticates and routes ciphertext but does not hold the wallet keys required to decrypt it.

Live messages

await messages.listenForLiveMessages({
  messageBox: 'general_inbox',
  onMessage: message => {
    console.log(message.sender, message.body)
  }
})

await messages.sendLiveMessage({
  recipient,
  messageBox: 'general_inbox',
  body: 'Live when possible, store-and-forward when needed.'
})

sendLiveMessage() uses an authenticated WebSocket and falls back to the HTTP send route when the socket is unavailable or does not acknowledge delivery. It captures and validates the same immutable recipient, message-box, body, message-ID, permission, and payment-ceiling authority as sendMessage() before opening or joining a socket. Room names are exact, control-free strings rather than values the client or server silently trims. Call disconnectWebSocket() when a long-lived client shuts down.

Socket options

socketOptions is forwarded to the underlying AuthSocketClient when the live socket is created. Use it to select Socket.IO transports when a deployment's fronting infrastructure does not carry Engine.IO HTTP polling:

const messages = new MessageBoxClient({
  walletClient: wallet,
  host: 'https://messagebox.example',
  socketOptions: { managerOptions: { transports: ['websocket'] } }
})

await messages.listenForLiveMessages({
  messageBox: 'general_inbox',
  onMessage: message => {
    console.log(message.sender, message.body)
  }
})

It also carries requestedCertificates, sessionManager, maxPendingAuthMessages, and onError.

Four fields are excluded from the type. wallet and originator are owned by the client, which always uses its own values. managerOptions.autoConnect is excluded because the socket connects when it is created and AuthSocketClient exposes no way to start one later, so disabling auto-connect could never connect; the constructor throws if it is passed as false.

managerOptions.retries is also excluded: AuthSocket does not send the raw Socket.IO acknowledgements that message retries require, so enabling retries would block subsequent authentication and application messages. The constructor rejects nonzero retries. Connection reconnection settings such as reconnection and reconnectionAttempts remain supported.

socketOptions applies only to the live socket path — initializeConnection(), listenForLiveMessages(), and sendLiveMessage(). It has no effect on sendMessage(), listMessages(), or acknowledgeMessage(), which use authenticated HTTP.

Host selection and public-service access

An explicitly configured host must use HTTPS except on loopback (localhost, *.localhost, 127.0.0.0/8, or [::1]). It must be an exact absolute URL without surrounding whitespace, credentials, a query, a fragment, or control characters. Route prefixes are supported:

const messages = new MessageBoxClient({
  walletClient: wallet,
  host: 'https://messaging.example.com/api'
})

Every Message Box HTTP response must be the result of successful BRC-103 mutual authentication. The client rejects AuthFetch's ordinary-HTTP compatibility fallback, requires a canonical curve-valid server identity, and keeps the first authenticated identity for each origin authoritative for that client instance. For durable independent trust, pin expected server identities by base URL:

const messages = new MessageBoxClient({
  walletClient: wallet,
  host: 'https://messaging.example.com/api',
  serverIdentityKeysByHost: {
    'https://messaging.example.com': '02…'
  }
})

Pins are per URL origin, so two route prefixes on the same origin cannot name different identities. Different overlay hosts may authenticate with different identities. A server-key change requires creating a new client with an updated, independently validated pin; it is never accepted silently within an instance.

Overlay advertisements are untrusted network input. Discovered destinations must use public HTTPS addresses; loopback, private, link-local, reserved, and documentation-only hosts are rejected before a request is made. If no valid advertisement exists, the configured host is used. Each returned transaction, output index, canonical PushDrop envelope, advertised identity, and signature is bounded and independently verified against the requested identity. The lookup provider therefore supplies candidates, not routing authority.

Message Box is a public protocol service. Compatible servers should remain browser-accessible by default with credential-free wildcard CORS, including opaque Origin: null callers such as mobile webviews. Operators may opt into an exact-origin allowlist or disable browser CORS. CSP governs served documents and is not API authorization. BRC-103 authentication, recipient ownership, permissions, payments, quotas, and message encryption remain the security boundaries.

Permissions and delivery quotes

await messages.setMessageBoxPermission({
  messageBox: 'notifications',
  sender: recipient,
  recipientFee: 10
})

const quote = await messages.getMessageBoxQuote({
  recipient,
  messageBox: 'notifications'
})

Permission fees use these values:

  • -1: blocked
  • 0: allowed without a recipient fee
  • positive integer: required satoshi amount

Set checkPermissions: true on sendMessage() when the client should quote and construct the required payment before sending. The mutually authenticated Message Box is authoritative for its own price, while the wallet remains the final spending authority. Applications can add an independent ceiling with maximumPayment; the client rejects a larger quote before requesting a wallet action:

await messages.sendMessage({
  recipient,
  messageBox: 'notifications',
  body: { title: 'Bounded-cost alert' },
  checkPermissions: true,
  maximumPayment: 25
})

Quote rows must bind every requested recipient and message-box name exactly once. Fees are bounded integers, blocked-recipient and status fields must agree, and aggregate arithmetic must stay in the safe-integer domain. Before a payment is transmitted, the client independently parses the wallet's Atomic BEEF and requires every delivery and recipient output to retain its requested script, amount, and remittance index. A custom wallet response is never treated as payment evidence merely because it returned transaction bytes.

Outgoing recipients are canonical compressed public keys. Message-box names, caller-supplied message IDs, and serialized bodies are exact and bounded; body objects and request records must contain only plain own data. The client captures the complete send authority before its first asynchronous operation, so later caller mutation cannot change the recipient, message box, body, flags, or payment ceiling. Server send results are accepted only when their recipient and message ID match the submitted message. Permission and device responses are also treated as untrusted protocol input: records, canonical identity keys, fees, timestamps, masked tokens, platform values, identifiers, and pagination arguments must match their bounded schema.

Peer payments

import { PeerPayClient } from '@bsv/message-box-client'

const payments = new PeerPayClient({ walletClient: wallet })

await payments.sendPayment({
  recipient,
  amount: 50_000
})

const incoming = await payments.listIncomingPayments()
for (const payment of incoming) {
  await payments.acceptPayment(payment)
}

PeerPayClient uses BRC-29 wallet-payment derivation and the same authenticated Message Box transport. It also supports live delivery, payment requests, responses, cancellations, and explicit rejection/refund flows. Its SDK peer accepts both historical number[] and binary Wallet Wire Uint8Array transaction results, while payment messages retain a portable JSON byte-array representation. Receipt remains compatible with pending messages whose typed-array bytes were already serialized as contiguous numeric keys. The same compatibility contract covers paid-message fees, batch delivery, token settlements, live-message fallback, and generic remittance transport. In a multi-recipient send, the quoted server delivery fee applies to every allowed recipient. The client checks the aggregate against maximumPayment and places it in the batch's single server remittance output. Malformed, sparse, or out-of-range byte records are rejected before wallet internalization and the source message remains available for retry or recovery. Incoming-payment reads are capped at 1,000 messages and validate canonical sender keys, transaction bytes, derivation metadata, positive integer amounts, output indexes, and message IDs. Live callbacks apply the same validation. acceptPayment and rejectPayment use the caller's message ID only, reload the authenticated inbox, require one unique match, and use the fresh record for all wallet, acknowledgement, and refund decisions. Payment-request lists and live callbacks likewise require a canonical body/envelope identity match, bounded fields, an unexpired request, and an affirmative 32-byte HMAC proof. Replayed request IDs from one sender fail closed. Fulfill and decline calls use only the selected message ID, reload the authenticated inbox, and reject concurrent processing of that ID by the same client instance. Response reads are bounded and preserve the authenticated sender and messageId; always bind both requestId and sender to the original request recipient.

Token settlement

PeerTokenClient routes token transfers and requests through Message Box while delegating token-standard-specific transaction work to registered TokenSettlementAdapter implementations:

import { PeerTokenClient } from '@bsv/message-box-client'

const tokens = new PeerTokenClient({
  walletClient: wallet,
  adapters: [myTokenAdapter]
})

The library does not assume a token-standard wire format. Adapters own the standard-specific construction, acceptance, termination, and receipt data for their protocol, while the client enforces common authority boundaries: canonical peer keys, bounded transaction/message fields, canonical positive integer amounts, exact adapter action verdicts, and unchanged requested protocol/asset/amount fields. Incoming reads are capped at 1,000 records. acceptToken() uses only the supplied message ID, reloads the authenticated inbox, requires one unique fresh match, and acknowledges only after the adapter returns the literal accept action. An acknowledgement outage after accepted custody is recoverable and does not turn the result into a truthy error string. Token requests apply the same bounded authenticated-envelope model. Live and listed requests require a canonical body/envelope sender match, an unexpired bounded schema, and an affirmative 32-byte HMAC proof. Fulfillment and decline operations reload the inbox by message ID and use only the unique fresh request; fulfillment also requires the selected source's protocol and asset to match it. Outgoing requests, cancellations, and response listings validate and bound all common fields before wallet or transport work. One client instance rejects concurrent fulfill/decline processing for the same token-request message.

RemittanceAdapter exposes Message Box as an SDK remittance communication layer.

HTTP and WebSocket contract

The client uses these authenticated HTTP routes:

| Method | Route | Purpose | | ------ | --------------------- | ---------------------------------------------------- | | POST | /sendMessage | Send to one or up to 100 recipients | | POST | /listMessages | Read a named box owned by the authenticated identity | | POST | /acknowledgeMessage | Delete acknowledged messages owned by the identity | | POST | /registerDevice | Register a Firebase push token | | GET | /devices | List the identity's registered devices | | POST | /permissions/set | Set a sender-specific or box-wide permission | | GET | /permissions/get | Read a permission | | GET | /permissions/list | List permissions | | GET | /permissions/quote | Quote one or up to 100 recipients |

The corresponding OpenAPI source is specs/messaging/message-box-http.yaml. Live delivery uses authenticated Socket.IO events and identity-owned rooms.

Public exports

  • MessageBoxClient
  • PeerPayClient
  • PeerTokenClient
  • RemittanceAdapter
  • TokenSettlementAdapter and its supporting types
  • messaging, payment, token, device, permission, quote, and batch-send types
  • standard message-box name constants

Development

From the repository root:

pnpm --filter @bsv/message-box-client typecheck
pnpm --filter @bsv/message-box-client lint
pnpm --filter @bsv/message-box-client test
pnpm --filter @bsv/message-box-client test:coverage
pnpm --filter @bsv/message-box-client pack:check
pnpm --filter @bsv/message-box-client test:browser

The unit suite is deterministic and does not contact a deployed service. test:integration is explicitly opt-in because it requires configured wallet, Message Box, database, WebSocket, and overlay services:

MESSAGE_BOX_RUN_INTEGRATION=true \
MESSAGE_BOX_INTEGRATION_HOST=http://127.0.0.1:8080 \
MESSAGE_BOX_WALLET_ORIGINATOR=localhost \
pnpm --filter @bsv/message-box-client test:integration

Targets under *.bsvb.tech require an additional explicit MESSAGE_BOX_ALLOW_PRODUCTION_INTEGRATION=true acknowledgement because the suite creates and acknowledges real messages.

The release tarball contains compiled ESM/CommonJS JavaScript, matching declarations and source maps, the UMD browser bundle, this README, and the license. It does not publish TypeScript source, tests, coverage, editor files, or package-manager locks.

License

TS Stack first-party material is under the Open BSV License Version 6. The UMD bundle incorporates separately licensed SDK material; keep THIRD_PARTY_NOTICES.md and LICENSES/ with the bundle.

Payment acceptance and acknowledgment ordering

acknowledgeNotification checks the original notification envelope, internalizes a present recipient payment with the configured originator, and acknowledges only after the wallet returns accepted: true. A notification without a payment can be acknowledged immediately. Failures, incomplete envelopes and payments with no wallet-payment outputs remain queued. Its boolean return contract is unchanged: false can mean no payment or a retained failed payment.

Incoming payment envelopes accept Atomic BEEF up to 32 MiB and at most 101 payment outputs, matching the existing 100-recipient batch plus delivery-fee output contract. The client snapshots those fields independently so a valid large transaction is not rejected by the smaller generic JSON-graph limit; sparse arrays, accessors, non-byte values, and oversized fields still fail closed before wallet work.

acceptPayment also requires affirmative wallet acceptance before acknowledgment; validation and internalization failures reject instead of returning a truthy error string. An acknowledgement failure after accepted custody is recoverable and does not change the successful result. For refundable amounts, rejectPayment internalizes first, sends the refund, and then acknowledges. A failed internalization prevents both refund and acknowledgment; a failed refund send leaves the message queued. The existing small-payment policy is unchanged.

This is the initial ordering remediation for issue #503. It does not provide a durable refund journal or exactly-once delivery. Reconcile uncertain refund-send outcomes before retrying, since a send may have completed before its response was lost. Payment-envelope retention and outcome reporting through listMessages/listMessagesLite, basket-insertion policy, and resumable refund semantics remain open. Use the original envelope for notification payment processing; a plain acknowledgment is not evidence that a payment was accepted.

Payment- and token-request fulfillment also cannot provide cross-process or cross-crash exactly-once settlement with the current protocol. If settlement succeeds but its response fails or is lost, the request remains queued and an automatic retry can pay twice. Do not retry an uncertain fulfillment until the wallet and peer have been reconciled. Persist each authenticated request ID and sender together with every attempted or completed settlement transaction ID. Before fulfilling or retrying, reject a transaction ID already associated with a different request and reject a different transaction ID for a request whose outcome has not been reconciled. Apply the same durable correlation in every service process; in-memory serialization is not cross-process replay protection.