This NIP specifies a general-purpose unidirectional payment channel protocol for high-frequency, low-value micropayments between two parties.
The protocol enables one party (the Payer) to lock funds on a blockchain and stream incremental payments to another party (the Payee) off-chain, using cryptographically signed receipts that can be settled on-chain at any time.
To support concurrent payment flows (e.g., multi-device or multi-session scenarios), a single channel can contain multiple sub-channels, each maintaining independent state with monotonic nonce and accumulated amount values.
The protocol is ledger-agnostic: core semantics and message structures are defined abstractly, allowing implementation on any blockchain that can enforce collateral locking, signature verification, and dispute resolution. Specific ledger bindings define concrete encoding formats, on-chain contract interfaces, and identity resolution mechanisms.
Protocol-level agent interactions—such as message relaying, API gateway access, or other infrastructure services—often involve high-frequency, low-value payments. Recording every payment directly on-chain is cost-prohibitive. A standard A2A payment channel enables:
Channel – A unidirectional payment relation (Payer → Payee) for a specific asset. Each channel is uniquely identified by a channelId deterministically derived from the payer, payee, and asset identifiers.
accumulatedAmount representing the total ever sent through this sub-channelnonce strictly increasing with each new receiptepoch to prevent cross-reset replay attacksProposal – An unsigned receipt generated by the payee for the next payment increment. The payer signs this proposal to create the next receipt.
Sub-channel – A logical concurrent payment stream within a channel, each with independent nonce and accumulatedAmount state. Sub-channels enable multi-device or multi-session payment flows without nonce conflicts. Each sub-channel is identified by a subChannelId.
Epoch – A channel lifecycle version number that increments each time the channel is closed and reopened. Receipts from previous epochs are rejected, preventing replay attacks after channel resets.
delta = receipt.accumulatedAmount - lastAcceptedAmount
delta = 0: Idempotent retry or channel initialization (no funds transferred)delta > 0: Normal payment requiring on-chain settlementOff-chain State – The latest receipt state accepted by the payee, stored locally for verifying monotonicity of new receipts. Also referred to as offChainLastAccepted.
onChainConfirmed.Invariant: onChainConfirmedNonce/Amount ≤ offChainLastAcceptedNonce/Amount
The protocol supports two channel initialization modes:
In this mode, the payer directly interacts with the blockchain to create and fund the channel before beginning off-chain payments.
sequenceDiagram
participant Payer
participant Payee
participant Blockchain
Payer->>Blockchain: Open channel (specify payee, asset type)
Note over Blockchain: Channel created on-chain
Payer->>Blockchain: Deposit to hub (lock collateral)
loop Authorize sub-channels (once per sub-channel)
Payer->>Blockchain: Authorize sub-channel (subChannelId, public key)
Note over Blockchain: Sub-channel authorized on-chain
end
loop Micropayments (off-chain)
Payer->>Payee: Signed Receipt (accumulatedAmount, nonce)
Note over Payee: Verify receipt and update off-chain state
Payee-->>Payer: Proposal for next receipt
Note over Payee: Optionally settle on-chain when delta >= threshold
Payee->>Blockchain: Settle receipt (transfer delta)
end
alt Cooperative Close
Payer->>Payee: Notify channel closure
Payee->>Blockchain: Settle final receipts
Payer->>Blockchain: Close channel
else Unilateral Close
Payer->>Blockchain: Initiate cancellation
Payee->>Blockchain: Dispute with newer receipt (if applicable)
Note over Blockchain: Challenge period elapses
Payer->>Blockchain: Finalize cancellation
end
In this mode, the payer begins sending receipts immediately, and channel creation is deferred until the first receipt is settled. A facilitator (typically the payee or a third-party service) handles on-chain operations and pays gas fees.
sequenceDiagram
participant Payer
participant Payee
participant Facilitator
participant Blockchain
Payer->>Payee: Request resource (no receipt)
Payee-->>Payer: 402 Payment Required (channel requirements)
Payer->>Payee: Request with Receipt (epoch=0, nonce=0, amount=0)
Note over Payee: Verify receipt, delta=0
Payee->>Facilitator: Settle request (create channel)
Facilitator->>Blockchain: Create channel on-chain (lock collateral)
Note over Blockchain: Channel created, epoch=0
Blockchain-->>Facilitator: txHash
Facilitator-->>Payee: Settlement confirmed
Note over Payee: Process request, compute cost
Payee-->>Payer: 200 OK + Proposal (nonce=1, amount=cost)
loop Micropayments (off-chain)
Payer->>Payee: Signed Receipt (nonce, accumulatedAmount)
Note over Payee: Verify receipt, compute delta
alt delta > 0
Payee-->>Payer: 200 OK + Proposal (next nonce, next amount)
par Async settlement
Payee->>Facilitator: Settle receipt (delta > 0)
Facilitator->>Blockchain: Transfer delta on-chain
Blockchain-->>Facilitator: txHash
Facilitator-->>Payee: Settlement confirmed
end
else delta = 0 (idempotent retry)
Payee-->>Payer: 200 OK (no settlement)
end
end
alt Cooperative Close
Payer->>Payee: Notify channel closure
Payee->>Facilitator: Settle final receipts
Facilitator->>Blockchain: Close channel
else Unilateral Close
Facilitator->>Blockchain: Initiate cancellation
Payee->>Facilitator: Dispute with newer receipt
Facilitator->>Blockchain: Submit dispute receipt
Note over Blockchain: Challenge period elapses
Facilitator->>Blockchain: Finalize cancellation
end
Key Differences:
A receipt is a payment commitment containing the following logical fields:
Note: The concrete encoding of these fields (e.g., field names, data types, serialization format) is defined by implementation bindings. For example, some implementations may use vmIdFragment as the sub-channel identifier, while others may use different naming conventions.
A signed receipt bundles the receipt with its cryptographic signature:
{
"receipt": { /* receipt fields */ },
"signature": "<bytes>" // signature over canonical serialization of receipt
}
Implementations MUST define:
Serialization Format – The canonical byte representation of receipts for signing (e.g., BCS, EIP-712, CBOR, or canonical JSON)
Signature Algorithm – The cryptographic algorithm(s) supported and how they map to key types (e.g., ECDSA with secp256k1/secp256r1, EdDSA with Ed25519)
Key Discovery – How verifiers obtain the public key for signature verification (e.g., via DID resolution, on-chain storage, or certificate infrastructure)
All receipts follow the same structure; their behavior is determined by the values and current channel state:
First Receipt (epoch=0, nonce=0, accumulatedAmount=0): Used for lazy channel initialization. Has delta=0, so no funds are transferred—only the channel structure is created on-chain.
Payment Receipt (delta > 0): Normal payment operation. The delta is settled on-chain either immediately or in batches.
Idempotent Retry (nonce and accumulatedAmount equal to last accepted values): No state progression. Verifiers return success without updates.
The protocol distinguishes between two types of state for each sub-channel:
The most recent receipt state that the payee has accepted and stored locally:
This state is used for:
The most recent receipt state that has been settled on the blockchain:
This state is used for:
The following invariant MUST hold at all times:
onChainConfirmedNonce ≤ offChainLastAcceptedNonce
onChainConfirmedAmount ≤ offChainLastAcceptedAmount
This ensures that off-chain state always leads or equals on-chain state, allowing for batched settlement strategies.
Implementations MUST perform the following verification steps when receiving a signed receipt:
active statereceipt.channelId matches the expected channelreceipt.epoch matches the current channel epochFor the target (channelId, epoch, subChannelId):
offChainLastAcceptedNonce and offChainLastAcceptedAmountreceipt.nonce > offChainLastAcceptedNonce OR receipt.nonce == offChainLastAcceptedNonce (idempotent case)receipt.accumulatedAmount >= offChainLastAcceptedAmountdelta = receipt.accumulatedAmount - offChainLastAcceptedAmountdelta = 0: Accept as idempotent retry or initialization; skip settlementdelta > 0: Proceed with normal payment processingdelta ≤ payer's declared maxAmount (if supported)If all verifications pass:
offChainLastAcceptedNonce = receipt.nonceoffChainLastAcceptedAmount = receipt.accumulatedAmountSettlement is the process of confirming receipts on-chain and transferring funds. Implementations MUST support the following settlement modes:
When receiving the first receipt for a channel:
onChainConfirmedNonce = 0 and onChainConfirmedAmount = 0When settling a receipt with positive delta:
receipt.nonce > onChainConfirmedNonce (strictly increasing)receipt.accumulatedAmount > onChainConfirmedAmount (strictly increasing)delta amount from payer’s locked collateral to payeeonChainConfirmedNonce = receipt.nonceonChainConfirmedAmount = receipt.accumulatedAmountIf a settlement transaction is submitted with nonce and accumulatedAmount equal to current on-chain state:
Implementations MAY choose when to trigger settlement:
delta >= settlementThresholdChannelOpenRequest to payee (off-chain message)ChannelOpenResponseSubChannelAuthorize)ChannelCloseRequestclosed)If the payee becomes unresponsive:
The protocol is transport-agnostic. This section provides informative guidelines for common transport profiles. Detailed specifications SHOULD be defined in separate binding documents.
HTTP services can support payment channels using a dedicated header (e.g., X-Payment-Channel-Data) to carry payment data encoded as Base64 JSON.
Request Payload (Payer → Payee):
{
"version": <number>, // Protocol version
"payerId": "<identifier>", // Optional payer identifier
"clientTxRef": "<string>", // Idempotency key
"maxAmount": "<amount>", // Optional spending limit for this request
"signedReceipt": { // Optional; required for paid routes
"receipt": { /* ... */ },
"signature": "<bytes>"
}
}
Response Payload (Payee → Payer):
{
"version": <number>, // Protocol version
"proposal": { /* ... */ }, // Unsigned receipt for next request
"cost": "<amount>", // Cost incurred for this request
"serviceTxRef": "<string>", // Service reference ID
"error": { // Protocol-level error (if any)
"code": "<string>",
"message": "<string>"
}
}
Interaction Pattern:
Error Handling:
| Condition | Recommended HTTP Status |
|---|---|
| Payment required / missing receipt | 402 Payment Required |
| Invalid signature | 403 Forbidden |
| Receipt conflict (nonce/epoch mismatch) | 409 Conflict |
| Channel not found | 404 Not Found |
| Malformed header | 400 Bad Request |
For Model Context Protocol integrations:
__nuwa_payment field)For Agent-to-Agent messaging:
chainId MUST be included in the signature scope (either in-band as a receipt field, or out-of-band via signing domain)onChainConfirmed* ≤ offChainLastAccepted*channelIdThe protocol supports evolution through:
| Term | Definition |
|---|---|
| Channel | A unidirectional payment relation (Payer → Payee) for a specific asset |
| Receipt | A cryptographically signed payment commitment containing channel, sub-channel, amount, and nonce information |
| Proposal | An unsigned receipt generated by the payee for the payer to sign, representing the next expected payment state |
| Signed Receipt | A receipt bundled with its cryptographic signature |
| Sub-channel | A logical concurrent payment stream within a channel, with independent nonce and accumulated amount |
Sub-channel ID (subChannelId) |
Identifier for a sub-channel (implementation-specific; may reference DID verification methods or other identifiers) |
| Epoch | Channel lifecycle version number that increments on each close/reopen cycle |
| Nonce | Monotonically increasing counter per sub-channel, must increment by 1 for each new receipt |
| Accumulated Amount | Total amount ever sent through a sub-channel since epoch began |
| Delta | Incremental payment amount computed as receipt.accumulatedAmount - lastAcceptedAmount |
| Off-chain State | Latest receipt state accepted by payee and stored locally (offChainLastAccepted) |
| On-chain State | Latest receipt state confirmed on blockchain through settlement (onChainConfirmed) |
| Settlement | The process of confirming a receipt on-chain and transferring funds |
| Facilitator | An optional third-party service that verifies receipts, handles on-chain settlement operations, and pays gas fees on behalf of participants (typically the payee or a dedicated service) |
| Lazy Channel Creation | Creating a channel on-chain using the first receipt (typically with delta=0) via a facilitator rather than requiring the payer to initialize the channel directly |
| Idempotent Retry | Submitting a receipt with values equal to the last accepted state; results in no state change |
Specific blockchain implementations SHOULD provide binding documents that define:
Implementation bindings may be developed for various blockchain ecosystems:
Each binding should be documented in a separate specification that references this core NIP-4 protocol.
This protocol has been implemented on the Rooch blockchain with the following components:
These implementations demonstrate concrete bindings of the abstract protocol defined in this specification and can serve as references for implementations on other blockchains.
This NIP defines the core protocol semantics for unidirectional payment channels. Implementations on specific blockchains should provide binding documents that specify concrete encoding formats, on-chain interfaces, and identity resolution mechanisms while maintaining compatibility with the abstract protocol defined herein.