@offckb/cli 0.1.0-rc4 → 0.1.0-rc5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,32 @@
1
1
  ## OffCKB
2
2
 
3
- CKB local development environment for your first try
3
+ [![npm](https://img.shields.io/npm/v/@offckb/cli.svg?maxAge=1000)](https://www.npmjs.com/package/@offckb/cli)
4
+ [![CI](https://github.com/retricsu/offckb/actions/workflows/node.js.yml/badge.svg)](https://github.com/retricsu/offckb/actions/workflows/node.js.yml)
5
+ [![npm](https://img.shields.io/npm/dt/@offckb/cli.svg?maxAge=1000)](https://www.npmjs.com/package/@offckb/cli)
6
+ [![npm](https://img.shields.io/npm/l/@offckb/cli.svg?maxAge=1000)](https://github.com/jeffijoe/@offckb/cli/blob/master/LICENSE.md)
7
+ [![node](https://img.shields.io/node/v/@offckb/cli.svg?maxAge=1000)](https://www.npmjs.com/package/@offckb/cli)
8
+
9
+ **CKB local development network for your first try.**
10
+
11
+ - One-line command to start a devnet
12
+ - no docker required
13
+ - pre-funded test accounts
14
+ - packed with the most useful scripts like Omnilock and Spore-contract
15
+ - multiple minimal Dapp templates to learn and get your hands dirty
16
+
17
+ Start building on Nervos blockchain, right now, right away!
18
+
19
+ ## Table of Contents
20
+
21
+ - [OffCKB](#offckb)
22
+ - [Table of Contents](#table-of-contents)
23
+ - [Install](#install)
24
+ - [Usage](#usage)
25
+ - [Get started](#get-started)
26
+ - [Built-in scripts](#built-in-scripts)
27
+ - [Accounts](#accounts)
28
+ - [About Lumos](#about-lumos)
29
+ - [Contributing](#contributing)
4
30
 
5
31
  ## Install
6
32
 
@@ -20,7 +46,7 @@ offckb accounts # list 20 accounts info with prefund CKB tokens
20
46
  offckb list-hashes # list built-in scripts hashes, equals `ckb list-hashes`
21
47
  ```
22
48
 
23
- ### Get started
49
+ ## Get started
24
50
 
25
51
  ```sh
26
52
  offckb init my-awesome-ckb-dapp
@@ -48,17 +74,43 @@ open another terminal and start the devnet:
48
74
 
49
75
  ```sh
50
76
  offckb node
77
+
78
+ # result
79
+ # ...
80
+ CKB-Miner: 2024-03-04 14:35:12.563 +00:00 client INFO ckb_miner::miner Found! #3181 0x3749481a320824fe21077eaa8ec9d024a7b62d031720c27c1ef1681e8ab349e8
81
+
82
+ CKB-Miner: 2024-03-04 14:35:17.567 +00:00 client INFO ckb_miner::miner Found! #3184 0xa612a9ea35f292a6473e23e88856283aea8b1bc6a607147bef5c06c94e964f2f
83
+ #...
51
84
  ```
52
85
 
53
86
  open another terminal and check the accounts to use:
54
87
 
55
88
  ```sh
56
89
  offckb accounts
90
+
91
+ # result
92
+ Print account list, each account is funded with 42_000_000_00000000 capacity in the genesis block.
93
+ [
94
+ {
95
+ privkey: '0x6109170b275a09ad54877b82f7d9930f88cab5717d484fb4741ae9d1dd078cd6',
96
+ pubkey: '0x02025fa7b61b2365aa459807b84df065f1949d58c0ae590ff22dd2595157bffefa',
97
+ lockScript: {
98
+ codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8',
99
+ hashType: 'type',
100
+ args: '0x8e42b1999f265a0078503c4acec4d5e134534297'
101
+ },
102
+ address: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqvwg2cen8extgq8s5puft8vf40px3f599cytcyd8',
103
+ args: '0x8e42b1999f265a0078503c4acec4d5e134534297'
104
+ },
105
+ {
106
+ privkey: '0x9f315d5a9618a39fdc487c7a67a8581d40b045bd7a42d83648ca80ef3b2cb4a1',
107
+ pubkey: '0x026efa0579f09cc7c1129b78544f70098c90b2ab155c10746316f945829c034a2d',
108
+ lockScript: {
57
109
  ```
58
110
 
59
111
  Copy some private keys and visit http://localhost:1234 to play your first CKB Dapp!
60
112
 
61
- ### Built-in scripts
113
+ ## Built-in scripts
62
114
 
63
115
  - [x] xUDT https://github.com/nervosnetwork/rfcs/pull/428
64
116
  - commit id: 410b16c
@@ -67,21 +119,23 @@ Copy some private keys and visit http://localhost:1234 to play your first CKB Da
67
119
  - [x] AnyoneCanPay https://github.com/cryptape/anyone-can-pay
68
120
  - commit id: b845b3b
69
121
  - [x] AlwaysSuccess https://github.com/nervosnetwork/ckb-production-scripts/blob/master/c/always_success.c
70
- - commit id: 410b16c
71
- - [ ] Spore https://github.com/sporeprotocol/spore-contract
122
+ - commit id: 410b16c
123
+ - [x] Spore https://github.com/sporeprotocol/spore-contract
124
+ - version: 0.2.2-beta.1
72
125
 
73
- ### Accounts
126
+ ## Accounts
74
127
 
75
128
  `offckb` comes with 20 accounts, each account is funded with 42_000_000_00000000 capacity in the genesis block.
76
129
 
77
130
  all the private keys are recorded in the `account/keys` file.
78
131
  detail informations about each account are recorded in the `account/account.json` file.
79
132
 
133
+ :warning: **DO NOT SEND REAL ASSETS INTO ALL THESE ACCOUNTS, YOU CAN LOOSE YOUR MONEY** :warning:
80
134
 
81
135
  ## About Lumos
82
136
 
83
137
  `offckb` uses [Lumos](https://github.com/ckb-js/lumos) as the CKB Dapp framework to build the template projects.
84
138
 
85
- ## Development
139
+ ## Contributing
86
140
 
87
141
  check [development doc](/docs/develop.md)
@@ -49,7 +49,21 @@ create_type_id = true
49
49
  [[genesis.system_cells]]
50
50
  file = { file = "always_success" }
51
51
  create_type_id = true
52
-
52
+ [[genesis.system_cells]]
53
+ file = { file = "spore-scripts/spore" }
54
+ create_type_id = false
55
+ [[genesis.system_cells]]
56
+ file = { file = "spore-scripts/cluster" }
57
+ create_type_id = false
58
+ [[genesis.system_cells]]
59
+ file = { file = "spore-scripts/cluster_agent" }
60
+ create_type_id = false
61
+ [[genesis.system_cells]]
62
+ file = { file = "spore-scripts/cluster_proxy" }
63
+ create_type_id = false
64
+ [[genesis.system_cells]]
65
+ file = { file = "spore-scripts/spore_extension_lua" }
66
+ create_type_id = false
53
67
 
54
68
  [genesis.system_cells_lock]
55
69
  code_hash = "0x0000000000000000000000000000000000000000000000000000000000000000"
@@ -21,21 +21,20 @@ function selectTemplate() {
21
21
  message: 'Select a dapp template',
22
22
  choices: [
23
23
  {
24
- name: 'Transfer CKB',
24
+ name: 'View and Transfer Balance',
25
25
  value: 'transfer',
26
- description: 'a simple dapp to check CKB balance and transfer CKB from address to address',
26
+ description: 'a simple dapp to check CKB balance and transfer CKB',
27
27
  },
28
- new prompts_1.Separator(),
29
28
  {
30
- name: 'Issue Token With XUDT scripts(coming)',
29
+ name: 'Issue Token via XUDT scripts',
31
30
  value: 'xudt',
32
- description: 'a simple dapp to issue your own token with XUDT scripts(coming)',
33
- disabled: true,
31
+ description: 'a simple dapp to issue your own token via XUDT scripts',
34
32
  },
33
+ new prompts_1.Separator(),
35
34
  {
36
- name: 'Issue Token With Max Supply via Omnilock And XUDT scripts(coming)',
35
+ name: 'Issue Token With Max Supply Limit via Omnilock And XUDT scripts(coming)',
37
36
  value: 'xudt',
38
- description: 'a simple dapp to issue your own token with XUDT scripts',
37
+ description: 'a simple dapp to issue your own token with max supply limit via XUDT scripts',
39
38
  disabled: true,
40
39
  },
41
40
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@offckb/cli",
3
- "version": "0.1.0-rc4",
4
- "description": "ckb development environment for professionals",
3
+ "version": "0.1.0-rc5",
4
+ "description": "ckb development network for your first try",
5
5
  "author": "Retric Su <retric@cryptape.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -4,7 +4,7 @@
4
4
  "SECP256K1_BLAKE160": {
5
5
  "CODE_HASH": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
6
6
  "HASH_TYPE": "type",
7
- "TX_HASH": "0x37f2b7799b199491f4732c572c086afdace0bf92992faf0b90bae44cdd119f9e",
7
+ "TX_HASH": "0x75be96e1871693f030db27ddae47890a28ab180e88e36ebb3575d9f1377d3da7",
8
8
  "INDEX": "0x0",
9
9
  "DEP_TYPE": "depGroup",
10
10
  "SHORT_ID": 1
@@ -12,14 +12,14 @@
12
12
  "SECP256K1_BLAKE160_MULTISIG": {
13
13
  "CODE_HASH": "0x5c5069eb0857efc65e1bca0c07df34c31663b3622fd3876c876320fc9634e2a8",
14
14
  "HASH_TYPE": "type",
15
- "TX_HASH": "0x37f2b7799b199491f4732c572c086afdace0bf92992faf0b90bae44cdd119f9e",
15
+ "TX_HASH": "0x75be96e1871693f030db27ddae47890a28ab180e88e36ebb3575d9f1377d3da7",
16
16
  "INDEX": "0x1",
17
17
  "DEP_TYPE": "depGroup"
18
18
  },
19
19
  "DAO": {
20
20
  "CODE_HASH": "0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e",
21
21
  "HASH_TYPE": "type",
22
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
22
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
23
23
  "INDEX": "0x2",
24
24
  "DEP_TYPE": "code",
25
25
  "SHORT_ID": 2
@@ -27,35 +27,35 @@
27
27
  "SUDT": {
28
28
  "CODE_HASH": "0x6283a479a3cf5d4276cd93594de9f1827ab9b55c7b05b3d28e4c2e0a696cfefd",
29
29
  "HASH_TYPE": "type",
30
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
30
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
31
31
  "INDEX": "0x5",
32
32
  "DEP_TYPE": "code"
33
33
  },
34
34
  "XUDT": {
35
35
  "CODE_HASH": "0x1a1e4fef34f5982906f745b048fe7b1089647e82346074e0f32c2ece26cf6b1e",
36
36
  "HASH_TYPE": "type",
37
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
37
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
38
38
  "INDEX": "0x6",
39
39
  "DEP_TYPE": "code"
40
40
  },
41
41
  "OMNILOCK": {
42
42
  "CODE_HASH": "0x9c6933d977360f115a3e9cd5a2e0e475853681b80d775d93ad0f8969da343e56",
43
43
  "HASH_TYPE": "type",
44
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
44
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
45
45
  "INDEX": "0x7",
46
46
  "DEP_TYPE": "code"
47
47
  },
48
48
  "ANYONE_CAN_PAY": {
49
49
  "CODE_HASH": "0xe09352af0066f3162287763ce4ddba9af6bfaeab198dc7ab37f8c71c9e68bb5b",
50
50
  "HASH_TYPE": "type",
51
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
51
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
52
52
  "INDEX": "0x8",
53
53
  "DEP_TYPE": "code"
54
54
  },
55
55
  "ALWAYS_SUCCESS": {
56
56
  "CODE_HASH": "0xbb4469004225b39e983929db71fe2253cba1d49a76223e9e1d212cdca1f79f28",
57
57
  "HASH_TYPE": "type",
58
- "TX_HASH": "0x920bb4250dc6216c08ee0713f86b3fcb01bf444027b08b7d92db0ed1c0fb9214",
58
+ "TX_HASH": "0x1dbed8dcfe0f18359c65c5e9546fd15cd69de73ea0a502345be30180649c9467",
59
59
  "INDEX": "0x9",
60
60
  "DEP_TYPE": "code"
61
61
  }
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Transfer CKB</title>
7
+ <title>View and Transfer Balance</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -55,7 +55,7 @@ export function App() {
55
55
 
56
56
  return (
57
57
  <div>
58
- <h1>Transfer CKB between addresses</h1>
58
+ <h1>View and Transfer Balance</h1>
59
59
  <label htmlFor="private-key">Private Key: </label>&nbsp;
60
60
  <input id="private-key" type="text" onChange={onInputPrivKey} />
61
61
  <ul>
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>happy coding with offckb</title>
7
+ <title>Issue Custom Token</title>
8
8
  </head>
9
9
  <body>
10
10
  <div id="root"></div>
@@ -1,7 +1,14 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import ReactDOM from 'react-dom';
3
- import { Script } from '@ckb-lumos/lumos';
4
- import { capacityOf, generateAccountFromPrivateKey, transfer } from './lib';
3
+ import { Cell, Script } from '@ckb-lumos/lumos';
4
+ import {
5
+ capacityOf,
6
+ computeLockScriptHashFromPrivateKey,
7
+ generateAccountFromPrivateKey,
8
+ issueToken,
9
+ queryIssuedTokenCells,
10
+ readTokenAmount,
11
+ } from './lib';
5
12
 
6
13
  const app = document.getElementById('root');
7
14
  ReactDOM.render(<App />, app);
@@ -12,8 +19,8 @@ export function App() {
12
19
  const [fromLock, setFromLock] = useState<Script>();
13
20
  const [balance, setBalance] = useState('0');
14
21
 
15
- const [toAddr, setToAddr] = useState('');
16
22
  const [amount, setAmount] = useState('');
23
+ const [cells, setCells] = useState<Cell[]>([]);
17
24
 
18
25
  useEffect(() => {
19
26
  const updateFromInfo = async () => {
@@ -29,10 +36,36 @@ export function App() {
29
36
  }
30
37
  }, [privKey]);
31
38
 
39
+ const onInputPrivKey = (e: React.ChangeEvent<HTMLInputElement>) => {
40
+ // Regular expression to match a valid private key with "0x" prefix
41
+ const priv = e.target.value;
42
+ const privateKeyRegex = /^0x[0-9a-fA-F]{64}$/;
43
+
44
+ const isValid = privateKeyRegex.test(priv);
45
+ if (isValid) {
46
+ setPrivKey(priv);
47
+ } else {
48
+ alert(
49
+ `Invalid private key: must start with 0x and 32 bytes length. Ensure you're using a valid private key from the offckb accounts list.`,
50
+ );
51
+ }
52
+ };
53
+
54
+ const enabledIssue = +amount > 0 && +balance > 6100000000;
55
+ const enabledCheck = privKey.length > 0;
32
56
  return (
33
57
  <div>
58
+ <h1>
59
+ Issue Custom Token{' '}
60
+ <small>
61
+ <a href="https://github.com/XuJiandong/rfcs/blob/xudt/rfcs/0052-extensible-udt/0052-extensible-udt.md#xudt-witness">
62
+ {'(xUDT specs)'}
63
+ </a>
64
+ </small>
65
+ </h1>
66
+ <p></p>
34
67
  <label htmlFor="private-key">Private Key: </label>&nbsp;
35
- <input id="private-key" type="text" onChange={(e) => setPrivKey(e.target.value)} />
68
+ <input id="private-key" type="text" onChange={onInputPrivKey} />
36
69
  <ul>
37
70
  <li>CKB Address: {fromAddr}</li>
38
71
  <li>
@@ -40,16 +73,34 @@ export function App() {
40
73
  <pre>{JSON.stringify(fromLock, null, 2)}</pre>
41
74
  </li>
42
75
 
43
- <li>Total capacity: {balance}</li>
76
+ <li>Total capacity: {(+balance).toLocaleString()}</li>
44
77
  </ul>
45
- <label htmlFor="to-address">Transfer to Address: </label>&nbsp;
46
- <input id="to-address" type="text" onChange={(e) => setToAddr(e.target.value)} />
47
78
  <br />
48
- <label htmlFor="amount">Amount</label>
79
+ <label htmlFor="amount">Token Amount</label>
49
80
  &nbsp;
50
81
  <input id="amount" type="text" onChange={(e) => setAmount(e.target.value)} />
51
82
  <br />
52
- <button onClick={() => transfer({ amount, from: fromAddr, to: toAddr, privKey }).catch(alert)}>Transfer</button>
83
+ <button disabled={!enabledIssue} onClick={() => issueToken(privKey, amount).catch(alert)}>
84
+ Issue token
85
+ </button>
86
+ <br />
87
+ <br />
88
+ <hr />
89
+ <p>after issued token, click the below button to check it</p>
90
+ <button disabled={!enabledCheck} onClick={() => queryIssuedTokenCells(privKey).then(setCells).catch(alert)}>
91
+ Check issued token
92
+ </button>
93
+ {cells.length > 0 && <h3>Result: all the cells which hosted this issued token</h3>}
94
+ {cells.map((cell, index) => (
95
+ <div key={index}>
96
+ <p>Cell #{index}</p>
97
+ <p>token amount: {readTokenAmount(cell.data).toNumber()}</p>
98
+ <p>issuer lockScript Hash: {computeLockScriptHashFromPrivateKey(privKey)}</p>
99
+ <p>token xudt args: {cell.cellOutput.type.args}</p>
100
+ <p>token holder's lockScript args: {cell.cellOutput.lock.args}</p>
101
+ <hr />
102
+ </div>
103
+ ))}
53
104
  </div>
54
105
  );
55
106
  }
@@ -1,8 +1,9 @@
1
- import { bytes } from '@ckb-lumos/codec';
2
- import { helpers, Address, Script, hd, config, Cell, commons, WitnessArgs, BI } from '@ckb-lumos/lumos';
1
+ import { helpers, Address, Script, hd, config, Cell, commons, BI, utils, CellDep } from '@ckb-lumos/lumos';
3
2
  import { values, blockchain } from '@ckb-lumos/base';
4
3
  import { indexer, lumosConfig, rpc } from './ckb';
5
- const { ScriptValue } = values;
4
+ import { TransactionSkeletonType } from '@ckb-lumos/helpers';
5
+ import { bytes, number } from '@ckb-lumos/codec';
6
+ import { xudtWitnessType } from './scheme';
6
7
 
7
8
  config.initializeConfig(lumosConfig);
8
9
 
@@ -28,6 +29,11 @@ export const generateAccountFromPrivateKey = (privKey: string): Account => {
28
29
  };
29
30
  };
30
31
 
32
+ export const computeLockScriptHashFromPrivateKey = (privkey: string) => {
33
+ const { lockScript } = generateAccountFromPrivateKey(privkey);
34
+ return utils.computeScriptHash(lockScript);
35
+ };
36
+
31
37
  export async function capacityOf(address: string): Promise<BI> {
32
38
  const collector = indexer.collector({
33
39
  lock: helpers.parseAddress(address, { config: lumosConfig }),
@@ -41,33 +47,74 @@ export async function capacityOf(address: string): Promise<BI> {
41
47
  return balance;
42
48
  }
43
49
 
44
- interface Options {
45
- from: string;
46
- to: string;
47
- amount: string;
48
- privKey: string;
50
+ export function addCellDep(txSkeleton: TransactionSkeletonType, newCellDep: CellDep): TransactionSkeletonType {
51
+ const cellDep = txSkeleton.get('cellDeps').find((cellDep) => {
52
+ return (
53
+ cellDep.depType === newCellDep.depType &&
54
+ new values.OutPointValue(cellDep.outPoint, { validate: false }).equals(
55
+ new values.OutPointValue(newCellDep.outPoint, { validate: false }),
56
+ )
57
+ );
58
+ });
59
+
60
+ if (!cellDep) {
61
+ txSkeleton = txSkeleton.update('cellDeps', (cellDeps) => {
62
+ return cellDeps.push({
63
+ outPoint: newCellDep.outPoint,
64
+ depType: newCellDep.depType,
65
+ });
66
+ });
67
+ }
68
+
69
+ return txSkeleton;
49
70
  }
50
71
 
51
- export async function transfer(options: Options): Promise<string> {
52
- let txSkeleton = helpers.TransactionSkeleton({});
53
- const fromScript = helpers.parseAddress(options.from, {
54
- config: lumosConfig,
72
+ export async function issueToken(privKey: string, amount: string) {
73
+ const { lockScript } = generateAccountFromPrivateKey(privKey);
74
+ const xudtDeps = lumosConfig.SCRIPTS.XUDT;
75
+ const lockDeps = lumosConfig.SCRIPTS.SECP256K1_BLAKE160;
76
+
77
+ const xudtArgs = utils.computeScriptHash(lockScript) + '00000000';
78
+ const typeScript = {
79
+ codeHash: xudtDeps.CODE_HASH,
80
+ hashType: xudtDeps.HASH_TYPE,
81
+ args: xudtArgs,
82
+ };
83
+
84
+ let txSkeleton = helpers.TransactionSkeleton();
85
+ txSkeleton = addCellDep(txSkeleton, {
86
+ outPoint: {
87
+ txHash: lockDeps.TX_HASH,
88
+ index: lockDeps.INDEX,
89
+ },
90
+ depType: lockDeps.DEP_TYPE,
91
+ });
92
+ txSkeleton = addCellDep(txSkeleton, {
93
+ outPoint: {
94
+ txHash: xudtDeps.TX_HASH,
95
+ index: xudtDeps.INDEX,
96
+ },
97
+ depType: xudtDeps.DEP_TYPE,
55
98
  });
56
- const toScript = helpers.parseAddress(options.to, { config: lumosConfig });
57
99
 
58
- if (BI.from(options.amount).lt(BI.from('6100000000'))) {
59
- throw new Error(
60
- `every cell's capacity must be at least 61 CKB, see https://medium.com/nervosnetwork/understanding-the-nervos-dao-and-cell-model-d68f38272c24`,
61
- );
62
- }
100
+ const targetOutput: Cell = {
101
+ cellOutput: {
102
+ capacity: '0x0',
103
+ lock: lockScript,
104
+ type: typeScript,
105
+ },
106
+ data: bytes.hexify(number.Uint128LE.pack(amount)),
107
+ };
63
108
 
64
109
  // additional 0.001 ckb for tx fee
65
110
  // the tx fee could calculated by tx size
66
111
  // this is just a simple example
67
- const neededCapacity = BI.from(options.amount).add(100000);
112
+ const capacity = helpers.minimalCellCapacity(targetOutput);
113
+ targetOutput.cellOutput.capacity = '0x' + capacity.toString(16);
114
+ const neededCapacity = BI.from(capacity.toString(10)).add(100000);
68
115
  let collectedSum = BI.from(0);
69
116
  const collected: Cell[] = [];
70
- const collector = indexer.collector({ lock: fromScript, type: 'empty' });
117
+ const collector = indexer.collector({ lock: lockScript, type: 'empty' });
71
118
  for await (const cell of collector.collect()) {
72
119
  collectedSum = collectedSum.add(cell.cellOutput.capacity);
73
120
  collected.push(cell);
@@ -78,76 +125,55 @@ export async function transfer(options: Options): Promise<string> {
78
125
  throw new Error(`Not enough CKB, ${collectedSum} < ${neededCapacity}`);
79
126
  }
80
127
 
81
- const transferOutput: Cell = {
82
- cellOutput: {
83
- capacity: BI.from(options.amount).toHexString(),
84
- lock: toScript,
85
- },
86
- data: '0x',
87
- };
88
-
89
128
  const changeOutput: Cell = {
90
129
  cellOutput: {
91
130
  capacity: collectedSum.sub(neededCapacity).toHexString(),
92
- lock: fromScript,
131
+ lock: lockScript,
93
132
  },
94
133
  data: '0x',
95
134
  };
96
135
 
97
136
  txSkeleton = txSkeleton.update('inputs', (inputs) => inputs.push(...collected));
98
- txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(transferOutput, changeOutput));
99
- txSkeleton = txSkeleton.update('cellDeps', (cellDeps) =>
100
- cellDeps.push({
101
- outPoint: {
102
- txHash: lumosConfig.SCRIPTS.SECP256K1_BLAKE160.TX_HASH,
103
- index: lumosConfig.SCRIPTS.SECP256K1_BLAKE160.INDEX,
104
- },
105
- depType: lumosConfig.SCRIPTS.SECP256K1_BLAKE160.DEP_TYPE,
106
- }),
107
- );
108
-
109
- const firstIndex = txSkeleton
110
- .get('inputs')
111
- .findIndex((input) =>
112
- new ScriptValue(input.cellOutput.lock, { validate: false }).equals(
113
- new ScriptValue(fromScript, { validate: false }),
114
- ),
115
- );
116
- if (firstIndex !== -1) {
117
- while (firstIndex >= txSkeleton.get('witnesses').size) {
118
- txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.push('0x'));
119
- }
120
- let witness: string = txSkeleton.get('witnesses').get(firstIndex)!;
121
- const newWitnessArgs: WitnessArgs = {
122
- /* 65-byte zeros in hex */
123
- lock: '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
124
- };
125
- if (witness !== '0x') {
126
- const witnessArgs = blockchain.WitnessArgs.unpack(bytes.bytify(witness));
127
- const lock = witnessArgs.lock;
128
- if (!!lock && !!newWitnessArgs.lock && !bytes.equal(lock, newWitnessArgs.lock)) {
129
- throw new Error('Lock field in first witness is set aside for signature!');
130
- }
131
- const inputType = witnessArgs.inputType;
132
- if (inputType) {
133
- newWitnessArgs.inputType = inputType;
134
- }
135
- const outputType = witnessArgs.outputType;
136
- if (outputType) {
137
- newWitnessArgs.outputType = outputType;
138
- }
139
- }
140
- witness = bytes.hexify(blockchain.WitnessArgs.pack(newWitnessArgs));
141
- txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(firstIndex, witness));
142
- }
143
-
137
+ txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(targetOutput, changeOutput));
138
+ /* 65-byte zeros in hex */
139
+ const lockWitness =
140
+ '0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
141
+ const outputTypeWitness = xudtWitnessType.pack({ extension_data: [] });
142
+ const witnessArgs = blockchain.WitnessArgs.pack({ lock: lockWitness, outputType: outputTypeWitness });
143
+ const witness = bytes.hexify(witnessArgs);
144
+ txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(0, witness));
145
+
146
+ // signing
144
147
  txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
145
148
  const message = txSkeleton.get('signingEntries').get(0)?.message;
146
- const Sig = hd.key.signRecoverable(message!, options.privKey);
149
+ const Sig = hd.key.signRecoverable(message!, privKey);
147
150
  const tx = helpers.sealTransaction(txSkeleton, [Sig]);
151
+ console.log(tx);
152
+
148
153
  const hash = await rpc.sendTransaction(tx, 'passthrough');
149
154
  console.log('The transaction hash is', hash);
150
155
  alert(`The transaction hash is ${hash}`);
156
+ }
157
+
158
+ export async function queryIssuedTokenCells(privKey: string) {
159
+ const { lockScript } = generateAccountFromPrivateKey(privKey);
160
+ const xudtDeps = lumosConfig.SCRIPTS.XUDT;
161
+
162
+ const xudtArgs = utils.computeScriptHash(lockScript) + '00000000';
163
+ const typeScript = {
164
+ codeHash: xudtDeps.CODE_HASH,
165
+ hashType: xudtDeps.HASH_TYPE,
166
+ args: xudtArgs,
167
+ };
168
+
169
+ const collected: Cell[] = [];
170
+ const collector = indexer.collector({ type: typeScript });
171
+ for await (const cell of collector.collect()) {
172
+ collected.push(cell);
173
+ }
174
+ return collected;
175
+ }
151
176
 
152
- return hash;
177
+ export function readTokenAmount(amount: string) {
178
+ return number.Uint128LE.unpack(amount);
153
179
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "@offckb/lumos-scaffold",
2
+ "name": "@offckb/xudt",
3
3
  "version": "0.1.0",
4
4
  "description": "",
5
5
  "main": "index.js",
@@ -0,0 +1,27 @@
1
+ import { molecule, number } from '@ckb-lumos/codec';
2
+ import { BytesOpt, Byte32, Bytes } from '@ckb-lumos/codec/lib/blockchain';
3
+ const { vector, option, table } = molecule;
4
+
5
+ const { Uint8 } = number;
6
+
7
+ const Script = table(
8
+ {
9
+ codeHash: Byte32,
10
+ hashType: Uint8,
11
+ args: Bytes,
12
+ },
13
+ ['codeHash', 'hashType', 'args'],
14
+ );
15
+ const ScriptOpt = option(Script);
16
+ const ScriptVecOpt = option(vector(Script));
17
+
18
+ // specs: https://github.com/XuJiandong/rfcs/blob/xudt/rfcs/0052-extensible-udt/0052-extensible-udt.md#xudt-witness
19
+ export const xudtWitnessType = table(
20
+ {
21
+ owner_script: ScriptOpt,
22
+ owner_signature: BytesOpt,
23
+ raw_extension_data: ScriptVecOpt,
24
+ extension_data: vector(Bytes),
25
+ },
26
+ ['owner_script', 'owner_signature', 'raw_extension_data', 'extension_data'],
27
+ );