Skip to content

Web app for interacting with User Defined Tokens on Nervos Network's CKB Blockchain

Notifications You must be signed in to change notification settings

WilfredTA/token_mint

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

52 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Preview

Table of Contents

Introduction

This is a demo web application for working with User Defined Tokens (UDTs) on Nervos Network's Layer 1 Blockchain, Common Knowledge Base.

It currently offers the following features:

  1. Import Keys
  2. Generate addresses
  3. Deploy new tokens of different amounts
  4. View the token balances of your addresses
  5. View the CKByte balances of your addresses

These additional features will be added soon:

  1. Issue tokens to a cell locked by fixed rate CKByte exchange rate
  2. View tokens available for exchange
  3. Exchange CKBytes for token
  4. Transfer tokens to other addresses

Architecture

Overview

At the highest level, this app is split into two parts: the client and server. The server is not much more than a query service for the client so that the client doesn't have to filter through a bunch of blockchain data on its own. Right now, the server caches blocks and some of their data and provides simply API endpoints for getting app-relevant data from the blockchain. It also generates raw transactions on behalf of the client based on parameters sent by the client (such as the address that will be used to sign the raw transaction). This is because generating the raw transaction requires gathering inputs, which is a query. The server also forwards signed transactions from the client to a CKB node, rather than the client sending them directly. Although there could be some issues with this in a production setting, I prefer it this way for now because it allows for additional logging.

Client

Dapp and Wallet

The client side is more complex than the server side and is actually split up into two parts: the Dapp and the Wallet. In the source code, the top level directory called /client is where the dapp code exists, while the top level directory called /wallet is where the wallet code exists.

The dapp provides the main interface for interacting with tokens, while the wallet provides the key management and transaction signing services.

The wallet is embedded within an iframe and itself has two parts: the wallet UI and the wallet service. The wallet UI is self explanatory (it provides the buttons, the inputs, etc). The wallet service - which is responsible not only for managing keys, but for signing transactions, generating keys, encrypting, decrypting and persisting certain user data in the browser - runs in an isolated web worker where it performs expensive operations.

Keyper Bridge

So far, I've spoken about the client side application in terms of two separate components: the wallet and the dapp itself. These components are isolated which raises the question: how do they communicate?

Naively, they would just use the Windows Message API. Both the wallet and the dapp post messages to each other and bind a handler to the window's message event.

There are two high level issues with this approach, though. The first is that it is difficult to manage the handler(s) as the application grows in sophistication. The second is that the application code and wallet code becomes completely dependent on a single API, making it tedious and difficult to switch to new ways of communicating if the dapp, for example, wanted to add support for desktop wallets communicating via websockets.

To illustrate the first issue, imagine that I have the following possible messages I can send to the wallet: signTx, getAccounts.

Naively, I might do something like this with the windows message API:

Imagine the messages passed are JSON and that the message data will have at least the following fields: {source: "wallet", type: "return_accounts" | "return_signTx"}

Dapp:

  // Find the embedded wallet
  let walletFrame = document.getElementById("wallet");

  window.on("message", (e) => {
    if (isJSON(e.data)) {
      let data = JSON.parse(e.data)
      if (data.source === "wallet") {
        switch(data.type) {
          case 'return_signTx':
            // handle the returned tx
            break;
          case 'return_accounts':
            // Handle the returned accounts
            break;
        }
      }
    }
  })

  walletFrame.postMessage(JSON.stringify({data: rawTx, type: "signTx"})

This seems fine at first: we just handle all the logic for managing responses from the wallet in a top level event handler like above. It quickly becomes an ineffective approach, though. Imagine I sent two signTx requests to the wallet, one after the other, and I needed to submit the FIRST signed transaction to CKB before submitting the SECOND.

In this case, the wallet will send two messages back of the same type: return_signedTx. However, the logic I want to use to handle each of these is different, so really I have to add a second layer of filters to determine which logic should be executed as a result of the message event: not only does data.type === "return_signedTx", but the response corresponds to the right request.. perhaps implement via message IDs: data.messageId === request.messageId.

That solves part of the problem, but I'd still need to figure out how to provide the corresponding routine that would execute when the message IDs match. This could be done via binding message event handlers to the window and unbinding them during the callback.

But what if I want to add an additional required field to messages (such as the messageId)? I'd have to change this in every location. Or what if I wanted to change the serialization strategy from JSON to something else? I'd have to change these everywhere. Of course, I could extract these to a set of functions that are called by every message handler in the beginning. That's definitely an improvement... But, again, if I wanted to add another piece of functionality to the message handling workflow (analogous to middleware), I'd have to add it everywhere.

The above summary describes some of the issues that occur as the communication between wallet and dapp becomes more sophisticated if I'm using the message API directly. But there's another type of challenge I haven't described: swapping out or adding entire components to this setup.

For example, if I wanted to support a different type of wallet - e.g., a desktop wallet - I'd need to use a different communication channel entirely (for example, websockets). I'd also have to ensure that the message serialization and deserialization worked properly, and if I wanted to support both types of wallets in the same app, I'd have to make sure all of the details about the message structure and semantics are the same.

These are the types of problems solved by KeyperBridge. It encapsulates all of this logic for serializing and deserializing messages, filtering through the contents of message channels, matching responses to their corresponding request handlers, etc. It provides easy to use mechansisms for extending and configuring message structure, serialization, functionality supported by the wallet, etc., all while allowing the dapp source code to treat the wallet as if it is just another object within the dapp's scope. Signing transactions with KeyperBridge looks like this:

let signedTx = await bridge.signTx(rawTx)
// do something with signedTx

This is an overview of the architecture:

Architecture

Demonstration of Functionality

Right now, the dapp allows you to deploy the UDT type script to your local chain, and also allows deploying tokens with governance locks, generating new accounts, and viewing account balances and UDT balances. Once the issuance script is built on the Keyper and CKB blockchain side, it will make it very easy to list custom tokens and their rates, issue them, and exchange CKBytes for UDTs.

Here is a visual demo of some of these functions:

Deploy Token

Deploy Token

Import Private Key

Import Key

Create New Account

Create Account

Sign Transaction Workflow

Sign Transaction Workflow

What is Keyper

Keyper is an ownership layer for the Nervos. Nervos LockScripts provide a high level of flexibility, but it can be challenging for wallets to support all the different variations. Keyper provides efficient management of LockScripts in a way that can be accessed with a common standardized interface.

Server Description

Coming soon

Setup Instructions

Supported Environments

  • Ubuntu Linux 18.04+
  • Ubuntu Linux 20.04+

Prerequisites

The following must be installed and available to build Token Mint.

Setting Up a CKB Node

A Nervos CKB node must also be available. Using a development node is recommended over a Testnet node or Mainnet node.

Cloning the Git Repo

git clone --recurse-submodules https://github.com/WilfredTA/token_mint

Installing Dependencies

The current user must have permission to manage Docker instances!

cd token_mint
./install_deps.sh

Setup Server

Edit the .env file in your server directory. Make sure to replace any paths with your relevant paths. The two demo private keys correspond to keys pre-loaded with native CKBytes on CKB nodes initialized in developer mode:

SECP_CODE_HASH=0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8
DEMO_PRIV_KEY_1=0xd00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc
DEMO_PRIV_KEY_2=0x63d86723e08f0f813a36ce6aa123bb2289d90680ae1e99d4de8cdb334553f24d
PATH_TO_CONTRACTS=~/Nervos-dev/token_mint/server/deps/ckb-miscellaneous-scripts/build/

Starting the Application

  1. Your CKB node must be installed and running.
  2. In a separate terminal window, from the server directory execute npm start.
  3. In a separate terminal window, from the wallet directory execute npm start.
  4. In a separate terminal window, from the client directory execute npm start.
  5. If it does not automatically open, point a web browser to http://localhost:3000.

Resetting the Application State

To reset the application back to it's original state, complete the steps below.

Reset Your CKB Node

Note: This step is optional and is only recommended for local development nodes.

  • Shut down your CKB node and CKB miner if they are running.
  • Use the command ckb reset-data --all
  • Restart your node and miner.

Reset Your Token Mint Server

  • Stop your server if it is running.
  • From the server directory, execute npm run reset.
  • Restart your server.

Clear Your Token Mint Wallet Data

  • From the main wallet screen, click the "Reset Wallet" button.

Disclaimer

This is meant for demonstration purposes only. The source code has not undergone a security review.

Having said that, I highly encourage experimenting with the app, or playing around with the web wallet setup and/or keyper-bridge in your own toy projects!

About

Web app for interacting with User Defined Tokens on Nervos Network's CKB Blockchain

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published