rchain-token
Version:
Fungibles and non-fungibles tokens on the RChain blockchain
217 lines (146 loc) • 9.1 kB
Markdown
### Rholang (RChain) token
Fungibles and non-fungibles tokens on the RChain blockchain.
This repo includes a CLI so you can very quickly start playing with rchain-token contract, and SDK that you can integrate in any javascript application. The code and architecture takes advantage of [rholang's](https://rchain.coop/) powerful model of computation, and state of the art OCAP (object capabilities) paradigm to manage authorizations.

Object capabilities is a safe, no-encryption and flexible way to express ownership, permissions or authorizations. See or read about object capabilities [here](https://www.youtube.com/watch?v=EGX2I31OhBE), [here](https://www.youtube.com/watch?v=ZnBbi6ifzdo) or [here](http://erights.org/elib/capability/ode/ode-capabilities.html).
### What is a master contract ?
A master contract hosts boxes, contracts (NFT or FT) and purses inside those contracts. A deployment of rchain-token is the deployment of the master contract. Swap capabilities can be inter-contract (for example you can swap NFT "pikachu" with 1000 "usdx" fungible tokens), boxes can send NFTs or FTs to each other. Inter-master communications or exchange would need some trusted bridges.
The goal is that there is only one production master contract deployed per RChain shard or network.
### Burning with _burn
`_burn` is a special box, if you withdraw NFT or FT to box `_burn` it will be ... burned.
### Wrapped REV
From release 16 every swap must be token-to-token , which means that you cannot put a sell order on a NFT, and sell it against REV. An automatic fungible wrapped REV contract exists for the purpose of having a guaranteed one-to-one fungible token that represents REV inside a master.
The id of the contract is `[prefix]rev`, `prefix` being a the first 3 letters of the registry URI of the master. Use the `CREDIT` capability to credit REV, and have wrapped rev in return. Withdraw to special box `_rev` to recover true REV from wrapped REV.
**Note**: wrapped REV contract use the same decimals as REV: 1 wrapped REV = 1 dust = 10^-8 true REV
```
# Credit 20 wrapped REV (2.000.000.000 dust)
# Trun 20 true REV into wrapped REV
node cli credit --quantity 2000000000
```
### Use CLI
Rename the `.env.example` file to `.env` and eventually update the values to connect to another RChain network than RChain testnet. You must also add your private key in `.env` file so it will not appear on the screen and command line history.
```
# Deploy a master contract, that handles both boxes and contracts
node cli deploy-master
```
##### non-fungible tokens
Non-fungibles (NFT) tokens are identified by an ID like "pikachu", "amazon" or "cat123". They are still represented by purses, all purses have `quantity: 1`, a NFT purse cannot be divided. Purse `"0"` is a special mint purse from which you can purchase, and give a `newId` of your choice (See `./tests-fungibles/index.js:L229`).
As the owner, be very careful to only give `quantity: 1` when you create purses (except for special purse `"0"`).
```
# the following steps assume you have already deployed a master contract
# deploy a box that can store FTs, NFTs and super keys
node cli deploy-box --box-id mybox
# deploy contract and save super key to box
node cli deploy --fungible false --contract-id "mynft"
# create a non-fungible "cat123" token (quantity is always 1 for non-fungibles)
node cli create-purse --quantity 1 --new-id "cat123"
# after the deployed is processed you can check that your purse is created
node cli view
```
##### fungible tokens
Fungibles tokens purses can have a quantity superior to one. For examples 100 gold tokens, 1000 shares that can be exchanged, or any ERC-20 tokens will be fungible. The IDs of purses are automatically incremented (0, 1, 2 etc.), they do not matter that much.
```
# the following steps assume you have already deployed a master contract and a box
# deploy contract and save super key to box
node cli deploy --fungible true --contract-id "mytoken"
# Create/mint a purse with 12 tokens "[prefix]mytoken" using the admin/owner capability
# new id allocation is automatic (unlike NFT)
node cli create-purse --quantity 12
# after the deployed is processed you can check that your purse is created
node cli view
```
##### CLI
**View**
```
# Check the content of a box (super keys and purses)
node cli view-box
```
```
# Check contract's locked (true/false), and purses
node cli view
```
```
# Check a specific purse in a contract
node cli view --purse-id 0
```
**Purses/tokens operations**
```
# Send a purse from a box to another box
CLI NOT IMPLEMENTED YET
```
##### Methods/operations available
ro = Read only, it does not change the state of box or contract in any wey
The methods prefixed with `PUBLIC_` are accessible by any rholang execution, the others are only available for the owners of a box or super key (contract), following the OCAP paradigm.
###### Master
All methods are public
```
ro PUBLIC_READ_PURSES_AT_INDEX: (contractId: String, index: Int) => String | Map(purses at index)
ro PUBLIC_READ_BOX: (boxId: String) => String | Map(box purses and config)
ro PUBLIC_READ_PURSE: ({ contractId: String, purseId: String }) => Map(purses)
ro PUBLIC_READ_PURSE_DATA: ({ contractId: String, purseId: String }) => Map(purses data)
ro PUBLIC_READ_CONFIG: (contractId: String) => String | Map(contract config)
PUBLIC_REGISTER_BOX: ({ boxId: String, publicKey: String }) => String | (true, { "boxId": String, "boxCh": box ocap object})
PUBLIC_DELETE_EXPIRED_PURSE: (contractId: String, purseId: String) => String | (true, Nil)
```
Check files in `./src` if you want to know how to get many purses, or all purses using the methods above.
###### Box
A box (OCAP) is usually stored in a private channel like `@(*deployerId, "rchain-token-box", "MASTER_REGISTRY_URI", "BOX_ID")`, see `op_deploy_box.rho` for example.
All the methods on a box are private (owner of the box only).
```
ro READ: () => Properties of the purse (quantity, price, box, publicKey)
REGISTER_CONTRACT: ({ contractId: string, fungible: Bool, fee: Nil | (String, int) }) => String | (true, { "contractId": String, "superKey": super key ocap object })
UPDATE_PURSE_PRICE: ({ contractId: String, purseId: String, price: (String, Int) | (String, String) | Nil }) => String | (true, super key ocap object)
UPDATE_PURSE_DATA: ({ contractId: String, purseId: String, data: any }) => String | (true, super key ocap object)
WITHDRAW: ({ contractId: String, purseId: String, quantity: Int, toBoxId: String, merge: Bool }) => String | (true, Nil)
CREDIT: ({ purseRevAddr: String, purseRevAddr: VaultAuthKey }) => String | (true, Nil)
SWAP: ({ contractId: String, purseId: String, quantity: Int, merge: Bool, newId: String | Nil, data: any }) => String | (true, Nil)
RENEW: ({ contractId: String, purseId: String, purseRevAddr: String, purseRevAddr: VaultAuthKey }) => String | (true, Nil)
```
###### Contract (super key)
The ownership of a contract is expressed by the ownership of a super key that is returned by `REGISTER_CONTRACT` method. A contract's super key (OCAP) is usually stored in a private channel like `@(*deployerId, "rchain-token-contract", "MASTER_REGISTRY_URI", "CONTRACT_ID")`, see `op_deploy.rho` for example.
All the methods on a super key are private (deployer of the contract only).
```
LOCK: () => String | (true, Nil) // cannot CREATE_PURSE/DELETE_PURSE/UPDATE_FEE anymore after a contract is locked
CREATE_PURSE: see `cli/createPurse.js` or `src/createPursesTerm`.
UPDATE_FEE: see `cli/updateFee.js` or `src/updateFeeTerm`.
DELETE_PURSE: see `cli/deletePurse.js` or `src/deletePursesTerm`.
```
##### Using SDK
The SDK files are in `./src`, if you have rchain-token (`fabcotech/rchain-token`) as a dependency of your project, you should be able to use them very easily.
```
import {
masterTerm,
} from "rchain-token";
const rholangTerm = masterTerm({
depth: 3,
contractDepth: 2,
})
```
##### Compile rholang -> javascript (src/)
\_Generate all javascript source code in `src/` from rholang with `npm run generate`
##### Testing
Have a local rnode instance running with the following bonds and wallets file. And automatic propose every x seconds. The private keys used for deployments are in `tests-ft/index.js` and `tests-nft/index.js`.
wallets.txt
```
0x3769b4e68650bdfc7b55375034be6ee52978a14f,1000000000000,0
0x7f847d40c3ec604fe3d4263bfdd04111eb9b4e32,1000000000000,0
```
bonds.txt
```
04be064356846e36e485408df50b877dd99ba406d87208add4c92b3c7d4e4c663c2fbc6a1e6534c7e5c0aec00b26486fad1daf20079423b7c8ebffbbdff3682b58 100000000000
```
Also have at least those 2 lines in your .env file
```
READ_ONLY_HOST=http://127.0.0.1:40403
VALIDATOR_HOST=http://127.0.0.1:40403
```
`npm run test:ft`
`npm run test:nft`
`npm run test:credit`
##### Stressing
Many stress tests exist, see `stress/stress_*` files, you might want to change the variables on the top of each file.
example:
```
node stress/stress_createPurses.js
node stress/stress_createPursesAndReadAll.js
```