Skip to content

PaymentRequestV2 and PaymentLinkV1 contract: immutable obligations, exact payouts, metadata, finite invoices, reusable checkout, CLI construction, and legacy compatibility.

PaymentRequestV2 represents a finite set of payment obligations in one collection. PaymentLinkV1 reuses the same terms model for repeatable fixed payments. Each obligation has its own approval and progress tracker. Payments go directly to the specified recipients when the transaction executes.

Use PaymentRequestV2 for a bill that can become paid. Use PaymentLinkV1 for a checkout that stays available for repeated purchases until its cutoff. Use Subscriptions for recurring authorization and charging.

JSON Input

Save this complete builder input as terms.json. The addresses are valid demonstration addresses, not recipients to pay. Replace them, the metadata, and the example 2030 cutoff before use. ubadge is the chain denomination; 1000000 is an exact base-unit amount.

json
{
  "version": 2,
  "kind": "invoice",
  "obligations": [
    {
      "id": "payment-1",
      "payer": {
        "kind": "anyone"
      },
      "payouts": [
        {
          "recipient": "bb1xvenxvenxvenxvenxvenxvenxvenxvenlrd2nm",
          "denom": "ubadge",
          "amount": "1000000"
        }
      ],
      "startTime": "1",
      "endTime": "1893456000000"
    }
  ],
  "name": "Example: anyone",
  "image": "https://example.com/payment.png",
  "description": "Editable payment example. Replace demo addresses, amounts, dates and metadata before creating a collection."
}
bash
bb build payment-request-v2 --schema
bb build payment-request-v2 --list-examples
bb build payment-request-v2 --example anyone | jq '.data' > terms.json
bb build payment-request-v2 --json terms.json --creator "$CREATOR" --output-file proposal.json

The example command generates a fresh cutoff. --schema returns the structural JSON Schema; the runtime validator also rejects incompatible combinations. JSON property names are case-sensitive. The builder parameter object includes metadata; stored PaymentRequestV2Terms contains only version, kind, and obligations. Do not pass the CLI {ok,data,warnings,error} envelope where a terms object is required.

Terms

The V2 builder takes a parameter object with version: 2, kind: "invoice" or "payment-link", obligations, and metadata. Supply either uri or the inline name, image, and description metadata fields. Every obligation contains:

FieldMeaning
idUnique reference within this collection; used by payment actions and tracking
payer{ "kind": "anyone" } or { "kind": "addresses", "addresses": [...] }
payoutsRecipient, denomination, and positive base-unit amount for each payout leg
startTimeFirst permitted payment instant, inclusive Unix milliseconds as an integer string
endTimeLast permitted payment instant, inclusive Unix milliseconds as an integer string
dueAtOptional informational due date; does not replace the payment window
requiredPaymentsFinite fixed-payment count; default is one when omitted on a nonpartial invoice
distinctPayersEnforce at most one payment per initiator for the obligation
partialFinite target in receipt units; payout amounts become amounts per unit

The runtime schema rejects unknown or incompatible fields. Payer counts and payment units describe permitted combinations. All amounts and times are exact integer strings; do not pass a JavaScript floating-point amount into these terms.

Build and Pay

bash
bb build payment-request-v2 --help
bb build payment-request-v2 --json terms.json --output-file proposal.json
bb pay-requests show 46
bb pay-requests status 46
bb pay-requests pay 46 --creator "$PAYER" --obligation payment-1 --units 1 --output-file payment.json

The first command constructs a collection proposal. Publish through the normal reviewed transaction flow, obtain the real collection ID, then query and pay that collection. --obligation is optional only when exactly one obligation exists. --units defaults to one and must be one for a fixed payment; it is an integer quantum count for partial payments.

bb pay-requests list --mine "$PAYER" --open includes eligible public requests and currently open unpaid obligations. A list result is discovery, not a reservation of capacity. Query status and simulate before signing.

The SDK equivalents are buildPaymentRequestV2, extractPaymentRequestV2Details, and the V2 payment helpers. MCP exposes build_payment_request_v2 through the same builder. Generated approvals, terms, and invariants must stay together. Editing customData after generation does not safely change an invoice.

Split Payouts and Assets

One obligation can pay several recipients. Every listed payout is required by the same approval. A failure to pay any leg causes the transaction to fail; it is not a best-effort recipient loop.

Several denominations mean all listed assets are owed. They are not alternative checkout currencies. Calculate totals separately by exact denomination. If an obligation has two legs of the same denomination, its total is their sum. Network and protocol fees are additional and must be reviewed separately.

Public payers exclude all payout recipients. This prevents a recipient from counting self-payment toward the invoice. Named addresses must be valid canonical addresses and must satisfy the builder's recipient/payer restrictions. Do not use address aliases that change after creation.

Immutable Terms

The standard builder freezes the economic terms, roster, approvals, conversion paths, and relevant collection permissions/invariants. The parser compares the actual on-chain configuration with the declared terms. This protects a payer from a manager silently replacing recipients or extending caps while retaining the same invoice description.

These are direct payments. There is no V2 cancellation, denial, escrow, automatic refund, conditional milestone release, or alternative-currency field. Unsupported features cannot be implemented by adding descriptive metadata. A separate agreement can arrange a later refund transaction, but it is not an enforceable branch of this invoice.

Set kind: "payment-link". Omit requiredPayments, partial, and distinctPayers. Each use is a separate fixed payment and receipt; multiple independent obligations are supported. A link never reaches a final paid state merely because someone paid once.

Use an opening window and hard cutoff when needed. Repeated requests can succeed until the cutoff, so retries must reconcile the previous transaction first. A payment link gives no permission to debit a wallet later. It is not a subscription.

Legacy Requests

The older PaymentRequest builder remains available as bb build payment-request and bb pay-requests build. The V2 path is explicitly bb build payment-request-v2. Do not reinterpret existing collections as V2 based only on a UI label such as invoice.

Legacy named requests can expose deny. Denial records refusal; the independent pay approval is still present, so denial is not cancellation. Public legacy requests cannot deny. V2 invoices and reusable links have no deny action. The still older Invoices format is also distinct and must retain its own parser and state handling.

See State and Tracking for partial evidence, payment activity, cutoff boundaries, and manager reporting.

Save this complete reusable-link input as payment-link.json. Each successful use pays the same fixed legs; there is no requiredPayments cap. Replace the demo recipient and metadata.

json
{
  "version": 2,
  "kind": "payment-link",
  "obligations": [
    {
      "id": "payment-1",
      "payer": {
        "kind": "anyone"
      },
      "payouts": [
        {
          "recipient": "bb1xvenxvenxvenxvenxvenxvenxvenxvenlrd2nm",
          "denom": "ubadge",
          "amount": "1000000"
        }
      ],
      "startTime": "1",
      "endTime": "1893456000000"
    }
  ],
  "name": "Example: link",
  "image": "https://example.com/payment.png",
  "description": "Editable payment example. Replace demo addresses, amounts, dates and metadata before creating a collection."
}
bash
bb build payment-request-v2 --json payment-link.json --creator "$CREATOR" --output-file payment-link-proposal.json

Input Limits

InputRuntime requirement
Obligations1–100, unique IDs
Obligation ID1–64 ASCII letters, digits, _ or -
Named roster1–100 unique canonical spendable bb1 addresses
Payouts1–50; each (recipient, denom) pair unique
Count, target, timestampPositive integer string no greater than 18446744073709551615
Coin amountPositive integer string below 2^255; totals must also fit
Denomination3–128 characters; first character a letter; remaining letters, digits, / : . _ -
WindowstartTime <= endTime; optional dueAt lies inside it

startTime: "0" is rejected; use "1" for an already-open example. Payers cannot also be recipients. Partial targets cannot combine with requiredPayments or enabled distinctPayers. A link cannot contain finite counts, partial targets, or enabled distinct-payer limits. Unknown fields are rejected instead of silently becoming guardrails.

Legacy Request JSON

Save the following input as payment-request.json, replace the demo addresses and metadata, then build it with the CLI command below. Legacy input uses display-unit amount and a single payer string. payer: "All" creates its public pay-only variant. It is not the V2 payer object, and a legacy deny action is not cancellation of the separate pay path.

json
{
  "amount": 5,
  "denom": "BADGE",
  "payer": "bb1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zql3w7",
  "recipient": "bb1xvenxvenxvenxvenxvenxvenxvenxvenlrd2nm",
  "expiration": "30d",
  "uri": "https://example.com/metadata.json"
}
bash
bb build payment-request --json payment-request.json \
  --amount "$(jq -r '.amount' payment-request.json)" \
  --denom "$(jq -r '.denom' payment-request.json)" \
  --payer "$(jq -r '.payer' payment-request.json)" \
  --recipient "$(jq -r '.recipient' payment-request.json)" \
  --creator "$CREATOR" --output-file payment-request-proposal.json

The repeated required flags are read from the same JSON file because CLI argument validation runs before loading JSON. Review the resulting proposal using the CLI lifecycle. SDK reference: buildPaymentRequest.

Edit this page on GitHub