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.

Thoughts?

3 Likes

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.

3 Likes

Excellent.

This is what I wanted to hear.

3 Likes

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.

4 Likes

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.

EDIT:

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.

EDIT2:

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

EDIT3:

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

2 Likes

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]...
3 Likes

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.

Edit;

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.