| nip | 4 |
|---|---|
| title | Unidirectional Payment Channel Protocol |
| author | jolestar(@jolestar) |
| discussions-to | #7 |
| status | Draft |
| type | Standards Track |
| category | Core |
| created | 2025-05-13 |
| updated | 2025-10-28 |
| requires | NIP-1, NIP-2 |
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:
- Scalable, low-cost micropayments.
- Automation friendly flows for agents acting on behalf of users.
- Interoperability across different ledgers that implement the same high-level semantics.
-
Channel – A unidirectional payment relation (Payer → Payee) for a specific asset. Each channel is uniquely identified by a
channelIddeterministically derived from the payer, payee, and asset identifiers. -
Receipt – A cryptographically signed payment commitment containing:
- The channel and sub-channel identifiers
- An
accumulatedAmountrepresenting the total ever sent through this sub-channel - A monotonic
noncestrictly increasing with each new receipt - The current channel
epochto prevent cross-reset replay attacks
-
Proposal – 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
nonceandaccumulatedAmountstate. Sub-channels enable multi-device or multi-session payment flows without nonce conflicts. Each sub-channel is identified by asubChannelId. -
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 – The incremental payment amount for a receipt, computed as:
delta = receipt.accumulatedAmount - lastAcceptedAmountdelta = 0: Idempotent retry or channel initialization (no funds transferred)delta > 0: Normal payment requiring on-chain settlement
-
Off-chain State – The latest receipt state accepted by the payee, stored locally for verifying monotonicity of new receipts. Also referred to as
offChainLastAccepted. -
On-chain State – The latest receipt state confirmed on the blockchain through settlement. Also referred to as
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:
- Mode 1 (Direct): Payer controls channel lifecycle; requires payer to pay gas for initialization; suitable for long-term relationships or when payer has on-chain presence.
- Mode 2 (Lazy): Lower barrier to entry for payer; facilitator absorbs gas costs; enables immediate usage without on-chain setup; suitable for micropayments and first-time users.
A receipt is a payment commitment containing the following logical fields:
- version – Protocol version number (for forward compatibility)
- chainId – Blockchain identifier (for cross-chain replay protection)
- channelId – Channel unique identifier
- epoch – Current channel epoch value
- subChannelId – Sub-channel identifier within this channel
- accumulatedAmount – Total amount ever sent through this sub-channel (monotonically non-decreasing)
- nonce – Strictly increasing counter per sub-channel (must increment by 1 for each new receipt)
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:
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. Hasdelta=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 (
nonceandaccumulatedAmountequal 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:
- offChainLastAcceptedNonce – Latest nonce value accepted by payee
- offChainLastAcceptedAmount – Latest accumulated amount accepted by payee
This state is used for:
- Verifying monotonicity of new receipts
- Generating proposals for next receipts
- Detecting replay attempts
The most recent receipt state that has been settled on the blockchain:
- onChainConfirmedNonce – Latest nonce confirmed on-chain
- onChainConfirmedAmount – Latest accumulated amount confirmed on-chain
This state is used for:
- Final dispute resolution
- Fund transfers
- Channel closure
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:
- Deserialize the receipt using the implementation's canonical format
- Resolve the payer's public key (e.g., via DID resolution or on-chain registry)
- Verify the signature matches the receipt payload using the appropriate algorithm
- Confirm the signing key is authorized for this sub-channel
- Verify the channel exists and is in
activestate - Verify
receipt.channelIdmatches the expected channel - Verify
receipt.epochmatches the current channel epoch - Reject receipts with mismatched epochs (prevents cross-reset replay)
For the target (channelId, epoch, subChannelId):
- Retrieve
offChainLastAcceptedNonceandoffChainLastAcceptedAmount - Verify
receipt.nonce > offChainLastAcceptedNonceORreceipt.nonce == offChainLastAcceptedNonce(idempotent case) - Verify
receipt.accumulatedAmount >= offChainLastAcceptedAmount - If both values are equal, treat as idempotent retry (no state update)
- Calculate
delta = receipt.accumulatedAmount - offChainLastAcceptedAmount - For
delta = 0: Accept as idempotent retry or initialization; skip settlement - For
delta > 0: Proceed with normal payment processing - Optionally verify
delta ≤ payer's declared maxAmount(if supported)
If all verifications pass:
- Update
offChainLastAcceptedNonce = receipt.nonce - Update
offChainLastAcceptedAmount = receipt.accumulatedAmount - Generate proposal for next receipt (if applicable)
Settlement 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:
- Create the channel structure on-chain
- Lock the required collateral (amount determined by implementation)
- Initialize
onChainConfirmedNonce = 0andonChainConfirmedAmount = 0 - Do NOT transfer any funds (delta is zero)
When settling a receipt with positive delta:
- Verify the receipt signature and channel state on-chain
- Ensure
receipt.nonce > onChainConfirmedNonce(strictly increasing) - Ensure
receipt.accumulatedAmount > onChainConfirmedAmount(strictly increasing) - Transfer
deltaamount from payer's locked collateral to payee - Update
onChainConfirmedNonce = receipt.nonce - Update
onChainConfirmedAmount = receipt.accumulatedAmount
If a settlement transaction is submitted with nonce and accumulatedAmount equal to current on-chain state:
- Do NOT revert the transaction
- Do NOT transfer funds again
- Return success (idempotency guarantee)
Implementations MAY choose when to trigger settlement:
- Immediate: Settle every receipt individually
- Threshold-based: Settle when
delta >= settlementThreshold - Time-based: Settle periodically (e.g., every N seconds)
- Batched: Aggregate multiple sub-channel receipts into one transaction
- Manual: Payee explicitly triggers settlement via management API
- Payer locks collateral on-chain (via deposit or payment hub)
- Payer sends
ChannelOpenRequestto payee (off-chain message) - Payee validates and responds with
ChannelOpenResponse - Payer authorizes one or more sub-channels (via
SubChannelAuthorize) - Channel is ready for micropayments
- Payer signs receipt for current payment
- Payee verifies receipt and updates off-chain state
- Payee generates proposal for next receipt
- Payee optionally settles receipts on-chain (batched or threshold-based)
- Repeat
- Payer sends
ChannelCloseRequest - Payee settles all pending receipts on-chain
- Payee confirms closure (channel state becomes
closed) - Remaining collateral is returned to payer
If the payee becomes unresponsive:
- Payer initiates cancellation on-chain
- Challenge period begins (implementation-defined duration)
- Payee MAY dispute with newer receipts during challenge period
- After challenge period expires, payer finalizes cancellation
- Outstanding delta (if any) is transferred, remaining collateral returned to payer
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:
- Client sends request with signed receipt (or omits for free/discovery endpoints)
- Service verifies receipt, processes request, computes cost
- Service generates proposal (unsigned receipt with incremented nonce and accumulated amount)
- Service returns response with proposal in header
- Client verifies proposal, signs it for next request
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:
- Payment data is passed via tool parameters (e.g.,
__nuwa_paymentfield) - Proposals are returned via MCP resource content
- Detailed specification should be defined in a separate MCP binding document
For Agent-to-Agent messaging:
- Payment data embedded in secure DID-authenticated envelopes (per NIP-2)
- Supports both request/response and notification patterns
- Detailed specification should be defined in a separate A2A binding document
- Epoch-based Protection: Receipts from previous epochs are rejected after channel reset
- Nonce Monotonicity: Each sub-channel requires strictly increasing nonce values
- Cross-chain Replay:
chainIdMUST be included in the signature scope (either in-band as a receipt field, or out-of-band via signing domain)
- Off-chain/On-chain Invariant: Implementations MUST ensure
onChainConfirmed* ≤ offChainLastAccepted* - Delta Validation: Verify computed delta does not exceed implementation-defined limits
- Sub-channel Isolation: Each sub-channel maintains independent nonce/amount state to prevent cross-stream conflicts
- Key Authorization: Verify signing keys are properly authorized for the sub-channel
- Fresh Signatures: Consider implementing timestamp checks or TTL mechanisms to limit replay windows
- Algorithm Agility: Support multiple signature algorithms but enforce minimum security levels
- Rate Limiting: Implement appropriate rate limits for receipt verification
- Resource Bounds: Limit number of sub-channels per channel
- Settlement Throttling: Prevent spam of on-chain settlement transactions
- Asset information is implicitly bound to
channelId - Verifiers MUST validate asset consistency throughout channel lifecycle
- Prevent asset substitution attacks during channel operations
The protocol supports evolution through:
- Version Field: Receipts include a version number allowing protocol upgrades
- Implementation-Specific Extensions: Bindings may add fields without breaking core semantics
- Profile Mechanism: New transport bindings can be defined independently
- Backward Compatibility: Newer versions should gracefully handle older receipt formats where possible
| 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:
- Receipt Encoding – Concrete serialization format (e.g., BCS for Move-based chains, EIP-712 for EVM, Borsh for Solana)
- Field Mapping – How logical receipt fields map to implementation-specific types and names
- Signature Algorithms – Supported cryptographic algorithms and their parameters
- ChannelId Generation – Rules for deriving deterministic channel identifiers
- Sub-channel ID Format – How sub-channels are identified (e.g., DID verification method fragments, device IDs)
- On-chain Contract Interface – Smart contract function signatures and behavior
- Identity Resolution – Mechanism for resolving payer/payee identities and public keys
- Payment Hub Architecture – How collateral is managed across multiple channels
- Multi-asset Support – Handling multiple asset types
- Gas Optimization – Batching and other efficiency mechanisms
- Challenge Period Duration – Timing for dispute resolution
Implementation bindings may be developed for various blockchain ecosystems:
- Move-based chains (e.g., Rooch, Sui, Aptos): Using BCS serialization and Move module interfaces
- EVM chains (e.g., Ethereum, Polygon): Using EIP-712 structured signing and Solidity contracts
- Cosmos chains: Using IBC-compatible channel identifiers and Cosmos SDK modules
- Solana: Using Borsh serialization and Solana program interfaces
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:
- Smart Contract: payment_channel.move - Move-based on-chain implementation with BCS serialization and DID-based authorization
- SDK: payment-kit - TypeScript SDK providing client libraries, middleware, and transport adapters for HTTP and MCP protocols
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.
{ "receipt": { /* receipt fields */ }, "signature": "<bytes>" // signature over canonical serialization of receipt }