|
| 1 | +--- |
| 2 | +title: CashTokens |
| 3 | +sidebar_label: CashTokens |
| 4 | +--- |
| 5 | + |
| 6 | +CashTokens are native tokens on Bitcoin Cash, meaning that they are validated by each full node on the network and their transaction rules checked by each miner when constructing new blocks. CashTokens added fungible and non-fungible token primitives. |
| 7 | +CashTokens was first proposed in February of 2022 and actived on Bitcoin Cash mainchain in May of 2023. |
| 8 | + |
| 9 | +:::tip |
| 10 | +The specification for CashTokens is the ['CHIP-2022-02-CashTokens: Token Primitives for Bitcoin '](https://github.com/cashtokens/cashtokens) document. |
| 11 | +::: |
| 12 | + |
| 13 | +## CashTokens Utxo data |
| 14 | + |
| 15 | +To understand CashTokens it is helpful to start with the layout of the UTXO data. In the `networkProvider` data from the SDK, the `token` property contains the new CashTokens fields: |
| 16 | + |
| 17 | +```ts |
| 18 | +interface Utxo { |
| 19 | + txid: string; |
| 20 | + vout: number; |
| 21 | + satoshis: bigint; |
| 22 | + token?: TokenDetails; |
| 23 | +} |
| 24 | + |
| 25 | +interface TokenDetails { |
| 26 | + amount: bigint; |
| 27 | + category: string; |
| 28 | + nft?: { |
| 29 | + capability: 'none' | 'mutable' | 'minting'; |
| 30 | + commitment: string; |
| 31 | + }; |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +Note that a UTXO can hold both an `amount` of fungible tokens as well as an `nft`, as long as both have the same `category` (also reffered to as "tokenId"). |
| 36 | + |
| 37 | +## CashTokens introspection data |
| 38 | +While CashTokens might seem overwhelming at first, realize that in contracts you will only use it through the following introspection details |
| 39 | + |
| 40 | +- **`bytes tx.inputs[i].tokenCategory`** - `tokenCategory` + `tokenCapability` of a specific input. |
| 41 | +- **`bytes tx.inputs[i].nftCommitment`** - NFT commitment data of a specific input. |
| 42 | +- **`int tx.inputs[i].tokenAmount`** - Amount of fungible tokens of a specific input. |
| 43 | + |
| 44 | +and their equivalent for outputs: |
| 45 | + |
| 46 | +- **`bytes tx.outputs[i].tokenCategory`** - `tokenCategory` + `tokenCapability` of a specific output. |
| 47 | +- **`bytes tx.outputs[i].nftCommitment`** - NFT commitment data of a specific output |
| 48 | +- **`int tx.outputs[i].tokenAmount`** - Amount of fungible tokens of a specific output. |
| 49 | + |
| 50 | +## CashTokens Gotchas |
| 51 | +There are two important "gotchas" to be aware of when developing with CashTokens in smart contracts for the first time |
| 52 | + |
| 53 | +#### 1) tokenCategory contains the nft-capability |
| 54 | +```solidity |
| 55 | +bytes tx.inputs[i].tokenCategory |
| 56 | +``` |
| 57 | + |
| 58 | +When accessing the `tokenCategory` through introspection the result returns `0x` when that specific item does not contain tokens. If the item does have tokens it returns the `bytes32 tokenCategory`. When the item contains an NFT with a capability, the 32-byte `tokenCategory` is concatenated together with `0x01` for a mutable NFT and `0x02` for a minting NFT. |
| 59 | + |
| 60 | +#### 2) tokenCategory encoding |
| 61 | + |
| 62 | +The `tokenCategory` introspection variable returns the tokenCategory in the original unreversed order, this is unlike wallets and explorers which use the reversed byte-order. So be careful about the byte-order of `tokenCategory` when working with BCH smart contracts. |
| 63 | + |
| 64 | +```ts |
| 65 | +// when using a standard encoded tokenId, reverse the hex before using it in your contract |
| 66 | +const contract = new Contract(artifact, [reverseHex(tokenId)], { provider }) |
| 67 | +``` |
| 68 | + |
| 69 | +generally not recommended to do the byte-reversal in script |
| 70 | +```solidity |
| 71 | + // NOT THIS |
| 72 | + require(tx.inputs[0].tokenCategory == providedTokenId.reverse()); |
| 73 | +``` |
| 74 | + |
| 75 | +#### 3) "invisibe" empty nfts |
| 76 | +Because the nft-capability has no separate introspection item, and nothing is appended to the `tokenCategory` in case of capability `none`, empty nfts can be "invisibe" when combined with fungible tokens. |
| 77 | + |
| 78 | +```solidity |
| 79 | + // Input 0 has an empty nft (commitment 0x) with providedTokenId |
| 80 | + // If there was no empty nft the tokenCategory would return 0x |
| 81 | + require(tx.inputs[0].nftCommitment == 0x); |
| 82 | + require(tx.inputs[0].tokenAmount == 0); |
| 83 | + require(tx.inputs[0].tokenCategory == providedTokenId); |
| 84 | +``` |
| 85 | + |
| 86 | +contrast this with the following scenario where there is also fungible tokens of the same category: |
| 87 | + |
| 88 | +```solidity |
| 89 | + // Input 0 might or might not have an empty nft (commitment 0x) with providedTokenId |
| 90 | + // Either way, the tokenCategory would return providedTokenId |
| 91 | + require(tx.inputs[0].nftCommitment == 0x); |
| 92 | + require(tx.inputs[0].tokenAmount == 10); |
| 93 | + require(tx.inputs[0].tokenCategory == providedTokenId); |
| 94 | +``` |
0 commit comments