Skip to content

Commit fa26642

Browse files
mr-zwetsrkalis
andauthored
add Cashtokens guide to docs (#345)
Co-authored-by: Rosco Kalis <[email protected]>
1 parent 0ccda72 commit fa26642

File tree

8 files changed

+191
-5
lines changed

8 files changed

+191
-5
lines changed

.cspell.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"algolia",
99
"altstack",
1010
"antlr",
11+
"authchain",
1112
"anyhedge",
1213
"anyonecanpay",
1314
"badlength",
@@ -66,6 +67,7 @@
6667
"fromaltstack",
6768
"gitattributes",
6869
"gitlab",
70+
"gotchas",
6971
"greaterthan",
7072
"greaterthanorequal",
7173
"hardcodes",
@@ -211,6 +213,7 @@
211213
"branchup",
212214
"bchguru",
213215
"bchn",
216+
"bcmr",
214217
"c0ffee",
215218
"cashcompiler",
216219
"cashninjas",

website/docs/guides/cashtokens.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
title: CashTokens
3+
sidebar_label: CashTokens
4+
---
5+
6+
CashTokens are native tokens on Bitcoin Cash, meaning that they are validated by all full nodes 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 activated on Bitcoin Cash mainnet in May of 2023.
8+
9+
:::tip
10+
You can read more about CashTokens on [cashtokens.org](https://cashtokens.org/) which has the full specification as well as a list of [usage examples](https://cashtokens.org/docs/spec/examples).
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+
### Fungible Tokens
35+
36+
The `amount` field is the amount of fungible tokens on the UTXO, the `category` is the "tokenId" for the token on the UTXO.
37+
The maximum size for a fungible token `amount` is the max signed 64-bit integer or `9223372036854775807`.
38+
39+
### Non-Fungible Tokens
40+
41+
The `nft` info on a UTXO will only be present if the UTXO contains an NFT. The `nft` object has 2 properties: the `capability` and the `commitment`. The `commitment` is the data field for the NFT which can is allowed to be up to 40 bytes.
42+
43+
Capability `none` then refers to an immutable NFT where the commitment cannot be changed. The `mutable` capability means the `commitment` field can change over time, usually to contain smart contract state. Lastly the `minting` capability means that the NFT can create new NFTs from the same `category`.
44+
45+
:::note
46+
A UTXO can hold both an `amount` of fungible tokens as well as an `nft`, as long as both tokens have the same `category`.
47+
This is quite a common pattern for covenants which want to hold contract state and fungible tokens on the same UTXO.
48+
:::
49+
50+
## CashTokens introspection data
51+
While CashTokens might seem overwhelming at first, realize that in contracts you will only use it through the following introspection details
52+
53+
- **`bytes tx.inputs[i].tokenCategory`** - `tokenCategory` + `tokenCapability` of a specific input.
54+
- **`bytes tx.inputs[i].nftCommitment`** - NFT commitment data of a specific input.
55+
- **`int tx.inputs[i].tokenAmount`** - Amount of fungible tokens of a specific input.
56+
57+
and their equivalent for outputs:
58+
59+
- **`bytes tx.outputs[i].tokenCategory`** - `tokenCategory` + `tokenCapability` of a specific output. (see [below](#1-tokencategory-contains-the-nft-capability)).
60+
- **`bytes tx.outputs[i].nftCommitment`** - NFT commitment data of a specific output
61+
- **`int tx.outputs[i].tokenAmount`** - Amount of fungible tokens of a specific output.
62+
63+
## CashTokens Gotchas
64+
There are a few important "gotchas" to be aware of when developing with CashTokens in smart contracts for the first time.
65+
66+
#### 1) tokenCategory contains the nft-capability
67+
```solidity
68+
bytes tx.inputs[i].tokenCategory
69+
```
70+
71+
When accessing the `tokenCategory` through introspection the result returns `0x` (empty byte string) 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.
72+
73+
If you want to check for an NFT using introspection, you have either split the `tokenCategory` from the `capability` or check the concatenation of the `tokenCategory` and `capability`.
74+
75+
```solidity
76+
// Constructor parameters: providedCategory
77+
78+
// Extract the separate tokenCategory and capability
79+
bytes32 tokenCategory, bytes capability = tx.inputs[0].tokenCategory.split(32);
80+
81+
// Check that the NFT is the correct category and has a "minting" capability
82+
require(providedCategory == tokenCategory);
83+
require(capability == 0x02);
84+
85+
// Alternatively:
86+
87+
// Check by concatenating the providedCategory and capability
88+
require(tx.inputs[0].tokenCategory == providedCategory + 0x02);
89+
```
90+
91+
#### 2) tokenCategory encoding
92+
93+
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.
94+
95+
```ts
96+
// when using a standard encoded tokenId, reverse the hex before using it in your contract
97+
const contract = new Contract(artifact, [reverseHex(tokenId)], { provider })
98+
```
99+
100+
It is not recommended to do the byte-reversal in script, because this adds extra unnecessary overhead to the script.
101+
```solidity
102+
// NOT THIS
103+
require(tx.inputs[0].tokenCategory == providedTokenId.reverse());
104+
```
105+
106+
#### 3) "invisible" empty nfts
107+
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 "invisible" when combined with fungible tokens.
108+
109+
First let's consider the case where a UTXO only holds an empty NFT:
110+
111+
```solidity
112+
// Input 0 has an empty nft (commitment 0x) with providedTokenId
113+
// If there was no empty nft the tokenCategory would return 0x
114+
require(tx.inputs[0].nftCommitment == 0x);
115+
require(tx.inputs[0].tokenAmount == 0);
116+
require(tx.inputs[0].tokenCategory == providedTokenId);
117+
```
118+
119+
Contrast this with the scenario where a UTXO holds both an empty NFT and fungible tokens of the same category:
120+
121+
```solidity
122+
// Input 0 might or might not have an empty nft (commitment 0x) with providedTokenId
123+
// Either way, the tokenCategory would return providedTokenId
124+
require(tx.inputs[0].nftCommitment == 0x);
125+
require(tx.inputs[0].tokenAmount == 10);
126+
require(tx.inputs[0].tokenCategory == providedTokenId);
127+
```
128+
129+
The NFT introspection fields (`nftCommitment` and `tokenCategory`) of these UTXOs look the same to the smart contract in both of these scenarios.
130+
131+
This means that a covenant UTXO holding both a minting NFT and the fungible token supply for the same token `category` cannot prevent that empty nfts are created by users when they are allowed to create a fungible token output. The possibility of these "junk" empty NFTs should be taken into account so they do not present any security problems for the contract system.
132+
133+
:::tip
134+
The easiest way to prevent issues with "junk" empty NFTs is to check that only NFTs with non-empty commitments can be interacted with in the contract system.
135+
:::
136+
137+
#### 4) Explicit vs implicit burning
138+
139+
CashTokens can be burned explicitly by sending them to an OP_RETURN output, which is provably unspendable. CashTokens can also be burned implicitly, by including them in the inputs but not the outputs of a transaction. Always be mindful when adding token-carrying inputs to not forget to add the tokens in the outputs, otherwise they will be considered as an implicit burn.
140+
141+
:::note
142+
Signing for CashTokens inputs is designed in such a way that pre-CashTokens wallets - which only know how to send and receive Bitcoin Cash - cannot spend CashTokens inputs and thus can never accidentally burn CashTokens this way.
143+
:::
144+
145+
## CashTokens Genesis transactions
146+
147+
A CashTokens genesis transaction is a transaction which creates a new `category` of CashTokens. To create a CashTokens genesis transaction you need a `vout0` UTXO because the txid of the UTXO will be you newly created `category`.
148+
149+
The requirement for a `vout0` UTXO can mean that you might need to create a setup transaction "pre-genesis" which will create this output. The "pre-genesis" txid then is your token's `category`.
150+
151+
:::tip
152+
CashTokens Creation is illustrated very nicely by transaction diagram in the specification document in the [section on token categories](https://cashtokens.org/docs/spec/chip#token-categories).
153+
:::
154+
155+
## CashTokens BCMR metadata
156+
157+
Although not directly related to smart contracts, BCMR metadata is important for user-facing CashTokens. This way users can see your token name, icon, description and any relevant project links directly in their wallet. Many CashTokens wallets use the [Paytaca BCMR indexer](https://bcmr.paytaca.com/) to fetch BCMR metadata info about CashTokens.
158+
159+
The Paytaca BCMR indexer listens for on-chain [authchain](https://github.com/bitjson/chip-bcmr?tab=readme-ov-file#zeroth-descendant-transaction-chains) transactions which publish metadata with an OP_RETURN publication output. These type of metadata updates are self-published on-chain identity claims. The zero-th output chain since the token genesis is the authchain. The UTXO at the "head" of this chain holds the authority to update the token's metadata.
160+
161+
:::tip
162+
For easy creation of CashTokens with BCMR metadata there is the Paytaca [CashTokens Studio](https://cashtokens.studio/) or to programmatically publish on-chain BCMR authchain updates there is the [AuthUpdate](https://github.com/mr-zwets/AuthUpdate) JS program.
163+
:::

website/docs/language/globals.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ Represents the `nSequence` number of a specific input. This value of the input's
131131
bytes tx.inputs[i].tokenCategory
132132
```
133133

134-
Represents the `tokenCategory` of a specific input. Returns 0 when that specific input contains no tokens. When the input 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.
134+
Represents the `tokenCategory` of a specific input. Returns `0x` when that specific input contains no tokens. When the input 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.
135135

136136
:::caution
137137
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.
@@ -149,7 +149,7 @@ Represents the NFT commitment data of a specific input.
149149
int tx.inputs[i].tokenAmount
150150
```
151151

152-
Represents the amount of fungible tokens of a specific input.
152+
Represents the amount of fungible tokens of a specific input. Maximum size for a `tokenAmount` is a 64-bit integer.
153153

154154
### tx.outputs
155155
Represents the list of outputs of the evaluated transaction. This is an array, and cannot be used on itself. You need to access an output with a specific index and specify the properties you want to access.
@@ -180,7 +180,7 @@ Represents the locking bytecode (`scriptPubKey`) of a specific output.
180180
bytes tx.output[i].tokenCategory
181181
```
182182

183-
Represents the `tokenCategory` of a specific output. Returns 0 when that specific output contains no tokens. When the output 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.
183+
Represents the `tokenCategory` of a specific output. Returns `0x` when that specific output contains no tokens. When the output 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.
184184

185185
:::caution
186186
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.
@@ -198,7 +198,7 @@ Represents the NFT commitment data of a specific output.
198198
int tx.output[i].tokenAmount
199199
```
200200

201-
Represents the amount of fungible tokens of a specific output.
201+
Represents the amount of fungible tokens of a specific output. Maximum size for a `tokenAmount` is a 64-bit integer.
202202

203203
## Constructing locking bytecode
204204
One of the main use cases of covenants is enforcing transaction outputs (where money is sent). To assist with enforcing these outputs, there is a number of `LockingBytecode` objects that can be instantiated. These locking bytecodes can then be compared to the locking bytecodes of transaction outputs.
File renamed without changes.

website/docs/sdk/electrum-network-provider.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,15 @@ interface Utxo {
5757
satoshis: bigint;
5858
token?: TokenDetails;
5959
}
60+
61+
interface TokenDetails {
62+
amount: bigint;
63+
category: string;
64+
nft?: {
65+
capability: 'none' | 'mutable' | 'minting';
66+
commitment: string;
67+
};
68+
}
6069
```
6170

6271
#### Example

website/docs/sdk/network-provider.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ interface Utxo {
3535
satoshis: bigint;
3636
token?: TokenDetails;
3737
}
38+
39+
interface TokenDetails {
40+
amount: bigint;
41+
category: string;
42+
nft?: {
43+
capability: 'none' | 'mutable' | 'minting';
44+
commitment: string;
45+
};
46+
}
3847
```
3948

4049
#### Example

website/docusaurus.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ module.exports = {
121121
{ from: '/docs/sdk', to: '/docs/sdk/instantiation' },
122122
{ from: '/docs/sdk/transactions-advanced', to: '/docs/sdk/transaction-builder' },
123123
{ from: '/docs/guides', to: '/docs/guides/covenants' },
124+
{ from: '/docs/guides/syntax-highlighting', to: '/docs/language/syntax-highlighting' },
124125
{ from: '/docs/getting-started', to: '/docs/basics/getting-started' },
125126
{ from: '/docs/examples', to: '/docs/language/examples' },
126127
],

website/sidebars.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
'language/functions',
1919
'language/globals',
2020
'language/examples',
21+
'language/syntax-highlighting'
2122
],
2223
},
2324
{
@@ -54,8 +55,8 @@ module.exports = {
5455
type: 'category',
5556
label: 'Guides',
5657
items: [
57-
'guides/syntax-highlighting',
5858
'guides/covenants',
59+
'guides/cashtokens',
5960
'guides/infrastructure',
6061
'guides/walletconnect',
6162
'guides/debugging',

0 commit comments

Comments
 (0)