# NUT-17: WebSockets `optional` `depends on: NUT-07` --- This NUT defines a websocket protocol that enables bidirectional communication between apps and mints using the JSON-RPC format. ## Subscriptions The websocket enables real-time subscriptions that wallets can use to receive notifications for a state change of a `MintQuoteResponse` ([NUT-04][04]), `MeltQuoteResponse` ([NUT-05][05]), `CheckStateResponse` ([NUT-07][07]). A summary of the subscription flow is the following: 1. A wallet connects to the websocket endpoint and sends a `WsRequest` with the `subscribe` command. 2. The mint responds with a `WsResponse` containing an ok or an error. 3. If the subscription was accepted, the mint sends a `WsNotification` of the current state of the subscribed objects and whenever there is an update for the wallet's subscriptions. 4. To close a subscription, the wallet sends `WsRequest` with the `unsubscribe` command. ## Specifications The websocket is reachable via the mint's URL path `/v1/ws`: ``` https://mint.com/v1/ws ``` `NUT-17` uses the JSON-RPC format for all messages. There are three types of messages defined in this NUT. ### Requests All requests from the wallet to the mint are of the form of a `WsRequest`: ```json { "jsonrpc": "2.0", "method": , "params": , "id": } ``` `WsRequestMethod` is a enum of strings with the supported commands `"subscribe"` and `"unsubscribe"`: ```ts enum WsRequestMethod { sub = "subscribe", unsub = "unsubscribe", } ``` `WsRequestParams` is a serialized JSON with the parameters of the corresponding command. #### Command: Subscribe To subscribe to updates, the wallet sends a `"subscribe"` command with the following `params` parameters: ```json { "kind": , "subId": , "filters": } ``` Here, `subId` is a unique UUID v7 generated by the wallet and allows the client to map its requests to the mint's responses. `SubscriptionKind` is an enum with the following possible values: ```ts enum SubscriptionKind { bolt11_melt_quote = "bolt11_melt_quote", bolt11_mint_quote = "bolt11_mint_quote", bolt12_melt_quote = "bolt12_melt_quote", bolt12_mint_quote = "bolt12_mint_quote", proof_state = "proof_state", } ``` The `filters` are an array of mint quote IDs ([NUT-04][04]), or melt quote IDs ([NUT-05][05]), or `Y`'s ([NUT-07][07]) of the corresponding object to receive updates from. As an example, `filters` would be of the following form to subscribe for updates of three different mint quote IDs: ```json ["20385fc7245...", "d06667cda9b...", "e14d8ca96f..."] ``` Note that `id` and `subId` are unrelated. The `subId` is the ID for each subscription, whereas `id` is part of the JSON-RPC spec and is an integer counter that must be incremented for every request sent over the websocket. **Important:** If the subscription is accepted by the mint, the mint MUST first respond with the _current_ state of the subscribed object and continue sending any further updates to it. For example, if the wallet subscribes to a `Proof.Y` of a `Proof` that has not been spent yet, the mint will first respond with a `ProofState` with `state == "UNSPENT"`. If the wallet then spends this `Proof`, the mint would send a `ProofState` with `state == "PENDING"` and then one with `state == "SPENT"`. In total, the mint would send three notifications to the wallet. #### Command: Unsubscribe The wallet should always unsubscribe any subscriptions that is isn't interested in anymore. The parameters for the `"unsubscribe"` command is only the subscription ID: ```json { "subId": } ``` ### Responses A `WsResponse` is returned by the mint to both the `"subscribe"` and `"unsubscribe"` commands and indicates whether the request was successful: ```json { "jsonrpc": "2.0", "result": { "status": "OK", "subId": }, "id": } ``` Here, the `id` corresponds to the `id` in the request (as part of the JSON-RPC spec) and `subId` corresponds to the subscription ID. ### Notifications `WsNotification`'s are sent from the mint to the wallet and contain subscription data in the following format ```ts { "jsonrpc": "2.0", "method": "subscribe", "params": { "subId": , "payload": NotificationPayload } } ``` `subId` is the subscription ID (previously generated by the wallet) this notification corresponds to. `NotificationPayload` carries the subscription data which is a `MintQuoteResponse` ([NUT-04][04]), a `MeltQuoteResponse` ([NUT-05][05]), or a `CheckStateResponse` ([NUT-07][07]), depending on what the corresponding `SubscriptionKind` was. ### Errors `WsErrors` for a given `WsRequest` are returned in the following format ```json { "jsonrpc": "2.0", "error": { "code": -32601, "message": "Human readable error message" }, "id": "1" } ``` ### Example: `ProofState` subscription To subscribe to the `ProofState` of a `Proof`, the wallet establishes a websocket connection to `https://mint.com/v1/ws` and sends a `WsRequest` with a `filters` chosen to be the a `Proof.Y` value of the `Proof` (see [NUT-00][00]). Note that `filters` is an array meaning multiple subscriptions of the same `kind` can be made in the same request. Wallet: ```json { "jsonrpc": "2.0", "id": 0, "method": "subscribe", "params": { "kind": "proof_state", "filters": [ "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d" ], "subId": "019e6d5a-2347-7000-8afa-051ae571f334" } } ``` The mint first responds with a `WsResponse` confirming that the subscription has been added. Mint: ```json { "jsonrpc": "2.0", "result": { "status": "OK", "subId": "019e6d5a-2347-7000-8afa-051ae571f334" }, "id": 0 } ``` The mint immediately sends the current `ProofState` of the subscription as a `WsNotification`. Mint: ```json { "jsonrpc": "2.0", "method": "subscribe", "params": { "subId": "019e6d5a-2347-7000-8afa-051ae571f334", "payload": { "Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", "state": "UNSPENT", "witness": null } } } ``` While leaving the websocket connection open, the wallet then spends the ecash. The mint sends `WsNotification` updating the wallet about state changes of the `ProofState` accordingly: Mint: ```json {"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "019e6d5a-2347-7000-8afa-051ae571f334", "payload": {"Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", "state": "PENDING"}}} {"jsonrpc": "2.0", "method": "subscribe", "params": {"subId": "019e6d5a-2347-7000-8afa-051ae571f334", "payload": {"Y": "02e208f9a78cd523444aadf854a4e91281d20f67a923d345239c37f14e137c7c3d", "state": "SPENT"}}} ``` The wallet then unsubscribes. Wallet: ```json { "jsonrpc": "2.0", "id": 1, "method": "unsubscribe", "params": { "subId": "019e6d5a-2347-7000-8afa-051ae571f334" } } ``` ## Mint info setting Mints signal websocket support via [NUT-06][06] using the following setting: ```json "nuts": { "17": { "supported": [ { "method": , "unit": , "commands": }, ... ] } } ``` Here, `commands` is an array of the commands that the mint supports. A mint that supports all commands would return `["bolt11_mint_quote", "bolt11_melt_quote", "bolt12_mint_quote", "bolt12_melt_quote", "proof_state"]`. Supported commands are given for each method-unit pair. Example: ```json "nuts": { "17": { "supported": [ { "method": "bolt11", "unit": "sat", "commands": [ "bolt11_mint_quote", "bolt11_melt_quote", "proof_state" ] }, ] } } ``` [00]: 00.md [01]: 01.md [02]: 02.md [03]: 03.md [04]: 04.md [05]: 05.md [06]: 06.md [07]: 07.md [08]: 08.md [09]: 09.md [10]: 10.md [11]: 11.md [12]: 12.md