8.9 KiB
CHIP-ppn: payment protocol 'next'
Title: Payment Protocol Next
Type: Standards
Layer: Applications
Maintainer: Tom Zander
Status: Draft
Initial Publication Date: 2023-01-01
Latest Revision Date: 2026-05-22
Version: 0.1.0
Table of Contents
Summary
This document describes the protocol for communication between at least two bitcoin cash wallets to allow a payment to be created.
Motivation
New technology is available that is poorly supported by older version of payment protocols. This document has the aim to include the way to pay for a lot of those types.
There are good ideas already done previously. In bip70 the concept of sending the transaction to the receiver who then is responsible for broadcast was made well known as the way to do offline payments. The bip175/370 idea of partial transactions introduced concepts that allows a much more flexible way of requesting payments that would enable many usecases.
None of these ideas have reached anywhere close to their potential and need to be re-packaged to allow a future proof payment protocol.
Technical Specification
Most payment protocols battle with making the protocol complete for the level of technology when they are released. And possibly beyond that. As the recent activation (2026) of payment-to-script shows, future proving a payment protocol is challenging in the traditional designs where a list of properties is given that is then assembled into a transaction by the receiver.
The challenge mostly falls away if the request itself includes a ready made transaction already. You can imagine a request for a small payment and the receiver simply creates the transaction that would pay them and then sends that to the customer to actually pay with.
This is a step further than what the partially signed bitcoin transactions (PBST) concepts championed nearly a decade ago. They still used fields.
The simplest format for sending details that allow a wallet to create a functional transaction from is in actual fact a transaction. Not some annotated data that can be connected into a transaction but an actual transaction.
What we aim to do in this document is that the receiver creates a partial transaction. Specifically one that has the output (amount, script etc) that pays the receiver. But it will be a full transaction. Likely with zero inputs, but any transaction parsing code would be able to parse it.
The simplest payment concept is when the customer receives this transaction it can add an input, maybe a change output and they sign their inputs. This transaction can then be sent back to the receiver as proof of payment.
Request / Response
The protocol is meant to be conducted over an open connection which allows not just a simple request to be posed, but answers to be given over the same channel. A request for payment may result in a fully functional transaction to be sent back to the one that initiated the payment request.
There are a list of different requests and there are specific responses that can be given for such requests. We'll go over them here.
ConnectionRequest
This request can be used to move from a system that can't do bidirectional communication to one that can. For instance a QR code on paper can contain this message with a URL to open a connection from the phone.
When using NFC and an unstable NFC connection is detected, this message might also be pushed in order to restart the request with an upgraded connection using WiFi or Bluetooth. (The NFC LLCP details are out of scope for this document).
This request is meant to look like a bip21 URL, in order to fit in existing systems. The URL would take the form of:
bitcoincash:?c=URI where the URI is the place where the conversation is to be restarted.
Valid Responses
- None
FundRequest
This request proposes a full, unsigned, transaction to the user with the request to fund it. It is basically requesting payment if the output provided belongs to the sender.
{ t="fundrequest", tx="txdata==", to=0 }
The txdata is a simple transaction, encoded like they are on-chain. But likely not signed or complete. The txdata is base64 encoded for serialization.
The optional field 'to' stands for 'tips-output'. The merchant may have included a separate output where tips are collected. If this field is defined then that output has to exist and has to have a non-zero amount. The wallet is then responsible to handle the tip output in cooperation with the user. It may be Ok to simply strip off the tip output if the user doesn't want to pay tips.
The 't' is meant to stand for type. The 'tx' stands for transaction. The 'to' is a zero-indexed tip-output.
Valid Responses
- Funded Payment
- Refused
Plan Request
This request proposes a full, unsigned, transaction to the user, to be funded and paid at a future date.
{ t="planrequest", tx="txdata==", d="2027-05-22" }
The 't' is meant to stand for type. The 'tx' stands for transaction. The 'd' is short for date.
Valid Responses
- None
Funded Payment
In response to a FundRequest there can be a payment. This is the same transaction as sent, but completed as far as possible.
The fund request transaction had to have at least one output. With a specific number of satoshis. The wallet is not allowed to change this, but it is expected to add one or more inputs that fund the requested output amount(s). The wallet is allowed to add a change address as well, or frankly any other input or output it wants.
{ t="fundedpayment", tx="txdata==", return="q.." }
The users wallet may include an optional 'return' address. Merchants can store this in their bookkeeping so if the user uses the legal option to get their money back, this is the address it will be sent to.
The 't' is meant to stand for type. The 'tx' stands for transaction. The 'd' is short for date.
Valid Responses
- InputRequest
- Reject
- Approve
InputRequest
When a client has sent a funded payment, this should include a fully signed transaction and funded transaction. This should never be an issue if the client is online, but they don't have to be and in that case an input may be missing. Imagine for instance the client being a smart card that has no Internet. The user may have topped it up with a transaction that didn't get send to the network when the user did so. This request for input recognizes that the client is very likely payment from an UTXO it itself has the transaction for.
So the request is simply to try to help resolve the payment.
{ t="inputRequest", inputs=[2] }
The 't' is meant to stand for type. The 'inputs' is the array of zero-indexed inputs that are missing.
Valid Responses
- Input
Input
The response to InputRequest
{ t="input", tx="txdata==" }
The 't' is meant to stand for type.
There are no valid responses to the input message, as the input messages themselves are just a response to the InputRequest. The flow moves back to Funded Payment.
Approve
The merchant has accepted the clients payment. The merchant will proceed in broadcasting that transaction to the network as soon as possible. This concludes the interaction.
{ t="approve" }
The 't' is meant to stand for type.
The response is to disconnect.
Refused
This is sent by the client. It can be any reason, from user canceling to not having enough balance.
{ t="refused" }
The 't' is meant to stand for type.
The response is to disconnect.
Reject
This is sent by the server when the funded transaction is somehow not good enough.
{ t="reject" }
The 't' is meant to stand for type.
The response is to disconnect.
An initial idea was to reject a transaction that has too low a fee. Allowing the wallet to auto-send an updated one with a higher fee. In 2026 this sounds like an unneeded complexity, but maybe in the future a new field may be added where the wallet then tries to submit again.
Link layer
A word is needed about the link layer. There are various methods to have a bi-directional communication between the different users. The obvious one is to route this over HTTP.
The protocol has intentionally been kept short and to the point in order to also be able to replace the link layer with a system like NFC. Very little needs changing (if anything) to make this possible.
We intentionally don't dictate concepts like https / x509 or similar here. That falls outside of this spec and would make it harder to reuse this on a different link layer.
Changelog
Copyright
Copyright (C) 2022-2026 Tom Zander
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.