Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 31 additions & 8 deletions 07.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,31 @@
NUT-07: Spendable check
NUT-07: Token state check
==========================

`optional` `author: calle`
`optional` `author: calle` `author: tb`

---

With the spendable check, wallets can ask the mint whether a specific token is already spent. This can be useful for different reasons. For example, when `Alice` prepares a token to be sent to `Carol`, she can mark these tokens in her database as *pending*. She can then, periodically or upon user input, check with the mint, if the token is still spendable or if it has been redeemed by `Carol` already (thus not spendable anymore). Another use case is for wallets that want to delete spent proofs from their database. Before deleting a proof, a wallet can check if the token is still spendable to be sure that they don't delete an unspent token by accident.
With the token state check, wallets can ask the mint whether a specific proof is already spent (`spendable`) and whether it is in-flight in a transaction (`pending`). Both of these attributes are returned as sepearte booleans.

### Token states

A wallet can internally represent the combination of these booleans the proof's `state`. A proof can either be `live` (`spendable == true && pending == false`), `burned` (`spendable == false && pending == (true || false)`), or `in-flight` (`spendable == true && pending == true`).

- A proof is `live` if it has been minted but not spent yet.
- A proof is `burned` if it has been redeemed and its secret is in the list of spent secrets of the mint.
- A proof is `in-flight` if it is being processed in a transaction (in an ongoing payment). An `in-flight` proof cannot be used in another transaction until it is `live` again.

**Important:** Before deleting spent proofs from their database, wallets **MUST** check if the proof is still `spendable` to make sure that they don't delete an unspent proof by accident.

**Note:** Mints **MUST** remember which proofs are currently `pending` to avoid reuse of the same token in multiple concurrent transactions. This can be achieved with a mutex whose key is the proof's `secret`.

## Use cases

#### Example 1: Ecash transaction
When `Alice` prepares a token to be sent to `Carol`, she can mark these tokens in her database as *db:pending*. She can then, periodically or upon user input, check with the mint if the proof is still spendable (mint returns `True` for `spendable`) or if it has been redeemed by `Carol` already (mint retrns `False` for `spendable`). If the proof is not spendable anymore (and, thus, has been redeemed by `Carol`), she can safely delete the proof from her database.

#### Example 2: Lightning payments
If `Alice` pays a very slow Lightning invoice (for example a HODL invoice) and closes her wallet in the meantime, the next time she comes online, she can check all proof marked as *db:pending* in her database to determine, whether the payment is still in flight (mint returns `True` for `pending` and `spendable`), it has succeeded (mint returns `False` for `pending` and `spendable`), or it has failed (mint returns `False` for `pending` and `True` for `spendable`).

## Example

Expand All @@ -15,15 +35,15 @@ With the spendable check, wallets can ask the mint whether a specific token is a
POST https://mint.host:3338/check
```

With the data being of the form `CheckSpendableRequest`:
With the data being of the form `CheckStateRequest`:

```json
{
"proofs": Proofs
}
```

`Proofs` is a list (array) of `Proof`s (see [NUT-0][00]). `Alice` CAN provide a full `Proof` but MUST provide at least the `secret` (which is the only thing that `Bob` needs to check whether the token has been spent).
`Proofs` is a list (array) of `Proof`s (see [NUT-0][00]). `Alice` CAN provide a full `Proof` but MUST provide at least the `secret` (which is the only thing that `Bob` needs to check whether the proof has been spent).

With curl:

Expand All @@ -43,15 +63,18 @@ curl -X POST https://mint.host:3338/check -d \
```
**Response** of `Bob`:

`Bob` will respond with a `CheckSpendableResponse`
`Bob` will respond with a `CheckStateResponse`

```json
{
"spendable": Array[bool]
"spendable": Array[bool],
"pending": Array[bool]
}
```

Where `[bool]` is an array of booleans indicating whether the provided `Proof` is still spendable. **Important:** The list of booleans MUST be in the same order as the proofs provided by `Alice` in the request.
Where `Array[bool]` is an array of booleans indicating whether the provided `Proof` is still spendable or pending.

**Important:** The list of booleans **MUST** be in the same order as the proofs provided by `Alice` in the request.

[00]: 00.md
[01]: 01.md
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Wallets and mints `MUST` implement all mandatory specs and `CAN` implement optio
### Optional
| # | Description | Wallets | Mints
|--- | --- | --- | --- |
| [07][07] | Token spendable check | [Nutshell][py], [Feni][feni], [Cashu.Me][cashume], [Nutstash][ns] | [Nutshell][py], [Feni][feni], [LNbits]
| [07][07] | Token state check | [Nutshell][py], [Feni][feni], [Cashu.Me][cashume], [Nutstash][ns] | [Nutshell][py], [Feni][feni], [LNbits]
| [08][08] | Overpaid Lightning fees | [Nutshell][py], [Feni][feni], [Cashu.Me][cashume], [Nutstash][ns] | [Nutshell][py], [LNbits]
| [09][09] | Mint info | - | [Nutshell][py]
| TBD | Multimint support | [Nutshell][py], [Cashu.Me][cashume], [Nutstash][ns] | N/A
Expand Down
4 changes: 2 additions & 2 deletions wallet/cashu_wallet_spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ Note that the following steps can also be performed by `Alice` herself if she wa
## 5 - Burn sent tokens
Here we describe how `Alice` checks with the mint whether the tokens she sent `Carol` have been redeemed so she can safely delete them from her database. This step is optional but highly recommended so `Alice` can properly account for the tokens and adjust her balance accordingly.
- `Alice` loads all `<send_proofs>` with `pending=True` from her database and might group them by the `send_id`.
- `Alice` constructs a JSON of the form `{"proofs" : [{"amount" : <amount>, "secret" : s, "C" : Z}, ...]}` from these (grouped) tokens. [*TODO: this object is called CheckSpendableRequest*]
- `Alice` constructs a JSON of the form `{"proofs" : [{"amount" : <amount>, "secret" : s, "C" : Z}, ...]}` from these (grouped) tokens (`CheckStateRequest`).
- `Alice` sends them to the mint `Bob` via the endpoint `POST /check` with the JSON as the body of the request.
- `Alice` receives a JSON of the form `{"spendable" : [true, false, ...]}` where each boolean value corresponds to the proof she sent to the mint before in the same order. The value is `True` if the token has not been claimed yet and `False` if it has already been claimed.
- `Alice` receives a JSON of the form `{"spendable" : [true, false, ...], "pending": [false, false, ....]}` where each boolean value corresponds to the proof she sent to the mint before in the same order. The value is `True` if the token has not been claimed yet and `False` if it has already been claimed.
- If `<spendable>` is `False`, `Alice` removes the proof from her list of spendable proofs.

## 6 - Pay a Lightning invoice
Expand Down