@offckb/cli 0.1.0-rc4 → 0.1.0-rc6
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 +74 -9
- package/ckb/devnet/specs/dev.toml +15 -1
- package/ckb/devnet/specs/spore-scripts/cluster +0 -0
- package/ckb/devnet/specs/spore-scripts/cluster_agent +0 -0
- package/ckb/devnet/specs/spore-scripts/cluster_proxy +0 -0
- package/ckb/devnet/specs/spore-scripts/spore +0 -0
- package/ckb/devnet/specs/spore-scripts/spore_extension_lua +0 -0
- package/dist/cfg/select.js +7 -8
- package/package.json +2 -2
- package/templates/config.json +8 -8
- package/templates/transfer/index.html +1 -1
- package/templates/transfer/index.tsx +1 -1
- package/templates/xudt/index.html +1 -1
- package/templates/xudt/index.tsx +178 -21
- package/templates/xudt/lib.ts +208 -109
- package/templates/xudt/package.json +1 -1
- package/templates/xudt/scheme.ts +27 -0
- package/templates/xudt/util.ts +66 -0
package/README.md
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
## OffCKB
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@offckb/cli)
|
|
4
|
+
[](https://github.com/retricsu/offckb/actions/workflows/node.js.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@offckb/cli)
|
|
6
|
+
[](https://github.com/jeffijoe/@offckb/cli/blob/master/LICENSE.md)
|
|
7
|
+
[](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
|
+
- [Step 1: Create A Project](#step-1-create-a-project)
|
|
27
|
+
- [Step 2: Start the Devnet](#step-2-start-the-devnet)
|
|
28
|
+
- [Step 3: Access Pre-funded Accounts](#step-3-access-pre-funded-accounts)
|
|
29
|
+
- [Built-in scripts](#built-in-scripts)
|
|
30
|
+
- [Accounts](#accounts)
|
|
31
|
+
- [About Lumos](#about-lumos)
|
|
32
|
+
- [Contributing](#contributing)
|
|
4
33
|
|
|
5
34
|
## Install
|
|
6
35
|
|
|
@@ -20,7 +49,9 @@ offckb accounts # list 20 accounts info with prefund CKB tokens
|
|
|
20
49
|
offckb list-hashes # list built-in scripts hashes, equals `ckb list-hashes`
|
|
21
50
|
```
|
|
22
51
|
|
|
23
|
-
|
|
52
|
+
## Get started
|
|
53
|
+
|
|
54
|
+
### Step 1: Create A Project
|
|
24
55
|
|
|
25
56
|
```sh
|
|
26
57
|
offckb init my-awesome-ckb-dapp
|
|
@@ -44,21 +75,53 @@ Server running at http://localhost:1234
|
|
|
44
75
|
✨ Built in 10ms
|
|
45
76
|
```
|
|
46
77
|
|
|
47
|
-
|
|
78
|
+
### Step 2: Start the Devnet
|
|
79
|
+
|
|
80
|
+
Open another terminal and run:
|
|
48
81
|
|
|
49
82
|
```sh
|
|
50
83
|
offckb node
|
|
84
|
+
|
|
85
|
+
# result
|
|
86
|
+
# ...
|
|
87
|
+
CKB-Miner: 2024-03-04 14:35:12.563 +00:00 client INFO ckb_miner::miner Found! #3181 0x3749481a320824fe21077eaa8ec9d024a7b62d031720c27c1ef1681e8ab349e8
|
|
88
|
+
|
|
89
|
+
CKB-Miner: 2024-03-04 14:35:17.567 +00:00 client INFO ckb_miner::miner Found! #3184 0xa612a9ea35f292a6473e23e88856283aea8b1bc6a607147bef5c06c94e964f2f
|
|
90
|
+
#...
|
|
51
91
|
```
|
|
52
92
|
|
|
53
|
-
open
|
|
93
|
+
You can leave this terminal open to keep the devnet running, feel free to `ctrl+c` to exit the terminal and stop the local blockchain.
|
|
94
|
+
|
|
95
|
+
### Step 3: Access Pre-funded Accounts
|
|
96
|
+
|
|
97
|
+
Open another terminal and check the accounts to use:
|
|
54
98
|
|
|
55
99
|
```sh
|
|
56
100
|
offckb accounts
|
|
101
|
+
|
|
102
|
+
# result
|
|
103
|
+
Print account list, each account is funded with 42_000_000_00000000 capacity in the genesis block.
|
|
104
|
+
[
|
|
105
|
+
{
|
|
106
|
+
privkey: '0x6109170b275a09ad54877b82f7d9930f88cab5717d484fb4741ae9d1dd078cd6',
|
|
107
|
+
pubkey: '0x02025fa7b61b2365aa459807b84df065f1949d58c0ae590ff22dd2595157bffefa',
|
|
108
|
+
lockScript: {
|
|
109
|
+
codeHash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8',
|
|
110
|
+
hashType: 'type',
|
|
111
|
+
args: '0x8e42b1999f265a0078503c4acec4d5e134534297'
|
|
112
|
+
},
|
|
113
|
+
address: 'ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqvwg2cen8extgq8s5puft8vf40px3f599cytcyd8',
|
|
114
|
+
args: '0x8e42b1999f265a0078503c4acec4d5e134534297'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
privkey: '0x9f315d5a9618a39fdc487c7a67a8581d40b045bd7a42d83648ca80ef3b2cb4a1',
|
|
118
|
+
pubkey: '0x026efa0579f09cc7c1129b78544f70098c90b2ab155c10746316f945829c034a2d',
|
|
119
|
+
lockScript: {
|
|
57
120
|
```
|
|
58
121
|
|
|
59
122
|
Copy some private keys and visit http://localhost:1234 to play your first CKB Dapp!
|
|
60
123
|
|
|
61
|
-
|
|
124
|
+
## Built-in scripts
|
|
62
125
|
|
|
63
126
|
- [x] xUDT https://github.com/nervosnetwork/rfcs/pull/428
|
|
64
127
|
- commit id: 410b16c
|
|
@@ -67,21 +130,23 @@ Copy some private keys and visit http://localhost:1234 to play your first CKB Da
|
|
|
67
130
|
- [x] AnyoneCanPay https://github.com/cryptape/anyone-can-pay
|
|
68
131
|
- commit id: b845b3b
|
|
69
132
|
- [x] AlwaysSuccess https://github.com/nervosnetwork/ckb-production-scripts/blob/master/c/always_success.c
|
|
70
|
-
- commit id: 410b16c
|
|
71
|
-
- [
|
|
133
|
+
- commit id: 410b16c
|
|
134
|
+
- [x] Spore https://github.com/sporeprotocol/spore-contract
|
|
135
|
+
- version: 0.2.2-beta.1
|
|
72
136
|
|
|
73
|
-
|
|
137
|
+
## Accounts
|
|
74
138
|
|
|
75
139
|
`offckb` comes with 20 accounts, each account is funded with 42_000_000_00000000 capacity in the genesis block.
|
|
76
140
|
|
|
77
141
|
all the private keys are recorded in the `account/keys` file.
|
|
78
142
|
detail informations about each account are recorded in the `account/account.json` file.
|
|
79
143
|
|
|
144
|
+
:warning: **DO NOT SEND REAL ASSETS INTO ALL THESE ACCOUNTS, YOU CAN LOOSE YOUR MONEY** :warning:
|
|
80
145
|
|
|
81
146
|
## About Lumos
|
|
82
147
|
|
|
83
148
|
`offckb` uses [Lumos](https://github.com/ckb-js/lumos) as the CKB Dapp framework to build the template projects.
|
|
84
149
|
|
|
85
|
-
##
|
|
150
|
+
## Contributing
|
|
86
151
|
|
|
87
152
|
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"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/cfg/select.js
CHANGED
|
@@ -21,21 +21,20 @@ function selectTemplate() {
|
|
|
21
21
|
message: 'Select a dapp template',
|
|
22
22
|
choices: [
|
|
23
23
|
{
|
|
24
|
-
name: 'Transfer
|
|
24
|
+
name: 'View and Transfer Balance',
|
|
25
25
|
value: 'transfer',
|
|
26
|
-
description: 'a simple dapp to check CKB balance and transfer CKB
|
|
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
|
|
29
|
+
name: 'Issue Token via XUDT scripts',
|
|
31
30
|
value: 'xudt',
|
|
32
|
-
description: 'a simple dapp to issue your own token
|
|
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-
|
|
4
|
-
"description": "ckb development
|
|
3
|
+
"version": "0.1.0-rc6",
|
|
4
|
+
"description": "ckb development network for your first try",
|
|
5
5
|
"author": "Retric Su <retric@cryptape.com>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
package/templates/config.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"SECP256K1_BLAKE160": {
|
|
5
5
|
"CODE_HASH": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
|
|
6
6
|
"HASH_TYPE": "type",
|
|
7
|
-
"TX_HASH": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
|
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>
|
|
58
|
+
<h1>View and Transfer Balance</h1>
|
|
59
59
|
<label htmlFor="private-key">Private Key: </label>
|
|
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>
|
|
7
|
+
<title>Issue Custom Token</title>
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root"></div>
|
package/templates/xudt/index.tsx
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
2
|
import ReactDOM from 'react-dom';
|
|
3
|
-
import { Script } from '@ckb-lumos/lumos';
|
|
4
|
-
import {
|
|
3
|
+
import { Cell, Script, Transaction, utils } from '@ckb-lumos/lumos';
|
|
4
|
+
import { issueToken, queryIssuedTokenCells, transferTokenToAddress } from './lib';
|
|
5
|
+
import { capacityOf, generateAccountFromPrivateKey, readTokenAmount } from './util';
|
|
5
6
|
|
|
6
7
|
const app = document.getElementById('root');
|
|
7
8
|
ReactDOM.render(<App />, app);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
function IssuedToken() {
|
|
10
11
|
const [privKey, setPrivKey] = useState('');
|
|
11
|
-
const [
|
|
12
|
-
const [fromLock, setFromLock] = useState<Script>();
|
|
12
|
+
const [lockScript, setLockScript] = useState<Script>();
|
|
13
13
|
const [balance, setBalance] = useState('0');
|
|
14
|
-
|
|
15
|
-
const [toAddr, setToAddr] = useState('');
|
|
16
14
|
const [amount, setAmount] = useState('');
|
|
15
|
+
const [issuedTokenCell, setIssuedTokenCell] = useState<Cell>();
|
|
16
|
+
const [txHash, setTxHash] = useState<string>();
|
|
17
17
|
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
const updateFromInfo = async () => {
|
|
20
20
|
const { lockScript, address } = generateAccountFromPrivateKey(privKey);
|
|
21
21
|
const capacity = await capacityOf(address);
|
|
22
|
-
|
|
23
|
-
setFromLock(lockScript);
|
|
22
|
+
setLockScript(lockScript);
|
|
24
23
|
setBalance(capacity.toString());
|
|
25
24
|
};
|
|
26
25
|
|
|
@@ -29,27 +28,185 @@ export function App() {
|
|
|
29
28
|
}
|
|
30
29
|
}, [privKey]);
|
|
31
30
|
|
|
31
|
+
const onInputPrivKey = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
32
|
+
const priv = e.target.value;
|
|
33
|
+
const privateKeyRegex = /^0x[0-9a-fA-F]{64}$/;
|
|
34
|
+
|
|
35
|
+
const isValid = privateKeyRegex.test(priv);
|
|
36
|
+
if (isValid) {
|
|
37
|
+
setPrivKey(priv);
|
|
38
|
+
} else {
|
|
39
|
+
alert(
|
|
40
|
+
`Invalid private key: must start with 0x and 32 bytes length. Ensure you're using a valid private key from the offckb accounts list.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
const enabledIssue = +amount > 0 && +balance > 6100000000;
|
|
45
|
+
|
|
32
46
|
return (
|
|
33
|
-
|
|
47
|
+
<>
|
|
48
|
+
<h2>Issue Custom Token</h2>
|
|
49
|
+
<p></p>
|
|
34
50
|
<label htmlFor="private-key">Private Key: </label>
|
|
35
|
-
<input id="private-key" type="text" onChange={
|
|
51
|
+
<input id="private-key" type="text" onChange={onInputPrivKey} />
|
|
36
52
|
<ul>
|
|
37
|
-
<li>
|
|
53
|
+
<li>Balance(Total Capacity): {(+balance).toLocaleString()}</li>
|
|
38
54
|
<li>
|
|
39
|
-
|
|
40
|
-
<pre>{JSON.stringify(
|
|
55
|
+
Lock Script:
|
|
56
|
+
<pre>{JSON.stringify(lockScript, null, 2)}</pre>
|
|
41
57
|
</li>
|
|
42
|
-
|
|
43
|
-
<li>Total capacity: {balance}</li>
|
|
58
|
+
<li>Lock Script Hash: {lockScript && utils.computeScriptHash(lockScript)}</li>
|
|
44
59
|
</ul>
|
|
45
|
-
<label htmlFor="to-address">Transfer to Address: </label>
|
|
46
|
-
<input id="to-address" type="text" onChange={(e) => setToAddr(e.target.value)} />
|
|
47
60
|
<br />
|
|
48
|
-
<label htmlFor="amount">Amount</label>
|
|
61
|
+
<label htmlFor="amount">Token Amount: </label>
|
|
49
62
|
|
|
50
|
-
<input id="amount" type="
|
|
63
|
+
<input id="amount" type="number" onChange={(e) => setAmount(e.target.value)} />
|
|
51
64
|
<br />
|
|
52
|
-
<button
|
|
65
|
+
<button
|
|
66
|
+
disabled={!enabledIssue}
|
|
67
|
+
onClick={() =>
|
|
68
|
+
issueToken(privKey, amount)
|
|
69
|
+
.then((result) => {
|
|
70
|
+
setIssuedTokenCell(result.targetOutput);
|
|
71
|
+
setTxHash(result.hash);
|
|
72
|
+
})
|
|
73
|
+
.catch(alert)
|
|
74
|
+
}
|
|
75
|
+
>
|
|
76
|
+
Issue Token
|
|
77
|
+
</button>
|
|
78
|
+
<div>
|
|
79
|
+
{txHash && issuedTokenCell && (
|
|
80
|
+
<>
|
|
81
|
+
<h4>Result</h4>
|
|
82
|
+
<li>Transaction hash: {txHash}</li>
|
|
83
|
+
<li>
|
|
84
|
+
Token xUDT args: {issuedTokenCell.cellOutput.type.args}{' '}
|
|
85
|
+
<strong>
|
|
86
|
+
{
|
|
87
|
+
'(Noticed that the xUDT args works like the unique id for your issued token, Think of it like an ERC20 contract address)'
|
|
88
|
+
}
|
|
89
|
+
</strong>
|
|
90
|
+
</li>
|
|
91
|
+
<li>
|
|
92
|
+
Token cell: <pre>{JSON.stringify(issuedTokenCell, null, 2)}</pre>
|
|
93
|
+
</li>
|
|
94
|
+
</>
|
|
95
|
+
)}
|
|
96
|
+
</div>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function ViewIssuedToken() {
|
|
102
|
+
const [xudtArgs, setXudtArgs] = useState<string>();
|
|
103
|
+
const [cells, setCells] = useState<Cell[]>([]);
|
|
104
|
+
return (
|
|
105
|
+
<>
|
|
106
|
+
<h2>View Custom Token</h2>
|
|
107
|
+
<div>
|
|
108
|
+
<label htmlFor="xudt-args">xDUT args: </label>
|
|
109
|
+
<input id="xudt-args" type="text" onChange={(e) => setXudtArgs(e.target.value)} />
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<button disabled={!xudtArgs} onClick={() => queryIssuedTokenCells(xudtArgs).then(setCells).catch(alert)}>
|
|
113
|
+
Query Issued Token
|
|
114
|
+
</button>
|
|
115
|
+
{cells.length > 0 && <h3>Result: all the cells which hosted this issued token</h3>}
|
|
116
|
+
{cells.map((cell, index) => (
|
|
117
|
+
<div key={index}>
|
|
118
|
+
<p>Cell #{index}</p>
|
|
119
|
+
<li>Token amount: {readTokenAmount(cell.data).toNumber()}</li>
|
|
120
|
+
<li>Token xUDT args: {cell.cellOutput.type.args}</li>
|
|
121
|
+
<li>Token holder's lock script args: {cell.cellOutput.lock.args}</li>
|
|
122
|
+
<hr />
|
|
123
|
+
</div>
|
|
124
|
+
))}
|
|
125
|
+
</>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function TransferIssuedToken() {
|
|
130
|
+
const [udtArgs, setUdtArgs] = useState<string>('');
|
|
131
|
+
const [senderPrivkey, setSenderPrivkey] = useState<string>('');
|
|
132
|
+
const [transferAmount, setTransferAmount] = useState('');
|
|
133
|
+
const [receiverAddress, setReceiverAddress] = useState('');
|
|
134
|
+
const [tx, setTx] = useState<Transaction>();
|
|
135
|
+
|
|
136
|
+
const onInputSenderPrivKey = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
137
|
+
const priv = e.target.value;
|
|
138
|
+
const privateKeyRegex = /^0x[0-9a-fA-F]{64}$/;
|
|
139
|
+
const isValid = privateKeyRegex.test(priv);
|
|
140
|
+
if (isValid) {
|
|
141
|
+
setSenderPrivkey(priv);
|
|
142
|
+
} else {
|
|
143
|
+
alert(
|
|
144
|
+
`Invalid private key: must start with 0x and 32 bytes length. Ensure you're using a valid private key from the offckb accounts list.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const enabledCheck =
|
|
150
|
+
senderPrivkey.length > 0 && udtArgs.length > 0 && transferAmount.length > 0 && receiverAddress.length > 0;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<>
|
|
154
|
+
<h2>Transfer Custom Token</h2>
|
|
155
|
+
<label htmlFor="sender-private-key">Private Key: </label>
|
|
156
|
+
<input id="sender-private-key" type="text" onChange={onInputSenderPrivKey} />
|
|
157
|
+
<br />
|
|
158
|
+
<label htmlFor="udt">xUDT args: </label>
|
|
159
|
+
|
|
160
|
+
<input id="udt" type="text" onChange={(e) => setUdtArgs(e.target.value)} />
|
|
161
|
+
<br />
|
|
162
|
+
<label htmlFor="transferAmount">Transfer Token Amount: </label>
|
|
163
|
+
|
|
164
|
+
<input id="transferAmount" type="number" onChange={(e) => setTransferAmount(e.target.value)} />
|
|
165
|
+
<br />
|
|
166
|
+
<label htmlFor="receiverAddress">Receiver Address: </label>
|
|
167
|
+
|
|
168
|
+
<input id="receiverAddress" type="text" onChange={(e) => setReceiverAddress(e.target.value)} />
|
|
169
|
+
<br />
|
|
170
|
+
<button
|
|
171
|
+
disabled={!enabledCheck}
|
|
172
|
+
onClick={() =>
|
|
173
|
+
transferTokenToAddress(udtArgs, senderPrivkey, transferAmount, receiverAddress)
|
|
174
|
+
.then((res) => setTx(res.tx))
|
|
175
|
+
.catch(alert)
|
|
176
|
+
}
|
|
177
|
+
>
|
|
178
|
+
Transfer Custom Token
|
|
179
|
+
</button>
|
|
180
|
+
<div>
|
|
181
|
+
{tx && (
|
|
182
|
+
<>
|
|
183
|
+
<h4>Result</h4>
|
|
184
|
+
<li>
|
|
185
|
+
Transaction: <pre>{JSON.stringify(tx, null, 2)}</pre>
|
|
186
|
+
</li>
|
|
187
|
+
</>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
</>
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function App() {
|
|
195
|
+
return (
|
|
196
|
+
<div>
|
|
197
|
+
<h1>
|
|
198
|
+
xUDT Scripts Dapp Example
|
|
199
|
+
<small>
|
|
200
|
+
<a href="https://github.com/XuJiandong/rfcs/blob/xudt/rfcs/0052-extensible-udt/0052-extensible-udt.md#xudt-witness">
|
|
201
|
+
{'(see xUDT specs)'}
|
|
202
|
+
</a>
|
|
203
|
+
</small>
|
|
204
|
+
</h1>
|
|
205
|
+
<IssuedToken />
|
|
206
|
+
<hr />
|
|
207
|
+
<ViewIssuedToken />
|
|
208
|
+
<hr />
|
|
209
|
+
<TransferIssuedToken />
|
|
53
210
|
</div>
|
|
54
211
|
);
|
|
55
212
|
}
|
package/templates/xudt/lib.ts
CHANGED
|
@@ -1,73 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { values, blockchain } from '@ckb-lumos/base';
|
|
1
|
+
import { helpers, hd, config, Cell, commons, BI, utils } from '@ckb-lumos/lumos';
|
|
2
|
+
import { blockchain, HexString } from '@ckb-lumos/base';
|
|
4
3
|
import { indexer, lumosConfig, rpc } from './ckb';
|
|
5
|
-
|
|
4
|
+
import { bytes, number } from '@ckb-lumos/codec';
|
|
5
|
+
import { xudtWitnessType } from './scheme';
|
|
6
|
+
import { addCellDep, generateAccountFromPrivateKey } from './util';
|
|
6
7
|
|
|
7
8
|
config.initializeConfig(lumosConfig);
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
lockScript
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
codeHash: template.CODE_HASH,
|
|
20
|
-
hashType: template.HASH_TYPE,
|
|
21
|
-
args: args,
|
|
22
|
-
};
|
|
23
|
-
const address = helpers.encodeToAddress(lockScript, { config: lumosConfig });
|
|
24
|
-
return {
|
|
25
|
-
lockScript,
|
|
26
|
-
address,
|
|
27
|
-
pubKey,
|
|
10
|
+
export async function issueToken(privKey: string, amount: string) {
|
|
11
|
+
const { lockScript } = generateAccountFromPrivateKey(privKey);
|
|
12
|
+
const xudtDeps = lumosConfig.SCRIPTS.XUDT;
|
|
13
|
+
const lockDeps = lumosConfig.SCRIPTS.SECP256K1_BLAKE160;
|
|
14
|
+
|
|
15
|
+
const xudtArgs = utils.computeScriptHash(lockScript) + '00000000';
|
|
16
|
+
const typeScript = {
|
|
17
|
+
codeHash: xudtDeps.CODE_HASH,
|
|
18
|
+
hashType: xudtDeps.HASH_TYPE,
|
|
19
|
+
args: xudtArgs,
|
|
28
20
|
};
|
|
29
|
-
};
|
|
30
21
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
let txSkeleton = helpers.TransactionSkeleton();
|
|
23
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
24
|
+
outPoint: {
|
|
25
|
+
txHash: lockDeps.TX_HASH,
|
|
26
|
+
index: lockDeps.INDEX,
|
|
27
|
+
},
|
|
28
|
+
depType: lockDeps.DEP_TYPE,
|
|
34
29
|
});
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return balance;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
interface Options {
|
|
45
|
-
from: string;
|
|
46
|
-
to: string;
|
|
47
|
-
amount: string;
|
|
48
|
-
privKey: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export async function transfer(options: Options): Promise<string> {
|
|
52
|
-
let txSkeleton = helpers.TransactionSkeleton({});
|
|
53
|
-
const fromScript = helpers.parseAddress(options.from, {
|
|
54
|
-
config: lumosConfig,
|
|
30
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
31
|
+
outPoint: {
|
|
32
|
+
txHash: xudtDeps.TX_HASH,
|
|
33
|
+
index: xudtDeps.INDEX,
|
|
34
|
+
},
|
|
35
|
+
depType: xudtDeps.DEP_TYPE,
|
|
55
36
|
});
|
|
56
|
-
const toScript = helpers.parseAddress(options.to, { config: lumosConfig });
|
|
57
37
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
38
|
+
const targetOutput: Cell = {
|
|
39
|
+
cellOutput: {
|
|
40
|
+
capacity: '0x0',
|
|
41
|
+
lock: lockScript,
|
|
42
|
+
type: typeScript,
|
|
43
|
+
},
|
|
44
|
+
data: bytes.hexify(number.Uint128LE.pack(amount)),
|
|
45
|
+
};
|
|
63
46
|
|
|
64
47
|
// additional 0.001 ckb for tx fee
|
|
65
48
|
// the tx fee could calculated by tx size
|
|
66
49
|
// this is just a simple example
|
|
67
|
-
const
|
|
50
|
+
const capacity = helpers.minimalCellCapacity(targetOutput);
|
|
51
|
+
targetOutput.cellOutput.capacity = '0x' + capacity.toString(16);
|
|
52
|
+
const neededCapacity = BI.from(capacity.toString(10)).add(100000);
|
|
68
53
|
let collectedSum = BI.from(0);
|
|
69
54
|
const collected: Cell[] = [];
|
|
70
|
-
const collector = indexer.collector({ lock:
|
|
55
|
+
const collector = indexer.collector({ lock: lockScript, type: 'empty' });
|
|
71
56
|
for await (const cell of collector.collect()) {
|
|
72
57
|
collectedSum = collectedSum.add(cell.cellOutput.capacity);
|
|
73
58
|
collected.push(cell);
|
|
@@ -78,76 +63,190 @@ export async function transfer(options: Options): Promise<string> {
|
|
|
78
63
|
throw new Error(`Not enough CKB, ${collectedSum} < ${neededCapacity}`);
|
|
79
64
|
}
|
|
80
65
|
|
|
81
|
-
const
|
|
66
|
+
const changeOutput: Cell = {
|
|
82
67
|
cellOutput: {
|
|
83
|
-
capacity:
|
|
84
|
-
lock:
|
|
68
|
+
capacity: collectedSum.sub(neededCapacity).toHexString(),
|
|
69
|
+
lock: lockScript,
|
|
85
70
|
},
|
|
86
71
|
data: '0x',
|
|
87
72
|
};
|
|
88
73
|
|
|
74
|
+
txSkeleton = txSkeleton.update('inputs', (inputs) => inputs.push(...collected));
|
|
75
|
+
txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(targetOutput, changeOutput));
|
|
76
|
+
/* 65-byte zeros in hex */
|
|
77
|
+
const lockWitness =
|
|
78
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
|
|
79
|
+
const outputTypeWitness = xudtWitnessType.pack({ extension_data: [] });
|
|
80
|
+
const witnessArgs = blockchain.WitnessArgs.pack({ lock: lockWitness, outputType: outputTypeWitness });
|
|
81
|
+
const witness = bytes.hexify(witnessArgs);
|
|
82
|
+
txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(0, witness));
|
|
83
|
+
|
|
84
|
+
// signing
|
|
85
|
+
txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
|
|
86
|
+
const message = txSkeleton.get('signingEntries').get(0)?.message;
|
|
87
|
+
const Sig = hd.key.signRecoverable(message!, privKey);
|
|
88
|
+
const tx = helpers.sealTransaction(txSkeleton, [Sig]);
|
|
89
|
+
console.log(tx);
|
|
90
|
+
|
|
91
|
+
const hash = await rpc.sendTransaction(tx, 'passthrough');
|
|
92
|
+
console.log('The transaction hash is', hash);
|
|
93
|
+
return { hash, targetOutput };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function queryIssuedTokenCells(xudtArgs: HexString) {
|
|
97
|
+
const xudtDeps = lumosConfig.SCRIPTS.XUDT;
|
|
98
|
+
const typeScript = {
|
|
99
|
+
codeHash: xudtDeps.CODE_HASH,
|
|
100
|
+
hashType: xudtDeps.HASH_TYPE,
|
|
101
|
+
args: xudtArgs,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const collected: Cell[] = [];
|
|
105
|
+
const collector = indexer.collector({ type: typeScript });
|
|
106
|
+
for await (const cell of collector.collect()) {
|
|
107
|
+
collected.push(cell);
|
|
108
|
+
}
|
|
109
|
+
return collected;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function transferTokenToAddress(
|
|
113
|
+
udtIssuerArgs: string,
|
|
114
|
+
senderPrivKey: string,
|
|
115
|
+
amount: string,
|
|
116
|
+
receiverAddress: string,
|
|
117
|
+
) {
|
|
118
|
+
const { lockScript: senderLockScript } = generateAccountFromPrivateKey(senderPrivKey);
|
|
119
|
+
|
|
120
|
+
const receiverLockScript = helpers.parseAddress(receiverAddress);
|
|
121
|
+
|
|
122
|
+
const xudtDeps = lumosConfig.SCRIPTS.XUDT;
|
|
123
|
+
const lockDeps = lumosConfig.SCRIPTS.SECP256K1_BLAKE160;
|
|
124
|
+
|
|
125
|
+
const xudtArgs = udtIssuerArgs;
|
|
126
|
+
const typeScript = {
|
|
127
|
+
codeHash: xudtDeps.CODE_HASH,
|
|
128
|
+
hashType: xudtDeps.HASH_TYPE,
|
|
129
|
+
args: xudtArgs,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
let txSkeleton = helpers.TransactionSkeleton();
|
|
133
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
134
|
+
outPoint: {
|
|
135
|
+
txHash: lockDeps.TX_HASH,
|
|
136
|
+
index: lockDeps.INDEX,
|
|
137
|
+
},
|
|
138
|
+
depType: lockDeps.DEP_TYPE,
|
|
139
|
+
});
|
|
140
|
+
txSkeleton = addCellDep(txSkeleton, {
|
|
141
|
+
outPoint: {
|
|
142
|
+
txHash: xudtDeps.TX_HASH,
|
|
143
|
+
index: xudtDeps.INDEX,
|
|
144
|
+
},
|
|
145
|
+
depType: xudtDeps.DEP_TYPE,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const targetOutput: Cell = {
|
|
149
|
+
cellOutput: {
|
|
150
|
+
capacity: '0x0',
|
|
151
|
+
lock: receiverLockScript,
|
|
152
|
+
type: typeScript,
|
|
153
|
+
},
|
|
154
|
+
data: bytes.hexify(number.Uint128LE.pack(amount)),
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const capacity = helpers.minimalCellCapacity(targetOutput);
|
|
158
|
+
targetOutput.cellOutput.capacity = '0x' + capacity.toString(16);
|
|
159
|
+
// additional 0.001 ckb for tx fee
|
|
160
|
+
// the tx fee could calculated by tx size
|
|
161
|
+
// this is just a simple example
|
|
162
|
+
const neededCapacity = BI.from(capacity.toString(10)).add(100000);
|
|
163
|
+
let collectedSum = BI.from(0);
|
|
164
|
+
let collectedAmount = BI.from(0);
|
|
165
|
+
const collected: Cell[] = [];
|
|
166
|
+
const collector = indexer.collector({ lock: senderLockScript, type: typeScript });
|
|
167
|
+
for await (const cell of collector.collect()) {
|
|
168
|
+
collectedSum = collectedSum.add(cell.cellOutput.capacity);
|
|
169
|
+
collectedAmount = collectedAmount.add(number.Uint128LE.unpack(cell.data));
|
|
170
|
+
collected.push(cell);
|
|
171
|
+
if (collectedAmount >= BI.from(amount)) break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let changeOutputTokenAmount = BI.from(0);
|
|
175
|
+
if (collectedAmount.gt(BI.from(amount))) {
|
|
176
|
+
changeOutputTokenAmount = collectedAmount.sub(BI.from(amount));
|
|
177
|
+
}
|
|
178
|
+
|
|
89
179
|
const changeOutput: Cell = {
|
|
90
180
|
cellOutput: {
|
|
91
|
-
capacity:
|
|
92
|
-
lock:
|
|
181
|
+
capacity: '0x0',
|
|
182
|
+
lock: senderLockScript,
|
|
183
|
+
type: typeScript,
|
|
93
184
|
},
|
|
94
|
-
data:
|
|
185
|
+
data: bytes.hexify(number.Uint128LE.pack(changeOutputTokenAmount.toString(10))),
|
|
95
186
|
};
|
|
96
187
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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'));
|
|
188
|
+
const changeOutputNeededCapacity = BI.from(helpers.minimalCellCapacity(changeOutput));
|
|
189
|
+
|
|
190
|
+
const extraNeededCapacity = collectedSum.lt(neededCapacity)
|
|
191
|
+
? neededCapacity.sub(collectedSum).add(changeOutputNeededCapacity)
|
|
192
|
+
: collectedSum.sub(neededCapacity).add(changeOutputNeededCapacity);
|
|
193
|
+
|
|
194
|
+
if (extraNeededCapacity.gt(0)) {
|
|
195
|
+
let extraCollectedSum = BI.from(0);
|
|
196
|
+
const extraCollectedCells: Cell[] = [];
|
|
197
|
+
const collector = indexer.collector({ lock: senderLockScript, type: 'empty' });
|
|
198
|
+
for await (const cell of collector.collect()) {
|
|
199
|
+
extraCollectedSum = extraCollectedSum.add(cell.cellOutput.capacity);
|
|
200
|
+
extraCollectedCells.push(cell);
|
|
201
|
+
if (extraCollectedSum >= extraNeededCapacity) break;
|
|
119
202
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
203
|
+
|
|
204
|
+
if (extraCollectedSum.lt(extraNeededCapacity)) {
|
|
205
|
+
throw new Error(`Not enough CKB for change, ${extraCollectedSum} < ${extraNeededCapacity}`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
txSkeleton = txSkeleton.update('inputs', (inputs) => inputs.push(...extraCollectedCells));
|
|
209
|
+
|
|
210
|
+
const change2Capacity = extraCollectedSum.sub(changeOutputNeededCapacity);
|
|
211
|
+
if (change2Capacity.gt(61000000000)) {
|
|
212
|
+
changeOutput.cellOutput.capacity = changeOutputNeededCapacity.toHexString();
|
|
213
|
+
const changeOutput2: Cell = {
|
|
214
|
+
cellOutput: {
|
|
215
|
+
capacity: change2Capacity.toHexString(),
|
|
216
|
+
lock: senderLockScript,
|
|
217
|
+
},
|
|
218
|
+
data: '0x',
|
|
219
|
+
};
|
|
220
|
+
txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(changeOutput2));
|
|
221
|
+
} else {
|
|
222
|
+
changeOutput.cellOutput.capacity = extraCollectedSum.toHexString();
|
|
139
223
|
}
|
|
140
|
-
witness = bytes.hexify(blockchain.WitnessArgs.pack(newWitnessArgs));
|
|
141
|
-
txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(firstIndex, witness));
|
|
142
224
|
}
|
|
143
225
|
|
|
226
|
+
txSkeleton = txSkeleton.update('inputs', (inputs) => inputs.push(...collected));
|
|
227
|
+
txSkeleton = txSkeleton.update('outputs', (outputs) => outputs.push(targetOutput, changeOutput));
|
|
228
|
+
/* 65-byte zeros in hex */
|
|
229
|
+
const lockWitness =
|
|
230
|
+
'0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000';
|
|
231
|
+
|
|
232
|
+
const inputTypeWitness = xudtWitnessType.pack({ extension_data: [] });
|
|
233
|
+
const outputTypeWitness = xudtWitnessType.pack({ extension_data: [] });
|
|
234
|
+
const witnessArgs = blockchain.WitnessArgs.pack({
|
|
235
|
+
lock: lockWitness,
|
|
236
|
+
inputType: inputTypeWitness,
|
|
237
|
+
outputType: outputTypeWitness,
|
|
238
|
+
});
|
|
239
|
+
const witness = bytes.hexify(witnessArgs);
|
|
240
|
+
txSkeleton = txSkeleton.update('witnesses', (witnesses) => witnesses.set(0, witness));
|
|
241
|
+
|
|
242
|
+
// signing
|
|
144
243
|
txSkeleton = commons.common.prepareSigningEntries(txSkeleton);
|
|
145
244
|
const message = txSkeleton.get('signingEntries').get(0)?.message;
|
|
146
|
-
const Sig = hd.key.signRecoverable(message!,
|
|
245
|
+
const Sig = hd.key.signRecoverable(message!, senderPrivKey);
|
|
147
246
|
const tx = helpers.sealTransaction(txSkeleton, [Sig]);
|
|
148
|
-
|
|
149
|
-
console.log('The transaction hash is', hash);
|
|
150
|
-
alert(`The transaction hash is ${hash}`);
|
|
247
|
+
console.log('tx: ', tx);
|
|
151
248
|
|
|
152
|
-
|
|
249
|
+
const txHash = await rpc.sendTransaction(tx, 'passthrough');
|
|
250
|
+
console.log('The transaction hash is', txHash);
|
|
251
|
+
return { txHash, tx };
|
|
153
252
|
}
|
|
@@ -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
|
+
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { helpers, Address, Script, hd, BI, CellDep } from '@ckb-lumos/lumos';
|
|
2
|
+
import { values } from '@ckb-lumos/base';
|
|
3
|
+
import { indexer, lumosConfig } from './ckb';
|
|
4
|
+
import { TransactionSkeletonType } from '@ckb-lumos/helpers';
|
|
5
|
+
import { number } from '@ckb-lumos/codec';
|
|
6
|
+
|
|
7
|
+
type Account = {
|
|
8
|
+
lockScript: Script;
|
|
9
|
+
address: Address;
|
|
10
|
+
pubKey: string;
|
|
11
|
+
};
|
|
12
|
+
export const generateAccountFromPrivateKey = (privKey: string): Account => {
|
|
13
|
+
const pubKey = hd.key.privateToPublic(privKey);
|
|
14
|
+
const args = hd.key.publicKeyToBlake160(pubKey);
|
|
15
|
+
const template = lumosConfig.SCRIPTS['SECP256K1_BLAKE160']!;
|
|
16
|
+
const lockScript = {
|
|
17
|
+
codeHash: template.CODE_HASH,
|
|
18
|
+
hashType: template.HASH_TYPE,
|
|
19
|
+
args: args,
|
|
20
|
+
};
|
|
21
|
+
const address = helpers.encodeToAddress(lockScript, { config: lumosConfig });
|
|
22
|
+
return {
|
|
23
|
+
lockScript,
|
|
24
|
+
address,
|
|
25
|
+
pubKey,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export async function capacityOf(address: string): Promise<BI> {
|
|
30
|
+
const collector = indexer.collector({
|
|
31
|
+
lock: helpers.parseAddress(address, { config: lumosConfig }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
let balance = BI.from(0);
|
|
35
|
+
for await (const cell of collector.collect()) {
|
|
36
|
+
balance = balance.add(cell.cellOutput.capacity);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return balance;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function addCellDep(txSkeleton: TransactionSkeletonType, newCellDep: CellDep): TransactionSkeletonType {
|
|
43
|
+
const cellDep = txSkeleton.get('cellDeps').find((cellDep) => {
|
|
44
|
+
return (
|
|
45
|
+
cellDep.depType === newCellDep.depType &&
|
|
46
|
+
new values.OutPointValue(cellDep.outPoint, { validate: false }).equals(
|
|
47
|
+
new values.OutPointValue(newCellDep.outPoint, { validate: false }),
|
|
48
|
+
)
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (!cellDep) {
|
|
53
|
+
txSkeleton = txSkeleton.update('cellDeps', (cellDeps) => {
|
|
54
|
+
return cellDeps.push({
|
|
55
|
+
outPoint: newCellDep.outPoint,
|
|
56
|
+
depType: newCellDep.depType,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return txSkeleton;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readTokenAmount(amount: string) {
|
|
65
|
+
return number.Uint128LE.unpack(amount);
|
|
66
|
+
}
|