NAP-VALUE / PR #30
Value Transfer
Full specification from the fetched upstream revision ↗. The document below is rendered from its original Markdown. Follow the latest source on GitHub ↗.
NAP-VALUE
Value Transfer
draft
NAP ID: NAP-VALUE
Domain: value
Depends:
relay— layering · optional — the shell MAY publish / query zap receipts viarelayor an internal pool Web binding (NIP-5D):window.napplet.value; domain presence signals availability.
Description
NAP-VALUE provides napplets with a shell-mediated interface for requesting value transfer. The first concrete use case is sending Nostr zaps, but the interface is named around value instead of payment so shells can support additional value rails without changing the napplet API. Napplets describe the intended transfer; the shell handles wallet integration, consent, zap request construction, signing, invoice payment, relay publishing, and result tracking.
Napplets never receive wallet credentials, NWC secrets, signing keys, or direct network access to payment backends. The shell is the policy and consent boundary.
API Surface
| Operation | Parameters | Result | Wire |
|---|---|---|---|
send |
request (ValueRequest) |
ValueResult |
value.send / value.send.result |
quote |
request (ValueRequest) |
ValueQuote |
value.quote / value.quote.result |
status |
transferId (tstr) |
ValueStatus |
value.status / value.status.result |
onStatus |
handler for ValueStatus |
Subscription handle |
value.status.changed |
Schemas
Primitive references:
| Name | Meaning |
|---|---|
NostrEvent |
NIP-01 signed event object. |
ValueTarget |
One of ZapTarget, LnurlTarget, or ValueExtensionTarget. |
Enumerations:
| Name | Values |
|---|---|
ValueRail |
zap, lnurl, or another rail name string. |
ValueTransferState |
pending, settled, failed, cancelled |
ZapTarget.type |
zap |
LnurlTarget.type |
lnurl |
ValueRequest:
| Field | Type | Required | Description |
|---|---|---|---|
rail |
ValueRail |
yes | Value rail to use. |
amountMsat |
uint |
yes | Transfer amount in millisatoshis for Lightning-compatible rails. |
comment |
tstr |
no | User-visible transfer comment. |
target |
ValueTarget |
yes | Transfer target. |
metadata |
map of tstr to any JSON value |
no | Rail-specific metadata. |
ZapTarget:
| Field | Type | Required | Description |
|---|---|---|---|
type |
zap |
yes | Target discriminator. |
pubkey |
tstr |
no | Recipient pubkey. |
eventId |
tstr |
no | Event being zapped. |
address |
tstr |
no | Addressable event being zapped. |
relays |
list of tstr |
no | Relay hints for zap request or receipt handling. |
LnurlTarget:
| Field | Type | Required | Description |
|---|---|---|---|
type |
lnurl |
yes | Target discriminator. |
lnurl |
tstr |
yes | LNURL target. |
ValueExtensionTarget:
| Field | Type | Required | Description |
|---|---|---|---|
type |
tstr |
yes | Extension rail target discriminator. |
other tstr keys |
any JSON value | no | Extension-specific target fields. |
ValueQuote:
| Field | Type | Required | Description |
|---|---|---|---|
ok |
bool |
yes | Whether the quote succeeded. |
amountMsat |
uint |
yes | Quoted amount in millisatoshis. |
rail |
ValueRail |
yes | Quoted rail. |
feesMsat |
uint |
no | Estimated fees in millisatoshis. |
expiresAt |
uint |
no | Expiration timestamp. |
error |
tstr |
no | Error reason when quote failed. |
ValueResult:
| Field | Type | Required | Description |
|---|---|---|---|
ok |
bool |
yes | Whether the transfer request was accepted or completed. |
transferId |
tstr |
yes | Shell-generated transfer identifier. |
status |
ValueTransferState |
yes | Current transfer state. |
rail |
ValueRail |
yes | Transfer rail. |
amountMsat |
uint |
yes | Transfer amount in millisatoshis. |
event |
NostrEvent |
no | Related Nostr event, such as a zap request or receipt. |
preimage |
tstr |
no | Payment preimage when available and safe to disclose. |
error |
tstr |
no | Error reason when transfer failed. |
ValueStatus:
| Field | Type | Required | Description |
|---|---|---|---|
ok |
bool |
yes | Whether the status lookup succeeded. |
transferId |
tstr |
yes | Shell-generated transfer identifier. |
status |
ValueTransferState |
yes | Current transfer state. |
rail |
ValueRail |
yes | Transfer rail. |
amountMsat |
uint |
yes | Transfer amount in millisatoshis. |
event |
NostrEvent |
no | Related Nostr event, such as a zap request or receipt. |
preimage |
tstr |
no | Payment preimage when available and safe to disclose. |
error |
tstr |
no | Error reason when transfer failed. |
updatedAt |
uint |
yes | Timestamp of the latest status update. |
send(request) -- Requests a value transfer. The shell presents any required consent UI, performs the transfer through its configured backend, and returns the initial result. For zap requests, the shell constructs and signs the kind 9734 zap request, pays the Lightning invoice, publishes any required Nostr event, and reports the resulting status.
quote(request) -- Returns a best-effort quote before transfer. Shells MAY use this to resolve an LNURL-pay endpoint, estimate fees, or validate that the request is payable. A quote is advisory; shells MUST revalidate during send.
status(transferId) -- Returns the latest known status for a prior transfer.
onStatus(handler) -- Registers for shell-pushed status updates, such as pending becoming settled after a zap receipt is observed.
Wire Protocol
value.* messages use the NIP-5D wire format ({ "type": "domain.action", ...payload }).
| Type | Direction | Payload fields |
|---|---|---|
value.quote |
napplet -> shell | id, request |
value.quote.result |
shell -> napplet | id, quote, error? |
value.send |
napplet -> shell | id, request |
value.send.result |
shell -> napplet | id, result, error? |
value.status |
napplet -> shell | id, transferId |
value.status.result |
shell -> napplet | id, status, error? |
value.status.changed |
shell -> napplet | status |
Key design notes:
- Request/result pairs use
idfor correlation. transferIdis generated by the shell and is scoped to the requesting napplet.value.status.changedis a shell push message and has noid.amountMsatis always millisatoshis for Lightning-compatible rails. Other rails MUST document their unit inmetadata.unituntil a dedicated rail-specific NAP exists.
Examples
Quote a zap:
-> {
"type": "value.quote",
"id": "q1",
"request": {
"rail": "zap",
"amountMsat": 21000,
"comment": "great note",
"target": { "type": "zap", "eventId": "abc...", "pubkey": "def...", "relays": ["wss://relay.example.com"] }
}
}
<- {
"type": "value.quote.result",
"id": "q1",
"quote": { "ok": true, "rail": "zap", "amountMsat": 21000, "feesMsat": 10, "expiresAt": 1234567999 }
}
Send a zap:
-> {
"type": "value.send",
"id": "s1",
"request": {
"rail": "zap",
"amountMsat": 21000,
"comment": "great note",
"target": { "type": "zap", "eventId": "abc...", "pubkey": "def...", "relays": ["wss://relay.example.com"] }
}
}
<- {
"type": "value.send.result",
"id": "s1",
"result": {
"ok": true,
"transferId": "value-1",
"status": "pending",
"rail": "zap",
"amountMsat": 21000
}
}
<- {
"type": "value.status.changed",
"status": {
"ok": true,
"transferId": "value-1",
"status": "settled",
"rail": "zap",
"amountMsat": 21000,
"updatedAt": 1234567901
}
}
User cancelled:
<- {
"type": "value.send.result",
"id": "s2",
"result": {
"ok": false,
"transferId": "value-2",
"status": "cancelled",
"rail": "zap",
"amountMsat": 21000,
"error": "user cancelled"
}
}
Error Handling
Result messages MAY include error when the request cannot be fulfilled. Common errors include "unsupported rail", "invalid target", "amount out of policy", "user cancelled", "wallet unavailable", "invoice expired", and "payment failed".
The shell SHOULD return a structured result with ok: false when a transfer was created and then failed or was cancelled. The shell SHOULD return a top-level error when no transfer was created.
Shell Behavior
- The shell MUST require explicit user consent before spending value unless the user has configured an allowance that covers the request.
- The shell MUST enforce per-napplet policy for allowed rails, maximum amount, and allowance use.
- The shell MUST construct and sign Nostr events required by a value rail. For zaps, this includes the kind 9734 zap request.
- The shell MUST NOT sign or pay opaque requests that it cannot inspect and present to the user.
- The shell MUST keep wallet credentials, NWC connection strings, LNURL secrets, payment preimages, and private keys out of the napplet sandbox.
- The shell MUST respond to every request with a result message carrying the same
id. - The shell SHOULD surface intermediate states through
value.status.changedwhen settlement is asynchronous. - The shell MAY implement rails through NWC, LNURL-pay, an internal wallet service, or another backend. Backend choice is an implementation detail.
- The shell MAY publish or query zap receipts through NAP-RELAY or an internal relay pool.
Security Considerations
- Spending value is user-visible and security-critical. Shells MUST treat every
value.sendas an untrusted request until consent and policy checks pass. - Napplets can attempt dark-pattern spending. Shell consent UI SHOULD show amount, target, rail, recipient identity, comment, and requesting napplet identity.
- Allowances are risky. Shells that implement allowances SHOULD bind them to the napplet identity, rail, maximum amount, recurrence window, and target constraints.
- Zap comments and metadata may leak user intent. Shells SHOULD present them before signing and sending.
- The shell signs zap requests and publishes value-related events. Napplets MUST NOT be given signing or encryption primitives through this interface.
- Payment failures MUST NOT be hidden as success. A settled state requires backend confirmation or a rail-specific proof accepted by shell policy.
Implementations
- (none yet)
Changelog
2c56394- Introduced NAP-VALUE for shell-mediated value transfer.05c1b92- Adopted injected-domain availability and linked the current upstream web binding.