Towards a singular payment protocol

As described in A look at cryptocurrency URIs there have been many efforts in the last decade of cryptocurrency development to create easier ways to facilitate payments using coin-specific URIs and payment protocols. The first widely used design was BIP-21 for Bitcoin, which formed the basis for other efforts for Ethereum, Bitcoin Cash, etc. The fundamental problem with all of these efforts is that they are coin-specific. Of course each cryptocurrency has its own aspects that require different options in a URI or protocol. Ethereum added support for gas, chain ID, and inserting code in hex format. Bitcoin Cash added support for multiple payment outputs, multiscan contracts, and compressed labels. Some of the Ethereum additions are specific to Ethereum, but for the most part all of these additions can be generalized and used by all cryptocurrencies.

A short note to maximalists. Don’t dismiss this just because another coin is mentioned here. There are advantages to this idea even for those who never want to interact with other coins, tokens, or blockchains.

At the end of the day, who is actually doing the development on these payment protocols? It’s the wallet developers, and many wallets support multiple cryptocurrencies. Nothing in the various proposals for payment system effects the underlying blockchains they operate on. These are on a higher layer. Of course there are reference implementations like Bitcoin Core, and those developers could resist support for something NIH, but with enough support from wallet developers and payment processors, and a fundamentally better protocol, even core developers should see the benefit of a shared way to manage payments.

This doesn’t prevent individual cryptocurrencies from developing their own systems separate from this, but by supporting a common system as well, it insures that wallet developers can support their cryptocurrencies for payments, and then, like now, they can then decide if a coin-specific protocol is also necessary. On the flip side, as new cryptocurrencies are introduced, they will no longer need to come up with their own payment protocol.

The suggestion here is to coin a new URI that is blockchain-agnostic. Something simple like coin: or pay: (odd that pay: is not registered at the IANA yet). Set up a common forum with developers from different blockchains to work together to figure out what each one needs, and how to generalize the protocol to support all of them.

To start out, there needs to be support for designating the currency to be paid. It is also possible to designate multiple currencies, or request payment in one currency in reference to a second (for example asking for BTC but giving the amount in USD). So a simple transaction requesting 1 BTC be sent to a BTC address could be (shown over multiple lines for clarity):

pay:?v=1
&currency=BTC
&amount=1
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3

Multiple currencies

You could request payment in multiple currencies, only one of which is necessary:

pay:?v=1
&currency=BTC%2CETH
&amount=1%2C50.4
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3%2C0x40F15be3a8cDAf4d95FAa874E70F07Ca040006B7%401

In the above example the multiple possible currencies, amounts, and addresses, are all comma-delimited (encoded as %2C). Unclear if this is an ideal solution, but this is done to differentiate from another scenario, which is multiple payments within a single transaction, such as:

pay:?v=1
&currency=BTC
&amount=1
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
&currency1=ETH
&amount1=50.4
&address1=0x40F15be3a8cDAf4d95FAa874E70F07Ca040006B7%401

In the above case, both the payment of 1 BTC and 50.4ETH is requested. In this example the second output is simply defined by adding a number to the variables (currency1, amount1, address1). In BUIP-86 this is done by adding [1] to each one, but its not clear why adding characters that need to be encoded is necessary. Of course multiple payments could be to the same currency, with sales tax and tip being sent to different addresses as an example.

Referencing another currency

Going back to the earlier example of two possible currencies for payment, you could also reference a third currency for the amount.

pay:?v=1
&currency=BTC%2CETH
&refcurrency=USD
&refamount=100
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3%2C0x40F15be3a8cDAf4d95FAa874E70F07Ca040006B7%401

In the above example, you are requesting to be paid $100 worth of either BTC or ETH. Your first question might be how do the sender and recipient agree to a conversion rate? In this example, there is no way to come to agreement. You would simply trust that the person will send you the correct amount. A fix for this later.

Recipient Pays

Another option should be to allow the payer of the transaction fees to be either the sender or the recipient. Right now the sender pays the fees in most cryptocurrencies. In the case of a customer buying something from a store, that means the customer is paying the fees. This is the reverse of how credit card payment networks work, where the seller pays the fees. The default could be the current sender pays, but there should be a way of specifying that the recipient pays the fees.

pay:?v=1
&currency=BTC
&amount=1
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
&feepay=r
&fee=0.0000825

The above is the same transaction as the first one above, with feepay=r added meaning recipient pays. The default, not needed but optional for clarity, would be feepay=s for sender pays the fees. The fee field could be used without feepay if the one requesting funds wants to insure the fee is large enough to get the transaction into the next block.

The fee is shown optionally, but it should be shown so that both sides agree the fee is reasonable. If the seller specifies that they will pay 0.0000825 BTC in fees (about $0.74 right now) the Wallet can display that, and check to see if that is enough currently to get the transaction processed. The wallet can check current transaction fees based on the size of the transaction, and determine if it’s enough to get processed, and roughly how long it will take (will it be processed in the next block, in the next 3 blocks, etc.). Taking a look at something like earn.com’s Bitcoin Fees page shows what this kind of prediction looks like, which at the time of this writing shows that transactions are making it into the next block with 33 Satoshis/byte, so with a 250 byte transaction (a roughly average transaction), that would cost 250 * 0.00000033, or 0.00008250, exactly what is shown. The seller could say they’re going to pay less, not worrying about getting the transaction confirmed a little later. Of course if it’s too small of a transaction size it would never get confirmed. There could be a mechanism in place to request a higher fee to insure quicker confirmation.

One possible form of abuse here would be that the seller would say they’re going to pay the next-block rate but really pay the 6-block rate. That would delay the confirmation, which is annoying, but wouldn’t affect what you are paying. Your wallet could also check the blockchain to verify that the fee paid is what was specified. If someone is abusing this, you would be able to call them on it, and let other people know (if it’s a merchant that could be an effective way of preventing this kind of abuse, if its your friend who paid for your lunch, not so much).

This doesn’t need to be offered only for currencies that allow fee payment by recipient. In the example above, the seller would have to specify the fee (it could be optional in currencies that had native support for recipient-pays-fees), which could then be deducted from the amount being sent. Instead of sending 1 BTC, your wallet would send 1 – 0.00008250 BTC, or 0.9999175 BTC. However, you would also pay the fee (since Bitcoin requires the sender to pay the fee) so you would in effect be paying exactly 1 BTC.

Sending fiat currencies

There’s no reason that this protocol needs to be limited to cryptocurrencies. Support for fiat currencies could allow wider acceptance by other payment processors. Taking into consideration the code problem where there is no standard for cryptocurrency codes, there’s a need to define which codes one is using to specify currency, and how you define where to send funds, since different countries use different systems. For fiat currencies, the currency code could be restricted to the ISO 4217 list, and specified in the transaction:

pay:?v=1
&currencylist=ISO4217
&currency=EUR
&amount=100
&addresstype=IBAN
&address=NL35RABO5251802137

This transaction would request that 100 Euros be sent to an IBAN address in the Netherlands. Of course, just like needing a wallet that supports Bitcoin for the previous transaction to work, you would need a wallet capable of sending EUR to an IBAN address for this to work. At the moment this would likely be a bank app. In the future this could be done through some kind of swap on the blockchain where you pay in BTC and the EUR gets sent to the IBAN address in the Netherlands.

Sending to the US and other countries that do not support IBAN addresses would require a slightly different approach. IBAN numbers map nicely to the idea of an address, like what a cryptocurrency uses, but US account numbers do not, unfortunately. Without needing to create special variables for US banking, a series of variables could be set up that would map to various banking requirements in different countries. As part of a final specification, those mapping would be identified in an appendix, and updated as needed. One possibility could be:

pay:?v=1
&currencylist=ISO4217
&currency=USD
&amount=100
&addresstype=SWIFT
&address=BOFAUS3N
&account=123456789012

The above example uses a SWIFT routing number as the ‘address’ and adds the account type. This particular SWIFT code is supposed to receive USD, and if you know you are sending another currency you’re supposed to use BOFAUS6S. Those kind of differences could be defined so wallets could do some logic-checking and make sure that the right codes are being used. SWIFT codes are used for international transfers into the US, whereas domestically ABA routing numbers are used:

pay:?v=1
&currencylist=ISO4217
&currency=USD
&amount=100
&addresstype=ABA
&address=011400495
&account=123456789012

This could be the exact same transfer as above, just made domestically. That ABA routing number is to Bank of America in New Hampshire, while the SWIFT code is used for the whole country (but really refers to the main branch in New York). There may be additional information needed, such as physical address of the bank, which could have its own field. Maybe one day the US will support IBAN or a similar system, but until then this can suffice.

Other systems like IFSC in India, BSB in Australia, CHATS in Hong Kong, and NUBAN in Nigeria could be similarly defined and supported.

Cryptocurrency lists

In the above example, ISO4217 is used as a recognized list for fiat currencies. There is no similar recognized list for cryptocurrencies, although its possible ISO will at some point release a comparable list of cryptocurrencies. However, there need not be a fully recognized list, or central list of lists, to know what each list name is referring to. A possible list for cryptocurrencies could be SLIP-44, which is the list of coin types for BIP-44, which defines a multi-account hierarchy for BIP-32 hierarchical deterministic wallets. In this case, the original example above could be written as:

pay:?v=1
&currencylist=SLIP44
&currency=BTC
&amount=1
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3

Wallet developers could simply agree on certain sources like SLIP44, or currency exchange lists, or any information source that is considered acceptable (such as CoinMarketCap’s All Cryptocurrencies list). Note that SLIP44 lists BTC, and not the alternative XBT (see The code problem for more about this issue), so even if a list is agreed upon, some list of synonymous codes might be useful for wallet developers.

OS implementation issues

This raises an implementation issue, mentioned also in A look at cryptocurrency URIs. On Android, if you trigger a URI that is handled by multiple apps on your phone, it will ask you which app to use to process the URI. On iOS, no such luck. Assuming you have an app that supports a particular URI scheme, iOS randomly chooses an app to handle it. Hopefully this will change in the future, but the workaround would be to scan the URI (as a QR Code or NFC Tag) in the app you want to use. If you only have one wallet app, and it supports the URI want, however, there isn’t a problem.

One thing that is not possible on any OS is the ability to further specify apps based on capabilities. Sure, you could have many URIs, which would work (paybtc:, payeth:, payusd:, payeur:, etc.) but that’s unwieldy. It also would be difficult if the URI specified more than one currency. It would be better if there was OS level support for the payment protocol (or for defining capabilities of a particular URI in general) such that wallets would not only specify they support the pay: URI scheme and what version they support, but also which currencies they support. That way if a URI is scanned and it specifies sending USD, and the only wallet that supports sending USD is a bank app, then that would open. If the URI requests sending BTC and you have three Bitcoin wallets, then it would ask you which one to use to process the URI. If the URI specified sending both BTC and ETH (like the 3rd example above) and you only had one wallet that supported both, then that wallet would open.

Being able to target wallets based on capabilities would also be useful for URIs that interact with smart contracts. There are plenty of wallets that support sending and receiving a blockchain’s currency (such as Ether) but not applications running on the blockchain (Dapps). Knowing that only one of the ETH wallets supports Dapps would save time and launch only the appropriate wallet. OS-level support for this kind of capability-matching could also allow the user to specify preferred wallets for different capabilities. For BTC open this wallet, for USD open that wallet, for ETH without needing Dapp support open this wallet, but with Dapp support, open that wallet, etc. If there’s a conflict (like preferring one wallet for BTC, another for ETH, and the transaction requiring both) then the OS could simply ask you which wallet to use (assuming both support both currencies).

One interesting thing you could do, which would not require OS-level changes (although it would help), is to allow different parts of the transaction to be handled by different wallets. For example, you need to pay BTC and USD, so it first loads your BTC wallet of choice, and if the wallet doesn’t support USD, it then essentially triggers the other part of the URI, launching the a bank app to handle the USD transfer, and filling in the appropriate information in that app to process.

This brings up what to do if the entire transaction cannot be handled by a single wallet, and even if it should default to loading into a single wallet even if it can. Perhaps it should always ask which wallet to handle each currency. Maybe the wallet could even allow you to send part of the amount, and trigger a URI to handle the rest, which would then let you choose another wallet and would fill in the balance. Where OS support could help here is in allowing you to choose an app that cannot handle the entire transaction. This is perhaps getting too deep into a rare issue of multiple currencies in a single transaction. Perhaps in the future, however, this might not be so rare, where for example a country has its own cryptocurrency which needs to be used to pay taxes, while the purchase could be in any currency.

Communication and agreement

You may have noticed that two problems have been mentioned that suggest two-way communications between the sender and recipient. First, the issue of agreeing to an exchange rate when basing the amount of one currency being sent on another currency value (such as asking for BTC in an amount equal to 5 USD). Second, agreeing to the fee amount to be paid in the situation where the recipient has agreed to pay the fee.

Starting with the issue of exchange rates, it is possible to avoid the need for two-way communications by agreeing on a mutual third party to provide the exchange rate information. This could be an exchange (for example, see a real API call for BTC/USD from Binance), or a bank, or any number of trusted providers of information, as long as both parties can access that information at roughly the same time. The party that generates the request can suggest one or more information sources, and if the sender agrees to one of them, then they don’t need to communicate between themselves, they just need to check the exchange rate and agree that the amount being sent is close enough to the exchange rate to be acceptable. The side that creates the request can also send the exchange rate that they accessed when sending it. The recipient can then check themselves and verify it is correct. If there is disagreement beyond a certain amount (it would probably never be exactly the same unless it was an exchange pair with very low trading volume) then there could be a default action, such as averaging the two. Here’s an example:

pay:?v=1
&currency=BTC
&refcurrency=USD
&refamount=100
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
&exchange=Binance
&exchangepair=BTC%2CUSD
&exchangeurl=https%3A%2F%2Fapi.binance.com%2Fapi%2Fv1%2Fticker%2Fprice%3Fsymbol%3DBTCUSDT
&exchangerate=8798.67

In this example, the request creator is asking for $100 worth of BTC. They are stating they trust Binance as the data source (exchange here is in terms of the exchange rate data provider, not the fact that Binance is an exchange themselves). The exchange pair is really optional, since that should be derived from the currency and refcurrency fields. The next two fields are also optional. The url of the API call is given, although that is almost certainly not necessary and might be abused. If the wallet doesn’t support Binance, then it shouldn’t allow it as a trusted source. Lastly, it has the exchange rate provided when they accessed the API.

Let’s say the above URI is scanned, and the wallet sends the same API call to Binance and gets 8806.68 as the exchange rate. The first rate comes to .011365354 BTC while the second one is .011355017 BTC. The difference is about a tenth of a percent, or about 9 cents in USD terms. The simplest default would be to average the two, make it .011360186 BTC and send it. There is some room for abuse here, of course. The party sending the request can always pad the number a small amount to get a bit more. If the deviation is larger than a certain amount, the transaction could be rejected, assuming the rate isn’t changing so much at the time. Another solution is to get the companies providing data sources to timestamp and sign their price quotes, and the person generating the request includes the signed version in their request which the wallet can check. As long as the timestamp is within a certain allowed time limit, then that rate can be used. Exchanges have no real incentive to do this since they would be helping enable transfers without them, but other sources of exchange rate information might be able to be convinced to allow this kind of API call.

In a similar fashion, the fee amount could be agreed to by trusting a third party without needing to communicate directly.

pay:?v=1
&currency=BTC
&amount=1
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
&feepay=r
&fee=0.0000825
&feedata=Earn.com &feeurl=https%3A%2F%2Fbitcoinfees.earn.com%2Fapi%2Fv1%2Ffees%2Frecommended
&feespeed=fast

In this example, a fee is specified, and the fee data source is provided, along with a URL. Again, the wallet should probably have an internal list of trusted data sources, and the URL shouldn’t be necessary. The feespeed field indicates which block is being targeted for inclusion of this transaction. The above API call from Earn.com offer three options – fastestFee, halfHourFee, and hourFee. This corresponds to the next block, 3 blocks, or 6 blocks (since each block takes approximately 10 minutes in Bitcoin). The timing is different with different blockchains, and will be very different when Lightning Network and similar technologies are deployed, but a few options could be implemented here, such as fast, medium, and slow which would correspond to next, 3 blocks (30 min), and 6 blocks (60 min) for BTC. A similar metric could be devised for different blockchains. If fees are reduced significantly, such as through Lightning, then this field could be ignored.

The initial goal was to include a discussion of a two-way communications concept here, to facilitate more complex payment options, but that is being pushed off to a separate post.

Labels, public and private

Most cryptocurrency URIs, being derived from BIP-21, use the label= and memo= fields. These general map to recipient and item description. Thus, a purchase from a store using BIP-21 might look like:

bitcoin:n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
?amount=0.00062
&label=Starbucks
&message=Cappuccino

This would request 0.00062 BTC to be paid to Starbucks for a Cappuccino. Using this payment system, it would look something like:

pay:?v=1
&currencylist=SLIP44
&currency=BTC
&amount=0.00062
&address=n4ijQMT8NtB58dZrgodiMMmjFZh4KyKEG3
&recipient=Starbucks
&description=Cappuccino

Since this is a new payment system, there’s no need to be backwards compatible here. Let’s say what we mean. There’s a field to specify who is being paid, and there’s a field to describe what the payment is for.

It would be possible to create fields that are specific to certain kinds of payments, such as sales tax or tips, but instead it makes more sense to keep these fields generic and allow wallets to offer customizations based on certain words used in the description. For example, there may be a way to calculate sales tax, or to choose a tip amount. To adapt an example from A look at cryptocurrencies URIs:

pay:?v=1
&currencylist=SLIP44
&currency=BCH
&recipient=Starbucks
&address=qq00x4m9x6nx9et54tlqwv732df36wvlrcs4fvtr9z
&amount=0.02
&description=Cappuccino
&recipient=State%20Taxes
&address1=qq28kthz8qdq49hx3hejs67fp0g04k82dvsulz8wcg
&amount1=0.001
&description1=Sales%20Tax
&address2=qqsrrmkrr0sqd7a6fvn0cn4vzmw7n7tkjss0407muv
&recipient2=Shared%20Tip%20Jar
&description2=Tip

There are three separate payments being made. First, 0.02 BCH for a cappuccino, 5% sales tax, and a request for a tip. The wallet could see that there is no amount for a payment that has the description ‘Tip’ and automatically present a tip calculator that lets the user select 10%, 12%, 15%, 20%, or a custom amount. The wallet would show the amount of BCH and the amount in a user-specified fiat currency for comparison. There’s no need to agree to which descriptions can have custom payment options like this, although there could be general consensus between wallet developers. Each wallet could implement these how they want.

Keeping track of the transactions, and the recipient and description fields that go with them could make wallets much more user friendly. Want to see how much you’ve paid to a specific store (even though you use a different address each time)? Want to check what you’ve sent to your friend? Want to calculate the tax implication of your buying and selling of cryptocurrencies? All of this is helped by a good wallet that can manage and present this data properly. Having clear fields helps the wallet to accomplish these tasks.

Another field which some currencies support is the public memo. Bitcoin can’t publicly add a memo to a transaction that is transferring value, but other cryptocurrencies can, and sometimes a public memo is required to complete a transaction (frequently when dealing with an exchange). In these cases we can make it very clear that this is public, and have a field such as pubmemo=.

One thing which is useful is to have shortened field names. This shortens the URI length considerably, which allows for smaller QR Codes and cheaper NFC tags. Here’s an example of the codes mentioned so far:

Field nameShort field name
accountac
addressad
addresstypeat
amountam
currencyc
currencylistcl
descriptiond
exchangex
exchangepairxp
exchangeratexr
exchangeurlxu
feef
feedatafd
feepayfp
feespeedfs
feeurlfu
pubmemopm
recipientre
refamountra
refcurrencyrc
versionv

Summary

To summarize, a common payment URI with the following characteristics:

  • can be used with crypto or fiat currencies
  • multiple currency options for a single transaction
  • multiple payments in a single transaction
  • allow a payment in one currency, but the value to be referenced from a second currency
  • fee payment by sender or recipient
  • calculation of valuation and fees through common trusted third parties

It’s worth pointing out that integration with smart contracts and tokens, etc. has been purposely left out of this proposal. It’s not that they should not be part of the final standard, it’s just that a lot of work needs to be put into coming up with a secure generalized method of dealing with those, and that’s better left to a future discussion among developers.

By itself, these are useful features of a payment system. The final piece of the puzzle is to allow secure two-way communications between sender and receiver (or buyer and seller) which allows confirmation of transactions, fee agreement, exchange rate agreement, etc. as well as a way to initiate the transaction in the first place. This will be discussed in a future article.

For now, however, what do you think of the ideas presented here? If you’re a wallet developer is this something you would want to integrate if you knew others would be adding it as well? Do you like the idea of a single payment protocol for all currencies? Where would you discuss this with other wallet developers? If you’re a merchant, would you like to support the features here? If you’re a bank, do you think your banking app could support this kind of payment? Please share your thoughts, both positive and negative (although always constructive) below.

Join the Conversation

9 Comments

    1. I haven’t looks closely at mimblewimble as part of this solution, but my first concern would be that as another blockchain solution, it could not be easily integrated with existing blockchains. It’s possible to use mimblewimble as a kind of sidechain, but I’m not sure of the benefit of that. I would appreciate understanding better how you see it being integrated here.

        1. What I mean is as a standalone blockchain I’m not sure I see how you think it fits into a payment solution for other blockchains. Obviously this can be used by any blockchain, including Grin and Beam, as a payment mechanism. That’s the basic idea, a single system for all blockchains that want to allow payments.

          1. In Grin/Beam a payer cannot construct a payment transaction based on some payee address. They will need further interaction with the payee, since the transaction signature is essentially a 2-of-2 multisig. How does your payment protocol support that further interaction?

          2. Well, that would require the second part of the proposal, mentioned above, which deals with secure two-way communications. I’m still working on it, but hope to post something soon. What transport layer exists for Grin and Beam now? How does the back and forth needed occur?

  1. Please consider joining the keybase discussion channel at grincoin.teams.wallet_dev where these issues can be discussed. Grin supports https, file exchange (cut&paste), keybase, and TOR, while several wallets implement their own transport mechanism.

Leave a comment

Your email address will not be published. Required fields are marked *