BRC-29 Simple Authenticated P2P Payment Protocol

BRC-29 defines how to send a peer-to-peer payment: derive a unique payment address from sender and recipient identity keys (using BRC-42 key derivation), construct a transaction with one or more P2PKH outputs to those addresses, and transmit the transaction via any transport (HTTP, WebSocket, message box). The receiver then internalizes the transaction into their wallet.

Interactive spec

At a glance

FieldValue
FormatAsyncAPI 3.0
Version1.0.0
Statusstable
Implementations@bsv/ts-paymail, @bsv/message-box-client

What problem this solves

Deriving unique payment addresses per payer without revealing the recipient's private key. With BRC-42/43 key derivation, a recipient can issue a new address to every payer (avoiding address reuse) without managing thousands of keys. The address is derived deterministically from sender+recipient keys, so only the recipient can receive to that address.

Authenticated peer payments without a central service. Traditional P2P payment requires a service to map paymail addresses to payment outputs. BRC-29 enables peer-to-peer: sender and recipient negotiate payment parameters directly (no intermediary), then sender constructs and transmits the transaction.

Transmit flexibility. BRC-29 specifies the transaction format (Atomic BEEF + BRC-100 envelope) but not how to send it. Payments can travel via HTTP POST, WebSocket message, message box, Paymail, or any transport layer.

Protocol overview

Three-step flow:

  1. Sender → Recipient (capability discovery) — Sender queries recipient's Paymail domain for supported capabilities (BRC-121). Learns recipient's public key and payment output derivation instructions.

  2. Sender → Recipient (payment message) — Sender derives payment address using BRC-42:

    • invoiceNumber = "2-3241645161d8-<prefix> <suffix>"
    • prefix = server-provided nonce (BRC-29 format)
    • suffix = derivation suffix (e.g., base64-encoded timestamp or counter)
    • Derives address and creates P2PKH output(s) to that address
    • Wraps in Atomic BEEF (BRC-95) format
    • Sends transaction via HTTP/WebSocket/message box
  3. Recipient → Sender (acknowledgment) — Recipient calls wallet.internalizeAction() to import transaction outputs. If the wallet accepts the payment, the recipient responds with acknowledgment (ACCEPTED or REJECTED).

Key types / channels

ChannelDirectionMessagePurpose
payment/sendSendPaymentMessageSender delivers Atomic BEEF transaction to recipient
payment/acknowledgeReceivePaymentAckRecipient confirms acceptance or rejection

PaymentMessage schema:

  • tx: AtomicBEEF — Transaction in BRC-95 Atomic BEEF format
  • amount: number — Total satoshis in outputs (metadata)
  • derivationPrefix: string — Server-provided nonce (BRC-29 format)
  • derivationSuffix: string — Client-provided derivation suffix
  • metadata: object — Optional sender metadata (memo, reference ID, etc.)

PaymentAck schema:

  • status: "ACCEPTED" | "REJECTED" — Whether recipient accepted the transaction
  • txid: string — Transaction ID (if accepted)
  • reason?: string — Explanation if rejected

Example: Send peer-to-peer payment via message box

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

const wallet = new WalletClient('auto', 'example.com')
const peerPay = new PeerPayClient({
  walletClient: wallet,
  messageBoxHost: 'https://message-box-us-1.bsvb.tech'
})

// Get recipient's identity key from Paymail, registry lookup, or direct exchange.
const recipientKey = '025706528f0f6894b2ba505007267ccff1133e004452a1f6b72ac716f246216366'

// Builds the BRC-29 settlement, sends the AtomicBEEF through MessageBox,
// and includes the derivation remittance the receiver needs to internalize it.
await peerPay.sendPayment({
  recipient: recipientKey,
  amount: 10000
})

Recipient receives and internalizes:

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

const peerPay = new PeerPayClient({
  walletClient: recipientWallet,
  messageBoxHost: 'https://message-box-us-1.bsvb.tech'
})

const payments = await peerPay.listIncomingPayments()

for (const payment of payments) {
  const result = await peerPay.acceptPayment(payment)
  if (typeof result === 'string') throw new Error(result)
  console.log('Payment accepted:', result.paymentResult)
}

Conformance vectors

BRC-29 conformance is tested in conformance/vectors/wallet/brc29/payment-derivation.json:

  • Key derivation with BRC-42 (prefix/suffix parsing)
  • Correct P2PKH output generation
  • Deterministic payment public keys

Implementations in ts-stack

PackageNotes
@bsv/ts-paymailPaymail client/server for capability discovery and payment routing
@bsv/message-box-clientMessage box transport for peer-to-peer payments
@bsv/wallet-toolboxinternalizeAction() method for receiving transactions

Spec artifact

brc29-payment-protocol.yaml