BIP21 Query Parameters for CashTokens

Just wanted to start a discussion here to coordinate BIP21 query parameters for CashTokens. Some earlier discussion in CashToken Devs, and thanks to @joemar_taganna of Paytaca for talking through some of this with me in November.

The CashTokens spec defines a CashTokens-required address type (essentially what BIP21’s req addition tried to do, but isn’t widely supported so can’t be relied upon), but it would also be great if token-aware wallets can seamlessly “opt-in” to receiving tokens without breaking non-token-aware wallets. Spitballing (using the test vectors from the CashTokens CHIP):

Proposal 1

  • t=1: this address can accept CashTokens.
    • e.g. bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl?t=1
  • c=HEX_ENCODED_CATEGORY: this address is requesting tokens of this specific category. If specified, implies t=1 (which can be omitted). Must be specified with either ft of nft (Ignored if neither or both ft and nft are specified.)
    • ft=INTEGER: this address is requesting INTEGER fungible tokens of the c category.
      • e.g. bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl?c=3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7&ft=100: requests 100 FT from category 3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7.
    • nft=HEX_ENCODED_COMMITMENT: this address is requesting an NFT of the c category with the exact commitment provided in HEX_ENCODED_COMMITMENT.
      • e.g. bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl?c=3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7&nft=: requests an NFT with an empty commitment from category 3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7

I think this would hold us over until we can clean up our payment protocol standards
(I suggest we shoot for compatibility with BitPay’s v2 JSON Payment Protocol spec and extend it with BCH-specific features.)

I’d also be open to a BIP21 parameter scheme that supports requesting multiple groups of tokens (e.g. both ft and nft at the same time, possibly from different/multiple categories). And maybe we should just start there rather than “Proposal 1” above?

And aside, if we can clean up our various payment protocol standards into a future token and covenant-supporting payment protocol, we should take the opportunity to substitute the ? in the BIP21 format for some character that can be encoded in an alphanumeric QR code.



My main concern is the situation where a user sends tokens to a wrong (normal BCH - not supporting token) address scenario.

And this will happen A LOT, I know it already.

But from what @bitcoincashautist explained to me I understand this is not a problem because user can just install a new wallet with token support, insert the same secret words into it and claim the tokens on his non-token address.

Please absolutely do correct me if I am wrong here.

1 Like

Recommendation for token wallets is to not allow users send tokens to non-token addresses. The user would have to go out of his way to convert his recipient’s non-token to token address, and then send the tokens. But even if he does that…

…what you wrote holds:

and also, the non-token wallet will be incapable of seeing or moving the tokens, so no risk of burns or accidentally sending them.



This is what I wanted to hear.


Yeah, not all wallets will always follow the full spec. I know how the world works…

But problem solved anyway, no biggie.

URLs don’t require you provide an argument to an option. (browser JS API here).

so if you are going for short, just write t. For instance: ‘?t&ft=’.

I do want to add the personal preference that when we are building usecases that actually exchange things like category, we should use the new ‘z’ based address.

Rationale: the example URL includes new tags, like ft and c, an old wallet will ignore the properties it doesn’t know. And as such will attempt to send BCH to the provided address. Payment completed, users very confused.

So, if the intention is to receive tokens, provide a tokens address to avoid UX hell.


Seems reasonable enough.

Since (as I learned) CashToken addresses are backward-compatible anyway, I don’t see a problem with making a completely different address in order for the old wallets to automatically reject it.

The most critical UX design mechanic is IMO for the address to start with “bitcoincash:”, so we always know we are dealing with BCH network.


Or, for even more clarity, we could even go with something like “cashtokens:ADDRESS”, but there are probably some UX downsides I haven’t thought of yet.


In case I proposed something stupid, anybody is free to correct me with new/better information.


Apparently I am misunderstaning something? I need to do more research.

So there’d be 2 scenarios:

  1. A catch-all backwards-compatible: bitcoincash:q...?t. The old wallet will ignore the ?t and send BCH without any trouble, while a new wallet will happily send tokens to it. Token extras should not be used here.
  2. A token address bitcoincash:z...?c=beef...beef&ft=100. Old wallets will refuse to send to it, and new wallets will know how to parse the c= and ft= and nft=

Makes sense


So there’d be 2 scenarios:

  1. A catch-all backwards-compatible: bitcoincash:q...?t . The old wallet will ignore the ?t and send BCH without any trouble, while a new wallet will happily send tokens to it. Token extras should not be used here.
  2. A token address bitcoincash:z...?c=beef...beef&ft=100 . Old wallets will refuse to send to it, and new wallets will know how to parse the c= and ft= and nft=

Minor observation, but important: You will need to take into account the P2SH addresses too. So the two scenarios should be:

  1. bitcoincash:[q|p]...
  2. bitcoincash:[z|r]...

I’d also be open to a BIP21 parameter scheme that supports requesting multiple groups of tokens (e.g. both ft and nft at the same time, possibly from different/multiple categories). And maybe we should just start there rather than “Proposal 1” above?

Your proposal is good enough in my opinion. This should cover >95% of use cases. No need for supporting multiple tokens, if the goal is for the URL to generate QR code that can still be quickly scanned. The multiple tokens support can be done later with the expanded version of JSON Payment Protocol.

Fantastic, thank you all for the feedback!

Ah, thanks @tom, for pointing out ?t for the flag!

And I agree on requiring z/r addresses for c + ft/nft URIs. To illustrate the issue: if c is being used, someone is requesting a specific token type, e.g. $100 in some stablecoin. If the QR code seems to work, but doesn’t fill an amount, the user might reasonably key-in “$100”, which an old wallet would proceed to convert using the current exchange rate and send in BCH. Now we have a complicated mess, where a merchant expected stablecoins and got whatever amount of BCH the user’s wallet decided was equivalent.

I also appreciate the clarity that BIP21 URIs with amount or c+ft/nft are basically an ultra-simple payment protocol, while address-only BIP21 URIs are used in other contexts (wallet “receive” screens, tip jars, etc.). On that note, we should probably define some standard expectations around BCH amounts sent with these payments.

I think it should be allowed (but not required) for BIP21 URIs to specify the amount expected to be included on the output, especially since it’s not even possible to send tokens without at least a dust BCH amount (~600-700 depending on address type). And many payments use cases offer customers multiple payment options, passing the cost of each to the customer (e.g. if you pay with BTC, you pay the merchants expected network fee to move that BTC again to an exchange; if you pay with CashTokens, the merchant will want enough BCH also to move that token output again.)

One optimization that I think is important: the current BIP21 amount field is specified in BCH, i.e. units of 100,000,000 sats. But that’s incompatible with good wallet privacy practices: rounded values decimate wallet privacy by making clustering much easier, so wallets should almost never send round BCH amounts. That means: all privacy-preserving payment amounts are more efficiently described in terms of satoshis: 10.12345678 vs 1012345678 (at least drops a ., and for amounts below 1.0, drops placeholder 0 digits).

So, adding to the proposal:

  • sat=INTEGER can be used to specify a satoshi amount. If amount and sat are both used, show an error. (Either support old wallets with amount, or if you only care about modern wallets, use sat.)
  • sat may optionally be included on token request URIs (c + ft/nft).
  • If sat is not set, “dust limit sats” is implied. this is what an optimal sending wallet would want to send anyways (no reason to waste satoshis), so if sat is not specified, we assume that’s what they want.
  • amount produces an error for token request URIs. We generally want to deprecate amount, and wallets that support token request URIs should all know how to use sat, so this is a safe “line in the sand” – there’s no reason to ever use amount for a token request URI.
  • we define a BIP21 alphanumeric mode: ? becomes :, = becomes -, & becomes +, and all letters are uppercase (examples below).
    • if message can be QR-alphanumeric-encoded by converting to uppercase, convert it to uppercase for transmission, and wallets should convert it back to lowercase. (Skip this for messages with mixed case or special characters outside of space, $, %, *, +, -, ., /, :.)

The above additions allow us to reduce the complexity of QR codes by up to ~35%, which translates directly to faster scanning in low-light/poor-camera quality/damaged QR code situations.

QR codes have an alphanumeric encoding mode that is notably more efficient than the byte encoding mode, and it’s practically universally supported. Here’s a nice utility to experiment with QR code encodings, and here are some examples for comparison (all at 25% error correction, a good choice for most wallets):

  • Simple address; 20% reduction:
    • bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl byte mode, uses 54/60 chars for 37x37 size
    • BITCOINCASH:QR7FZMEP8G7H7YMFXY74LGC0V950J3R2959LHTXXSL, alphanumeric mode, uses 54/67 chars for 33x33 size
  • Simple address + indicate token support; 20% reduction:
    • bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl?t byte mode, 56/60 chars for 37x37 size
    • BITCOINCASH:QR7FZMEP8G7H7YMFXY74LGC0V950J3R2959LHTXXSL:T alphanumeric mode, uses 56/67 chars for 33x33 size
  • unrounded ~1M sat request (~1/100th of 1 BCH, ~$1.30 USD currently); 35% reduction:
    • bitcoincash:qr7fzmep8g7h7ymfxy74lgc0v950j3r2959lhtxxsl?amount=.01045678, byte mode, uses 71/74 chars for 41x41 size
    • BITCOINCASH:QR7FZMEP8G7H7YMFXY74LGC0V950J3R2959LHTXXSL:SAT-1045678, alphanumeric mode, uses 66/67 chars for 33x33 size
  • 10,000 FT request, dust sats; 15% reduction:
    • bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v?c=3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7&ft=10000 byte mode, uses 130/130 chars for 53x53 size
    • BITCOINCASH:ZR7FZMEP8G7H7YMFXY74LGC0V950J3R295Z4Y4GQ0V:C-3A29BD2FE2CA319181035844DCFF236C518BAE26F417911043DC653B1A9DEDC7+FT-10000 alphanumeric mode, uses 130/157 chars for 49x49 size
  • 100,000 FT request, dust sats; 26% reduction:
    • bitcoincash:zr7fzmep8g7h7ymfxy74lgc0v950j3r295z4y4gq0v?c=3a29bd2fe2ca319181035844dcff236c518bae26f417911043dc653b1a9dedc7&ft=100000 byte mode, uses 131/151 chars for 57x57 size
    • BITCOINCASH:ZR7FZMEP8G7H7YMFXY74LGC0V950J3R295Z4Y4GQ0V:C-3A29BD2FE2CA319181035844DCFF236C518BAE26F417911043DC653B1A9DEDC7+FT-100000 alphanumeric mode, uses 131/157 chars for 49x49 size

(Related note for anyone who wasn’t involved in the discussion during the CashTokens CHIP process: we should keep bitcoincash: rather than attempt to reduce it to bch: or otherwise. These examples demonstrate why bitcoincash: is usually as efficient as bch: in QR encoding, and there’s also more background here on why bitcoincash: is a major user experience win for BCH.)

Yeah, the more I’ve thought about this, the more clearly I think we should keep BIP21 URIs to “single purpose” payment requests. One BCH amount + either one NFT or one batch of FTs.

It’s always nice to be able to support more features, but in this case, it would impose a non-trivial cost on all wallet implementations. They would first need to be able to support UTXO selection across multiple types of assets (meaning Knapsack-problem territory), then they need to be able to allocate them to separate outputs as necessary, and fee calculation across all those outputs of varying token prefix sizes is also non-trivial if you’re not using some higher level wallet “engine” like Libauth’s tooling.

Alright, thoughts anyone? Anything else we’re missing?

I’m now thinking I’ll put together a CHIP to formalize these details. :rocket:

1 Like

If we wer able to do a new design from zero, then this makes a lot of sense.

But I don’t think the gain is good enough to warrent the extra complexity in all the wallets and bip21 supporting places.
The gain is quite small, and it becomes hard to explain when amount is allowed and when sats is allowed. Remember that bip21 is used in the wider ecosystem, not just BCH.

In general, I understand the drive to shorten the URL and get smaller QRs.

Personally I’d strive to keep stuff working and instead aim to work on that new payment protocol. In the QR part, we would encode basics needed on starting an out-of-bounds peer to peer communication and nothing more.

Re; uppercase to have smaller QRs.

The reason its bigger is because we encode a byte-array into human-readable text at massive loss. A 17 byte address gets encoded using cashaddress as 54 characters.
So it needs pointing out that QR codes can be massively smaller if you just stop requiring the text in it to follow the cashaddr or similar specs.

If you break the spec anyway, what benefit is there to keep using latin1? Why not just a raw byte-array for a > 50 % discount?

Now, I’m not entirely serious with that counter suggestion, I just think a URL that is instantly reusable in a webpage has a lot of merit and while smaller QRs are useful, I’m tempted to say that bip21 should either be followed (and thus no uppercasing) or we should investigate a better payment protocol that doesn’t attempt to encode all this in the QR, but makes the QR just a handshake opening up bi-directional communication elsewhere.


I did the same research here. Two QRs are included to show the difference between raw and cashaddress encoded.

1 Like

Make sense. The idea is to not make it hard for existing wallets and thus avoid unnecessary complexity in everyday’s transactions.

I think this aligns with out goal to have P2P cash transactions as easy and seamless as possible for everyone.

Yep, we can always create a new, more modern and improved standard with all the bells and whistles and call it BIP-210 or something.

Also, make it backward incompatible with BIP-21 to avoid any possible confusion for old wallets which understand only BIP-21.

This will minimize the technical debt and also allow for future improvements.

Hi all, thanks again for all the comments here so far. Thanks also to @Jonas for calling attention to this issue again in CashToken Devs this past week and for publishing a proposal here: CHIP-2023-05 URI Scheme Extensions for CashTokens - #5 by Jonas

I’ve been thinking about how some of the other payment info-sharing standards fit into this discussion. BIP70, BIP71, BIP72, BIP73, JSON Payment Protocol, and JSON Payment Protocol v2 are each in varying states of use across the ecosystem, and thinking about these downstream uses actually helps to resolve some design questions about how best to extend the URI scheme (see various notes in this rationale).

There are also lots of subtle ways in which these older standards have morphed, fallen out of use, or conflict with each other, and piecing together a complete implementation is quite difficult for developers of new Bitcoin Cash wallets.

For example our “BIP21” isn’t really specified anywhere, existing wallets just implement something resembling the 2012 spec and change the protocol scheme to bitcoincash. (And in doing so, many unintentionally break valuable use cases).

All this to say: I think it would be most valuable for us to resolve this topic by developing a complete specification for Bitcoin Cash’s current “payment protocol”: including both the complete URI scheme and all BCH-specific modifications/extensions to related standards.

The goal is to have a single, “baseline” specification which assembles everything we consider to be standard now in 2023.

This CHIP should be backwards-compatible wherever possible, resolve inconsistencies between existing standards, clarify items that have been widely replaced or deprecated, add CashTokens support, and generally serve as a foundation for future BCH payment protocol-related standards (payjoin and other privacy standards, draft and partially-signed transactions, new P2P transmission strategies, decentralized application-related payment protocols, etc.).

I’ve started an initial draft here:

1 Like

Actually, what you are observing is the cash-address specification having modified or extended the bip21 spec. Addresses according to the cash-address spec now include the prefix.
So indeed the upgrade is that bip21 style addresses no longer have the prefix. The addresses used do. Check what happens when you create a testnet address and request a payment to that, for instance.

Hmm, did you actually read the link there?
What that says is that a website can register a protocol handler as such:

Registers a handler for scheme at url. For example, an online telephone messaging service could register itself as a handler of the sms: scheme, so that if the user clicks on such a link, they are given the opportunity to use that web site.

Allowing a website to handle the bip21 url.

This is allowed and possible. There are no usecases removed.

What you linked to is that this is NOT possible for protocol handlers like ftp and bitcoin…

I think you inverted that somehow.

Personally I’d say we should follow the advice listed in the spec there to add bitcoincash to the list of prohibited protocol handlers so clicking on such a link will always open a native application instead.

Thanks for looking it over @tom!

:100: you’re definitely right – later specs just extend earlier ones, and you can certainly read them in order to understand where we are. (Though you also need to do some research to understand which parts of BIP70-73 have declined in usage/support.)

My intention is just for this spec to establish a new baseline that doesn’t require newcomers to piece things together so much. And IMO, we’re long overdue for BCH to have its own primary source standards document(s) rather than e.g. linking the bitcoin/bips repo on GitHub.

I may be misunderstanding you, but just a TLDR on protocol scheme safelisting in browsers: registerProtocolHandler (MDN docs) requries that you either:

  1. use web+ in your prefix – i.e. web+bitcoincash, or
  2. use one of the safelisted schemes, one of which is bitcoin.

Otherwise, the function returns a runtime SecurityError. Basically, the safelist includes schemes that no one can be considered to “own” and therefore aren’t locked down by the browser (ftp, irc, magnet, mailto, etc.).

The bitcoin: prefix made the cut because it predated standardization (e.g. I was building applications at BitPay using it in 2013). ethereum and other such schemes have been blocked from inclusion since ~2018 for various reasons.

For all of this, support is limited to non-Safari desktop browsers (~35% of global traffic), but it remains a useful feature that appropriate BCH wallets should consider supporting. (In addition to more modern, secure standards like app-specific deep linking.)

By including it in the spec, I’m saying BCH devs who care about this use case should not shy away from registering support for bitcoin: in addition to web+bitcoincash: or other prefixes (rationale). After all, these devs and their users are the people actually using bitcoin: (cash) for day-to-day payments.

Otherwise, no need to care, it’s just another a spec-completeness thing.

When you get a chance, would you take a look at Rationale: Inclusion of Alphanumeric Mode and let me know if I missed anything?

In attempting to implement this, I found that the QR alphanumeric mode is almost perfect for our use case:

CashAddresses encode information at 5 bits per character; alphanumeric-encoded QR codes coincidentally encode information at 5.5 bits per character (for similar reasons), so only minimal theoretical efficiency gains are possible from encoding address information using QR code binary encoding. […]
[…] At this length, the 12 character reduction does not reduce QR code complexity at all: regardless of bech32 encoding, both QR codes would be 53x53 modules.

(Lots more discussion in the CHIP.)

TLDR: the QR code standard already has an almost-perfect encoding for Bitcoin Cash URIs, and the full implementation essentially already exists (as a required part of all QR code libraries). BCH wallet devs just have to support swapping a few characters during encoding/decoding.

1 Like

While I understand the idea of having “our own”, it has to be measured against the network effect that sharing standards brings.

A standard is meant to enable network effects. Forking a standard and adding incompatible stuff to it is killing network effects.

This breaks the network effect pretty darn hard, though. Not sure if I’d call that perfect.

Your incompatible changes means all the existing parsers will be unable to communicate with it. Existing usecases should in my opinion use the existing spec. The known bip21 with cashaddress modification. That support has taken us nearly 5 years to request. Breaking it now is not something I support.

Short version;

if you want special treatment in webbrowsers, register the bitcoincash prefix.

The prefix bitcoincash is not part of bip21. It is part of cash-address. Which, maybe it wasn’t clear, means it is part of the checksum of the address. You can’t change it without breaking all addresses people use today in BCH.

1 Like

I’m on the fence here…

I opened an issue about it: