Lay2 - pw-sdk - Build DApps on CKB and Run Them Everywhere

Team and Background

why are you the right person / team for this project?

Lay2 is a dev team focusing on blockchain protocols and applications. Our members all have 10+ years R&D experiences in various fields of Internet applications, and have been working in blockchain industry for 2+ years. Lay2’s technology stack in blockchain consists of layer 2 technologies such as lightning network and state channel. We believe that making practical blockchain product is hard but worthy, and have developed a framework and several dapps in payment and games.

Project and Justification

why is this a valuable addition to the Nervos Ecosystem?

Nervos is still at the early stage, and the infrastructure is still incomplete no matter for developer or users. This situation has brought Nervos a bilateral cold start problem: if there are not enough qualified developers to construct the ecosystem, it will be hard to attract users. Simultaneously, without a large number of users, it will be difficult to attract qualified developers. Furthermore, some early public blockchains have established a formidable bilateral ecosystem due to their first-mover advantage and wealth effect. This phenomenon, resulting a network effect, has further exacerbated the difficulty of bilateral cold start of Nervos ecosystem.

Fortunately, the Cell Model and the RISC-V based VM of CKB have brought unprecedented flexibility and extensibility, which makes it possible for Nervos to open a new path. There is no need to wait for facilities such as wallets, DApp browsers, to complete the long process of developing, launching, accumulating applications and attracting users. Instead, Nervos can quickly get access to a large number of users by directly adopting other blockchains’ infrastructure, which means that it could enter the application development stage directly without struggling with the bilateral cold start problem. Lay2 is providing CKB with this capability by originally designed product, pw-sdk.

Different from ordinary sdks, DApps developed with pw-sdk (called pw-dapps) can directly run in wallets of Bitcoin, Ethereum, EOS, Tron, etc. There are two meanings of “directly” here: On one hand, no specific development is required of wallet maintainers to support pw-dapps. In fact, they don’t even need to support the CKB chain, e.g. MetaMask can already run pw-dapps now. On the other hand, for blockchain users, any blockchain address is already a valid CKB address, and there is no need to create a CKB wallet before using pw-dapps. Both user experience and security of asset of pw-dapps can be consistent with native DApps on those blockchains.

As for the ecosystem, pw-sdk will bring a large number of mature crypto infrastructures and their user base to Nervos. This allows the construction of Nervos ecosystem to upgrade from a single-threaded serial mode of “infrastructure construction-> product development-> user accumulation-> market feedback" to a multi-threaded mode in which “Infrastructure construction", “product research and development", “market feedback" can advance in parallel. In the same time, it will also largely eliminated the stage of “user accumulation”.It is foreseeable that pw-sdk will greatly accelerate the development of Nervos ecosystem, and It can also show the blockchain world the unique capabilities and great value potential of CKB as a new generation of encrypted economic infrastructure.

Technical Specification and Implementation

how will you implement this successfully?

The pw-sdk includes:

  • pw-lib: cryptographic primitives library

Like Keccak-256 hash library which is used to identify the signature of ethereum format, ECDSA Secp256r1(NIST P-256) library, etc.

  • pw-lock: universal multi-chain lock script

Pw-lock is able to verify the signatures from wallets of Bitcoin, Ethereum, EOS and other blockchains, and each address (distinguish by public key) of those blockchains can be mapped to a valid CKB address.

Pw-lock will be implemented with type id. We will apply to add pw-lock into the short payload format address standard when it’s stable, therefore the mapped addresses from other blockchains will have the same format as the default CKB address.

  • pw-core: front-end sdk

A javascript sdk implemented in Typescript. It will allow developers directly run their CKB DApps in wallets of all blockchains supported by pw-lock, and interact with pw-server.

Timeline / Roadmap

is the timeline reasonable and justified?

It will take about 10 months to get things done, and part of them has been completed so far. As the project progresses, we will continue to release our achievements step by step, so that the community could experience and participate in.


Checkpoint 1: April 2020

pw-lib, including:

  • Keccak-256 support

pw-lock, including:

  • Ethereum signature support
  • EIP-712 support
  • Can be deployed with type id

pw-core, including:

  • API design

Checkpoint 2: June 2020

pw-lib, including:

  • Secp256r1 (ECC P256) support

pw-lock, including:

  • Web Authn support

pw-core, including:

  • Basic API implementation like signing, sending transactions, etc.
  • Basic documents and tutorials
  • DApp demo based on pw-core

Checkpoint 3:August 2020

  • All pw components will support BTC, EOS and more blockchains.

Checkpoint 4:November 2020

  • Full version of pw-lock
  • Full version of pw-core
  • Documents and demos

Special thanks to @stwith for helping with the translation, to @xxuejie @ash @keith for providing technical support and to all test group members for giving valuable feedbacks.

11 Likes

Thanks for submitting the proposal! What will be the upgrade policy of pw-lock, i.e. who can upgrade the pw-lock lock script?

The upgradability of type id is a doube-edged sword, and this is even more true for lock script. There is always a trade-off between upgradability and determinism, and what we can do is try to narrow the gap between the two sides.

For this question, as the pw-lock is still under heavy development, the lock script on Aggron testnet can be upgraded by the cell owner (i.e. the Lay2 team) for now. But we also have some thoughts on possible ways to reduce the risk:

  • Adopt multisig in the lock script of the type id cell, and if any vote dapps come out, we can and also involve the idea of the community.

  • Include formal analysis methods or other test cases in the type script of the type id cell, which can prevent upgrades with poor quality or unwanted behaviors.

  • Reserve a fail-safe lock (e.g. the official lock) in the code of lock script, and make sure it will preserve in any future upgrades.

The discussion on this issue is very important and should be continued. We welcome everyone to contribute ideas and suggestions.

A good UX design may help by allowing users to choose between type_hash and code_hash script reference, leave the upgradability vs. security choice to users as different users have different preferences.

That’s a good point. We can map one external address to two different ckb addresses and give detailed tips to help users make their choice. We can add this feature in the poc and expect some feedbacks.

In this post we will walk through some important technical and functional points mentioned in the abstract, and extend some of them into possible usage scenarios in the future.

pw-lib related

As we all know, customizable cryptographic primitive is such a highlight of CKB that we have heard this feature too many times in core team’s keynotes and AMAs. But why this is important? What cryptographic primitives can we deploy on CKB and how should we make use of them? Let us show you some ideas with some libraries we will introduce in pw-lib.

  • Keccak-256 Hash Algorithm

Keccak, sometimes called SHA-3, is the hashing function of Ethereum. In fact, Keccak-256 is a superset of SHA-3, and the finalized NIST SHA-3 is the standard “SHA-3”, which is different with Ethereum’s hash algorithm.

Keccak-256 provides 256-bit hashes. It is used to generate transaction hash, block hash, and even address in Ethereum. In pw-core, Keccak-256 is used to generate hash to be signed and put in witness. But the real indispensability of Keccak-256 in pw-lock, where we need to recover Ethereum address from the public key. The EIP-712 typed data signature also needs Keccak-256 as the only hash algorithm.

  • SHA-256 Hash Algorithm

SHA-256 stands for Secure Hash Algorithm - 256 bit. Although it looks a lot ‘stronger’ than the SHA-3 algorithm mentioned above, it actually belongs to SHA-2 family, and naturally weaker than SHA-3.

The most famous usage of SHA-256 is Bitcoin mining. It is also used to generate Bitcoin address, and widely used as EOS’s hash algorithm. It is necessary for pw-lock to support both Bitcoin wallets and EOS wallets.

  • Secp256r1 (ECC P-256) Asymmetric Encryption Algorithm

Different from hash algorithms which are used to map data of arbitrary size to a fixed bit string in a one-way form, asymmetric encryption algorithms are used for encrypting or digitally signing data.

ECC stands for Elliptic Curve Cryptography, both Secp256k1 and Secp256r1 belong to this algorithm family. Since Bitcoin first adopted Secp256k1 as the signature algorithm, almost all successors have followed this design, and so has CKB. However, the Secp256r1 algorithm, known as P-256, has a wider acceptance in browsers and smart devices. By adopting P-256, we can open a wider range of application scenarios for CKB.

  • More to expect

As the scenario grows, there will be more demand for cryptographic libraries on CKB, such as SHA3-256 for Tron adaptation, and RSA for interacting with secure web applications. Thanks to the RISC-V vm and the cell model of CKB, we can easily integrate those libraries continuously without causing any painful interruption to both developers and users.

pw-lock related

  • Ethereum Signature Support (including EIP-712)
/* secp256k1_keccak256_sighash_all.c */
keccak_init(&sha3_ctx);
unsigned char eth_prefix[28]= {
  0x19, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x20, 0x53,0x69,
  0x67, 0x6e, 0x65, 0x64, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61,0x67, 0x65,
  0x3a, 0x0a, 0x33, 0x32
};
keccak_update(&sha3_ctx, eth_prefix, 28);
keccak_update(&sha3_ctx, message, 32);
keccak_final(&sha3_ctx, message);
	
/* Personal hash */
ret = verify_signature(message, lock_bytes, args_bytes_seg.ptr);
if(ret == CKB_SUCCESS){
  return CKB_SUCCESS;
}

/* Typed Data hash */
ret = calculate_typed_data(message, message);
if(ret != CKB_SUCCESS){
  return ret;
}
return verify_signature(message, lock_bytes, args_bytes_seg.ptr)

This is part of the code in pw-lock, which shows the logic to verify the signature in witnesses. Among them, ‘keccak_init’ initializes context for hash calculating, ‘keccak_update’ appends messages to original string, and ‘keccak_final’ finishes the calculation. As we can see, the lock first tries to recover and verify public key from the message as a personal_sign signature. If that fails, then the signature is treated as a typed data signature. Full code: https://github.com/lay2dev/ckb-system-scripts/blob/master/c/secp256k1_keccak256_sighash_all.c

  • How to Support EOS Signature

EOS has a different account model from Ethereum. Firstly, EOS accounts are not generated from public key. They are 12 characters long and can contain the letters a-z and the digits 1–5. In fact, the key-pairs act only as password to unlock the account. Secondly, there can be several different keys associated to one account, with different permissions. For example, EOS account ‘zhixiandaren’ has two permissions (the default two):

Associated public keys for EOS account 'zhixiandaren': 
- active: EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo
- owner : EOS6P9tLLAQYJbmPzoTEG9vpS6GMHJyWW4bUeppqtAwcUuJpZNXYo

In this case there is only one key used, but normally there should be two different keys. The owner key has the highest authority and should not be used frequently, and common operations such as transfers and votes are handled by the active key.

To support EOS signature, pw-lock should compare the recovered key with the active key, which should be passed to the script in args. To be noted, this is for common EOS accounts, other cases like contract permission (@eos.code) are not included for now.

pw-core related

  • How Can We Map Other Blockchain Address to CKB Address

Let’s first talk about what exactly CKB address stands for. As we all know, if Alice want to send crypto currency to Bob, all she needs to know is Bob’s address, much like sending him an email. But how can Bob actually use the received assets, like login to his email account and forward an email? This comes to the lock script - a script which determines whether you are the right person to unlock the assets, just like a script determines whether Bob has the right password to login to the email account ‘[email protected]’.

Our blockchain account has a pair of keys: private key and public key, and the address is often transformed from the public key (e.g. Ethereum address is the last 20 bytes of the public key). When we send a transaction, we have to create a digital signature with our private key and send it along with the message. When the blockchain verifies your access, the public key is firstly recovered from the signature, and then transformed into an address, which will be compared to the address we declared to own.

For common blockchains who have a fixed lock logic, it’s already enough to confirm ownership with signature and address. But CKB is so flexible that everyone can create his lock script with any logic in it. So besides the ‘address’ part from the public key, there is another part to identify which lock script will be used to verify the ‘address’. Plus some other parts like prefix and checksum, and some transformation, we can finally get a valid CKB address, like this:

args:  /* the 'address' transformed from public key */
b39bbc0b3673c7d36450bc14cfcdad2d559c6c64

code_hash: /* which lock script to use */
9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8

address generated:
ckb1jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t24n3kxgj53qk

Let’s take the address apart to get a clearer view:

ckb1 - the human readable part

jda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xw3vumhs9nvu786dj9p0q5elx66t2 - the Base32 encoded payload part, payload format: 0x02/0x04 | code_hash | args

4n3kxgj53qks- the checksum

More detailed information about CKB address can be found here: https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0021-ckb-address-format/0021-ckb-address-format.md

Finally, we have understood that a CKB address contains not only information of the public key, but also the lock script intended to be used. Now we can explain why the Ethereum addresses can be mapped to CKB addresses: We created a lock script which has the same logic with the Ethereum’s lock (the code_hash part), and put the Ethereum address in the args. That’s all. Sounds easy isn’t it? Yes, but only on CKB.

  • How to Sign CKB Transactions With Wallets of Other Blockchains

Let’s take MetaMask as an example:

/* personal_sign */
const DefaultSinger = (from, message) =>
  new Promise((resolve, reject) => {
    const params = [message, from]
    const method = 'personal_sign'

    window.web3.currentProvider.sendAsync({ method, params, from },
      function(err, result) {
        err && reject(err)
        result.error && reject(result.error)
        resolve(result.result)
      })
  }
  
/* signTypedData */
const TypedDataSigner = (from, message, { inputCapacity, outputs }) =>
  new Promise((resolve, reject) => {
    const typedData = buildTypedData(inputCapacity, outputs, message)
    const params = [from, typedData]
    const method = 'eth_signTypedData_v4'

    window.web3.currentProvider.sendAsync({ method, params, from },
      function(err, result) {
        err && reject(err)
        resolve(result.result)
      })
  }

Full code: https://github.com/lay2dev/ckb.pw/blob/master/src/services/eth/core.js

Above is the common method to call signing methods in Ethereum web3 environment. The only difference between signing a CKB transaction with an Ethereum transaction is the ‘message’ part. Now we already know that pw-lock is able to recover Ethereum address from the signature with Keccak-256, so the only thing we have to alter is hashing CKB transaction with Keccak-256, and the we just pass it to the signers above. There is something different when it comes to EIP-712 signature, as we have to assemble the message in form of typed data.

2 Likes

404

应该是最近在重构的原因

1 Like

The branch has been merged into the master, I’ll change it right now, thanks!

1 Like

This grant has now been approved. For more info and the official announcement please check here!

UPDATE: Checkpoint 1 Completed

First let’s recall the goals of Checkpoint 1:

pw-lib

At this stage, the only concept belonging to pw-lib is the adoption of hash function Keccak-256. Thanks to the power ckb-vm, we can achieve this by reference of a single file:

Currently this file is part of pw-lock. As we move on, there will be more complex implements like secp256r1 algorithm, and then we will open a stand-alone repository for pw-lib.

pw-lock

Full support for Ethereum signature, including:

  • eth_personalSign
  • eth_signTypedData_v4

Details can be found in our pw-lock repo (a :star: is very much welcomed :stuck_out_tongue:) :

pw-core

We have release the initial version of API Design here:

As this is an early design document, there are several things should be mentioned:

  • Content may change significantly in the future.
  • Not all properties and methods are listed. Only needed ones to show the design concept.
  • Not all interfaces and types are listed. Only referred ones.
  • We are very welcome and look forward to your suggestions or comments. You can deliver them by creating issues as well as join our discord channel and talk to us directly.

bonus

We also have the anyone-can-pay-lock integrated into pw-lock, so every pw-lock cell is already an anyone-can-pay cell, with no threshold for neither ckb amount nor sudt amount.

That’s all, thank you all for paying attention to Lay2, and I believe there will be news from us again soon :stuck_out_tongue:.

6 Likes

Lay2’s first grant milestone has been completed, reviewed, and approved!

Take a look at Lay2’s Grant Milestone #1:

5 Likes

This is super exciting !!

3 Likes

UPDATE: Checkpoint 2 Completed

First let’s recall the goals of Checkpoint 2:

pw-lib

We are glad to announce that the ECDSA P-256 (Secp256r1) has been successfully transplanted onto CKB.

This repo can work as an independent lockscript, and in the future we’ll also integrate the ability into pw-lock.

Currently the number of consumed cycles is 25M. We’ve made lots of efforts to bring it down from 100M to 50M, and finally to an acceptable value. We believe there is still room for optimization, so any ideas or practical operations that will help are welcomed.

pw-lock

With the help of P-256 lockscript, we can now use Web Authn compatible browsers and devices to create a CKB address directly from a webpage, with a hardware security level key management.

You can already experience the magic feature with this demo site, and the repo of this site is here: GitHub - lay2dev/ckb-webauthn-demo: CKB WebAuthn Demo.

Instructions for playing with the demo:

  1. FIDO Authenticator

    If you already have an authenticator compatible with the FIDO protocol (e.g. yubikey), then almost all platforms (except iOS for now) with modern browsers are available to play. We’ll make extra works to add iOS support later.

  2. Mac with Touch ID

    You can use the built-in Touch ID as an authenticator on you Mac, but this only works on Chrome for now. Apple’s progress in this area has been relatively slow, and we are looking forward to see the built-in biometrics authenticators are well supported with Safari on both macOS and iOS.

  3. Android Phone with Fingerprint

    You can use the built-in fingerprint on your Android phones as an authenticator to play with the demo. However cases have been reported that sometimes the signing procedure will have no response when you click the ‘Send’ button. We’ll take a deep look into these cases later.

pw-core

  • API Implementation

    We have implemented many major interfaces and also reserved a lot of customizable slots for developers who want to extend the basic abilities. Here let’s see how to send CKB with pw-core, and detailed examples can be found in the README file.

    import PWCore, {
      EthProvider,
      PwCollector,
      ChainID,
      Address,
      Amount,
      AddressType,
    } from '@lay2/pw-core';
    
    // insdie an async scope
    
    const pwcore = await new PWCore('https://ckb-node-url').init(
      new EthProvider(), // a built-in Provider for Ethereum env.
      new PwCollector() // a custom Collector to retrive cells from cache server.
    );
    
    const txHash = await pwcore.send(
      new Address('0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d', AddressType.eth),
      new Amount('100')
    );
    

    That’s it! If CKB transaction (with Ethereum wallets, e.g. MetaMask) is the only thing you need, you can already start your integration with pw-core.

  • Documents

    We have release the alpha version of pw-core here:

    https://www.npmjs.com/package/@lay2/pw-core

    We introduced the full picture of pw-core in the README file mentioned above, and the details of the sdk can be found in the API doc site. We will continue to improve details of the documents.

  • Demo dApp

    We’ve rebuild the Simplestdapp project with pw-core, you can play with the demo here: https://sd.lay2.dev, and the repo of this project is here: GitHub - lay2dev/simplestdapp: Simplest CKB dApp with pw-core adopted. As we use EthProvider for this dApp, you should open the demo in an Ethereum environment, such as MetaMask, imToken, etc. And if you need CKBs (devnet tokens) to play, please post your ETH address in this thread (or just pm me). The transactions can be check on our devnet explorer: https://explorer.ckb.pw.

thanks to @liusong1111 for his great work.

bonus

  • SHA-256 hash algorithm has also been added to pw-lib and integrated into the P-256 lockscript.

pw-core adopts ckb-js-tookit and ckb-sdk-js. Special thanks to the contributors for their awesome works.

8 Likes

congrats~ looking forward to the call!

1 Like

Respect!My holy Lay2 :heart_eyes: :heart_eyes:

Sorry for the mistake, the correct progress is : 100M -> 50M -> 30M -> 25M

1 Like

Hi guys, we want to make a small adjustment to Checkpoint 3:

Instead of BTC, we would like to add support for Tron first. The reason is simple: TRC-20 USDT.

As we know that the gas price on Ethereum these days is incredibly high. This deeply affects the motivation of users to use swap ETH and USDT for CKB, and more importantly, for USDT on CKB in the future.

Another reason is that there is almost no such thing as ‘BTC dApp browser’, so supporting BTC won’t benefit dApp developers so much.

We have already successfully add support for EOS, and are working hard to add support for Tron. This change actually affects our schedule so the review for Checkpoint 3 maybe delayed for one week or so, sorry about that.

3 Likes

UPDATE: Checkpoint 3 Completed

First let’s recall the goals of Checkpoint 3

The original version is:

And we submitted a proposal to change that into:

The detailed consideration of changing the objective is explained in the above post, and so far this proposal has not received any opposition, so we’ll proceed with the new objectives.

pw-lib

To support EOS, we need SHA-256 hash algorithm on CKB, which has already been achieved in checkpoint 2 as a bonus.

pw-lock

Things are different now, because we have 3 address formats instead of 1. To identify them, we add a 1-byte mark in front of the 65-byte witness:

Witnesses[0].lock: [FLAG | Witness]
/* 
FLAG BYTE:
0x01 - ETH
0x02 - EOS
0x03 - Tron
*/

In the actual code:

if(lock_bytes_seg.size == SIGNATURE_SIZE){
    // backward compatible with old witness format
    *chain_id = 1;
    memcpy(lock_bytes, lock_bytes_seg.ptr, SIGNATURE_SIZE);
  }else{
    // get the flag byte in new format
    memcpy(chain_id, lock_bytes_seg.ptr, 1);
    memcpy(lock_bytes, (lock_bytes_seg.ptr + 1), SIGNATURE_SIZE);
  }

More changes for adapting the 2 new blockchains will be addressed in our review call.

As we already put pw-lock on main-net and it affects a lot of assets now, we won’t change the master branch unless the code is solid enough. So we put the new features in a new branch feature_eostron, which can be found here:

https://github.com/lay2dev/pw-lock/tree/feature_eostron

pw-core

Thanks to the flexible design of pw-core, it’s simple to add support for EOS and Tron. What we need to do is:

  • Add 2 new Providers: EosProvider and TronProvider, which will use scatterjs and tronWeb to retrive addresses from the outside providers.
  • Add 2 new Signers: EosSigner and TronSigner.
  • Add 2 new Address types, and adjust the logic of methods for new formats.
  • Adjust witness format to add flag byte in front of original witness

Also we add a new branch instead of directly update the master branch of pw-core, even all changes are backward compatible. You can find the code here:

https://github.com/lay2dev/pw-core/tree/feature_eostron

bonus

Yes, we have a demo for you to try out :stuck_out_tongue:

https://pw-eostron-demo.vercel.app/

Open it with EOS and Tron env then you can see your CKB address mapped from new blockchains.

You can aquire testnet CKB from the facuet of Aggron testnet, and the link is in the Demo page.

Souce code of the demo:

4 Likes

UPDATE: Checkpoint 4 Completed

First let’s recall the goals of Checkpoint 4

pw-lock

Full version of pw-lock includes:

Support for Ethereum

Support for EOS

Support for Tron

Support for Webauthn

Support for ACP (bonus)


pw-core

Full version of pw-core includes:

Providers

EthProvider

With this provider, you can access Ethereum account in any standard Ethereum environment (where window.ethereum or window.web3 exists). EthProvider provides callback on address change, and an ensResolver method to convert ENS name to Ethereum address. Also it has a built-in sign function which provides the personalSign method from EIP-1193.

Web3ModalProvider (bonus)

With this provider, you can access Ethereum account in a non-Ethereum environment with the help of plugins, such as WallectConnect and Torus.

WalletConnect is an open source protocol for connecting decentralised applications to mobile wallets with QR code scanning or deep linking. A user can interact securely with any Dapp from their mobile phone, making WalletConnect wallets a safer choice compared to desktop or browser extension wallets.

Torus is a user-friendly key management system for DApps. Users can use their familiar SNS accounts to login and create a crypto account, providing a seamless crypto experience for Internet users.

EosProvider

With this provider, you can access ordinary EOS account (12 characters) with the help of ScatterJS and ScatterEOS. As we know that EOS account is a user defined string, and what we need is the public key behind it, so we provide a method getEosPublicKey to fetch the public key from an EOS account. And if you are going to use EOS account to create an Address instance, there is another higher level method getCKBLockArgsForEosAccount in this provider, which can directly transform an EOS account to the param needed. Also it has a built-in sign function which provides the getArbitrarySignature function from scatter-js’s schema (eosjs2).

TronProvider

With this provider, you can access Tron account in any standard TronWeb environment (where window.tronWeb exists). Also it has a built-in sign function which provides the trx.sign function from TronWeb’s API schema.

Builders

SimpleBuilder

SimpleBuilder is designed to meet the most basic needs: send some CKB to specific address. Usage of SimpleBuilder is simple:

const builder = new SimpleBuilder(
	new Address('some-eth-address', AddressType.eth),
  new Amount('100')
);

const rawTx = await builder.build();

However, simplicity does not mean it is not powerful. The first param is an Address instance, which means you can use all address types supported by pw-core, such as ETH / EOS / Tron, and of course CKB (both standard address and long address). The second param is an Amount instance, and it can accept any decimals as the second param (8 by default). Furthermore, if the address is ACP capable, the minimal value of the amount param can be adjusted automatically.

ACPBuilder (bonus)

As the name indicates, it’s a builder for sending CKB to ACP (Any Can Pay) address. First the builder will check if the address is a registered ACP address (just use address.isAcp() method), and then use the collector to fetch a live cell of this address. If the two steps succeed, the builder will return a valid ACP transaction, else it will throw corresponding exceptions.

SUDTBuilder (bonus)

This is a high level builder for assembling transactions compatible with the sUDT (Simple UDT) specification. SUDTBuilder makes sending sUDT as easy as sending CKB with SimpleBuilder, and only two params are added to specify which sudt will be sent and to determine if the sender is willing to provide CKB along with sUDT if necessary. Still, it’s not so simple as it looks like. If you know more about how CKB and sUDT works, you’ll understand that there are many different cases in assembling sUDT transactions. Let’s list all the cases we’ve covered here:

  • Sender’s sUDT cell has extra CKB for transaction fee

    • Receiver has an ACP cell to receive sUDT

      This is the simplest case of sUDT transaction. All we have to do is just put the sender’s cell as well as the receiver’s sUDT ACP cell as the inputs, and modify the sUDT amount of both cells to the correct value in the outputs. Then we calculate the transaction fee and cover it with sender’s cell capacity.

    • Receiver doen’s have an ACP cell to receive sUDT

      This case is quite common especially in the early days of sUDT adoption. First you should as k the sender’s opinion about sending extra CKB along with the sUDT if needed, and then fill the withCKB param value accordingly. Then the builder will determine which strategy to choose when assembling the transaction.

  • Sender’s sUDT cell has no extra CKB for transaction fee

    In this case, the builder will query extra available live cell of the sender’s address for transaction fee, and other processes are the same with above.

Signers

DefaultSigner

In the latest version, we combine 3 provider-related signers (EthSigner, TronSigner, EosSigner) into one - the DefaultSigner. Instead of creating a independent signer for each built-in provider, we add a sign method in Provider and require the provider instance in the constructor of DefaultSigner. This new design make it easy for developers who only need default signature from current provider.

Signer

This is the base class of all signers, including DefaultSigner. It provides the standard signing process of a regular CKB transaction. If you want to customize this process, let’s say you want to use a new hash algorigthm defined in SomeHasher, you can create a SomeSigner extends Signer and pass a SomeHasher instance in SomeSigner’s constructor, just like this:

class SomeSigner extends Signer {
  constructor() {
    super(new SomeHasher());
  }
}

Models

In order to hide the underlying details of CKB transaction and make life a lot easier for dApp developers, we provide many model classes with friendly methods. Here are some code snippets for several representative classes, and for more of them you can refer to our doc site.

Address

/* create an Address instance from a CKB address */
const ckbAddress = new Address('ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq', AddressType.ckb);

/* create an Address instance from an Ethereum address */
const ethAddress = new Address('0x308f27c8595b2ee9e6a5faa875b4c1f9de6b679a', AddressType.eth);

/* get the original address string */
console.log('ckb: ', ckbAddress.addressString)
console.log('eth: ', ethAddress.addressString)
// ckb: ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq
// eth: 0x308f27c8595b2ee9e6a5faa875b4c1f9de6b679a

/* get the corresponding CKB address */
console.log('ckb: ', ckbAddress.toCKBAddress())
console.log('eth: ', ethAddress.toCKBAddress())
// ckb: ckb1qyqdmeuqrsrnm7e5vnrmruzmsp4m9wacf6vsxasryq
// eth: ckt1q3vvtay34wndv9nckl8hah6fzzcltcqwcrx79apwp2a5lkd07fdxxvy0yly9jkewa8n2t74gwk6vr7w7ddne5jrkf6c

/* get the corresponding lock script hash (with the toHash method of class Script) */
console.log('ckb: ', ethAddress.toLockScript().toHash())
console.log('eth: ', ethAddress.toLockScript().toHash())
// ckb: 0xe9e412caf497c69e9612d305be13f9173752b9e75bc5a9b6d1ca51eb38d07d59
// eth: 0x0963476f28975bf93da673cd2442bd69c4b2d4e720af5a67ecece8a03b8926b5

/* check if the address is an ACP address */
console.log('ckb: ', ckbAddress.isAcp())
console.log('eth: ', ethAddress.isAcp())
// false
// true

// get the minimal CKB amount (an Amount instance) you can transfer to this address
console.log('ckb: ', ckbAddress.minPaymentAmount().toString() + ' CKB')
console.log('eth: ', ethAddress.minPaymentAmount().toString() + ' CKB')
// 61 CKB
// 0.00000001 CKB

Amount

const ckb100 = new Amount('100');
const shannon100 = new Amount('100', AmountUnit.shannon);
const usdt = new Amount('1234.5678', 6); // Assume usdt's decimals is 6

/* format */

console.log(`${ckb100.toString()} CKB is ${ckb100.toString(AmountUnit.shannon, {commify: true})} Shannon`);
// 100 CKB is 1,000,000 Shannon

console.log(`${shannon100.toString(AmountUnit.shannon)} Shannon is ${shannon100.toString()} CKB`)
// 100 Shannon is 0.000001 CKB

console.log(`${usdt.toString(6, {fixed: 2, commify: true})} USDT is rounded from ${usdt.toString(6, {pad: true})} USDT`);
// 1,234.57 USDT is rounded from 1234.567800 USDT


/* compare */

console.log('100 CKB is greater than 100 Shannon: ', ckb100.gt(shannon100));
console.log('100 CKB is less than 100 Shannon: ', ckb100.lt(shannon100));
// 100 CKB is greater than 100 Shannon: true
// 100 CKB is less than 100 Shannon: false

console.log('100 Shannon is equal to 0.000001 CKB: ', shannon100.eq(new Amount('0.000001')));
// 100 Shannon is equal to 0.000001 CKB: true


/* calculate */

console.log(`100 CKB + 100 Shannon = ${ckb100.add(shannon100).toString()} CKB`);
console.log(`100 CKB - 100 Shannon = ${ckb100.sub(shannon100).toString()} CKB`);
// 100 CKB + 100 Shannon = 100.000001 CKB
// 100 CKB - 100 Shannon = 99.999999 CKB

// Amount is assumed with unit, so if we want to perform multiplication or division, the best way is to convert the Amount instance to JSBI BigInt, and convert back to Amount instance if necessary.
const bn = JSBI.mul(ckb100.toBigInt(), JSBI.BigInt(10));
const amount = new Amount(bn.toString())
console.log(`100 CKB * 10 = ${amount.toString(AmountUnit.ckb, {commify: true})} CKB`);
// 100 CKB * 10 = 1,000 CKB

Cell

/* load from blockchain with a rpc instance and an outpoint */
const cell1 = Cell.loadFromBlockchain(rpc, outpoint);

/* convert rpc formated data to a Cell instance */
const cell2 = Cell.fromRPC(rpcData);

/* check how many capacity is occupied by scripts and data */
const occupiedCapacity = cell1.occupiedCapacity();

/* check if the cell's capacity is enough for the actual size */
cell1.spaceCheck();

/* check if the cell is empty (no data is stored) */
cell2.isEmpty()

/* adjust the capacity to the minimal value of this cell */
cell2.resize();

/* set / get amount of an sUDT cell */
const cell3 = cell2.clone();
cell3.setSUDTAmount(new Amount(100));
console.log('sUDT amount: ', cell3.getSUDTAmount().toString());
// sUDT amount: 100

/* playing with data */
cell1.setData('data');
cell2.setHexData('0x64617461');
console.log('data of cell 1: ', cell1.getData());
console.log('data of cell 2: ', cell1.getData());
console.log('hex data of cell 1: ', cell1.getHexData());
// data of cell 1: data
// data of cell 2: data
// hex data of cell 1: 0x64617461


Documents and Demos

Document Site

We will continue to improve the documentation according to our progress and feedbacks from developers.

Demos & Products

6 Likes