diff --git a/18.md b/18.md index 34571360f..06f67927d 100644 --- a/18.md +++ b/18.md @@ -24,6 +24,8 @@ A Payment Request is defined as follows "u": str , "s": bool , "m": Array[str] , + "mp": bool , + "sm": Array[SupportedMethod] , "d": str , "t": Array[Transport] , "nut10": NUT10Option , @@ -33,14 +35,52 @@ A Payment Request is defined as follows Here, the fields are - `i`: Payment id to be included in the payment payload -- `a`: The amount of the requested payment -- `u`: The unit of the requested payment (MUST be set if `a` is set) +- `a`: The amount of the requested payment, net of input fees (see [Input fees](#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`: A set of mints from which the payment is requested +- `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][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][05]) via at least one of the listed payment methods. Each `SupportedMethod` is defined as: + +```json +{ + "mn": str, + "mf": int // 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][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][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. @@ -146,11 +186,13 @@ This is an example payment request expressed as JSON: } ``` -This payment request serializes to (see [here](https://cbor.nemo157.com/#type=hex&value=a3617482a261694800ffd48b8f5ecf80617081a36161016173784061636331323433356537623834383463336366313835303134393231386166393066373136613532626634613565643334376534386563633133663737333838616358210244538319de485d55bed3b29a642bee5879375ab9e7a620e11e48ba482421f3cfa261694800ad268c4d1f5826617082a3616102617378403133323364336434373037613538616432653233616461346539663166343966356135623461633762373038656230643631663733386634383330376538656561635821023456aa110d84b4ac747aebd82c3b005aca50bf457ebd5737a4414fac3ae7d94da36161016173784035366263626362623763633634303662336661356435376432313734663465666638623434303262313736393236643361353764336333646362623539643537616358210273129c5719e599379a974a626363c333c56cafc0e6d01abe46d5808280789c63616d75687474703a2f2f6c6f63616c686f73743a33333338617563736174)): +This payment request serializes to: ```sh creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U= ``` [00]: 00.md +[02]: 02.md +[05]: 05.md [10]: 10.md diff --git a/26.md b/26.md index 555d8bcf7..773cee410 100644 --- a/26.md +++ b/26.md @@ -31,18 +31,22 @@ The payment request is encoded as a sequence of TLV fields. Each TLV entry consi - **Length** (2 bytes, big-endian): Length of value in bytes - **Value** (variable): Field data +Fields of type `u64` are encoded as fixed 8-byte big-endian values. + ### Top-Level TLV Tags -| Tag | Field | Type | Description | -| ---- | ----------- | --------- | -------------------------------------------------------------------- | -| 0x01 | id | string | Payment identifier (corresponds to `i` in JSON) | -| 0x02 | amount | u64 | Amount in base units (corresponds to `a` in JSON) | -| 0x03 | unit | u8/string | Currency unit (corresponds to `u` in JSON) | -| 0x04 | single_use | u8 | Single-use flag: 0=false, 1=true (corresponds to `s` in JSON) | -| 0x05 | mint | string | Mint URL (repeatable for multiple mints, corresponds to `m` in JSON) | -| 0x06 | description | string | Human-readable description (corresponds to `d` in JSON) | -| 0x07 | transport | sub-TLV | Transport configuration (repeatable, corresponds to `t` in JSON) | -| 0x08 | nut10 | sub-TLV | NUT-10 spending conditions (corresponds to `nut10` in JSON) | +| Tag | Field | Type | Description | +| ---- | ---------------- | --------- | ------------------------------------------------------------------------------------------------- | +| 0x01 | id | string | Payment identifier (corresponds to `i` in JSON) | +| 0x02 | amount | u64 | Amount in base units (corresponds to `a` in JSON) | +| 0x03 | unit | u8/string | Currency unit (corresponds to `u` in JSON) | +| 0x04 | single_use | u8 | Single-use flag: 0=false, 1=true (corresponds to `s` in JSON) | +| 0x05 | mint | string | Mint URL (repeatable for multiple mints, corresponds to `m` in JSON) | +| 0x06 | description | string | Human-readable description (corresponds to `d` in JSON) | +| 0x07 | transport | sub-TLV | Transport configuration (repeatable, corresponds to `t` in JSON) | +| 0x08 | nut10 | sub-TLV | NUT-10 spending conditions (corresponds to `nut10` in JSON) | +| 0x09 | mint_preferred | u8 | Mint list strictness flag: 0=false, 1=true; defaults to 0 if absent (corresponds to `mp` in JSON) | +| 0x0a | supported_method | sub-TLV | Supported payment method with optional fee (repeatable, corresponds to `sm` in JSON) | All fields are optional. Unknown tags MUST be ignored to maintain forward compatibility. @@ -146,6 +150,17 @@ The following kind values are defined for NUT-10 spending conditions: Additional kind values may be defined in future NUT specifications. Unknown kind values SHOULD be preserved when re-encoding but MAY be ignored during validation. +### Supported Method Sub-TLV (Tag 0x0a) + +Each supported method is encoded as a nested TLV structure. The tag is repeatable, one sub-TLV per method, decoding to the `sm` array in JSON: + +| Sub-Tag | Field | Type | Description | +| ------- | ------ | ------ | ----------------------------------------------------------------- | +| 0x01 | method | string | Method name, e.g. `"bolt11"` (corresponds to `mn` in JSON) | +| 0x02 | fee | u64 | Optional per-method fee; absent = 0 (corresponds to `mf` in JSON) | + +When the per-method fee applies and how much the payer owes is defined in [NUT-18][18]. + ## Example This is an example payment request expressed as JSON: diff --git a/tests/18-tests.md b/tests/18-tests.md index e88a95705..912e39580 100644 --- a/tests/18-tests.md +++ b/tests/18-tests.md @@ -27,7 +27,7 @@ A basic payment request with required fields. Encoded (CBOR serialized to base64url): ``` -creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxeTI4d3VtbjhnaGo3dW45ZDNzaGp0bnl2OWtoMnVld2Q5aHN6OW1od2RlbjV0ZTB3ZmprY2N0ZTljdXJ4dmVuOWVlaHFjdHJ2NWhzenJ0aHdkZW41dGUwZGVoaHh0bnZkYWtxcWd5ZGFxeTdjdXJrNDM5eWtwdGt5c3Y3dWRoZGh1NjhzdWNtMjk1YWtxZWZkZWhrZjBkNDk1Y3d1bmw1YWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF4Imh0dHBzOi8vbm9mZWVzLnRlc3RudXQuY2FzaHUuc3BhY2U= +creqApWF0gaNhdGVub3N0cmFheKlucHJvZmlsZTFxcXNnbTZxZmEzYzhkdHoyZnZ6aHZmcWVhY213bTBlNTBwZTNrNXRmbXZwamptbjB2ajdtMnRncHozbWh4dWU2OXVoaHlldHZ2OXVqdWVycGQ0Nmh4dG5mZHVxM3dhbW53dmF6N3RtanY0a3h6N2Z3OHFlbnh2ZXd3ZGN4emNtOTl1cXM2YW1ud3Zhejd0bXdkYWVqdW1yMGRzNGxqaDduYWeBgmFuYjE3YWloYjdhOTAxNzZhYQphdWNzYXRhbYF3aHR0cHM6Ly84MzMzLnNwYWNlOjMzMzg ``` ### Complete Payment Request @@ -157,3 +157,24 @@ Encoded: ``` creqApWFpaGM5ZTQ1ZDJhYWEZAfRhdWNzYXRhbYF4GGh0dHBzOi8vbWludC5leGFtcGxlLmNvbWVudXQxMKNha2RQMlBLYWR4QjAyYzNiNWJiMjdlMzYxNDU3YzkyZDkzZDc4ZGQ3M2QzZDUzNzMyMTEwYjJjZmU4YjUwZmJjMGFiYzYxNWU5YzMzMWF0gYJndGltZW91dGQzNjAw ``` + +### Preferred Mint List with Supported Methods + +A payment request specifying a preferred mint list with `mp` set to `true` and the supported payment methods (`sm`). `bolt12` carries a per-method fee (`mf`) that applies to payments from non-preferred mints. + +```json +{ + "i": "preferred_fee_methods", + "a": 100, + "u": "sat", + "m": ["https://mint.example.com"], + "mp": true, + "sm": [{ "mn": "bolt11" }, { "mn": "bolt12", "mf": 5 }] +} +``` + +Encoded: + +``` +creqApmFpdXByZWZlcnJlZF9mZWVfbWV0aG9kc2FhGGRhdWNzYXRhbYF4GGh0dHBzOi8vbWludC5leGFtcGxlLmNvbWJtcPVic22CoWJtbmZib2x0MTGiYm1uZmJvbHQxMmJtZgU= +``` diff --git a/tests/26-test.md b/tests/26-test.md index e6a2dde7a..3e6dda341 100644 --- a/tests/26-test.md +++ b/tests/26-test.md @@ -413,3 +413,26 @@ Encoded: ``` CREQB1QYQQKCM4WD6X7M2LW4HXJAQZQQYQQQQQQQQQQQRYQVQQXCN5VVZSQXRGW368QUE69UHK66TWWSHX27RPD4CXCEFWVDHK6PZHCW8 ``` + +--- + +### Preferred Mint List with Supported Methods + +A payment request specifying a preferred mint list with `mp` set to `true` (tag 0x09) and the supported payment methods (`sm`, tag 0x0a). `bolt12` carries a per-method fee (`mf`, sub-tag 0x02) that applies to payments from non-preferred mints. + +```json +{ + "i": "preferred_fee_methods", + "a": 100, + "u": "sat", + "m": ["https://mint.example.com"], + "mp": true, + "sm": [{ "mn": "bolt11" }, { "mn": "bolt12", "mf": 5 }] +} +``` + +Encoded: + +``` +CREQB1QYQP2URJV4NX2UNJV4J97EN9V40K6ET5DPHKGUCZQQYQQQQQQQQQQQRYQVQQZQQ9QQVXSAR5WPEN5TE0D45KUAPWV4UXZMTSD3JJUCM0D5YSQQGPPGQQJQGQQE3X7MR5XYCS5QQ5QYQQVCN0D36RZVSZQQYQQQQQQQQQQQQ9FJ2568 +```