Skip to content

Fungibles and non-fungibles tokens on the RChain blockchain

License

Notifications You must be signed in to change notification settings

fabcotech/rchain-token

Repository files navigation

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 powerful model of computation, and state of the art OCAP (object capabilities) paradigm to manage authorizations.

RChain token

Object capabilities is a safe, no-encryption and flexible way to express ownership, permissions or authorizations. See or read about object capabilities here, here or here.

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

1111Wbd8KLeWBVsxByF9iksJ4QRRjEF3nq1ScgAw7bMbtomxHsqqd,100000000000,0
1111Fw75V1rzXzk6djeTs85peRVBHS84eM3vvM4RmezjGAuv5gxax,100000000000,0
1111NLFxg5UjGXDscDvE1mHctz6zKPhoS1sVRfLQnxjhGjkpFAtUo,100000000000,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

About

Fungibles and non-fungibles tokens on the RChain blockchain

Resources

License

Stars

Watchers

Forks

Packages

No packages published