Skip to content

Latest commit

 

History

History
 
 

mvp-dapp

Kuai MVP DApp

This is the minimum viable product(MVP) designed to demonstrate Kuai's ability to manipulate a group of cells on the Nervos Common Knowledge Base (CKB).

The Kuai MVP DApp is a partially implemented data.did.id, which serves as a decentralized account profile. In comparison to the fully implemented version, the Kuai MVP DApp focuses solely on on- and off-chain data management. On-chain state verification will be supported in the next stage of development.

You can find an online preview of the Kuai MVP DApp at https://kuai-mvp-dapp-ui.vercel.app/.

kuai mvp dapp cover

Deploy on Railway

Development Guide

A comprehensive guide is available at docs/tutorials/mvp-dapp.md

Code snippets

The magical get could be found at store model of Kuai

  public load(path?: string) {
    let local = {}
    const customizer = (objValue: StructSchema, srcValue: StructSchema) => {
      if (Array.isArray(objValue)) {
        return objValue.concat(srcValue)
      }
    }
    Object.values(this.states).forEach((state) => {
      local = mergeWith(local, state, customizer)
    })

    if (path) {
      return get(local, path, null)
    }

    return local
  }

The magical set could be found at store model of Kuai

  initOnChain(value: GetStorageStruct<StructSchema>): GetOnChainStorage<StructSchema> {
    const res: StorageSchema<string> = {}
    if ('data' in value) {
      const { offset, hexString } = this.serializeField('data', value.data)
      res.data = `0x${offset ? '0'.repeat(offset * ByteCharLen) : ''}${hexString.slice(2)}`
    }
    if ('witness' in value) {
      const { offset, hexString } = this.serializeField('witness', value.witness)
      res.witness = `0x${offset ? '0'.repeat(offset * ByteCharLen) : ''}${hexString.slice(2)}`
    }
    if ('lockArgs' in value) {
      const { offset, hexString } = this.serializeField('lockArgs', value.lockArgs)
      res.lockArgs = `0x${offset ? '0'.repeat(offset * ByteCharLen) : ''}${hexString.slice(2)}`
    }
    if ('typeArgs' in value) {
      const { offset, hexString } = this.serializeField('typeArgs', value.typeArgs)
      res.typeArgs = `0x${offset ? '0'.repeat(offset * ByteCharLen) : ''}${hexString.slice(2)}`
    }
    return res as GetOnChainStorage<StructSchema>
  }

With the power of magical get and set, the business model record, representing a storage with a custom schema, has the ability to manipulate cells by users' mind

The main code of business logic could be found at record model of mvp dapp

  update(newValue: StoreType['data']) {
    const inputs = Object.values(this.chainData)
    if (!inputs.length) throw new InternalServerError('No mvp cell to set value')
    const { data } = this.initOnChain({ data: newValue })
    const outputCapacity = inputs
      .reduce((pre: BI, cur) => pre.add(cur.cell.cellOutput.capacity), BI.from(0))
      .sub(TX_FEE)
    if (outputCapacity.lt('6100000000')) throw new Error('not enough capacity')
    const outputs: Cell[] = [
      {
        cellOutput: {
          ...inputs[0]!.cell.cellOutput,
          capacity: outputCapacity.toHexString(),
        },
        data: `${inputs[0]!.cell.data.slice(0, 2 + this.schemaOption!.data.offset * 2)}${data.slice(
          2 + this.schemaOption!.data.offset * 2,
        )}`,
      },
    ]
    return {
      inputs: inputs.map((v) => v.cell),
      outputs: outputs,
    }
  }

TODO: the update method, which returns inputs/outputs, should be delivered in store model natively because inputs/outputs is the only meaningful result of a modification.

With the help of get and update, the following workflow is feasible

  1. dump the entire state into local by load();
  2. manipulate on the local state;
  3. generate cell transition from prev state to new state by update().
  4. transform the cell transition into a transaction by view layer, tx.view in this case.

It's quite similar to the setState method in React. No matter how many updates are scheduled by setState, only the final state will be rendered in a frame.

What's Next

  1. manipulate states on several cells
    1. load and merge a global state from cells;
    2. locate the correct cell on updating state
  2. implement the contract model based on store model: ckb-js#3
  3. declare models by decorator with parameters: as https://github.com/ckb-js/kuai/blob/develop/packages/models/src/examples/dapp/actors/child.ts#L4
  4. instantiate models on demand: ckb-js#160
  5. support declarative routes: ckb-js#159

Prerequisites

CKB Node and Redis Service are required in this MVP.

Get CKB Node Ready

How to run CKB node locally

Available CKB nodes of community

Run Redis

Getting redis started

Getting started

Get Contract Ready

Deploy your own contract

Follow ckb-js#306 to deploy your own contract. Or

Connect to the demo contract deployed on the Testnet

The deployment information located at mvp-dapp/contract/deployed_demo, rename it to mvp-dapp/contract/deployed to connect to

Start server

Clone this project locally

$ git clone https://github.com/ckb-js/kuai.git

Install dependencies and build kuai modules locally

$ cd kuai
$ npm i
$ npm run build

Run the mvp dapp

  • run the mvp dapp in develop mode
$ cd ./packages/samples/mvp-dapp
$ npm run dev
  • run the mvp dapp in production mode
$ cd ./packages/samples/mvp-dapp
$ npm run build
# Service is expected to run on port 3000
$ npm run start:prod