Skip to content

NUT-18: Payment Requests

optional


This NUT introduces a standardised format for payment requests, that supply a sending wallet with all information necessary to complete the transaction. This enables many use-cases where a transaction is better initiated by the receiver (e.g. point of sale).

Flow

  1. Receiver creates a payment request, encodes it and displays it to the sender
  2. Sender scans the request and constructs a matching token
  3. Sender sends the token according to the transport specified in the payment request
  4. Receiver receives the token and finalises the transaction

Payment Request

A Payment Request is defined as follows

{
  "i": str <optional>,
  "a": int <optional>,
  "u": str <optional>,
  "s": bool <optional>,
  "m": Array[str] <optional>,
  "mp": bool <optional>,
  "sm": Array[SupportedMethod] <optional>,
  "d": str <optional>,
  "t": Array[Transport] <optional>,
  "nut10": NUT10Option <optional>,
}

Here, the fields are

  • i: Payment id to be included in the payment payload
  • a: The amount of the requested payment, net of input fees (see Input fees)
  • u: The unit of the requested payment (MUST be set if a or sm is set)
  • s: Whether the payment request is for single use
  • m: The mint list from which payment will be accepted
  • mp: Whether the mint list is advisory (true) or strict (false or omitted)
  • sm: A list of SupportedMethod entries (e.g. "bolt11", "bolt12", "onchain") that the payee will accept, each with an optional per-method fee
  • d: A human readable description that the sending wallet will display after scanning the request
  • t: The method of Transport chosen to transmit the payment (can be multiple, sorted by preference)
  • nut10: The required NUT-10 locking condition

Mint list

A Payment Request containing a mint list m describes the mints the receiver accepts payments from or prefers.

If m is set and mp is not present, or is false, the mint list is strict: the sender MUST only send proofs from these mints, and the receiver SHOULD ignore payments sent from other mints.

If m is set and mp is true, the mint list is preferred: the receiver accepts payments from other mints, but the payer SHOULD use a mint in m when possible.

If m is not set, mp SHOULD be ignored.

Supported payment methods

If sm is set, the payer MUST send ecash from a mint that supports melting the request unit (see NUT-05) via at least one of the listed payment methods. Each SupportedMethod is defined as:

{
  "mn": str,
  "mf": int <optional> // omitted = 0
}
  • mn: the method name (e.g. "bolt11", "bolt12", "onchain")
  • mf: additional fee, in the request unit, that compensates the receiver for melting out via this method

The per-method fee applies only to payments the receiver may need to melt out of: those from a mint outside m, or from any mint if m is not set. Payments from a mint in m carry no per-method fee.

When a fee applies, the payer owes the lowest mf among the listed methods their mint supports, and MUST add it to the requested amount. For example, if the request lists bolt11 (no fee) and onchain (mf = 50), a mint supporting both owes nothing, while an onchain-only mint owes 50.

Input fees

Received proofs cost the receiver an input fee when they are later swapped or melted (see NUT-02). The requested amount is therefore net of input fees: the payer MUST select proofs such that

sum(proofs) - input_fee(proofs) >= a + mf (where applicable)

with input_fee computed from the sending mint's keyset input_fee_ppk values as described in NUT-02. This protects the receiver from dust proof sets that are expensive to redeem (e.g. 1000 x 1 sat proofs at 250 ppk would cost the receiver 250 sats to swap).

Locking conditions

The payment request can include optional locking conditions the payee requires from the payer. For example, the payee might require a P2PK-locked token so that they can receive payments offline.

The nut10 field specifies the payee's requested locking condition as a NUT10Option object. Its elements are derived from NUT-10's well-known secret. The NUT10Option is defined as follows:

{
  "k": str,
  "d": str,
  "t": Array[Array[str, str]] <optional>
}
  • k: NUT-10 secret kind,
  • d: NUT-10 secret data,
  • t: optional NUT-10 payment tags

[!IMPORTANT] The payee must validate the incoming tokens themselves in order to decide whether they can accept the payment. This includes checking the DLEQ proof and whether the token includes a long-enough timelock to satisfy the payee.

Transport

Transport specifies methods for sending the ecash to the receiver. A transport consists of a type and a target.

[!IMPORTANT] The transport can be empty! If the transport is empty, we implicitly assume that the payment will be in-band. An example is X-Cashu where the payment is expected in the HTTP header of a request. We can only hope that the protocol you're using has a well-defined transport.

{
  "t": str,
  "a": str,
  "g": Array[Array[str, str]] <optional>
}
  • t: type of Transport
  • a: target of Transport
  • g: optional tags for the Transport

Tags

Tags are an optional array of [tag, value, value, ...] tuples that can be used to specify additional features about the transport. A single tag can have multiple values.

Transport types

The supported transport types are described below.

Nostr

  • type: nostr
  • target: <nprofile>
  • tags: [["n", "17"]]

The n tag specifies the NIPs the receiver supports. At least one tag value MUST be specified. For NIP-17 direct messages, the sender sends a PaymentRequestPayload as the message content.

HTTP POST

  • type: post
  • target: <endpoint url>

The execute the payment, the sender makes a POST request to the specified endpoint URL with the PaymentRequestPayload as the body.

Payment payload

If not specified otherwise, the payload sent to the receiver is a PaymentRequestPayload JSON serialized object as follows:

{
  "id": str <optional>,
  "memo": str <optional>,
  "mint": str,
  "unit": <str_enum>,
  "proofs": Array<Proof>
}

Here, id is the payment id (corresponding to i in request), memo is an optional memo to be sent to the receiver with the payment, mint is the mint URL from which the ecash is from, unit is the unit of the payment, and proofs is an array of proofs (see NUT-00, can also include DLEQ proofs).

Encoded Request

The payment request is serialized using CBOR, encoded in base64_urlsafe, together with a prefix creq and a version A:

"creq" + "A" + base64_urlsafe(CBOR(PaymentRequest))

Example

This is an example payment request expressed as JSON:

{
  "i": "b7a90176",
  "a": 10,
  "u": "sat",
  "m": ["https://nofees.testnut.cashu.space"],
  "t": [
    {
      "t": "nostr",
      "a": "nprofile1qy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsz9mhwden5te0wfjkccte9curxven9eehqctrv5hszrthwden5te0dehhxtnvdakqqgydaqy7curk439ykptkysv7udhdhu68sucm295akqefdehkf0d495cwunl5",
      "g": [["n", "17"]]
    }
  ]
}

This payment request serializes to:

creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U=