├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── ckb-js-toolkit.esm.js ├── ckb-js-toolkit.node.js ├── ckb-js-toolkit.node.js.map └── ckb-js-toolkit.umd.js ├── images └── toolkit.svg ├── index.d.ts ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── cell_collectors │ ├── index.js │ └── rpc_collector.js ├── index.js ├── normalizers.js ├── reader.js ├── rpc.js ├── transaction_dumper.js ├── transformers.js └── validators.js ├── testfiles └── blockchain.umd.js └── tests ├── serializers.js ├── transformers.js └── validators.js /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ develop ] 9 | pull_request: 10 | branches: [ develop ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [18.x, 20.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm run dist 29 | - run: npm run test 30 | - run: npm run fmt 31 | - run: git diff --exit-code 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Xuejie Xiao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ckb-js-toolkit 2 | ============== 3 | 4 | JavaScript toolkit for Nervos CKB. It contains a series of independent tools that can aid develoment of CKB dapps. This is different from a normal CKB SDK, since it tries to minimize the work done in an SDK, while providing more utlities that could be handy. 5 | 6 | # Design Goals 7 | 8 | The toolkit here is built with specific design goals, if some of the design feels quirky to you, you might want to revisit this section to see if there is some tradeoff involved. 9 | 10 | ## Version Stability 11 | 12 | One design goal here is the stability of the toolkit, meaning the toolkit version you use can stay unchanged unless one of the following conditions is met: 13 | 14 | * A security vulnerability occurs 15 | * A fork happens in CKB 16 | 17 | ## Code Compatibility 18 | 19 | In the meantime, we also want to ensure code compatibility: we might upgrade the toolkit from time to time with new features. But when you are upgrading the toolkit version, you shouldn't expect any code breaks. Sometimes this might be infeasible, such as when security bugs or forks happen, but for the vast majority of the changes, we do want to ensure code compatibility at all costs. 20 | 21 | And before you ask, personally I don't trust in [Semantic Versioning](https://semver.org/). I do want to ensure code compatibility with all my efforts regardless of the specific part changed in the version number. 22 | 23 | ## Runtime type checking 24 | 25 | This might be a controversial one: with the whole JavaScript world moving into static typed languages such as TypeScript, ReasonML, etc. I'm building the toolkit here with plain JavaScript doing runtime type checking. This is because none of existing static typed languages in the JavaScript world provide decent type checking stories when you are calling the code from JavaScript. I've seen too many times that a piece of code runs `JSON.parse` on some user input data, then cast the resulting object directly to a TypeScript object, ignoring all the interface checks. If all you use in TypeScript in your project, that won't be a problem, but for a CKB Toolkit built for JavaScript, not TypeScript, nor ReasonML. I want to consider the case that some people would want to use JavaScript directly to call the code in the toolkit. That's why I'm opting for pure JavaScript in this project with runtime checking code. Speed is not much of a concern here, security is a huge concern. 26 | 27 | In the future we might certainly provide TypeScript typing files(and maybe also ReasonML definition files), but that is just a different metric. Runtime type checking is still a critical aspect in this project. 28 | 29 | # Table Of Contents 30 | 31 | * [RPC](#rpc) 32 | * [Reader](#reader) 33 | * [Utility Functions](#utility-functions) 34 | + [Validators](#validators) 35 | + [Transformers](#transformers) 36 | + [Normalizers](#normalizers) 37 | * [Cell Collectors](#cell-collectors) 38 | 39 | # RPC 40 | 41 | RPC class provides a way to make RPC calls directly to CKB. It works by utilizing [Proxy object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) so the toolkit can cope with new RPCs naturally in CKB. This is an example of `Code Compatibility` design goal. 42 | 43 | ``` 44 | node --experimental-repl-await 45 | Welcome to Node.js v13.9.0. 46 | Type ".help" for more information. 47 | > const toolkit = require("ckb-js-toolkit") 48 | > const rpc = new toolkit.RPC("http://127.0.0.1:9115/rpc") 49 | > await rpc.get_blockchain_info() 50 | { 51 | alerts: [], 52 | chain: 'ckb', 53 | difficulty: '0x5bb23548f6795', 54 | epoch: '0x708047900028b', 55 | is_initial_block_download: true, 56 | median_time: '0x170aee25ea5' 57 | } 58 | > await rpc.get_tip_header() 59 | { 60 | compact_target: '0x1a2cab56', 61 | dao: '0x1a7930d4812eeb308acafdfe3cf1230088a770976aef78000032f12fbd5f0107', 62 | epoch: '0x708047900028b', 63 | hash: '0x1d0c693d8a78c9e2294ac7304934c635d7b65274fcdf46d5ce3d13ed66768cfd', 64 | nonce: '0xe2b8ce400000000000000194cf350200', 65 | number: '0xfcaf2', 66 | parent_hash: '0x9cf27f050122efb35c362d105d980062d8ea29a8e0f86ec6a4ea06178a5a0381', 67 | proposals_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', 68 | timestamp: '0x170aee486ee', 69 | transactions_root: '0x16c74b3272430f1a89bb2fca39a571bf7a1bffecb314de528b6cbe9ba0f5d280', 70 | uncles_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', 71 | version: '0x0' 72 | } 73 | > await rpc.get_header_by_number("0x12") 74 | { 75 | compact_target: '0x1a08a97e', 76 | dao: '0x18a6312eb520a12e8e552ee0f286230026c8799e8500000000bc29f1c9fefe06', 77 | epoch: '0x6cf0012000000', 78 | hash: '0xbc00f447149004a38a8f7347dcd0a2050a2f10f4b926325d797c0f3e0d10f99b', 79 | nonce: '0x93317ca10000055400000000ba7e0100', 80 | number: '0x12', 81 | parent_hash: '0x4a191e12132587e852ddc30c73008426da6322a7def06ee153682afdceab16e7', 82 | proposals_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', 83 | timestamp: '0x16e71002eff', 84 | transactions_root: '0xcc05a6b1fc20464d4f07a40611f2cab00618ff66cc271ccee9a9fe5b4ea96a45', 85 | uncles_hash: '0x0000000000000000000000000000000000000000000000000000000000000000', 86 | version: '0x0' 87 | } 88 | ``` 89 | 90 | Please refer to [CKB's RPC documentation](https://github.com/nervosnetwork/ckb/tree/develop/rpc) for more details on different RPCs. All values accepted by the RPC module has to be JSON ready values(such as hex strings or objects) following CKB's JSONRPC formatting rules. See [Validators](#validators) and [Transformers](#transformers) sections below on how the toolkit can aid the conversion work. 91 | 92 | # Reader 93 | 94 | Reader class serves a unique purpose: depending on sources of data, we might get values in different formats: 95 | 96 | * Hex string might be provided in CKB RPC responses 97 | * ArrayBuffer might be provided by CKB syscalls 98 | * Raw string might also be used for coding convenience 99 | 100 | One big question, is how we can manage all those different data formats? How can we ensure we can convert them to the correct hex format beforing sending them to CKB's RPC? Reader class serves this purpose: 101 | 102 | ``` 103 | node --experimental-repl-await 104 | Welcome to Node.js v13.9.0. 105 | Type ".help" for more information. 106 | > const toolkit = require("ckb-js-toolkit"); 107 | undefined 108 | > const { Reader } = toolkit; 109 | undefined 110 | > const reader1 = new Reader("0x31323334") 111 | undefined 112 | > const arraybuffer = new ArrayBuffer(4) 113 | undefined 114 | > const view = new DataView(arraybuffer) 115 | undefined 116 | > view.setUint8(0, 0x31) 117 | undefined 118 | > view.setUint8(1, 0x32) 119 | undefined 120 | > view.setUint8(2, 0x33) 121 | undefined 122 | > view.setUint8(3, 0x34) 123 | undefined 124 | > const reader2 = new Reader(arraybuffer) 125 | undefined 126 | > const reader3 = Reader.fromRawString("1234") 127 | undefined 128 | > reader1.serializeJson() 129 | '0x31323334' 130 | > reader2.serializeJson() 131 | '0x31323334' 132 | > reader3.serializeJson() 133 | '0x31323334' 134 | ``` 135 | 136 | Here we are setting `reader1`, `reader2` and `reader3` using differnet methods, the result here stays the same no matter what format the source data uses. Later in the [Transformers](#transformers) section we will see how we can combine Reader with transformers for a unified coding experience. 137 | 138 | # Utility Functions 139 | 140 | The toolkit doesn't provide pre-defined models on common constructs. Instead, it is defined to let you have your own model, and work with it. For example, typical Web project probably has models defined from an ORM over a SQL database. I don't want you to have both a cell data structure defined in your ORM, and in the toolkit. We wanta to take a different path here: you only need to define the data structure once, whether it's defined from an ORM, or defined as an ECMAScript class with methods customed to your project. All you need to do, is to follow rules defined by transformers, you will be able to use the utility functions defined here. 141 | 142 | The diagram below illustrates the relations between the utility functions: 143 | 144 | ![Utility Function Relations](images/toolkit.svg) 145 | 146 | `Normalizers`, `Transformers` and `Validators` are included in this repository. `Denormalizers` are kept in the more experimental [ckb-js-toolkit-contrib](https://github.com/xxuejie/ckb-js-toolkit-contrib) project for now, and might be moved here once it is stablized. `Serializers` and `Deserializers` refer to JavaScript source generated by the [moleculec-es](https://github.com/xxuejie/moleculec-es) project. In most cases, you don't have to use `moleculec-es` directly, a ready-to-use file has been put in [ckb-js-toolkit-contrib](https://github.com/xxuejie/ckb-js-toolkit-contrib/blob/master/src/blockchain.js). 147 | 148 | There is one exception here: `Deserializers` are not functions actually, they are [classes](https://github.com/xxuejie/ckb-js-toolkit-contrib/blob/747db7616116a3d5511f633f71b0415b53e83060/src/blockchain.js#L691) that is constructed from ArrayBuffer to better leverage molecule's zero copy design. 149 | 150 | You might also notice there is no conversions from JSON Ready Object to objects in your own data structures. This part will have to be implemented by yourself if needed. 151 | 152 | ## Validators 153 | 154 | ### Overview 155 | 156 | Validator is an example of the `Runtime type checking` design goal, it provides a series of functions are provided here to validate that a JSON object follows required format for a CKB data structure, such as script, outpoint, transaction, block, etc. So when you have prepared the values required by the RPC class, you can pass the value through the validators here, to ensure they are of the correct format. 157 | 158 | First, let's look at one example: 159 | 160 | ``` 161 | node --experimental-repl-await 162 | Welcome to Node.js v13.9.0. 163 | Type ".help" for more information. 164 | > const toolkit = require("ckb-js-toolkit") 165 | > const rpc = new toolkit.RPC("http://127.0.0.1:9115/rpc") 166 | > const transaction = (await rpc.get_transaction("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c")).transaction 167 | > toolkit.validators.ValidateTransaction(transaction) 168 | Uncaught: 169 | Error: transaction does not have correct keys! Required keys: [cell_deps, header_deps, inputs, outputs, outputs_data, version, witnesses], optional keys: [], actual keys: [cell_deps, hash, header_deps, inputs, outputs, outputs_data, version, witnesses] 170 | > delete transaction.hash 171 | > transaction 172 | { 173 | cell_deps: [ 174 | { dep_type: 'code', out_point: [Object] }, 175 | { dep_type: 'code', out_point: [Object] } 176 | ], 177 | header_deps: [], 178 | inputs: [ { previous_output: [Object], since: '0x0' } ], 179 | outputs: [ 180 | { capacity: '0x2b95fd500', lock: [Object], type: null }, 181 | { capacity: '0x2b95fd500', lock: [Object], type: null } 182 | ], 183 | outputs_data: [ 184 | '0x02000000e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c03000000e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c01000000', 185 | '0x02000000e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c03000000e2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c04000000' 186 | ], 187 | version: '0x0', 188 | witnesses: [ 189 | '0x3ed693609d3ff7049415d33a2fff8eed581cf2790fae4785e27793330cfd55a6109b84a94494eae64217339bc9aafdf4e6453964c90e6752d96e20d127ca874901' 190 | ] 191 | } 192 | > toolkit.validators.ValidateTransaction(transaction) 193 | undefined 194 | > transaction.outputs[1].lock.args = 123 195 | 123 196 | > toolkit.validators.ValidateTransaction(transaction) 197 | Uncaught Error: transaction.outputs[1].lock.args must be a hex string! 198 | at nt (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:46599) 199 | at st (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:47003) 200 | at lt (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:47614) 201 | at /home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:48132 202 | at ct (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:48073) 203 | at ht (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:48343) 204 | at Object.dt [as ValidateTransaction] (/home/ubuntu/code/ckb-js-toolkit/dist/ckb-js-toolkit.node.js:1:48755) 205 | ``` 206 | 207 | From the example above, we can deduce some insights: 208 | 209 | * Validator function will check if given object has correct keys for each field required in the object. For example, a transaction object do not need `hash` field, but CKB's RPC response contains this field, hence the first validator invocation fails. Later when we delete the `hash` key, the object passes all validation. 210 | * By default validator function recursively check all fields to make sure they follow the correct format. In the above example, the validator would signal error when we change `args` field of one output's lock script to `123`, which is an invalid value. You will also notice that the error message generated in this case is: `transaction.outputs[1].lock.args must be a hex string!`, it contains the full path of the error value: `transaction.outputs[1].lock.args`. 211 | 212 | Notice you are not required to use validator functions in your dapp: if you are familiar with CKB's data structure, you are perfectly good ignoring all validator functions, and ensure the objects are in correct format yourself. But I personally I believe validators are gonna be a very handy component in your toolbox when you run into formatting errors in your dapp. 213 | 214 | ### Function prototypes 215 | 216 | For each CKB data structure, we have prepared a validator function, which means right now the following functions are available: 217 | 218 | * [ValidateScript](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L65) 219 | * [ValidateOutPoint](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L83) 220 | * [ValidateCellInput](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L92) 221 | * [ValidateCellOutput](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L106) 222 | * [ValidateCellDep](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L125) 223 | * [ValidateRawTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L195) 224 | * [ValidateTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L215) 225 | * [ValidateRawHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L255) 226 | * [ValidateHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L279) 227 | * [ValidateUncleBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L316) 228 | * [ValidateBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L335) 229 | * [ValidateCellbaseWitness](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L371) 230 | * [ValidateWitnessArgs](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/validators.js#L385) 231 | 232 | Each validator function uses exactly the same function prototype as below: 233 | 234 | ```js 235 | function validate(value, { nestedValidation = true, debugPath = "" } = {}) 236 | ``` 237 | 238 | `value` here contains the JSON object to check against. `nestedValidation` controls if nested validation is performed. For example, when you are validating the structure of a transaction, you can use this flag to control whether you want to validate transaction itself, or if you want to do recursive validation on all the included fields, such as inputs, outputs, etc. `debugPath` here is a value used to generate full value path in error messages, in 99% of the cases, you can safely ignore this value and just use the default ones. 239 | 240 | The validator functions here would throw errors in case validation fails, otherwise the validation is considered successful. Return values of vaildator functions shall not be used. 241 | 242 | ## Transformers 243 | 244 | ### Overview 245 | 246 | Transformer also provides a series of functions much like validators do, but transformers serve a different purpose: while RPC accepts JSON ready values such as strings or plain objects, you might not want the same thing in your dapps. Chances are you have some custom classes that wrap a CKB cell or even a CKB transaction, and will need separate processing work to transform the values into the final formats. Another case might be that you are leveraging the Reader class introduced above a lot, and you need to transform the very inner `args` part of cell outputs in your current transaction before you can send it to the RPC. Transformers fill in exactly this role: they help us transform custom data formats following a very single rule to the formats acceptable by CKB RPCs. This might sound quite abstract here, let's also look at one example. First, let's prepare a supporting commonjs JavaScript file: 247 | 248 | ``` 249 | $ cat << EOF > test.js 250 | class Script { 251 | constructor(code_hash, args) { 252 | this.code_hash = code_hash; 253 | this.args = args; 254 | } 255 | 256 | serializeJson() { 257 | return { 258 | code_hash: this.code_hash, 259 | hash_type: "data", 260 | args: this.args 261 | }; 262 | } 263 | } 264 | 265 | module.exports = Script; 266 | EOF 267 | ``` 268 | 269 | Here we are defining a custom class wrapping Script. We can now see how transformers will help us use the wrapper class: 270 | 271 | ``` 272 | node --experimental-repl-await 273 | Welcome to Node.js v13.9.0. 274 | Type ".help" for more information. 275 | > const toolkit = require("ckb-js-toolkit"); 276 | undefined 277 | > const Script = require("./test.js"); 278 | undefined 279 | > const script = new Script(new toolkit.Reader("0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c"), "0x") 280 | undefined 281 | > script 282 | Script { 283 | code_hash: a { 284 | string: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c' 285 | }, 286 | args: '0x' 287 | } 288 | > toolkit.validators.ValidateScript(script) 289 | Uncaught: 290 | Error: script does not have correct keys! Required keys: [args, code_hash, hash_type], optional keys: [], actual keys: [args, code_hash] 291 | > const transformedScript = toolkit.transformers.TransformScript(script) 292 | undefined 293 | > transformedScript 294 | { 295 | code_hash: '0x71a7ba8fc96349fea0ed3a5c47992e3b4084b031a42264a018e0072e8172e46c', 296 | hash_type: 'data', 297 | args: '0x' 298 | } 299 | > toolkit.validators.ValidateScript(transformedScript) 300 | undefined 301 | ``` 302 | 303 | If we pass objects instantiated from Script class directly, validator functions will fail, and so will CKB RPC(even if we haven't tested it here). However, if we pass the objects into transformer functions, the transformed value will have the correct format required by CKB's RPC. This shows that you can use whatever data format you want in your dapp, as long as it can be transformed to correct format by the transformers, you can largely ignore CKB's format rules in your dapp. 304 | 305 | Of course, not any format can be transformed by transformer functions here. You might already noticed the `serializeJson` function included in the `Script` class, this is exactly how transformer functions work on. What's not so obvious, here, is that transformer functions will recursively invoke `serializeJson` on nested entities to make sure all sub-fields are also transformed properly. For example, the `code_hash` passed to Script object here, is actually a Reader instance, while `serializeJson` of Script class ignored the type of `code_hash`, transformer functions also perform the necessary transformation, which converts Reader object into hex strings. 306 | 307 | Unlike validators, we expect you to heavily rely on transformers in your dapps. These days it will be very unlikely that people use plain old JavaScript objects to store data, we tend to use all kinds of abstractions that help us organize our code better. 308 | 309 | ### Function prototypes 310 | 311 | For each CKB data structure, we have prepared a transformer function, which means right now the following functions are available: 312 | 313 | * [TransformScript](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L44) 314 | * [TransformOutPoint](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L62) 315 | * [TransformCellInput](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L88) 316 | * [TransformCellOutput](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L105) 317 | * [TransformCellDep](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L123) 318 | * [TransformRawTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L148) 319 | * [TransformTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L169) 320 | * [TransformRawHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L191) 321 | * [TransformHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L216) 322 | * [TransformUncleBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L242) 323 | * [TransformBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L259) 324 | * [TransformCellbaseWitness](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L278) 325 | * [TransformWitnessArgs](https://github.com/xxuejie/ckb-js-toolkit/blob/48eb43f10da07d30ebc9411c7a0714905ef9164f/src/transformers.js#L295) 326 | 327 | Each transformer function uses exactly the same function prototype as below: 328 | 329 | ```js 330 | function transform(value, { validation = true, debugPath = "" } = {}) 331 | ``` 332 | 333 | `value` here contains the value to perform transformation(see next section for transformation rules). `validation` controls if validators should also be called after transformation. `debugPath` works like the same value in validators, and could safely be ignored in 99% of the cases. 334 | 335 | ### Transformation Rules 336 | 337 | Transformer functions use the following transformation rules: 338 | 339 | * If the provide value is an object with method `serializeJson`, invoke the method and use the return value to replace provided value; 340 | * Check if the value is an object, throw error if it is not; 341 | * Remove all extra keys that should not exist for the type of current value object; 342 | * For each field in the object, apply the transformation rules recursively. 343 | 344 | ### Note on Reader class 345 | 346 | We have already provided `serializeJson` method for all instances of the Reader class, that means you can freely use Reader class in your code. As long as you apply transformer functions before sending the values to CKB RPC, all the Reader objects will be transformed to correct hex strings. 347 | 348 | ## Normalizers 349 | 350 | ### Overview 351 | 352 | Normalizers serve a different purpose: in addition to the molecule serialization format, CKB still encodes certain domain specific knowledge to interpret the data. This brings a gap between the JSON data structure used in CKB RPC, and the molecule serialized formats used internally. If you are only using CKB RPC, you probably will not use this package, but if you need to work with CKB in a deeper level(for example, if you are writing a smart contract in JavaScript), chances are you might need this package. 353 | 354 | A sample usage for this package can be seen in the included [tests](https://github.com/xxuejie/ckb-js-toolkit/blob/master/tests/serializers.js). A normalizer function takes plain JavaScript object that can be validated by validator function, it then emits another transformed plain JavaScript object which can be serialized by [moleculec-es](https://github.com/xxuejie/moleculec-es) into serialized ArrayBuffer data in molecule format. 355 | 356 | ### Function prototypes 357 | 358 | For each CKB data structure, we have prepared a normalizer function, which means right now the following functions are available: 359 | 360 | * [NormalizeScript](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L71) 361 | * [NormalizeOutPoint](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L92) 362 | * [NormalizeCellInput](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L107) 363 | * [NormalizeCellOutput](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L117) 364 | * [NormalizeCellDep](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L133) 365 | * [NormalizeRawTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L161) 366 | * [NormalizeTransaction](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L175) 367 | * [NormalizeRawHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L189) 368 | * [NormalizeHeader](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L207) 369 | * [NormalizeUncleBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L218) 370 | * [NormalizeBlock](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L228) 371 | * [NormalizeCellbaseWitness](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L237) 372 | * [NormalizeWitnessArgs](https://github.com/xxuejie/ckb-js-toolkit/blob/d17eda8dc41689b14913500332085d9a9ae85a01/src/normalizers.js#L247) 373 | 374 | Each normalizer function uses exactly the same function prototype as below: 375 | 376 | ```js 377 | function transform(value, { debugPath = "" } = {}) 378 | ``` 379 | 380 | `value` here contains the value which can be validated by corresponding validator function. `debugPath` works like the same value in validators, and could safely be ignored in 99% of the cases. 381 | 382 | # Cell Collectors 383 | 384 | One extremely typical tasks in CKB, is to index and query cells with certain properties, such as live cells with the same lock script hash. Cell collectors has been provided in the toolkit to aid this task. 385 | 386 | As usual, first let's see an example: 387 | 388 | ``` 389 | node --experimental-repl-await 390 | Welcome to Node.js v13.9.0. 391 | Type ".help" for more information. 392 | > const toolkit = require("ckb-js-toolkit"); 393 | undefined 394 | > const rpc = new toolkit.RPC("http://127.0.0.1:9115/rpc") 395 | undefined 396 | > const collector = new toolkit.cell_collectors.RPCCollector(rpc, "0x7c7232c0af4a7261674a45e14916f926ecec64b911f539e573fb4bb7817d001e") 397 | undefined 398 | > for await (const cell of collector.collect()) { console.log(cell); } 399 | { 400 | cell_output: { 401 | capacity: '0x3faa252260000', 402 | lock: { 403 | args: '0xfa3afa2134319f9471cf21024f032831bc4651ad', 404 | code_hash: '0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8', 405 | hash_type: 'type' 406 | }, 407 | type: null 408 | }, 409 | out_point: { 410 | index: '0x7', 411 | tx_hash: '0xe2fb199810d49a4d8beec56718ba2593b665db9d52299a0f9e6e75416d73ff5c' 412 | }, 413 | block_hash: '0x92b197aa1fba0f63633922c61c92375c9c074a93e85963554f5499fe1450d0e5', 414 | data: null, 415 | block_number: '0x0' 416 | } 417 | ``` 418 | 419 | Here we are using [RPCCollector](https://github.com/xxuejie/ckb-js-toolkit/blob/master/src/cell_collectors/rpc_collector.js) to scan CKB via RPCs, and gather all live cells satisfying a given lock script hash. Notice [async iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator) is leveraged here to provide a clean API to the end users. 420 | 421 | ## Collector Interface 422 | 423 | A cell collector should satisfy the following rules: 424 | 425 | * It should provide a `collect` method that returns an async iterator; 426 | * The async iterator should generate plain JavaScript objects with the following fields: 427 | + `cell_output`: CellOutput object that can be validated by `ValidateCellOutput` 428 | + `out_point`: OutPoint object that can be validated by `ValidateOutPoint` 429 | + `block_hash`: A 66-byte long hex string containing block header hash 430 | * `data`: An optional hex string containing cell data, depending on the specific cell collector, this could be omitted, in which case `null` should be used here. 431 | 432 | In the above example we are only showing RPCCollector, there might be many different implementations of cell collectors, assuming they satisfy the above rules. In the future we might add more cell collector implementations here, and you are also welcome to create your own implementations of cell collectors. 433 | 434 | One additional note here, is that even though RPCCollector above only gathers cells with the same lock script hash, this is not the case for your custom cell collectors. The only true requirement of a cell collector, is the async iterator interface. You can use any filtering logic in your cell collector, for example, you can gather by type script hash, or by lock script code hash, or you can even call external services, such as [animagus](https://github.com/xxuejie/animagus) to provide you with the cells to use. 435 | 436 | ## Collector Index 437 | 438 | ### RPCCollector 439 | 440 | RPCCollector uses [get_cells_by_lock_hash](https://github.com/nervosnetwork/ckb/tree/develop/rpc#get_cells_by_lock_hash) to fetch matched live cells via CKB RPC. It only allows fetching live cells matching provided lock script hash, and can be controlled to either provide or omit cell data. 441 | 442 | Note RPCCollector is a naive implementation, meaning it would do the full scanning every time you call `collect`. This means it could be quite slow, and you should never use it in production. It is only used here for demostration purposes. 443 | -------------------------------------------------------------------------------- /images/toolkit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 |
Your Own Object
Camel or snake case
Your Own Object...
JSON Ready Object
Snake case
JSON Ready Object...
CKB
RPC
CKB...
Transformers
Transformers
Validators
Validators
Molecule Ready Object
Snake case
Molecule Ready Objec...
Normalizers
Normalizers
ArrayBuffer
ArrayBuffer
Serializers
Serializers
Denormalizers
Denormalizers
Molecule Object
over ArrayBuffer
Molecule Object...
Deserializers
Deserializers
CKB
Hasher
CKB...
Viewer does not support full SVG 1.1
-------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import JSBI from "jsbi"; 2 | 3 | export class Reader { 4 | constructor(reader: string | ArrayBuffer | Reader); 5 | static fromRawString(s: string): Reader; 6 | 7 | length(): number; 8 | indexAt(i: number): number; 9 | toArrayBuffer(): ArrayBuffer; 10 | serializeJson(): string; 11 | } 12 | 13 | export type RPCValue = any; 14 | export type RPCSyncHandler = (...params: RPCValue[]) => RPCValue; 15 | export type RPCHandler = (...params: RPCValue[]) => Promise; 16 | 17 | export class BatchRPCMethods { 18 | send: RPCHandler; 19 | } 20 | 21 | export class BatchRPCProxy { 22 | [method: string]: RPCSyncHandler; 23 | } 24 | 25 | export type BatchRPC = BatchRPCMethods & BatchRPCProxy; 26 | 27 | export class RPCMethods { 28 | batch: () => BatchRPC; 29 | } 30 | 31 | export class RPCProxy { 32 | [method: string]: RPCHandler; 33 | } 34 | 35 | export class RPC { 36 | /** 37 | * To preserve compatibility, we still provide the default constructor 38 | * but it cannot tell if you are calling the sync method batch, or other 39 | * async methods. You will need to distinguish between them yourself. 40 | */ 41 | constructor(uri: string, options?: object); 42 | [method: string]: RPCHandler | RPCSyncHandler; 43 | 44 | static create(uri: string): RPCMethods & RPCProxy; 45 | } 46 | 47 | export function HexStringToBigInt(hexString: string): JSBI; 48 | export function BigIntToHexString(i: JSBI): string; 49 | 50 | export interface ValidatorOptions { 51 | nestedValidation?: boolean; 52 | debugPath?: string; 53 | } 54 | type ValidatorFunction = (value: object, options?: ValidatorOptions) => void; 55 | 56 | export namespace validators { 57 | const ValidateScript: ValidatorFunction; 58 | const ValidateOutPoint: ValidatorFunction; 59 | const ValidateCellInput: ValidatorFunction; 60 | const ValidateCellOutput: ValidatorFunction; 61 | const ValidateCellDep: ValidatorFunction; 62 | const ValidateRawTransaction: ValidatorFunction; 63 | const ValidateTransaction: ValidatorFunction; 64 | const ValidateRawHeader: ValidatorFunction; 65 | const ValidateHeader: ValidatorFunction; 66 | const ValidateUncleBlock: ValidatorFunction; 67 | const ValidateBlock: ValidatorFunction; 68 | const ValidateCellbaseWitness: ValidatorFunction; 69 | const ValidateWitnessArgs: ValidatorFunction; 70 | } 71 | 72 | export interface TransformerOptions { 73 | validation?: boolean; 74 | debugPath?: string; 75 | } 76 | type TransformerFunction = ( 77 | value: object, 78 | options?: TransformerOptions 79 | ) => object; 80 | 81 | export namespace transformers { 82 | const TransformScript: TransformerFunction; 83 | const TransformOutPoint: TransformerFunction; 84 | const TransformCellInput: TransformerFunction; 85 | const TransformCellOutput: TransformerFunction; 86 | const TransformCellDep: TransformerFunction; 87 | const TransformRawTransaction: TransformerFunction; 88 | const TransformTransaction: TransformerFunction; 89 | const TransformRawHeader: TransformerFunction; 90 | const TransformHeader: TransformerFunction; 91 | const TransformUncleBlock: TransformerFunction; 92 | const TransformBlock: TransformerFunction; 93 | const TransformCellbaseWitness: TransformerFunction; 94 | const TransformWitnessArgs: TransformerFunction; 95 | } 96 | 97 | export interface NormalizerOptions { 98 | debugPath?: string; 99 | } 100 | type NormalizerFunction = ( 101 | value: object, 102 | options?: NormalizerOptions 103 | ) => object; 104 | 105 | export namespace normalizers { 106 | const NormalizeScript: NormalizerFunction; 107 | const NormalizeOutPoint: NormalizerFunction; 108 | const NormalizeCellInput: NormalizerFunction; 109 | const NormalizeCellOutput: NormalizerFunction; 110 | const NormalizeCellDep: NormalizerFunction; 111 | const NormalizeRawTransaction: NormalizerFunction; 112 | const NormalizeTransaction: NormalizerFunction; 113 | const NormalizeRawHeader: NormalizerFunction; 114 | const NormalizeHeader: NormalizerFunction; 115 | const NormalizeUncleBlock: NormalizerFunction; 116 | const NormalizeBlock: NormalizerFunction; 117 | const NormalizeCellbaseWitness: NormalizerFunction; 118 | const NormalizeWitnessArgs: NormalizerFunction; 119 | } 120 | 121 | export interface Cell { 122 | cell_output: object; 123 | out_point: object; 124 | block_hash: string; 125 | data?: string; 126 | block_number?: string; 127 | } 128 | 129 | export interface CellCollectorResults { 130 | [Symbol.asyncIterator](): AsyncIterator; 131 | } 132 | 133 | export interface CellCollector { 134 | collect(): CellCollectorResults; 135 | } 136 | 137 | export namespace cell_collectors { 138 | interface RPCCollectorOptions { 139 | skipCellWithContent?: boolean; 140 | loadData?: boolean; 141 | } 142 | 143 | class RPCCollector implements CellCollector { 144 | constructor(rpc: RPC, lockHash: string, options?: RPCCollectorOptions); 145 | 146 | collect(): CellCollectorResults; 147 | } 148 | } 149 | 150 | export type DepGroupUnpacker = (data: Reader) => Array; 151 | export interface TransactionDumperOptions { 152 | validateTransaction?: boolean; 153 | depGroupUnpacker?: DepGroupUnpacker; 154 | } 155 | 156 | export class TransactionDumper { 157 | constructor(rpc: RPC, options?: TransactionDumperOptions); 158 | 159 | dump(tx: object): Promise; 160 | } 161 | 162 | export const VERSION: string; 163 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ckb-js-toolkit", 3 | "version": "0.11.1", 4 | "description": "JavaScript toolkits used to build dapps for Nervos CKB", 5 | "main": "dist/ckb-js-toolkit.node.js", 6 | "browser": "dist/ckb-js-toolkit.esm.js", 7 | "types": "index.d.ts", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/xxuejie/ckb-js-toolkit.git" 11 | }, 12 | "files": [ 13 | "index.d.ts", 14 | "dist" 15 | ], 16 | "scripts": { 17 | "dist": "rollup -c --environment BUILD:production", 18 | "test": "rollup -c --environment BUILD:development && ava", 19 | "fmt": "prettier --write \"{src,tests}/**/*.js\" index.d.ts", 20 | "update-test-files": "curl -L https://raw.githubusercontent.com/nervosnetwork/ckb/27c36a55e6358fd04153ec3da4638b6e10660da6/util/types/schemas/blockchain.mol -o testfiles/blockchain.mol && moleculec --language - --schema-file testfiles/blockchain.mol --format json > testfiles/blockchain.json && moleculec-es -inputFile testfiles/blockchain.json -outputFile testfiles/blockchain.esm.js && rollup -f umd -i testfiles/blockchain.esm.js -o testfiles/blockchain.umd.js --name Blockchain && rm testfiles/blockchain.mol testfiles/blockchain.json testfiles/blockchain.esm.js" 21 | }, 22 | "author": "Xuejie Xiao", 23 | "license": "MIT", 24 | "dependencies": { 25 | "cross-fetch": "^3.0.6", 26 | "jsbi": "^3.1.2" 27 | }, 28 | "devDependencies": { 29 | "@rollup/plugin-commonjs": "^15.0.0", 30 | "@rollup/plugin-node-resolve": "^9.0.0", 31 | "@rollup/plugin-replace": "^2.3.3", 32 | "ava": "^3.5.0", 33 | "prettier": "1.19.1", 34 | "rollup": "^2.22.0", 35 | "rollup-plugin-terser": "^7.0.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import resolve from "@rollup/plugin-node-resolve"; 2 | import commonjs from "@rollup/plugin-commonjs"; 3 | import replace from "@rollup/plugin-replace"; 4 | import { terser } from "rollup-plugin-terser"; 5 | 6 | import * as package_json from "./package.json"; 7 | 8 | const isProduction = process.env.BUILD === 'production'; 9 | const outputFolder = isProduction ? "dist" : "build"; 10 | const sourcemap = !isProduction; 11 | 12 | module.exports = [ 13 | { 14 | input: "src/index.js", 15 | output: { 16 | file: outputFolder + "/ckb-js-toolkit.node.js", 17 | format: "cjs", 18 | sourcemap: true 19 | }, 20 | plugins: [ 21 | replace({__development_build__: package_json.version}), 22 | resolve({preferBuiltins: true}), 23 | commonjs(), 24 | isProduction && terser() 25 | ] 26 | }, 27 | // TODO: do we need sourcemap for UMD and ESM versions? 28 | { 29 | input: "src/index.js", 30 | output: { 31 | file: outputFolder + "/ckb-js-toolkit.umd.js", 32 | format: "umd", 33 | name: "CKBJSToolkit", 34 | sourcemap: sourcemap 35 | }, 36 | plugins: [ 37 | replace({__development_build__: package_json.version}), 38 | resolve({browser: true, preferBuiltins: false}), 39 | commonjs(), 40 | isProduction && terser() 41 | ] 42 | }, 43 | { 44 | input: "src/index.js", 45 | output: { 46 | file: outputFolder + "/ckb-js-toolkit.esm.js", 47 | format: "esm", 48 | sourcemap: sourcemap 49 | }, 50 | plugins: [ 51 | replace({__development_build__: package_json.version}), 52 | resolve({browser: true, preferBuiltins: false}), 53 | commonjs(), 54 | isProduction && terser() 55 | ] 56 | } 57 | ]; 58 | -------------------------------------------------------------------------------- /src/cell_collectors/index.js: -------------------------------------------------------------------------------- 1 | export { RPCCollector } from "./rpc_collector"; 2 | -------------------------------------------------------------------------------- /src/cell_collectors/rpc_collector.js: -------------------------------------------------------------------------------- 1 | import JSBI from "jsbi"; 2 | import { Reader } from "../reader"; 3 | import { HexStringToBigInt, BigIntToHexString } from "../rpc"; 4 | 5 | export class RPCCollector { 6 | constructor( 7 | rpc, 8 | lockHash, 9 | { 10 | skipCellWithContent = true, 11 | loadData = false, 12 | loadBlockNumber = true 13 | } = {} 14 | ) { 15 | this.rpc = rpc; 16 | this.lockHash = new Reader(lockHash).serializeJson(); 17 | this.skipCellWithContent = skipCellWithContent; 18 | this.loadData = loadData; 19 | this.loadBlockNumber = loadBlockNumber; 20 | } 21 | 22 | async *collect() { 23 | const to = HexStringToBigInt(await this.rpc.get_tip_block_number()); 24 | let currentFrom = JSBI.BigInt(0); 25 | while (JSBI.lessThanOrEqual(currentFrom, to)) { 26 | let currentTo = JSBI.add(currentFrom, JSBI.BigInt(100)); 27 | if (JSBI.greaterThan(currentTo, to)) { 28 | currentTo = to; 29 | } 30 | const cells = await this.rpc.get_cells_by_lock_hash( 31 | this.lockHash, 32 | BigIntToHexString(currentFrom), 33 | BigIntToHexString(currentTo) 34 | ); 35 | for (const cell of cells) { 36 | if (this.skipCellWithContent) { 37 | if ( 38 | cell.type || 39 | JSBI.greaterThan( 40 | HexStringToBigInt(cell.output_data_len), 41 | JSBI.BigInt(100) 42 | ) 43 | ) { 44 | continue; 45 | } 46 | } 47 | let data = null; 48 | if (this.loadData) { 49 | const cellWithData = await this.rpc.get_live_cell( 50 | cell.out_point, 51 | true 52 | ); 53 | data = cellWithData.cell.data.content; 54 | } 55 | let block_number = null; 56 | if (this.loadBlockNumber) { 57 | const header = await this.rpc.get_header(cell.block_hash); 58 | block_number = header.number; 59 | } 60 | yield { 61 | cellbase: cell.cellbase, 62 | cell_output: { 63 | capacity: cell.capacity, 64 | lock: cell.lock, 65 | type: cell.type 66 | }, 67 | out_point: cell.out_point, 68 | block_hash: cell.block_hash, 69 | data: data, 70 | output_data_len: cell.output_data_len, 71 | block_number 72 | }; 73 | } 74 | currentFrom = JSBI.add(currentTo, JSBI.BigInt(1)); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { Reader } from "./reader.js"; 2 | export { TransactionDumper } from "./transaction_dumper.js"; 3 | export { RPC, HexStringToBigInt, BigIntToHexString } from "./rpc.js"; 4 | export * as normalizers from "./normalizers"; 5 | export * as transformers from "./transformers"; 6 | export * as validators from "./validators"; 7 | export * as cell_collectors from "./cell_collectors"; 8 | export const VERSION = "__development_build__"; 9 | -------------------------------------------------------------------------------- /src/normalizers.js: -------------------------------------------------------------------------------- 1 | // This package provides normalizer functions. Even though CKB uses molecule 2 | // as the serialization layer. There is still CKB specific knowledge that does 3 | // not belong in molecule. For example, all numbers in CKB protocols are 4 | // serialized using little endian format. This package tries to encode such 5 | // knowledge. The goal here, is that you are free to use whatever types that 6 | // makes most sense to represent the values. As long as you pass your object 7 | // through the normalizers here, molecule should be able to serialize the values 8 | // into correct formats required by CKB. 9 | // 10 | // Note this is only used when you need to deal with CKB structures in molecule 11 | // format. If you are using RPCs or GraphQL to interact with CKB, chances are you 12 | // will not need this package. 13 | import JSBI from "jsbi"; 14 | import { Reader } from "./reader"; 15 | import { BigIntToHexString } from "./rpc"; 16 | 17 | function normalizeHexNumber(length) { 18 | return function(debugPath, value) { 19 | if (!(value instanceof ArrayBuffer)) { 20 | let intValue = BigIntToHexString(JSBI.BigInt(value)).substr(2); 21 | if (intValue.length % 2 !== 0) { 22 | intValue = "0" + intValue; 23 | } 24 | if (intValue.length / 2 > length) { 25 | throw new Error( 26 | `${debugPath} is ${intValue.length / 27 | 2} bytes long, expected length is ${length}!` 28 | ); 29 | } 30 | const view = new DataView(new ArrayBuffer(length)); 31 | for (let i = 0; i < intValue.length / 2; i++) { 32 | const start = intValue.length - (i + 1) * 2; 33 | view.setUint8(i, parseInt(intValue.substr(start, 2), 16)); 34 | } 35 | value = view.buffer; 36 | } 37 | if (value.byteLength < length) { 38 | const array = new Uint8Array(length); 39 | array.set(new Uint8Array(value), 0); 40 | value = array.buffer; 41 | } 42 | return value; 43 | }; 44 | } 45 | 46 | function normalizeRawData(length) { 47 | return function(debugPath, value) { 48 | value = new Reader(value).toArrayBuffer(); 49 | if (length > 0 && value.byteLength !== length) { 50 | throw new Error( 51 | `${debugPath} has invalid length ${value.byteLength}, required: ${length}` 52 | ); 53 | } 54 | return value; 55 | }; 56 | } 57 | 58 | function normalizeObject(debugPath, object, keys) { 59 | const result = {}; 60 | 61 | for (const [key, f] of Object.entries(keys)) { 62 | const value = object[key]; 63 | if (!value) { 64 | throw new Error(`${debugPath} is missing ${key}!`); 65 | } 66 | result[key] = f(`${debugPath}.${key}`, value); 67 | } 68 | return result; 69 | } 70 | 71 | export function NormalizeScript(script, { debugPath = "script" } = {}) { 72 | return normalizeObject(debugPath, script, { 73 | code_hash: normalizeRawData(32), 74 | hash_type: function(debugPath, value) { 75 | switch (value) { 76 | case "data": 77 | return 0; 78 | case "type": 79 | return 1; 80 | case 0: 81 | return value; 82 | case 1: 83 | return value; 84 | default: 85 | throw new Error(`${debugPath}.hash_type has invalid value: ${value}`); 86 | } 87 | }, 88 | args: normalizeRawData(-1) 89 | }); 90 | } 91 | 92 | export function NormalizeOutPoint(outPoint, { debugPath = "out_point" } = {}) { 93 | return normalizeObject(debugPath, outPoint, { 94 | tx_hash: normalizeRawData(32), 95 | index: normalizeHexNumber(4) 96 | }); 97 | } 98 | 99 | function toNormalize(normalize) { 100 | return function(debugPath, value) { 101 | return normalize(value, { 102 | debugPath 103 | }); 104 | }; 105 | } 106 | 107 | export function NormalizeCellInput( 108 | cellInput, 109 | { debugPath = "cell_input" } = {} 110 | ) { 111 | return normalizeObject(debugPath, cellInput, { 112 | since: normalizeHexNumber(8), 113 | previous_output: toNormalize(NormalizeOutPoint) 114 | }); 115 | } 116 | 117 | export function NormalizeCellOutput( 118 | cellOutput, 119 | { debugPath = "cell_output" } = {} 120 | ) { 121 | const result = normalizeObject(debugPath, cellOutput, { 122 | capacity: normalizeHexNumber(8), 123 | lock: toNormalize(NormalizeScript) 124 | }); 125 | if (cellOutput.type) { 126 | result.type_ = NormalizeScript(cellOutput.type, { 127 | debugPath: `${debugPath}.type` 128 | }); 129 | } 130 | return result; 131 | } 132 | 133 | export function NormalizeCellDep(cellDep, { debugPath = "cell_dep" } = {}) { 134 | return normalizeObject(debugPath, cellDep, { 135 | out_point: toNormalize(NormalizeOutPoint), 136 | dep_type: function(debugPath, value) { 137 | switch (value) { 138 | case "code": 139 | return 0; 140 | case "dep_group": 141 | return 1; 142 | case 0: 143 | return value; 144 | case 1: 145 | return value; 146 | default: 147 | throw new Error(`${debugPath}.dep_type has invalid value: ${value}`); 148 | } 149 | } 150 | }); 151 | } 152 | 153 | function toNormalizeArray(normalizeFunction) { 154 | return function(debugPath, array) { 155 | return array.map((item, i) => { 156 | return normalizeFunction(`${debugPath}[${i}]`, item); 157 | }); 158 | }; 159 | } 160 | 161 | export function NormalizeRawTransaction( 162 | rawTransaction, 163 | { debugPath = "raw_transaction" } = {} 164 | ) { 165 | return normalizeObject(debugPath, rawTransaction, { 166 | version: normalizeHexNumber(4), 167 | cell_deps: toNormalizeArray(toNormalize(NormalizeCellDep)), 168 | header_deps: toNormalizeArray(normalizeRawData(32)), 169 | inputs: toNormalizeArray(toNormalize(NormalizeCellInput)), 170 | outputs: toNormalizeArray(toNormalize(NormalizeCellOutput)), 171 | outputs_data: toNormalizeArray(normalizeRawData(-1)) 172 | }); 173 | } 174 | 175 | export function NormalizeTransaction( 176 | transaction, 177 | { debugPath = "transaction" } = {} 178 | ) { 179 | const rawTransaction = NormalizeRawTransaction(transaction, { 180 | debugPath: `(raw)${debugPath}` 181 | }); 182 | const result = normalizeObject(debugPath, transaction, { 183 | witnesses: toNormalizeArray(normalizeRawData(-1)) 184 | }); 185 | result.raw = rawTransaction; 186 | return result; 187 | } 188 | 189 | export function NormalizeRawHeader( 190 | rawHeader, 191 | { debugPath = "raw_header" } = {} 192 | ) { 193 | return normalizeObject(debugPath, rawHeader, { 194 | version: normalizeHexNumber(4), 195 | compact_target: normalizeHexNumber(4), 196 | timestamp: normalizeHexNumber(8), 197 | number: normalizeHexNumber(8), 198 | epoch: normalizeHexNumber(8), 199 | parent_hash: normalizeRawData(32), 200 | transactions_root: normalizeRawData(32), 201 | proposals_hash: normalizeRawData(32), 202 | uncles_hash: normalizeRawData(32), 203 | dao: normalizeRawData(32) 204 | }); 205 | } 206 | 207 | export function NormalizeHeader(header, { debugPath = "header" } = {}) { 208 | const rawHeader = NormalizeRawHeader(header, { 209 | debugPath: `(raw)${debugPath}` 210 | }); 211 | const result = normalizeObject(debugPath, header, { 212 | nonce: normalizeHexNumber(16) 213 | }); 214 | result.raw = rawHeader; 215 | return result; 216 | } 217 | 218 | export function NormalizeUncleBlock( 219 | uncleBlock, 220 | { debugPath = "uncle_block" } = {} 221 | ) { 222 | return normalizeObject(debugPath, uncleBlock, { 223 | header: toNormalize(NormalizeHeader), 224 | proposals: toNormalizeArray(normalizeRawData(10)) 225 | }); 226 | } 227 | 228 | export function NormalizeBlock(block, { debugPath = "block" } = {}) { 229 | return normalizeObject(debugPath, block, { 230 | header: toNormalize(NormalizeHeader), 231 | uncles: toNormalizeArray(toNormalize(NormalizeUncleBlock)), 232 | transactions: toNormalizeArray(toNormalize(NormalizeTransaction)), 233 | proposals: toNormalizeArray(normalizeRawData(10)) 234 | }); 235 | } 236 | 237 | export function NormalizeCellbaseWitness( 238 | cellbaseWitness, 239 | { debugPath = "cellbase_witness" } = {} 240 | ) { 241 | return normalizeObject(debugPath, cellbaseWitness, { 242 | lock: toNormalize(NormalizeScript), 243 | message: normalizeRawData(-1) 244 | }); 245 | } 246 | 247 | export function NormalizeWitnessArgs( 248 | witnessArgs, 249 | { debugPath = "witness_args" } = {} 250 | ) { 251 | const result = {}; 252 | if (witnessArgs.lock) { 253 | result.lock = normalizeRawData(-1)(`${debugPath}.lock`, witnessArgs.lock); 254 | } 255 | if (witnessArgs.input_type) { 256 | result.input_type = normalizeRawData(-1)( 257 | `${debugPath}.input_type`, 258 | witnessArgs.input_type 259 | ); 260 | } 261 | if (witnessArgs.output_type) { 262 | result.output_type = normalizeRawData(-1)( 263 | `${debugPath}.output_type`, 264 | witnessArgs.output_type 265 | ); 266 | } 267 | return result; 268 | } 269 | -------------------------------------------------------------------------------- /src/reader.js: -------------------------------------------------------------------------------- 1 | class ArrayBufferReader { 2 | constructor(buffer) { 3 | this.view = new DataView(buffer); 4 | } 5 | 6 | length() { 7 | return this.view.byteLength; 8 | } 9 | 10 | indexAt(i) { 11 | return this.view.getUint8(i); 12 | } 13 | 14 | toArrayBuffer() { 15 | return this.view.buffer; 16 | } 17 | 18 | serializeJson() { 19 | return ( 20 | "0x" + 21 | Array.prototype.map 22 | .call(new Uint8Array(this.view.buffer), x => 23 | ("00" + x.toString(16)).slice(-2) 24 | ) 25 | .join("") 26 | ); 27 | } 28 | } 29 | 30 | class HexStringReader { 31 | constructor(string) { 32 | this.string = string; 33 | } 34 | 35 | length() { 36 | return this.string.length / 2 - 1; 37 | } 38 | 39 | indexAt(i) { 40 | return parseInt(this.string.substr(2 + i * 2, 2), 16); 41 | } 42 | 43 | toArrayBuffer() { 44 | const buffer = new ArrayBuffer(this.length()); 45 | const view = new DataView(buffer); 46 | 47 | for (let i = 0; i < this.length(); i++) { 48 | view.setUint8(i, this.indexAt(i)); 49 | } 50 | return buffer; 51 | } 52 | 53 | serializeJson() { 54 | return this.string; 55 | } 56 | } 57 | 58 | export class Reader { 59 | constructor(input) { 60 | if ( 61 | input instanceof HexStringReader || 62 | input instanceof ArrayBufferReader 63 | ) { 64 | return input; 65 | } 66 | if (typeof input === "string") { 67 | if (!input.startsWith("0x") || input.length % 2 != 0) { 68 | throw new Error( 69 | "Hex string must start with 0x, and has even numbered length!" 70 | ); 71 | } 72 | return new HexStringReader(input); 73 | } 74 | if (input instanceof ArrayBuffer) { 75 | return new ArrayBufferReader(input); 76 | } 77 | throw new Error("Reader can only accept hex string or ArrayBuffer!"); 78 | } 79 | 80 | static fromRawString(string) { 81 | const buffer = new ArrayBuffer(string.length); 82 | const view = new DataView(buffer); 83 | 84 | for (let i = 0; i < string.length; i++) { 85 | const c = string.charCodeAt(i); 86 | if (c > 0xff) { 87 | throw new Error("fromRawString can only accept UTF-8 raw string!"); 88 | } 89 | view.setUint8(i, c); 90 | } 91 | return new ArrayBufferReader(buffer); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/rpc.js: -------------------------------------------------------------------------------- 1 | import { default as crossFetch } from "cross-fetch"; 2 | import JSBI from "jsbi"; 3 | 4 | function mergeOptions(overrideOptions, defaultOptions) { 5 | defaultOptions = defaultOptions || {}; 6 | const headers = Object.assign( 7 | {}, 8 | defaultOptions.headers || {}, 9 | overrideOptions.headers || {} 10 | ); 11 | return Object.assign({}, defaultOptions, overrideOptions, { 12 | headers: headers 13 | }); 14 | } 15 | 16 | const batchHandler = { 17 | get: (target, method, receiver) => { 18 | if (method === "send") { 19 | return async () => { 20 | const response = await target.fetch( 21 | target.uri, 22 | mergeOptions( 23 | { 24 | method: "post", 25 | headers: { 26 | "Content-Type": "application/json" 27 | }, 28 | body: JSON.stringify(target.payload) 29 | }, 30 | target.defaultOptions 31 | ) 32 | ); 33 | return await response.json(); 34 | }; 35 | } 36 | return (...params) => { 37 | const id = target.id; 38 | target.id = target.id + 1; 39 | target.payload.push({ 40 | jsonrpc: "2.0", 41 | id: id, 42 | method: method, 43 | params: params 44 | }); 45 | return receiver; 46 | }; 47 | } 48 | }; 49 | 50 | const handler = { 51 | get: (target, method) => { 52 | if (method === "batch") { 53 | return () => { 54 | return new Proxy( 55 | { 56 | id: Math.round(Math.random() * 10000000), 57 | payload: [], 58 | uri: target.uri, 59 | defaultOptions: target.defaultOptions 60 | }, 61 | batchHandler 62 | ); 63 | }; 64 | } 65 | return async (...params) => { 66 | const id = Math.round(Math.random() * 10000000); 67 | const response = await target.fetch( 68 | target.uri, 69 | mergeOptions( 70 | { 71 | method: "post", 72 | headers: { 73 | "Content-Type": "application/json" 74 | }, 75 | body: JSON.stringify({ 76 | jsonrpc: "2.0", 77 | id: id, 78 | method: method, 79 | params: params 80 | }) 81 | }, 82 | target.defaultOptions 83 | ) 84 | ); 85 | const data = await response.json(); 86 | if (data.id !== id) { 87 | throw new Error("JSONRPCError: response ID does not match request ID!"); 88 | } 89 | if (data.error) { 90 | throw new Error( 91 | `JSONRPCError: server error ${JSON.stringify(data.error)}` 92 | ); 93 | } 94 | return data.result; 95 | }; 96 | } 97 | }; 98 | 99 | export class RPC { 100 | constructor(uri, defaultOptions = {}, fetch = crossFetch) { 101 | this.uri = uri; 102 | this.defaultOptions = defaultOptions; 103 | this.fetch = fetch; 104 | return new Proxy(this, handler); 105 | } 106 | 107 | static create(uri) { 108 | return new RPC(uri); 109 | } 110 | } 111 | 112 | export function HexStringToBigInt(hexString) { 113 | return JSBI.BigInt(hexString); 114 | } 115 | 116 | export function BigIntToHexString(bigInt) { 117 | return "0x" + bigInt.toString(16); 118 | } 119 | -------------------------------------------------------------------------------- /src/transaction_dumper.js: -------------------------------------------------------------------------------- 1 | import JSBI from "jsbi"; 2 | import { Reader } from "./reader"; 3 | import { ValidateTransaction, ValidateOutPoint } from "./validators"; 4 | 5 | export class TransactionDumper { 6 | constructor( 7 | rpc, 8 | { validateTransaction = true, depGroupUnpacker = null } = {} 9 | ) { 10 | this.rpc = rpc; 11 | this.validateTransaction = validateTransaction; 12 | this.depGroupUnpacker = depGroupUnpacker; 13 | } 14 | 15 | async dumpByHash(txHash) { 16 | const tx = (await this.rpc.get_transaction(txHash)).transaction; 17 | delete tx.hash; 18 | return await this.dump(tx); 19 | } 20 | 21 | async dump(tx) { 22 | if (this.validateTransaction) { 23 | ValidateTransaction(tx); 24 | } 25 | const mockInputs = []; 26 | for (const input of tx.inputs) { 27 | const { output, data, header } = await this._resolveOutPoint( 28 | input.previous_output 29 | ); 30 | mockInputs.push({ input, output, data, header }); 31 | } 32 | const mockCellDeps = []; 33 | for (const cellDep of tx.cell_deps) { 34 | const { output, data, header } = await this._resolveOutPoint( 35 | cellDep.out_point 36 | ); 37 | mockCellDeps.push({ 38 | cell_dep: cellDep, 39 | output, 40 | data, 41 | header 42 | }); 43 | if (cellDep.dep_type === "dep_group") { 44 | if (!this.depGroupUnpacker) { 45 | throw new Error( 46 | "depGroupUnpacker must be provided when the transaction contains dep_group!" 47 | ); 48 | } 49 | const outPoints = this.depGroupUnpacker(new Reader(data)); 50 | for (const outPoint of outPoints) { 51 | ValidateOutPoint(outPoint); 52 | const { output, data, header } = await this._resolveOutPoint( 53 | outPoint 54 | ); 55 | mockCellDeps.push({ 56 | cell_dep: { 57 | out_point: outPoint, 58 | dep_type: "code" 59 | }, 60 | output, 61 | data, 62 | header 63 | }); 64 | } 65 | } 66 | } 67 | const mockHeaderDeps = []; 68 | for (const headerDep of tx.header_deps) { 69 | mockHeaderDeps.push(await this.rpc.get_header(headerDep)); 70 | } 71 | return JSON.stringify({ 72 | mock_info: { 73 | inputs: mockInputs, 74 | cell_deps: mockCellDeps, 75 | header_deps: mockHeaderDeps 76 | }, 77 | tx 78 | }); 79 | } 80 | 81 | async _resolveOutPoint(out_point) { 82 | const txStatus = await this.rpc.get_transaction(out_point.tx_hash); 83 | if (!txStatus || !txStatus.transaction) { 84 | throw new Error(`Transaction ${out_point.tx_hash} cannot be found!`); 85 | } 86 | const tx = txStatus.transaction; 87 | const index = JSBI.toNumber(JSBI.BigInt(out_point.index)); 88 | if (index >= tx.outputs.length) { 89 | throw new Error( 90 | `Transaction ${out_point.tx_hash} does not have output ${index}!` 91 | ); 92 | } 93 | const data = { 94 | output: tx.outputs[index], 95 | data: tx.outputs_data[index] 96 | }; 97 | if (txStatus.tx_status.status === "committed") { 98 | data.header = txStatus.tx_status.block_hash; 99 | } 100 | return data; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/transformers.js: -------------------------------------------------------------------------------- 1 | // This package provides transformer functions that transform JavaScript objects 2 | // into JSON ready objects that can be passed to RPC. It following the following 3 | // rules: 4 | // 5 | // 1. If the specified object has a serializeJson method, it would invoke this 6 | // method and use the result to replace current object. 7 | // 2. It then restricts the keys of the object to keys required by the specified 8 | // entity(i.e., a Script would only have code_hash, hash_type, args keys),for each 9 | // sub-field, it then recursively perform the steps here from step 1. 10 | // 3. It then optionally run validator functions to ensure the resulting object 11 | // follows specified rules. 12 | // 13 | // Note rule 1 here provides the flexibility in defining your own structures: you 14 | // could define a class containing custom data structures, then provide a 15 | // serializeJson that transforms the custom one into the rigid data structure 16 | // required by CKB. You can also leverage the Reader class we provide as much as 17 | // possible. Since Reader class does provide serializeJson methods, transformers 18 | // here will transform them to valid hex strings required by CKB. 19 | import * as validators from "./validators"; 20 | 21 | function invokeSerializeJson(debugPath, value) { 22 | if (value instanceof Object && value.serializeJson instanceof Function) { 23 | return value.serializeJson.call(value); 24 | } 25 | return value; 26 | } 27 | 28 | function transformObject(debugPath, object, keys) { 29 | object = invokeSerializeJson(debugPath, object); 30 | if (!(object instanceof Object)) { 31 | throw new Error(`Transformed ${debugPath} is not an object!`); 32 | } 33 | const result = {}; 34 | 35 | for (const [key, f] of Object.entries(keys)) { 36 | let value = object[key]; 37 | if (!value) { 38 | const camelKey = key.replace(/(_[a-z])/g, group => 39 | group.toUpperCase().replace("_", "") 40 | ); 41 | value = object[camelKey]; 42 | } 43 | if (value) { 44 | result[key] = f(`${debugPath}.${key}`, value); 45 | } 46 | } 47 | return result; 48 | } 49 | 50 | export function TransformScript( 51 | script, 52 | { validation = true, debugPath = "script" } = {} 53 | ) { 54 | script = transformObject(debugPath, script, { 55 | code_hash: invokeSerializeJson, 56 | hash_type: invokeSerializeJson, 57 | args: invokeSerializeJson 58 | }); 59 | 60 | if (validation) { 61 | validators.ValidateScript(script, { 62 | debugPath: `(transformed) ${debugPath}` 63 | }); 64 | } 65 | return script; 66 | } 67 | 68 | export function TransformOutPoint( 69 | outPoint, 70 | { validation = true, debugPath = "out_point" } = {} 71 | ) { 72 | outPoint = transformObject(debugPath, outPoint, { 73 | tx_hash: invokeSerializeJson, 74 | index: invokeSerializeJson 75 | }); 76 | 77 | if (validation) { 78 | validators.ValidateOutPoint(outPoint, { 79 | debugPath: `(transformed) ${debugPath}` 80 | }); 81 | } 82 | return outPoint; 83 | } 84 | 85 | function toInvoke(transform) { 86 | return function(debugPath, value) { 87 | return transform(value, { 88 | validation: false, 89 | debugPath 90 | }); 91 | }; 92 | } 93 | 94 | export function TransformCellInput( 95 | cellInput, 96 | { validation = true, debugPath = "cell_input" } = {} 97 | ) { 98 | cellInput = transformObject(debugPath, cellInput, { 99 | since: invokeSerializeJson, 100 | previous_output: toInvoke(TransformOutPoint) 101 | }); 102 | 103 | if (validation) { 104 | validators.ValidateCellInput(cellInput, { 105 | debugPath: `(transformed) ${debugPath}` 106 | }); 107 | } 108 | return cellInput; 109 | } 110 | 111 | export function TransformCellOutput( 112 | cellOutput, 113 | { validation = true, debugPath = "cell_output" } = {} 114 | ) { 115 | cellOutput = transformObject(debugPath, cellOutput, { 116 | capacity: invokeSerializeJson, 117 | lock: toInvoke(TransformScript), 118 | type: toInvoke(TransformScript) 119 | }); 120 | 121 | if (validation) { 122 | validators.ValidateCellOutput(cellOutput, { 123 | debugPath: `(transformed) ${debugPath}` 124 | }); 125 | } 126 | return cellOutput; 127 | } 128 | 129 | export function TransformCellDep( 130 | cellDep, 131 | { validation = true, debugPath = "cell_dep" } = {} 132 | ) { 133 | cellDep = transformObject(debugPath, cellDep, { 134 | out_point: toInvoke(TransformOutPoint), 135 | dep_type: invokeSerializeJson 136 | }); 137 | 138 | if (validation) { 139 | validators.ValidateCellDep(cellDep, { 140 | debugPath: `(transformed) ${debugPath}` 141 | }); 142 | } 143 | return cellDep; 144 | } 145 | 146 | function toInvokeArray(invokeFunction) { 147 | return function(debugPath, array) { 148 | return array.map((item, i) => { 149 | return invokeFunction(`${debugPath}[${i}]`, item); 150 | }); 151 | }; 152 | } 153 | 154 | export function TransformRawTransaction( 155 | rawTransaction, 156 | { validation = true, debugPath = "raw_transaction" } = {} 157 | ) { 158 | rawTransaction = transformObject(debugPath, rawTransaction, { 159 | version: invokeSerializeJson, 160 | cell_deps: toInvokeArray(toInvoke(TransformCellDep)), 161 | header_deps: toInvokeArray(invokeSerializeJson), 162 | inputs: toInvokeArray(toInvoke(TransformCellInput)), 163 | outputs: toInvokeArray(toInvoke(TransformCellOutput)), 164 | outputs_data: toInvokeArray(invokeSerializeJson) 165 | }); 166 | 167 | if (validation) { 168 | validators.ValidateRawTransaction(rawTransaction, { 169 | debugPath: `(transformed) ${debugPath}` 170 | }); 171 | } 172 | return rawTransaction; 173 | } 174 | 175 | export function TransformTransaction( 176 | transaction, 177 | { validation = true, debugPath = "transaction" } = {} 178 | ) { 179 | transaction = transformObject(debugPath, transaction, { 180 | version: invokeSerializeJson, 181 | cell_deps: toInvokeArray(toInvoke(TransformCellDep)), 182 | header_deps: toInvokeArray(invokeSerializeJson), 183 | inputs: toInvokeArray(toInvoke(TransformCellInput)), 184 | outputs: toInvokeArray(toInvoke(TransformCellOutput)), 185 | outputs_data: toInvokeArray(invokeSerializeJson), 186 | witnesses: toInvokeArray(invokeSerializeJson) 187 | }); 188 | 189 | if (validation) { 190 | validators.ValidateTransaction(transaction, { 191 | debugPath: `(transformed) ${debugPath}` 192 | }); 193 | } 194 | return transaction; 195 | } 196 | 197 | export function TransformRawHeader( 198 | rawHeader, 199 | { validation = true, debugPath = "raw_header" } = {} 200 | ) { 201 | rawHeader = transformObject(debugPath, rawHeader, { 202 | version: invokeSerializeJson, 203 | compact_target: invokeSerializeJson, 204 | timestamp: invokeSerializeJson, 205 | number: invokeSerializeJson, 206 | epoch: invokeSerializeJson, 207 | parent_hash: invokeSerializeJson, 208 | transactions_root: invokeSerializeJson, 209 | proposals_hash: invokeSerializeJson, 210 | uncles_hash: invokeSerializeJson, 211 | dao: invokeSerializeJson 212 | }); 213 | 214 | if (validation) { 215 | validators.ValidateRawHeader(rawHeader, { 216 | debugPath: `(transformed) ${debugPath}` 217 | }); 218 | } 219 | return rawHeader; 220 | } 221 | 222 | export function TransformHeader( 223 | header, 224 | { validation = true, debugPath = "header" } = {} 225 | ) { 226 | header = transformObject(debugPath, header, { 227 | version: invokeSerializeJson, 228 | compact_target: invokeSerializeJson, 229 | timestamp: invokeSerializeJson, 230 | number: invokeSerializeJson, 231 | epoch: invokeSerializeJson, 232 | parent_hash: invokeSerializeJson, 233 | transactions_root: invokeSerializeJson, 234 | proposals_hash: invokeSerializeJson, 235 | uncles_hash: invokeSerializeJson, 236 | dao: invokeSerializeJson, 237 | nonce: invokeSerializeJson 238 | }); 239 | 240 | if (validation) { 241 | validators.ValidateHeader(header, { 242 | debugPath: `(transformed) ${debugPath}` 243 | }); 244 | } 245 | return header; 246 | } 247 | 248 | export function TransformUncleBlock( 249 | uncleBlock, 250 | { validation = true, debugPath = "uncle_block" } = {} 251 | ) { 252 | uncleBlock = transformObject(debugPath, uncleBlock, { 253 | header: toInvoke(TransformHeader), 254 | proposals: toInvokeArray(invokeSerializeJson) 255 | }); 256 | 257 | if (validation) { 258 | validators.ValidateUncleBlock(uncleBlock, { 259 | debugPath: `(transformed) ${debugPath}` 260 | }); 261 | } 262 | return uncleBlock; 263 | } 264 | 265 | export function TransformBlock( 266 | block, 267 | { validation = true, debugPath = "block" } = {} 268 | ) { 269 | block = transformObject(debugPath, block, { 270 | header: toInvoke(TransformHeader), 271 | uncles: toInvokeArray(toInvoke(TransformUncleBlock)), 272 | transactions: toInvokeArray(toInvoke(TransformTransaction)), 273 | proposals: toInvokeArray(invokeSerializeJson) 274 | }); 275 | 276 | if (validation) { 277 | validators.ValidateBlock(block, { 278 | debugPath: `(transformed) ${debugPath}` 279 | }); 280 | } 281 | return block; 282 | } 283 | 284 | export function TransformCellbaseWitness( 285 | cellbaseWitness, 286 | { validation = true, debugPath = "cellbase_witness" } = {} 287 | ) { 288 | cellbaseWitness = transformObject(debugPath, cellbaseWitness, { 289 | lock: toInvoke(TransformScript), 290 | message: invokeSerializeJson 291 | }); 292 | 293 | if (validation) { 294 | validators.ValidateCellbaseWitness(cellbaseWitness, { 295 | debugPath: `(transformed) ${debugPath}` 296 | }); 297 | } 298 | return cellbaseWitness; 299 | } 300 | 301 | export function TransformWitnessArgs( 302 | witnessArgs, 303 | { validation = true, debugPath = "witness_args" } = {} 304 | ) { 305 | witnessArgs = transformObject(debugPath, witnessArgs, { 306 | lock: invokeSerializeJson, 307 | input_type: invokeSerializeJson, 308 | output_type: invokeSerializeJson 309 | }); 310 | 311 | if (validation) { 312 | validators.ValidateWitnessArgs(witnessArgs, { 313 | debugPath: `(transformed) ${debugPath}` 314 | }); 315 | } 316 | return witnessArgs; 317 | } 318 | -------------------------------------------------------------------------------- /src/validators.js: -------------------------------------------------------------------------------- 1 | // This package provides validator functions that check JSON objects are 2 | // following the correct format, so we can submit them to CKB via RPC 3 | // directly 4 | 5 | function assertObject(debugPath, object) { 6 | if (!(object instanceof Object)) { 7 | throw new Error(`${debugPath} is not an object!`); 8 | } 9 | } 10 | 11 | function assertObjectWithKeys( 12 | debugPath, 13 | object, 14 | expectedKeys, 15 | optionalKeys = [] 16 | ) { 17 | assertObject(debugPath, object); 18 | const providedKeys = Object.keys(object).sort(); 19 | const requiredLength = expectedKeys.length; 20 | const maximalLength = expectedKeys.length + optionalKeys.length; 21 | const errorMessage = `${debugPath} does not have correct keys! Required keys: [${expectedKeys 22 | .sort() 23 | .join(", ")}], optional keys: [${optionalKeys 24 | .sort() 25 | .join(", ")}], actual keys: [${providedKeys.join(", ")}]`; 26 | if ( 27 | providedKeys.length < requiredLength || 28 | providedKeys.length > maximalLength 29 | ) { 30 | throw new Error(errorMessage); 31 | } 32 | let optionalProvidedKeys = providedKeys.filter( 33 | key => !expectedKeys.includes(key) 34 | ); 35 | if (providedKeys.length - optionalProvidedKeys.length !== requiredLength) { 36 | throw new Error(errorMessage); 37 | } 38 | if (optionalProvidedKeys.find(key => !optionalKeys.includes(key))) { 39 | throw new Error(errorMessage); 40 | } 41 | } 42 | 43 | function assertHexString(debugPath, string) { 44 | if (!/^0x([0-9a-fA-F][0-9a-fA-F])*$/.test(string)) { 45 | throw new Error(`${debugPath} must be a hex string!`); 46 | } 47 | } 48 | 49 | function assertHash(debugPath, hash) { 50 | assertHexString(debugPath, hash); 51 | if (hash.length != 66) { 52 | throw new Error(`${debugPath} must be a hex string of 66 bytes long!`); 53 | } 54 | } 55 | 56 | function assertInteger(debugPath, i) { 57 | if (i === "0x0") { 58 | return; 59 | } 60 | if (!/^0x[1-9a-fA-F][0-9a-fA-F]*$/.test(i)) { 61 | throw new Error(`${debugPath} must be a hex integer!`); 62 | } 63 | } 64 | 65 | export function ValidateScript( 66 | script, 67 | { nestedValidation = true, debugPath = "script" } = {} 68 | ) { 69 | assertObjectWithKeys( 70 | debugPath, 71 | script, 72 | ["code_hash", "hash_type", "args"], 73 | [] 74 | ); 75 | assertHash(`${debugPath}.code_hash`, script.code_hash); 76 | assertHexString(`${debugPath}.args`, script.args); 77 | 78 | const DATA_VARIANTS = /^data([1-9][0-9]*)?$/; 79 | if (script.hash_type !== "type" && !DATA_VARIANTS.test(script.hash_type)) { 80 | throw new Error(`${debugPath}.hash_type must be either data or type!`); 81 | } 82 | } 83 | 84 | export function ValidateOutPoint( 85 | outPoint, 86 | { nestedValidation = true, debugPath = "out_point" } = {} 87 | ) { 88 | assertObjectWithKeys(debugPath, outPoint, ["tx_hash", "index"], []); 89 | assertHash(`${debugPath}.tx_hash`, outPoint.tx_hash); 90 | assertInteger(`${debugPath}.index`, outPoint.index); 91 | } 92 | 93 | export function ValidateCellInput( 94 | cellInput, 95 | { nestedValidation = true, debugPath = "cell_input" } = {} 96 | ) { 97 | assertObjectWithKeys(debugPath, cellInput, ["since", "previous_output"], []); 98 | assertInteger(`${debugPath}.since`, cellInput.since); 99 | 100 | if (nestedValidation) { 101 | ValidateOutPoint(cellInput.previous_output, { 102 | debugPath: `${debugPath}.previous_output` 103 | }); 104 | } 105 | } 106 | 107 | export function ValidateCellOutput( 108 | cellOutput, 109 | { nestedValidation = true, debugPath = "cell_output" } = {} 110 | ) { 111 | assertObjectWithKeys(debugPath, cellOutput, ["capacity", "lock"], ["type"]); 112 | assertInteger(`${debugPath}.capacity`, cellOutput.capacity); 113 | 114 | if (nestedValidation) { 115 | ValidateScript(cellOutput.lock, { 116 | debugPath: `${debugPath}.lock` 117 | }); 118 | if (cellOutput.type) { 119 | ValidateScript(cellOutput.type, { 120 | debugPath: `${debugPath}.type` 121 | }); 122 | } 123 | } 124 | } 125 | 126 | export function ValidateCellDep( 127 | cellDep, 128 | { nestedValidation = true, debugPath = "cell_dep" } = {} 129 | ) { 130 | assertObjectWithKeys(debugPath, cellDep, ["out_point", "dep_type"], []); 131 | if (cellDep.dep_type !== "code" && cellDep.dep_type !== "dep_group") { 132 | throw new Error(`${debugPath}.dep_type must be either code or dep_group!`); 133 | } 134 | 135 | if (nestedValidation) { 136 | ValidateOutPoint(cellDep.out_point, { 137 | debugPath: `${debugPath}.out_point` 138 | }); 139 | } 140 | } 141 | 142 | function assertArray(debugPath, array, validateFunction, nestedValidation) { 143 | if (!Array.isArray(array)) { 144 | throw new Error(`${debugPath} is not an array!`); 145 | } 146 | if (nestedValidation) { 147 | for (let i = 0; i < array.length; i++) { 148 | validateFunction(`${debugPath}[${i}]`, array[i]); 149 | } 150 | } 151 | } 152 | 153 | function toAssert(validateFunction, nestedValidation) { 154 | return function(debugPath, value) { 155 | validateFunction(value, { 156 | nestedValidation: nestedValidation, 157 | debugPath: debugPath 158 | }); 159 | }; 160 | } 161 | 162 | function assertCommonTransaction(debugPath, rawTransaction, nestedValidation) { 163 | assertInteger(`${debugPath}.version`, rawTransaction.version); 164 | assertArray( 165 | `${debugPath}.cell_deps`, 166 | rawTransaction.cell_deps, 167 | toAssert(ValidateCellDep, nestedValidation), 168 | nestedValidation 169 | ); 170 | assertArray( 171 | `${debugPath}.header_deps`, 172 | rawTransaction.header_deps, 173 | assertHash, 174 | nestedValidation 175 | ); 176 | assertArray( 177 | `${debugPath}.inputs`, 178 | rawTransaction.inputs, 179 | toAssert(ValidateCellInput, nestedValidation), 180 | nestedValidation 181 | ); 182 | assertArray( 183 | `${debugPath}.outputs`, 184 | rawTransaction.outputs, 185 | toAssert(ValidateCellOutput, nestedValidation), 186 | nestedValidation 187 | ); 188 | assertArray( 189 | `${debugPath}.outputs_data`, 190 | rawTransaction.outputs_data, 191 | assertHexString, 192 | nestedValidation 193 | ); 194 | } 195 | 196 | export function ValidateRawTransaction( 197 | rawTransaction, 198 | { nestedValidation = true, debugPath = "raw_transaction" } = {} 199 | ) { 200 | assertObjectWithKeys( 201 | debugPath, 202 | rawTransaction, 203 | [ 204 | "version", 205 | "cell_deps", 206 | "header_deps", 207 | "inputs", 208 | "outputs", 209 | "outputs_data" 210 | ], 211 | [] 212 | ); 213 | assertCommonTransaction(debugPath, rawTransaction, nestedValidation); 214 | } 215 | 216 | export function ValidateTransaction( 217 | transaction, 218 | { nestedValidation = true, debugPath = "transaction" } = {} 219 | ) { 220 | assertObjectWithKeys( 221 | debugPath, 222 | transaction, 223 | [ 224 | "version", 225 | "cell_deps", 226 | "header_deps", 227 | "inputs", 228 | "outputs", 229 | "outputs_data", 230 | "witnesses" 231 | ], 232 | [] 233 | ); 234 | assertCommonTransaction(debugPath, transaction, nestedValidation); 235 | assertArray( 236 | `${debugPath}.witnesses`, 237 | transaction.witnesses, 238 | assertHexString, 239 | nestedValidation 240 | ); 241 | } 242 | 243 | function assertCommonHeader(debugPath, rawHeader) { 244 | assertInteger(`${debugPath}.version`, rawHeader.version); 245 | assertInteger(`${debugPath}.compact_target`, rawHeader.compact_target); 246 | assertInteger(`${debugPath}.timestamp`, rawHeader.timestamp); 247 | assertInteger(`${debugPath}.number`, rawHeader.number); 248 | assertInteger(`${debugPath}.epoch`, rawHeader.epoch); 249 | assertHash(`${debugPath}.parent_hash`, rawHeader.parent_hash); 250 | assertHash(`${debugPath}.transactions_root`, rawHeader.transactions_root); 251 | assertHash(`${debugPath}.proposals_hash`, rawHeader.proposals_hash); 252 | assertHash(`${debugPath}.uncles_hash`, rawHeader.uncles_hash); 253 | assertHash(`${debugPath}.dao`, rawHeader.dao); 254 | } 255 | 256 | export function ValidateRawHeader( 257 | rawHeader, 258 | { nestedValidation = true, debugPath = "raw_header" } = {} 259 | ) { 260 | assertObjectWithKeys( 261 | debugPath, 262 | rawHeader, 263 | [ 264 | "version", 265 | "compact_target", 266 | "timestamp", 267 | "number", 268 | "epoch", 269 | "parent_hash", 270 | "transactions_root", 271 | "proposals_hash", 272 | "uncles_hash", 273 | "dao" 274 | ], 275 | [] 276 | ); 277 | assertCommonHeader(debugPath, rawHeader); 278 | } 279 | 280 | export function ValidateHeader( 281 | header, 282 | { nestedValidation = true, debugPath = "header" } = {} 283 | ) { 284 | assertObjectWithKeys( 285 | debugPath, 286 | header, 287 | [ 288 | "version", 289 | "compact_target", 290 | "timestamp", 291 | "number", 292 | "epoch", 293 | "parent_hash", 294 | "transactions_root", 295 | "proposals_hash", 296 | "uncles_hash", 297 | "dao", 298 | "nonce" 299 | ], 300 | [] 301 | ); 302 | assertHexString(`${debugPath}.nonce`, header.nonce); 303 | if (header.nonce.length != 34) { 304 | throw new Error( 305 | `${debugPath}.nonce must be a hex string of 34 bytes long!` 306 | ); 307 | } 308 | } 309 | 310 | function assertProposalShortId(debugPath, shortId) { 311 | assertHexString(debugPath, shortId); 312 | if (shortId.length != 22) { 313 | throw new Error(`${debugPath} must be a hex string of 22 bytes long!`); 314 | } 315 | } 316 | 317 | export function ValidateUncleBlock( 318 | uncleBlock, 319 | { nestedValidation = true, debugPath = "uncle_block" } = {} 320 | ) { 321 | assertObjectWithKeys(debugPath, uncleBlock, ["header", "proposals"], []); 322 | 323 | if (nestedValidation) { 324 | ValidateHeader(uncleBlock.header, { 325 | debugPath: `${debugPath}.header` 326 | }); 327 | } 328 | assertArray( 329 | `${debugPath}.proposals`, 330 | uncleBlock.proposals, 331 | assertProposalShortId, 332 | nestedValidation 333 | ); 334 | } 335 | 336 | export function ValidateBlock( 337 | block, 338 | { nestedValidation = true, debugPath = "block" } = {} 339 | ) { 340 | assertObjectWithKeys( 341 | debugPath, 342 | block, 343 | ["header", "uncles", "transactions", "proposals"], 344 | [] 345 | ); 346 | 347 | if (nestedValidation) { 348 | ValidateHeader(block.header, { 349 | debugPath: `${debugPath}.header` 350 | }); 351 | } 352 | assertArray( 353 | `${debugPath}.uncles`, 354 | block.uncles, 355 | toAssert(ValidateUncleBlock, nestedValidation), 356 | nestedValidation 357 | ); 358 | assertArray( 359 | `${debugPath}.transactions`, 360 | block.transactions, 361 | toAssert(ValidateTransaction, nestedValidation), 362 | nestedValidation 363 | ); 364 | assertArray( 365 | `${debugPath}.proposals`, 366 | block.proposals, 367 | assertProposalShortId, 368 | nestedValidation 369 | ); 370 | } 371 | 372 | export function ValidateCellbaseWitness( 373 | cellbaseWitness, 374 | { nestedValidation = true, debugPath = "cellbase_witness" } = {} 375 | ) { 376 | assertObjectWithKeys(debugPath, cellbaseWitness, ["lock", "message"], []); 377 | assertHexString(`${debugPath}.message`, cellbaseWitness.message); 378 | 379 | if (nestedValidation) { 380 | ValidateScript(cellbaseWitness.lock, { 381 | debugPath: `${debugPath}.lock` 382 | }); 383 | } 384 | } 385 | 386 | export function ValidateWitnessArgs( 387 | witnessArgs, 388 | { nestedValidation = true, debugPath = "witness_args" } = {} 389 | ) { 390 | assertObjectWithKeys( 391 | debugPath, 392 | witnessArgs, 393 | [], 394 | ["lock", "input_type", "output_type"] 395 | ); 396 | 397 | if (witnessArgs.lock) { 398 | assertHexString(`${debugPath}.lock`, witnessArgs.lock); 399 | } 400 | if (witnessArgs.input_type) { 401 | assertHexString(`${debugPath}.input_type`, witnessArgs.input_type); 402 | } 403 | if (witnessArgs.output_type) { 404 | assertHexString(`${debugPath}.output_type`, witnessArgs.output_type); 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /testfiles/blockchain.umd.js: -------------------------------------------------------------------------------- 1 | (function (global, factory) { 2 | typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : 3 | typeof define === 'function' && define.amd ? define(['exports'], factory) : 4 | (global = global || self, factory(global.Blockchain = {})); 5 | }(this, (function (exports) { 'use strict'; 6 | 7 | function dataLengthError(actual, required) { 8 | throw new Error(`Invalid data length! Required: ${required}, actual: ${actual}`); 9 | } 10 | 11 | function assertDataLength(actual, required) { 12 | if (actual !== required) { 13 | dataLengthError(actual, required); 14 | } 15 | } 16 | 17 | function assertArrayBuffer(reader) { 18 | if (reader instanceof Object && reader.toArrayBuffer instanceof Function) { 19 | reader = reader.toArrayBuffer(); 20 | } 21 | if (!(reader instanceof ArrayBuffer)) { 22 | throw new Error("Provided value must be an ArrayBuffer or can be transformed into ArrayBuffer!"); 23 | } 24 | return reader; 25 | } 26 | 27 | function verifyAndExtractOffsets(view, expectedFieldCount, compatible) { 28 | if (view.byteLength < 4) { 29 | dataLengthError(view.byteLength, ">4"); 30 | } 31 | const requiredByteLength = view.getUint32(0, true); 32 | assertDataLength(view.byteLength, requiredByteLength); 33 | if (requiredByteLength === 4) { 34 | return [requiredByteLength]; 35 | } 36 | if (requiredByteLength < 8) { 37 | dataLengthError(view.byteLength, ">8"); 38 | } 39 | const firstOffset = view.getUint32(4, true); 40 | if (firstOffset % 4 !== 0 || firstOffset < 8) { 41 | throw new Error(`Invalid first offset: ${firstOffset}`); 42 | } 43 | const itemCount = firstOffset / 4 - 1; 44 | if (itemCount < expectedFieldCount) { 45 | throw new Error(`Item count not enough! Required: ${expectedFieldCount}, actual: ${itemCount}`); 46 | } else if ((!compatible) && itemCount > expectedFieldCount) { 47 | throw new Error(`Item count is more than required! Required: ${expectedFieldCount}, actual: ${itemCount}`); 48 | } 49 | if (requiredByteLength < firstOffset) { 50 | throw new Error(`First offset is larger than byte length: ${firstOffset}`); 51 | } 52 | const offsets = []; 53 | for (let i = 0; i < itemCount; i++) { 54 | const start = 4 + i * 4; 55 | offsets.push(view.getUint32(start, true)); 56 | } 57 | offsets.push(requiredByteLength); 58 | for (let i = 0; i < offsets.length - 1; i++) { 59 | if (offsets[i] > offsets[i + 1]) { 60 | throw new Error(`Offset index ${i}: ${offsets[i]} is larger than offset index ${i + 1}: ${offsets[i + 1]}`); 61 | } 62 | } 63 | return offsets; 64 | } 65 | 66 | function serializeTable(buffers) { 67 | const itemCount = buffers.length; 68 | let totalSize = 4 * (itemCount + 1); 69 | const offsets = []; 70 | 71 | for (let i = 0; i < itemCount; i++) { 72 | offsets.push(totalSize); 73 | totalSize += buffers[i].byteLength; 74 | } 75 | 76 | const buffer = new ArrayBuffer(totalSize); 77 | const array = new Uint8Array(buffer); 78 | const view = new DataView(buffer); 79 | 80 | view.setUint32(0, totalSize, true); 81 | for (let i = 0; i < itemCount; i++) { 82 | view.setUint32(4 + i * 4, offsets[i], true); 83 | array.set(new Uint8Array(buffers[i]), offsets[i]); 84 | } 85 | return buffer; 86 | } 87 | class Uint32 { 88 | constructor(reader, { validate = true } = {}) { 89 | this.view = new DataView(assertArrayBuffer(reader)); 90 | if (validate) { 91 | this.validate(); 92 | } 93 | } 94 | 95 | validate(compatible = false) { 96 | assertDataLength(this.view.byteLength, 4); 97 | } 98 | 99 | indexAt(i) { 100 | return this.view.getUint8(i); 101 | } 102 | 103 | raw() { 104 | return this.view.buffer; 105 | } 106 | 107 | toBigEndianUint32() { 108 | return this.view.getUint32(0, false); 109 | } 110 | 111 | toLittleEndianUint32() { 112 | return this.view.getUint32(0, true); 113 | } 114 | 115 | static size() { 116 | return 4; 117 | } 118 | } 119 | 120 | function SerializeUint32(value) { 121 | const buffer = assertArrayBuffer(value); 122 | assertDataLength(buffer.byteLength, 4); 123 | return buffer; 124 | } 125 | 126 | class Uint64 { 127 | constructor(reader, { validate = true } = {}) { 128 | this.view = new DataView(assertArrayBuffer(reader)); 129 | if (validate) { 130 | this.validate(); 131 | } 132 | } 133 | 134 | validate(compatible = false) { 135 | assertDataLength(this.view.byteLength, 8); 136 | } 137 | 138 | indexAt(i) { 139 | return this.view.getUint8(i); 140 | } 141 | 142 | raw() { 143 | return this.view.buffer; 144 | } 145 | 146 | toBigEndianBigUint64() { 147 | return this.view.getBigUint64(0, false); 148 | } 149 | 150 | toLittleEndianBigUint64() { 151 | return this.view.getUint64(0, true); 152 | } 153 | 154 | static size() { 155 | return 8; 156 | } 157 | } 158 | 159 | function SerializeUint64(value) { 160 | const buffer = assertArrayBuffer(value); 161 | assertDataLength(buffer.byteLength, 8); 162 | return buffer; 163 | } 164 | 165 | class Uint128 { 166 | constructor(reader, { validate = true } = {}) { 167 | this.view = new DataView(assertArrayBuffer(reader)); 168 | if (validate) { 169 | this.validate(); 170 | } 171 | } 172 | 173 | validate(compatible = false) { 174 | assertDataLength(this.view.byteLength, 16); 175 | } 176 | 177 | indexAt(i) { 178 | return this.view.getUint8(i); 179 | } 180 | 181 | raw() { 182 | return this.view.buffer; 183 | } 184 | 185 | static size() { 186 | return 16; 187 | } 188 | } 189 | 190 | function SerializeUint128(value) { 191 | const buffer = assertArrayBuffer(value); 192 | assertDataLength(buffer.byteLength, 16); 193 | return buffer; 194 | } 195 | 196 | class Byte32 { 197 | constructor(reader, { validate = true } = {}) { 198 | this.view = new DataView(assertArrayBuffer(reader)); 199 | if (validate) { 200 | this.validate(); 201 | } 202 | } 203 | 204 | validate(compatible = false) { 205 | assertDataLength(this.view.byteLength, 32); 206 | } 207 | 208 | indexAt(i) { 209 | return this.view.getUint8(i); 210 | } 211 | 212 | raw() { 213 | return this.view.buffer; 214 | } 215 | 216 | static size() { 217 | return 32; 218 | } 219 | } 220 | 221 | function SerializeByte32(value) { 222 | const buffer = assertArrayBuffer(value); 223 | assertDataLength(buffer.byteLength, 32); 224 | return buffer; 225 | } 226 | 227 | class Uint256 { 228 | constructor(reader, { validate = true } = {}) { 229 | this.view = new DataView(assertArrayBuffer(reader)); 230 | if (validate) { 231 | this.validate(); 232 | } 233 | } 234 | 235 | validate(compatible = false) { 236 | assertDataLength(this.view.byteLength, 32); 237 | } 238 | 239 | indexAt(i) { 240 | return this.view.getUint8(i); 241 | } 242 | 243 | raw() { 244 | return this.view.buffer; 245 | } 246 | 247 | static size() { 248 | return 32; 249 | } 250 | } 251 | 252 | function SerializeUint256(value) { 253 | const buffer = assertArrayBuffer(value); 254 | assertDataLength(buffer.byteLength, 32); 255 | return buffer; 256 | } 257 | 258 | class Bytes { 259 | constructor(reader, { validate = true } = {}) { 260 | this.view = new DataView(assertArrayBuffer(reader)); 261 | if (validate) { 262 | this.validate(); 263 | } 264 | } 265 | 266 | validate(compatible = false) { 267 | if (this.view.byteLength < 4) { 268 | dataLengthError(this.view.byteLength, ">4"); 269 | } 270 | const requiredByteLength = this.length() + 4; 271 | assertDataLength(this.view.byteLength, requiredByteLength); 272 | } 273 | 274 | raw() { 275 | return this.view.buffer.slice(4); 276 | } 277 | 278 | indexAt(i) { 279 | return this.view.getUint8(4 + i); 280 | } 281 | 282 | length() { 283 | return this.view.getUint32(0, true); 284 | } 285 | } 286 | 287 | function SerializeBytes(value) { 288 | const item = assertArrayBuffer(value); 289 | const array = new Uint8Array(4 + item.byteLength); 290 | (new DataView(array.buffer)).setUint32(0, item.byteLength, true); 291 | array.set(new Uint8Array(item), 4); 292 | return array.buffer; 293 | } 294 | 295 | class BytesOpt { 296 | constructor(reader, { validate = true } = {}) { 297 | this.view = new DataView(assertArrayBuffer(reader)); 298 | if (validate) { 299 | this.validate(); 300 | } 301 | } 302 | 303 | validate(compatible = false) { 304 | if (this.hasValue()) { 305 | this.value().validate(compatible); 306 | } 307 | } 308 | 309 | value() { 310 | return new Bytes(this.view.buffer, { validate: false }); 311 | } 312 | 313 | hasValue() { 314 | return this.view.byteLength > 0; 315 | } 316 | } 317 | 318 | function SerializeBytesOpt(value) { 319 | if (value) { 320 | return SerializeBytes(value); 321 | } else { 322 | return new ArrayBuffer(0); 323 | } 324 | } 325 | 326 | class BytesVec { 327 | constructor(reader, { validate = true } = {}) { 328 | this.view = new DataView(assertArrayBuffer(reader)); 329 | if (validate) { 330 | this.validate(); 331 | } 332 | } 333 | 334 | validate(compatible = false) { 335 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 336 | for (let i = 0; i < len(offsets) - 1; i++) { 337 | new Bytes(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); 338 | } 339 | } 340 | 341 | length() { 342 | if (this.view.byteLength < 8) { 343 | return 0; 344 | } else { 345 | return this.view.getUint32(4, true) / 4 - 1; 346 | } 347 | } 348 | 349 | indexAt(i) { 350 | const start = 4 + i * 4; 351 | const offset = this.view.getUint32(start, true); 352 | let offset_end = this.view.byteLength; 353 | if (i + 1 < this.length()) { 354 | offset_end = this.view.getUint32(start + 4, true); 355 | } 356 | return new Bytes(this.view.buffer.slice(offset, offset_end), { validate: false }); 357 | } 358 | } 359 | 360 | function SerializeBytesVec(value) { 361 | return serializeTable(value.map(item => SerializeBytes(item))); 362 | } 363 | 364 | class Byte32Vec { 365 | constructor(reader, { validate = true } = {}) { 366 | this.view = new DataView(assertArrayBuffer(reader)); 367 | if (validate) { 368 | this.validate(); 369 | } 370 | } 371 | 372 | validate(compatible = false) { 373 | if (this.view.byteLength < 4) { 374 | dataLengthError(this.view.byteLength, ">4"); 375 | } 376 | const requiredByteLength = this.length() * Byte32.size() + 4; 377 | assertDataLength(this.view.byteLength, requiredByteLength); 378 | for (let i = 0; i < 0; i++) { 379 | const item = this.indexAt(i); 380 | item.validate(compatible); 381 | } 382 | } 383 | 384 | indexAt(i) { 385 | return new Byte32(this.view.buffer.slice(4 + i * Byte32.size(), 4 + (i + 1) * Byte32.size()), { validate: false }); 386 | } 387 | 388 | length() { 389 | return this.view.getUint32(0, true); 390 | } 391 | } 392 | 393 | function SerializeByte32Vec(value) { 394 | const array = new Uint8Array(4 + Byte32.size() * value.length); 395 | (new DataView(array.buffer)).setUint32(0, value.length, true); 396 | for (let i = 0; i < value.length; i++) { 397 | const itemBuffer = SerializeByte32(value[i]); 398 | array.set(new Uint8Array(itemBuffer), 4 + i * Byte32.size()); 399 | } 400 | return array.buffer; 401 | } 402 | 403 | class ScriptOpt { 404 | constructor(reader, { validate = true } = {}) { 405 | this.view = new DataView(assertArrayBuffer(reader)); 406 | if (validate) { 407 | this.validate(); 408 | } 409 | } 410 | 411 | validate(compatible = false) { 412 | if (this.hasValue()) { 413 | this.value().validate(compatible); 414 | } 415 | } 416 | 417 | value() { 418 | return new Script(this.view.buffer, { validate: false }); 419 | } 420 | 421 | hasValue() { 422 | return this.view.byteLength > 0; 423 | } 424 | } 425 | 426 | function SerializeScriptOpt(value) { 427 | if (value) { 428 | return SerializeScript(value); 429 | } else { 430 | return new ArrayBuffer(0); 431 | } 432 | } 433 | 434 | class ProposalShortId { 435 | constructor(reader, { validate = true } = {}) { 436 | this.view = new DataView(assertArrayBuffer(reader)); 437 | if (validate) { 438 | this.validate(); 439 | } 440 | } 441 | 442 | validate(compatible = false) { 443 | assertDataLength(this.view.byteLength, 10); 444 | } 445 | 446 | indexAt(i) { 447 | return this.view.getUint8(i); 448 | } 449 | 450 | raw() { 451 | return this.view.buffer; 452 | } 453 | 454 | static size() { 455 | return 10; 456 | } 457 | } 458 | 459 | function SerializeProposalShortId(value) { 460 | const buffer = assertArrayBuffer(value); 461 | assertDataLength(buffer.byteLength, 10); 462 | return buffer; 463 | } 464 | 465 | class UncleBlockVec { 466 | constructor(reader, { validate = true } = {}) { 467 | this.view = new DataView(assertArrayBuffer(reader)); 468 | if (validate) { 469 | this.validate(); 470 | } 471 | } 472 | 473 | validate(compatible = false) { 474 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 475 | for (let i = 0; i < len(offsets) - 1; i++) { 476 | new UncleBlock(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); 477 | } 478 | } 479 | 480 | length() { 481 | if (this.view.byteLength < 8) { 482 | return 0; 483 | } else { 484 | return this.view.getUint32(4, true) / 4 - 1; 485 | } 486 | } 487 | 488 | indexAt(i) { 489 | const start = 4 + i * 4; 490 | const offset = this.view.getUint32(start, true); 491 | let offset_end = this.view.byteLength; 492 | if (i + 1 < this.length()) { 493 | offset_end = this.view.getUint32(start + 4, true); 494 | } 495 | return new UncleBlock(this.view.buffer.slice(offset, offset_end), { validate: false }); 496 | } 497 | } 498 | 499 | function SerializeUncleBlockVec(value) { 500 | return serializeTable(value.map(item => SerializeUncleBlock(item))); 501 | } 502 | 503 | class TransactionVec { 504 | constructor(reader, { validate = true } = {}) { 505 | this.view = new DataView(assertArrayBuffer(reader)); 506 | if (validate) { 507 | this.validate(); 508 | } 509 | } 510 | 511 | validate(compatible = false) { 512 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 513 | for (let i = 0; i < len(offsets) - 1; i++) { 514 | new Transaction(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); 515 | } 516 | } 517 | 518 | length() { 519 | if (this.view.byteLength < 8) { 520 | return 0; 521 | } else { 522 | return this.view.getUint32(4, true) / 4 - 1; 523 | } 524 | } 525 | 526 | indexAt(i) { 527 | const start = 4 + i * 4; 528 | const offset = this.view.getUint32(start, true); 529 | let offset_end = this.view.byteLength; 530 | if (i + 1 < this.length()) { 531 | offset_end = this.view.getUint32(start + 4, true); 532 | } 533 | return new Transaction(this.view.buffer.slice(offset, offset_end), { validate: false }); 534 | } 535 | } 536 | 537 | function SerializeTransactionVec(value) { 538 | return serializeTable(value.map(item => SerializeTransaction(item))); 539 | } 540 | 541 | class ProposalShortIdVec { 542 | constructor(reader, { validate = true } = {}) { 543 | this.view = new DataView(assertArrayBuffer(reader)); 544 | if (validate) { 545 | this.validate(); 546 | } 547 | } 548 | 549 | validate(compatible = false) { 550 | if (this.view.byteLength < 4) { 551 | dataLengthError(this.view.byteLength, ">4"); 552 | } 553 | const requiredByteLength = this.length() * ProposalShortId.size() + 4; 554 | assertDataLength(this.view.byteLength, requiredByteLength); 555 | for (let i = 0; i < 0; i++) { 556 | const item = this.indexAt(i); 557 | item.validate(compatible); 558 | } 559 | } 560 | 561 | indexAt(i) { 562 | return new ProposalShortId(this.view.buffer.slice(4 + i * ProposalShortId.size(), 4 + (i + 1) * ProposalShortId.size()), { validate: false }); 563 | } 564 | 565 | length() { 566 | return this.view.getUint32(0, true); 567 | } 568 | } 569 | 570 | function SerializeProposalShortIdVec(value) { 571 | const array = new Uint8Array(4 + ProposalShortId.size() * value.length); 572 | (new DataView(array.buffer)).setUint32(0, value.length, true); 573 | for (let i = 0; i < value.length; i++) { 574 | const itemBuffer = SerializeProposalShortId(value[i]); 575 | array.set(new Uint8Array(itemBuffer), 4 + i * ProposalShortId.size()); 576 | } 577 | return array.buffer; 578 | } 579 | 580 | class CellDepVec { 581 | constructor(reader, { validate = true } = {}) { 582 | this.view = new DataView(assertArrayBuffer(reader)); 583 | if (validate) { 584 | this.validate(); 585 | } 586 | } 587 | 588 | validate(compatible = false) { 589 | if (this.view.byteLength < 4) { 590 | dataLengthError(this.view.byteLength, ">4"); 591 | } 592 | const requiredByteLength = this.length() * CellDep.size() + 4; 593 | assertDataLength(this.view.byteLength, requiredByteLength); 594 | for (let i = 0; i < 0; i++) { 595 | const item = this.indexAt(i); 596 | item.validate(compatible); 597 | } 598 | } 599 | 600 | indexAt(i) { 601 | return new CellDep(this.view.buffer.slice(4 + i * CellDep.size(), 4 + (i + 1) * CellDep.size()), { validate: false }); 602 | } 603 | 604 | length() { 605 | return this.view.getUint32(0, true); 606 | } 607 | } 608 | 609 | function SerializeCellDepVec(value) { 610 | const array = new Uint8Array(4 + CellDep.size() * value.length); 611 | (new DataView(array.buffer)).setUint32(0, value.length, true); 612 | for (let i = 0; i < value.length; i++) { 613 | const itemBuffer = SerializeCellDep(value[i]); 614 | array.set(new Uint8Array(itemBuffer), 4 + i * CellDep.size()); 615 | } 616 | return array.buffer; 617 | } 618 | 619 | class CellInputVec { 620 | constructor(reader, { validate = true } = {}) { 621 | this.view = new DataView(assertArrayBuffer(reader)); 622 | if (validate) { 623 | this.validate(); 624 | } 625 | } 626 | 627 | validate(compatible = false) { 628 | if (this.view.byteLength < 4) { 629 | dataLengthError(this.view.byteLength, ">4"); 630 | } 631 | const requiredByteLength = this.length() * CellInput.size() + 4; 632 | assertDataLength(this.view.byteLength, requiredByteLength); 633 | for (let i = 0; i < 0; i++) { 634 | const item = this.indexAt(i); 635 | item.validate(compatible); 636 | } 637 | } 638 | 639 | indexAt(i) { 640 | return new CellInput(this.view.buffer.slice(4 + i * CellInput.size(), 4 + (i + 1) * CellInput.size()), { validate: false }); 641 | } 642 | 643 | length() { 644 | return this.view.getUint32(0, true); 645 | } 646 | } 647 | 648 | function SerializeCellInputVec(value) { 649 | const array = new Uint8Array(4 + CellInput.size() * value.length); 650 | (new DataView(array.buffer)).setUint32(0, value.length, true); 651 | for (let i = 0; i < value.length; i++) { 652 | const itemBuffer = SerializeCellInput(value[i]); 653 | array.set(new Uint8Array(itemBuffer), 4 + i * CellInput.size()); 654 | } 655 | return array.buffer; 656 | } 657 | 658 | class CellOutputVec { 659 | constructor(reader, { validate = true } = {}) { 660 | this.view = new DataView(assertArrayBuffer(reader)); 661 | if (validate) { 662 | this.validate(); 663 | } 664 | } 665 | 666 | validate(compatible = false) { 667 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 668 | for (let i = 0; i < len(offsets) - 1; i++) { 669 | new CellOutput(this.view.buffer.slice(offsets[i], offsets[i + 1]), { validate: false }).validate(); 670 | } 671 | } 672 | 673 | length() { 674 | if (this.view.byteLength < 8) { 675 | return 0; 676 | } else { 677 | return this.view.getUint32(4, true) / 4 - 1; 678 | } 679 | } 680 | 681 | indexAt(i) { 682 | const start = 4 + i * 4; 683 | const offset = this.view.getUint32(start, true); 684 | let offset_end = this.view.byteLength; 685 | if (i + 1 < this.length()) { 686 | offset_end = this.view.getUint32(start + 4, true); 687 | } 688 | return new CellOutput(this.view.buffer.slice(offset, offset_end), { validate: false }); 689 | } 690 | } 691 | 692 | function SerializeCellOutputVec(value) { 693 | return serializeTable(value.map(item => SerializeCellOutput(item))); 694 | } 695 | 696 | class Script { 697 | constructor(reader, { validate = true } = {}) { 698 | this.view = new DataView(assertArrayBuffer(reader)); 699 | if (validate) { 700 | this.validate(); 701 | } 702 | } 703 | 704 | validate(compatible = false) { 705 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 706 | new Byte32(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 707 | if (offsets[2] - offsets[1] !== 1) { 708 | throw new Error(`Invalid offset for hash_type: ${offsets[1]} - ${offsets[2]}`) 709 | } 710 | new Bytes(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); 711 | } 712 | 713 | getCodeHash() { 714 | const start = 4; 715 | const offset = this.view.getUint32(start, true); 716 | const offset_end = this.view.getUint32(start + 4, true); 717 | return new Byte32(this.view.buffer.slice(offset, offset_end), { validate: false }); 718 | } 719 | 720 | getHashType() { 721 | const start = 8; 722 | const offset = this.view.getUint32(start, true); 723 | const offset_end = this.view.getUint32(start + 4, true); 724 | return new DataView(this.view.buffer.slice(offset, offset_end)).getUint8(0); 725 | } 726 | 727 | getArgs() { 728 | const start = 12; 729 | const offset = this.view.getUint32(start, true); 730 | const offset_end = this.view.byteLength; 731 | return new Bytes(this.view.buffer.slice(offset, offset_end), { validate: false }); 732 | } 733 | } 734 | 735 | function SerializeScript(value) { 736 | const buffers = []; 737 | buffers.push(SerializeByte32(value.code_hash)); 738 | const hashTypeView = new DataView(new ArrayBuffer(1)); 739 | hashTypeView.setUint8(0, value.hash_type); 740 | buffers.push(hashTypeView.buffer); 741 | buffers.push(SerializeBytes(value.args)); 742 | return serializeTable(buffers); 743 | } 744 | 745 | class OutPoint { 746 | constructor(reader, { validate = true } = {}) { 747 | this.view = new DataView(assertArrayBuffer(reader)); 748 | if (validate) { 749 | this.validate(); 750 | } 751 | } 752 | 753 | getTxHash() { 754 | return new Byte32(this.view.buffer.slice(0, Byte32.size()), { validate: false }); 755 | } 756 | 757 | getIndex() { 758 | return new Uint32(this.view.buffer.slice(0 + Byte32.size(), Uint32.size()), { validate: false }); 759 | } 760 | 761 | validate(compatible = false) { 762 | assertDataLength(this.view.byteLength, this.size()); 763 | this.getTxHash().validate(compatible); 764 | this.getIndex().validate(compatible); 765 | } 766 | static size() { 767 | return 0 + Byte32.size() + Uint32.size(); 768 | } 769 | } 770 | 771 | function SerializeOutPoint(value) { 772 | const array = new Uint8Array(0 + Byte32.size() + Uint32.size()); 773 | array.set(new Uint8Array(SerializeByte32(value.tx_hash)), 0); 774 | array.set(new Uint8Array(SerializeUint32(value.index)), 0 + Byte32.size()); 775 | return array.buffer; 776 | } 777 | 778 | class CellInput { 779 | constructor(reader, { validate = true } = {}) { 780 | this.view = new DataView(assertArrayBuffer(reader)); 781 | if (validate) { 782 | this.validate(); 783 | } 784 | } 785 | 786 | getSince() { 787 | return new Uint64(this.view.buffer.slice(0, Uint64.size()), { validate: false }); 788 | } 789 | 790 | getPreviousOutput() { 791 | return new OutPoint(this.view.buffer.slice(0 + Uint64.size(), OutPoint.size()), { validate: false }); 792 | } 793 | 794 | validate(compatible = false) { 795 | assertDataLength(this.view.byteLength, this.size()); 796 | this.getSince().validate(compatible); 797 | this.getPreviousOutput().validate(compatible); 798 | } 799 | static size() { 800 | return 0 + Uint64.size() + OutPoint.size(); 801 | } 802 | } 803 | 804 | function SerializeCellInput(value) { 805 | const array = new Uint8Array(0 + Uint64.size() + OutPoint.size()); 806 | array.set(new Uint8Array(SerializeUint64(value.since)), 0); 807 | array.set(new Uint8Array(SerializeOutPoint(value.previous_output)), 0 + Uint64.size()); 808 | return array.buffer; 809 | } 810 | 811 | class CellOutput { 812 | constructor(reader, { validate = true } = {}) { 813 | this.view = new DataView(assertArrayBuffer(reader)); 814 | if (validate) { 815 | this.validate(); 816 | } 817 | } 818 | 819 | validate(compatible = false) { 820 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 821 | new Uint64(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 822 | new Script(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 823 | new ScriptOpt(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); 824 | } 825 | 826 | getCapacity() { 827 | const start = 4; 828 | const offset = this.view.getUint32(start, true); 829 | const offset_end = this.view.getUint32(start + 4, true); 830 | return new Uint64(this.view.buffer.slice(offset, offset_end), { validate: false }); 831 | } 832 | 833 | getLock() { 834 | const start = 8; 835 | const offset = this.view.getUint32(start, true); 836 | const offset_end = this.view.getUint32(start + 4, true); 837 | return new Script(this.view.buffer.slice(offset, offset_end), { validate: false }); 838 | } 839 | 840 | getType() { 841 | const start = 12; 842 | const offset = this.view.getUint32(start, true); 843 | const offset_end = this.view.byteLength; 844 | return new ScriptOpt(this.view.buffer.slice(offset, offset_end), { validate: false }); 845 | } 846 | } 847 | 848 | function SerializeCellOutput(value) { 849 | const buffers = []; 850 | buffers.push(SerializeUint64(value.capacity)); 851 | buffers.push(SerializeScript(value.lock)); 852 | buffers.push(SerializeScriptOpt(value.type_)); 853 | return serializeTable(buffers); 854 | } 855 | 856 | class CellDep { 857 | constructor(reader, { validate = true } = {}) { 858 | this.view = new DataView(assertArrayBuffer(reader)); 859 | if (validate) { 860 | this.validate(); 861 | } 862 | } 863 | 864 | getOutPoint() { 865 | return new OutPoint(this.view.buffer.slice(0, OutPoint.size()), { validate: false }); 866 | } 867 | 868 | getDepType() { 869 | return this.view.getUint8(0 + OutPoint.size()); 870 | } 871 | 872 | validate(compatible = false) { 873 | assertDataLength(this.view.byteLength, this.size()); 874 | this.getOutPoint().validate(compatible); 875 | } 876 | static size() { 877 | return 0 + OutPoint.size() + 1; 878 | } 879 | } 880 | 881 | function SerializeCellDep(value) { 882 | const array = new Uint8Array(0 + OutPoint.size() + 1); 883 | const view = new DataView(array.buffer); 884 | array.set(new Uint8Array(SerializeOutPoint(value.out_point)), 0); 885 | view.setUint8(0 + OutPoint.size(), value.dep_type); 886 | return array.buffer; 887 | } 888 | 889 | class RawTransaction { 890 | constructor(reader, { validate = true } = {}) { 891 | this.view = new DataView(assertArrayBuffer(reader)); 892 | if (validate) { 893 | this.validate(); 894 | } 895 | } 896 | 897 | validate(compatible = false) { 898 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 899 | new Uint32(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 900 | new CellDepVec(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 901 | new Byte32Vec(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); 902 | new CellInputVec(this.view.buffer.slice(offsets[3], offsets[4]), { validate: false }).validate(); 903 | new CellOutputVec(this.view.buffer.slice(offsets[4], offsets[5]), { validate: false }).validate(); 904 | new BytesVec(this.view.buffer.slice(offsets[5], offsets[6]), { validate: false }).validate(); 905 | } 906 | 907 | getVersion() { 908 | const start = 4; 909 | const offset = this.view.getUint32(start, true); 910 | const offset_end = this.view.getUint32(start + 4, true); 911 | return new Uint32(this.view.buffer.slice(offset, offset_end), { validate: false }); 912 | } 913 | 914 | getCellDeps() { 915 | const start = 8; 916 | const offset = this.view.getUint32(start, true); 917 | const offset_end = this.view.getUint32(start + 4, true); 918 | return new CellDepVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 919 | } 920 | 921 | getHeaderDeps() { 922 | const start = 12; 923 | const offset = this.view.getUint32(start, true); 924 | const offset_end = this.view.getUint32(start + 4, true); 925 | return new Byte32Vec(this.view.buffer.slice(offset, offset_end), { validate: false }); 926 | } 927 | 928 | getInputs() { 929 | const start = 16; 930 | const offset = this.view.getUint32(start, true); 931 | const offset_end = this.view.getUint32(start + 4, true); 932 | return new CellInputVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 933 | } 934 | 935 | getOutputs() { 936 | const start = 20; 937 | const offset = this.view.getUint32(start, true); 938 | const offset_end = this.view.getUint32(start + 4, true); 939 | return new CellOutputVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 940 | } 941 | 942 | getOutputsData() { 943 | const start = 24; 944 | const offset = this.view.getUint32(start, true); 945 | const offset_end = this.view.byteLength; 946 | return new BytesVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 947 | } 948 | } 949 | 950 | function SerializeRawTransaction(value) { 951 | const buffers = []; 952 | buffers.push(SerializeUint32(value.version)); 953 | buffers.push(SerializeCellDepVec(value.cell_deps)); 954 | buffers.push(SerializeByte32Vec(value.header_deps)); 955 | buffers.push(SerializeCellInputVec(value.inputs)); 956 | buffers.push(SerializeCellOutputVec(value.outputs)); 957 | buffers.push(SerializeBytesVec(value.outputs_data)); 958 | return serializeTable(buffers); 959 | } 960 | 961 | class Transaction { 962 | constructor(reader, { validate = true } = {}) { 963 | this.view = new DataView(assertArrayBuffer(reader)); 964 | if (validate) { 965 | this.validate(); 966 | } 967 | } 968 | 969 | validate(compatible = false) { 970 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 971 | new RawTransaction(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 972 | new BytesVec(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 973 | } 974 | 975 | getRaw() { 976 | const start = 4; 977 | const offset = this.view.getUint32(start, true); 978 | const offset_end = this.view.getUint32(start + 4, true); 979 | return new RawTransaction(this.view.buffer.slice(offset, offset_end), { validate: false }); 980 | } 981 | 982 | getWitnesses() { 983 | const start = 8; 984 | const offset = this.view.getUint32(start, true); 985 | const offset_end = this.view.byteLength; 986 | return new BytesVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 987 | } 988 | } 989 | 990 | function SerializeTransaction(value) { 991 | const buffers = []; 992 | buffers.push(SerializeRawTransaction(value.raw)); 993 | buffers.push(SerializeBytesVec(value.witnesses)); 994 | return serializeTable(buffers); 995 | } 996 | 997 | class RawHeader { 998 | constructor(reader, { validate = true } = {}) { 999 | this.view = new DataView(assertArrayBuffer(reader)); 1000 | if (validate) { 1001 | this.validate(); 1002 | } 1003 | } 1004 | 1005 | getVersion() { 1006 | return new Uint32(this.view.buffer.slice(0, Uint32.size()), { validate: false }); 1007 | } 1008 | 1009 | getCompactTarget() { 1010 | return new Uint32(this.view.buffer.slice(0 + Uint32.size(), Uint32.size()), { validate: false }); 1011 | } 1012 | 1013 | getTimestamp() { 1014 | return new Uint64(this.view.buffer.slice(0 + Uint32.size() + Uint32.size(), Uint64.size()), { validate: false }); 1015 | } 1016 | 1017 | getNumber() { 1018 | return new Uint64(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size(), Uint64.size()), { validate: false }); 1019 | } 1020 | 1021 | getEpoch() { 1022 | return new Uint64(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size(), Uint64.size()), { validate: false }); 1023 | } 1024 | 1025 | getParentHash() { 1026 | return new Byte32(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size(), Byte32.size()), { validate: false }); 1027 | } 1028 | 1029 | getTransactionsRoot() { 1030 | return new Byte32(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size(), Byte32.size()), { validate: false }); 1031 | } 1032 | 1033 | getProposalsHash() { 1034 | return new Byte32(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size(), Byte32.size()), { validate: false }); 1035 | } 1036 | 1037 | getUnclesHash() { 1038 | return new Byte32(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size(), Byte32.size()), { validate: false }); 1039 | } 1040 | 1041 | getDao() { 1042 | return new Byte32(this.view.buffer.slice(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size(), Byte32.size()), { validate: false }); 1043 | } 1044 | 1045 | validate(compatible = false) { 1046 | assertDataLength(this.view.byteLength, this.size()); 1047 | this.getVersion().validate(compatible); 1048 | this.getCompactTarget().validate(compatible); 1049 | this.getTimestamp().validate(compatible); 1050 | this.getNumber().validate(compatible); 1051 | this.getEpoch().validate(compatible); 1052 | this.getParentHash().validate(compatible); 1053 | this.getTransactionsRoot().validate(compatible); 1054 | this.getProposalsHash().validate(compatible); 1055 | this.getUnclesHash().validate(compatible); 1056 | this.getDao().validate(compatible); 1057 | } 1058 | static size() { 1059 | return 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size(); 1060 | } 1061 | } 1062 | 1063 | function SerializeRawHeader(value) { 1064 | const array = new Uint8Array(0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size()); 1065 | array.set(new Uint8Array(SerializeUint32(value.version)), 0); 1066 | array.set(new Uint8Array(SerializeUint32(value.compact_target)), 0 + Uint32.size()); 1067 | array.set(new Uint8Array(SerializeUint64(value.timestamp)), 0 + Uint32.size() + Uint32.size()); 1068 | array.set(new Uint8Array(SerializeUint64(value.number)), 0 + Uint32.size() + Uint32.size() + Uint64.size()); 1069 | array.set(new Uint8Array(SerializeUint64(value.epoch)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size()); 1070 | array.set(new Uint8Array(SerializeByte32(value.parent_hash)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size()); 1071 | array.set(new Uint8Array(SerializeByte32(value.transactions_root)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size()); 1072 | array.set(new Uint8Array(SerializeByte32(value.proposals_hash)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size()); 1073 | array.set(new Uint8Array(SerializeByte32(value.uncles_hash)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size()); 1074 | array.set(new Uint8Array(SerializeByte32(value.dao)), 0 + Uint32.size() + Uint32.size() + Uint64.size() + Uint64.size() + Uint64.size() + Byte32.size() + Byte32.size() + Byte32.size() + Byte32.size()); 1075 | return array.buffer; 1076 | } 1077 | 1078 | class Header { 1079 | constructor(reader, { validate = true } = {}) { 1080 | this.view = new DataView(assertArrayBuffer(reader)); 1081 | if (validate) { 1082 | this.validate(); 1083 | } 1084 | } 1085 | 1086 | getRaw() { 1087 | return new RawHeader(this.view.buffer.slice(0, RawHeader.size()), { validate: false }); 1088 | } 1089 | 1090 | getNonce() { 1091 | return new Uint128(this.view.buffer.slice(0 + RawHeader.size(), Uint128.size()), { validate: false }); 1092 | } 1093 | 1094 | validate(compatible = false) { 1095 | assertDataLength(this.view.byteLength, this.size()); 1096 | this.getRaw().validate(compatible); 1097 | this.getNonce().validate(compatible); 1098 | } 1099 | static size() { 1100 | return 0 + RawHeader.size() + Uint128.size(); 1101 | } 1102 | } 1103 | 1104 | function SerializeHeader(value) { 1105 | const array = new Uint8Array(0 + RawHeader.size() + Uint128.size()); 1106 | array.set(new Uint8Array(SerializeRawHeader(value.raw)), 0); 1107 | array.set(new Uint8Array(SerializeUint128(value.nonce)), 0 + RawHeader.size()); 1108 | return array.buffer; 1109 | } 1110 | 1111 | class UncleBlock { 1112 | constructor(reader, { validate = true } = {}) { 1113 | this.view = new DataView(assertArrayBuffer(reader)); 1114 | if (validate) { 1115 | this.validate(); 1116 | } 1117 | } 1118 | 1119 | validate(compatible = false) { 1120 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 1121 | new Header(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 1122 | new ProposalShortIdVec(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 1123 | } 1124 | 1125 | getHeader() { 1126 | const start = 4; 1127 | const offset = this.view.getUint32(start, true); 1128 | const offset_end = this.view.getUint32(start + 4, true); 1129 | return new Header(this.view.buffer.slice(offset, offset_end), { validate: false }); 1130 | } 1131 | 1132 | getProposals() { 1133 | const start = 8; 1134 | const offset = this.view.getUint32(start, true); 1135 | const offset_end = this.view.byteLength; 1136 | return new ProposalShortIdVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 1137 | } 1138 | } 1139 | 1140 | function SerializeUncleBlock(value) { 1141 | const buffers = []; 1142 | buffers.push(SerializeHeader(value.header)); 1143 | buffers.push(SerializeProposalShortIdVec(value.proposals)); 1144 | return serializeTable(buffers); 1145 | } 1146 | 1147 | class Block { 1148 | constructor(reader, { validate = true } = {}) { 1149 | this.view = new DataView(assertArrayBuffer(reader)); 1150 | if (validate) { 1151 | this.validate(); 1152 | } 1153 | } 1154 | 1155 | validate(compatible = false) { 1156 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 1157 | new Header(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 1158 | new UncleBlockVec(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 1159 | new TransactionVec(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); 1160 | new ProposalShortIdVec(this.view.buffer.slice(offsets[3], offsets[4]), { validate: false }).validate(); 1161 | } 1162 | 1163 | getHeader() { 1164 | const start = 4; 1165 | const offset = this.view.getUint32(start, true); 1166 | const offset_end = this.view.getUint32(start + 4, true); 1167 | return new Header(this.view.buffer.slice(offset, offset_end), { validate: false }); 1168 | } 1169 | 1170 | getUncles() { 1171 | const start = 8; 1172 | const offset = this.view.getUint32(start, true); 1173 | const offset_end = this.view.getUint32(start + 4, true); 1174 | return new UncleBlockVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 1175 | } 1176 | 1177 | getTransactions() { 1178 | const start = 12; 1179 | const offset = this.view.getUint32(start, true); 1180 | const offset_end = this.view.getUint32(start + 4, true); 1181 | return new TransactionVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 1182 | } 1183 | 1184 | getProposals() { 1185 | const start = 16; 1186 | const offset = this.view.getUint32(start, true); 1187 | const offset_end = this.view.byteLength; 1188 | return new ProposalShortIdVec(this.view.buffer.slice(offset, offset_end), { validate: false }); 1189 | } 1190 | } 1191 | 1192 | function SerializeBlock(value) { 1193 | const buffers = []; 1194 | buffers.push(SerializeHeader(value.header)); 1195 | buffers.push(SerializeUncleBlockVec(value.uncles)); 1196 | buffers.push(SerializeTransactionVec(value.transactions)); 1197 | buffers.push(SerializeProposalShortIdVec(value.proposals)); 1198 | return serializeTable(buffers); 1199 | } 1200 | 1201 | class CellbaseWitness { 1202 | constructor(reader, { validate = true } = {}) { 1203 | this.view = new DataView(assertArrayBuffer(reader)); 1204 | if (validate) { 1205 | this.validate(); 1206 | } 1207 | } 1208 | 1209 | validate(compatible = false) { 1210 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 1211 | new Script(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 1212 | new Bytes(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 1213 | } 1214 | 1215 | getLock() { 1216 | const start = 4; 1217 | const offset = this.view.getUint32(start, true); 1218 | const offset_end = this.view.getUint32(start + 4, true); 1219 | return new Script(this.view.buffer.slice(offset, offset_end), { validate: false }); 1220 | } 1221 | 1222 | getMessage() { 1223 | const start = 8; 1224 | const offset = this.view.getUint32(start, true); 1225 | const offset_end = this.view.byteLength; 1226 | return new Bytes(this.view.buffer.slice(offset, offset_end), { validate: false }); 1227 | } 1228 | } 1229 | 1230 | function SerializeCellbaseWitness(value) { 1231 | const buffers = []; 1232 | buffers.push(SerializeScript(value.lock)); 1233 | buffers.push(SerializeBytes(value.message)); 1234 | return serializeTable(buffers); 1235 | } 1236 | 1237 | class WitnessArgs { 1238 | constructor(reader, { validate = true } = {}) { 1239 | this.view = new DataView(assertArrayBuffer(reader)); 1240 | if (validate) { 1241 | this.validate(); 1242 | } 1243 | } 1244 | 1245 | validate(compatible = false) { 1246 | const offsets = verifyAndExtractOffsets(this.view, 0, true); 1247 | new BytesOpt(this.view.buffer.slice(offsets[0], offsets[1]), { validate: false }).validate(); 1248 | new BytesOpt(this.view.buffer.slice(offsets[1], offsets[2]), { validate: false }).validate(); 1249 | new BytesOpt(this.view.buffer.slice(offsets[2], offsets[3]), { validate: false }).validate(); 1250 | } 1251 | 1252 | getLock() { 1253 | const start = 4; 1254 | const offset = this.view.getUint32(start, true); 1255 | const offset_end = this.view.getUint32(start + 4, true); 1256 | return new BytesOpt(this.view.buffer.slice(offset, offset_end), { validate: false }); 1257 | } 1258 | 1259 | getInputType() { 1260 | const start = 8; 1261 | const offset = this.view.getUint32(start, true); 1262 | const offset_end = this.view.getUint32(start + 4, true); 1263 | return new BytesOpt(this.view.buffer.slice(offset, offset_end), { validate: false }); 1264 | } 1265 | 1266 | getOutputType() { 1267 | const start = 12; 1268 | const offset = this.view.getUint32(start, true); 1269 | const offset_end = this.view.byteLength; 1270 | return new BytesOpt(this.view.buffer.slice(offset, offset_end), { validate: false }); 1271 | } 1272 | } 1273 | 1274 | function SerializeWitnessArgs(value) { 1275 | const buffers = []; 1276 | buffers.push(SerializeBytesOpt(value.lock)); 1277 | buffers.push(SerializeBytesOpt(value.input_type)); 1278 | buffers.push(SerializeBytesOpt(value.output_type)); 1279 | return serializeTable(buffers); 1280 | } 1281 | 1282 | exports.Block = Block; 1283 | exports.Byte32 = Byte32; 1284 | exports.Byte32Vec = Byte32Vec; 1285 | exports.Bytes = Bytes; 1286 | exports.BytesOpt = BytesOpt; 1287 | exports.BytesVec = BytesVec; 1288 | exports.CellDep = CellDep; 1289 | exports.CellDepVec = CellDepVec; 1290 | exports.CellInput = CellInput; 1291 | exports.CellInputVec = CellInputVec; 1292 | exports.CellOutput = CellOutput; 1293 | exports.CellOutputVec = CellOutputVec; 1294 | exports.CellbaseWitness = CellbaseWitness; 1295 | exports.Header = Header; 1296 | exports.OutPoint = OutPoint; 1297 | exports.ProposalShortId = ProposalShortId; 1298 | exports.ProposalShortIdVec = ProposalShortIdVec; 1299 | exports.RawHeader = RawHeader; 1300 | exports.RawTransaction = RawTransaction; 1301 | exports.Script = Script; 1302 | exports.ScriptOpt = ScriptOpt; 1303 | exports.SerializeBlock = SerializeBlock; 1304 | exports.SerializeByte32 = SerializeByte32; 1305 | exports.SerializeByte32Vec = SerializeByte32Vec; 1306 | exports.SerializeBytes = SerializeBytes; 1307 | exports.SerializeBytesOpt = SerializeBytesOpt; 1308 | exports.SerializeBytesVec = SerializeBytesVec; 1309 | exports.SerializeCellDep = SerializeCellDep; 1310 | exports.SerializeCellDepVec = SerializeCellDepVec; 1311 | exports.SerializeCellInput = SerializeCellInput; 1312 | exports.SerializeCellInputVec = SerializeCellInputVec; 1313 | exports.SerializeCellOutput = SerializeCellOutput; 1314 | exports.SerializeCellOutputVec = SerializeCellOutputVec; 1315 | exports.SerializeCellbaseWitness = SerializeCellbaseWitness; 1316 | exports.SerializeHeader = SerializeHeader; 1317 | exports.SerializeOutPoint = SerializeOutPoint; 1318 | exports.SerializeProposalShortId = SerializeProposalShortId; 1319 | exports.SerializeProposalShortIdVec = SerializeProposalShortIdVec; 1320 | exports.SerializeRawHeader = SerializeRawHeader; 1321 | exports.SerializeRawTransaction = SerializeRawTransaction; 1322 | exports.SerializeScript = SerializeScript; 1323 | exports.SerializeScriptOpt = SerializeScriptOpt; 1324 | exports.SerializeTransaction = SerializeTransaction; 1325 | exports.SerializeTransactionVec = SerializeTransactionVec; 1326 | exports.SerializeUint128 = SerializeUint128; 1327 | exports.SerializeUint256 = SerializeUint256; 1328 | exports.SerializeUint32 = SerializeUint32; 1329 | exports.SerializeUint64 = SerializeUint64; 1330 | exports.SerializeUncleBlock = SerializeUncleBlock; 1331 | exports.SerializeUncleBlockVec = SerializeUncleBlockVec; 1332 | exports.SerializeWitnessArgs = SerializeWitnessArgs; 1333 | exports.Transaction = Transaction; 1334 | exports.TransactionVec = TransactionVec; 1335 | exports.Uint128 = Uint128; 1336 | exports.Uint256 = Uint256; 1337 | exports.Uint32 = Uint32; 1338 | exports.Uint64 = Uint64; 1339 | exports.UncleBlock = UncleBlock; 1340 | exports.UncleBlockVec = UncleBlockVec; 1341 | exports.WitnessArgs = WitnessArgs; 1342 | 1343 | Object.defineProperty(exports, '__esModule', { value: true }); 1344 | 1345 | }))); 1346 | -------------------------------------------------------------------------------- /tests/serializers.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const { normalizers, Reader } = require("../build/ckb-js-toolkit.node.js"); 3 | const CKB = require("../testfiles/blockchain.umd.js"); 4 | 5 | test("normalize and serialize script", t => { 6 | const value = { 7 | code_hash: 8 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 9 | args: "0xaabbccdd44332211", 10 | hash_type: "type" 11 | }; 12 | const normalizedValue = normalizers.NormalizeScript(value); 13 | const serializedValue = CKB.SerializeScript(normalizedValue); 14 | const serializedHex = new Reader(serializedValue).serializeJson(); 15 | t.deepEqual( 16 | serializedHex, 17 | "0x3d0000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80108000000aabbccdd44332211" 18 | ); 19 | }); 20 | 21 | test("normalize and serialize script with integer hash type", t => { 22 | const value = { 23 | code_hash: 24 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 25 | args: "0xaabbccdd44332211", 26 | hash_type: 1 27 | }; 28 | const normalizedValue = normalizers.NormalizeScript(value); 29 | const serializedValue = CKB.SerializeScript(normalizedValue); 30 | const serializedHex = new Reader(serializedValue).serializeJson(); 31 | t.deepEqual( 32 | serializedHex, 33 | "0x3d0000001000000030000000310000009bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce80108000000aabbccdd44332211" 34 | ); 35 | }); 36 | 37 | test("normalize invalid script", t => { 38 | const value = { 39 | code_hash: 40 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 41 | args: "0xaabbccdd4433221", 42 | hash_type: "type" 43 | }; 44 | t.throws(() => { 45 | normalizers.NormalizeScript(value); 46 | }); 47 | }); 48 | 49 | test("normalize invalid script type", t => { 50 | const value = { 51 | code_hash: 52 | "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", 53 | args: "0xaabbccdd44332211", 54 | hash_type: "invalidtype" 55 | }; 56 | t.throws(() => { 57 | normalizers.NormalizeScript(value); 58 | }); 59 | }); 60 | 61 | test("normalize and serialize outpoint", t => { 62 | const value = { 63 | tx_hash: 64 | "0x4565f957aa65ca5d094ede05cbeaedcee70f5a71200ae2e31b643d2952c929bc", 65 | index: 3 66 | }; 67 | const normalizedValue = normalizers.NormalizeOutPoint(value); 68 | const serializedValue = CKB.SerializeOutPoint(normalizedValue); 69 | const serializedHex = new Reader(serializedValue).serializeJson(); 70 | t.deepEqual( 71 | serializedHex, 72 | "0x4565f957aa65ca5d094ede05cbeaedcee70f5a71200ae2e31b643d2952c929bc03000000" 73 | ); 74 | }); 75 | 76 | test("normalize and serialize outpoint with hex number", t => { 77 | const value = { 78 | tx_hash: 79 | "0x4565f957aa65ca5d094ede05cbeaedcee70f5a71200ae2e31b643d2952c929bc", 80 | index: "0x3" 81 | }; 82 | const normalizedValue = normalizers.NormalizeOutPoint(value); 83 | const serializedValue = CKB.SerializeOutPoint(normalizedValue); 84 | const serializedHex = new Reader(serializedValue).serializeJson(); 85 | t.deepEqual( 86 | serializedHex, 87 | "0x4565f957aa65ca5d094ede05cbeaedcee70f5a71200ae2e31b643d2952c929bc03000000" 88 | ); 89 | }); 90 | 91 | test("normalize invalid outpoint", t => { 92 | const value = { 93 | tx_hash: 94 | "0x4565f957aa65ca5d094ede05cbeaedcee70f5a71200ae2e31b643d2952c929bc", 95 | index: "0x123412341" 96 | }; 97 | t.throws(() => { 98 | normalizers.NormalizeOutPoint(value); 99 | }); 100 | }); 101 | 102 | test("normalize and serialize cellinput", t => { 103 | const value = { 104 | since: "0x60a0001234", 105 | previous_output: { 106 | tx_hash: 107 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 108 | index: "0x10" 109 | } 110 | }; 111 | const normalizedValue = normalizers.NormalizeCellInput(value); 112 | const serializedValue = CKB.SerializeCellInput(normalizedValue); 113 | const serializedHex = new Reader(serializedValue).serializeJson(); 114 | t.deepEqual( 115 | serializedHex, 116 | "0x341200a060000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da10000000" 117 | ); 118 | }); 119 | 120 | test("normalize invalid cellinput", t => { 121 | const value = { 122 | since: "0x60a0001234", 123 | previous_output: "hahah" 124 | }; 125 | t.throws(() => { 126 | normalizers.NormalizeCellInput(value); 127 | }); 128 | }); 129 | 130 | test("normalize and serialize celloutput", t => { 131 | const value = { 132 | capacity: "0x10", 133 | lock: { 134 | code_hash: 135 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 136 | args: "0x1234", 137 | hash_type: "data" 138 | }, 139 | type: { 140 | code_hash: 141 | "0xa98c57135830e1b900000000f6c4b8870828199a786b26f09f7dec4bc27a73db", 142 | args: "0x", 143 | hash_type: "type" 144 | } 145 | }; 146 | const normalizedValue = normalizers.NormalizeCellOutput(value); 147 | const serializedValue = CKB.SerializeCellOutput(normalizedValue); 148 | const serializedHex = new Reader(serializedValue).serializeJson(); 149 | t.deepEqual( 150 | serializedHex, 151 | "0x8400000010000000180000004f000000100000000000000037000000100000003000000031000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da0002000000123435000000100000003000000031000000a98c57135830e1b900000000f6c4b8870828199a786b26f09f7dec4bc27a73db0100000000" 152 | ); 153 | }); 154 | 155 | test("normalize and serialize celloutput without type", t => { 156 | const value = { 157 | capacity: "0x10", 158 | lock: { 159 | code_hash: 160 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 161 | args: "0x1234", 162 | hash_type: "data" 163 | } 164 | }; 165 | const normalizedValue = normalizers.NormalizeCellOutput(value); 166 | const serializedValue = CKB.SerializeCellOutput(normalizedValue); 167 | const serializedHex = new Reader(serializedValue).serializeJson(); 168 | t.deepEqual( 169 | serializedHex, 170 | "0x4f00000010000000180000004f000000100000000000000037000000100000003000000031000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da00020000001234" 171 | ); 172 | }); 173 | 174 | test("normalize invalid celloutput", t => { 175 | const value = { 176 | capacity: "0x102030405060708090", 177 | lock: { 178 | code_hash: 179 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 180 | args: "0x1234", 181 | hash_type: "data" 182 | } 183 | }; 184 | t.throws(() => { 185 | normalizers.NormalizeCellOutput(value); 186 | }); 187 | }); 188 | 189 | test("normalize and serialize celldep", t => { 190 | const value = { 191 | dep_type: "code", 192 | out_point: { 193 | tx_hash: 194 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 195 | index: "0x11" 196 | } 197 | }; 198 | const normalizedValue = normalizers.NormalizeCellDep(value); 199 | const serializedValue = CKB.SerializeCellDep(normalizedValue); 200 | const serializedHex = new Reader(serializedValue).serializeJson(); 201 | t.deepEqual( 202 | serializedHex, 203 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da1100000000" 204 | ); 205 | }); 206 | 207 | test("normalize and serialize transaction", t => { 208 | const value = { 209 | version: "0x0", 210 | cell_deps: [ 211 | { 212 | dep_type: "code", 213 | out_point: { 214 | tx_hash: 215 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 216 | index: "0x0" 217 | } 218 | } 219 | ], 220 | header_deps: [ 221 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 222 | ], 223 | inputs: [ 224 | { 225 | since: "0x10", 226 | previous_output: { 227 | tx_hash: 228 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 229 | index: "0x2" 230 | } 231 | } 232 | ], 233 | outputs: [ 234 | { 235 | capacity: "0x1234", 236 | lock: { 237 | code_hash: 238 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 239 | args: "0x1234", 240 | hash_type: "data" 241 | } 242 | } 243 | ], 244 | outputs_data: ["0xabcdef"], 245 | witnesses: ["0x31313131"] 246 | }; 247 | const normalizedValue = normalizers.NormalizeTransaction(value); 248 | const serializedValue = CKB.SerializeTransaction(normalizedValue); 249 | const serializedHex = new Reader(serializedValue).serializeJson(); 250 | t.deepEqual( 251 | serializedHex, 252 | "0x1f0100000c0000000f010000030100001c00000020000000490000006d0000009d000000f40000000000000001000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300000000000001000000b39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6010000001000000000000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73010200000057000000080000004f00000010000000180000004f000000341200000000000037000000100000003000000031000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302000200000012340f0000000800000003000000abcdef10000000080000000400000031313131" 253 | ); 254 | }); 255 | 256 | test("normalize and serialize header", t => { 257 | const value = { 258 | compact_target: "0x1a2d3494", 259 | number: "0xfb1bc", 260 | parent_hash: 261 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 262 | nonce: "0x449b385049af131a0000001584a00100", 263 | timestamp: "0x170aba663c3", 264 | transactions_root: 265 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 266 | proposals_hash: 267 | "0x0000000000000000000000000000000000000000000000000000000000000000", 268 | uncles_hash: 269 | "0x0000000000000000000000000000000000000000000000000000000000000000", 270 | version: "0x0", 271 | epoch: "0x7080612000287", 272 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 273 | }; 274 | const normalizedValue = normalizers.NormalizeHeader(value); 275 | const serializedValue = CKB.SerializeHeader(normalizedValue); 276 | const serializedHex = new Reader(serializedValue).serializeJson(); 277 | t.deepEqual( 278 | serializedHex, 279 | "0x0000000094342d1ac363a6ab70010000bcb10f000000000087020012060807003134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e7901070001a084150000001a13af4950389b44" 280 | ); 281 | }); 282 | 283 | test("normalize and serialize uncle block", t => { 284 | const value = { 285 | header: { 286 | compact_target: "0x1a2d3494", 287 | number: "0xfb1bc", 288 | parent_hash: 289 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 290 | nonce: "0x449b385049af131a0000001584a00100", 291 | timestamp: "0x170aba663c3", 292 | transactions_root: 293 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 294 | proposals_hash: 295 | "0x0000000000000000000000000000000000000000000000000000000000000000", 296 | uncles_hash: 297 | "0x0000000000000000000000000000000000000000000000000000000000000000", 298 | version: "0x0", 299 | epoch: "0x7080612000287", 300 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 301 | }, 302 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 303 | }; 304 | const normalizedValue = normalizers.NormalizeUncleBlock(value); 305 | const serializedValue = CKB.SerializeUncleBlock(normalizedValue); 306 | const serializedHex = new Reader(serializedValue).serializeJson(); 307 | t.deepEqual( 308 | serializedHex, 309 | "0xf40000000c000000dc0000000000000094342d1ac363a6ab70010000bcb10f000000000087020012060807003134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e7901070001a084150000001a13af4950389b440200000012345678901234567890abcdeabcdeabcdeabcde" 310 | ); 311 | }); 312 | 313 | test("normalize and serialize block", t => { 314 | const value = { 315 | header: { 316 | compact_target: "0x1a2d3494", 317 | number: "0xfb1bc", 318 | parent_hash: 319 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 320 | nonce: "0x449b385049af131a0000001584a00100", 321 | timestamp: "0x170aba663c3", 322 | transactions_root: 323 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 324 | proposals_hash: 325 | "0x0000000000000000000000000000000000000000000000000000000000000000", 326 | uncles_hash: 327 | "0x0000000000000000000000000000000000000000000000000000000000000000", 328 | version: "0x0", 329 | epoch: "0x7080612000287", 330 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 331 | }, 332 | transactions: [ 333 | { 334 | version: "0x0", 335 | cell_deps: [ 336 | { 337 | dep_type: "code", 338 | out_point: { 339 | tx_hash: 340 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 341 | index: "0x0" 342 | } 343 | } 344 | ], 345 | header_deps: [ 346 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 347 | ], 348 | inputs: [ 349 | { 350 | since: "0x10", 351 | previous_output: { 352 | tx_hash: 353 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 354 | index: "0x2" 355 | } 356 | } 357 | ], 358 | outputs: [ 359 | { 360 | capacity: "0x1234", 361 | lock: { 362 | code_hash: 363 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 364 | args: "0x1234", 365 | hash_type: "data" 366 | } 367 | } 368 | ], 369 | outputs_data: ["0xabcdef"], 370 | witnesses: ["0x1111"] 371 | } 372 | ], 373 | uncles: [], 374 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 375 | }; 376 | const normalizedValue = normalizers.NormalizeBlock(value); 377 | const serializedValue = CKB.SerializeBlock(normalizedValue); 378 | const serializedHex = new Reader(serializedValue).serializeJson(); 379 | t.deepEqual( 380 | serializedHex, 381 | "0x2502000014000000e4000000e80000000d0200000000000094342d1ac363a6ab70010000bcb10f000000000087020012060807003134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e7901070001a084150000001a13af4950389b440400000025010000080000001d0100000c0000000f010000030100001c00000020000000490000006d0000009d000000f40000000000000001000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300000000000001000000b39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6010000001000000000000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73010200000057000000080000004f00000010000000180000004f000000341200000000000037000000100000003000000031000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302000200000012340f0000000800000003000000abcdef0e000000080000000200000011110200000012345678901234567890abcdeabcdeabcdeabcde" 382 | ); 383 | }); 384 | 385 | test("normalize and serialize cellbase witness", t => { 386 | const value = { 387 | lock: { 388 | code_hash: 389 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 390 | args: "0x1234", 391 | hash_type: "data" 392 | }, 393 | message: "0x1234abcdef" 394 | }; 395 | const normalizedValue = normalizers.NormalizeCellbaseWitness(value); 396 | const serializedValue = CKB.SerializeCellbaseWitness(normalizedValue); 397 | const serializedHex = new Reader(serializedValue).serializeJson(); 398 | t.deepEqual( 399 | serializedHex, 400 | "0x4c0000000c0000004300000037000000100000003000000031000000a98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da00020000001234050000001234abcdef" 401 | ); 402 | }); 403 | 404 | test("normalize and serialize witness args", t => { 405 | const value = { 406 | lock: "0x1234", 407 | input_type: "0x4678", 408 | output_type: "0x2312" 409 | }; 410 | const normalizedValue = normalizers.NormalizeWitnessArgs(value); 411 | const serializedValue = CKB.SerializeWitnessArgs(normalizedValue); 412 | const serializedHex = new Reader(serializedValue).serializeJson(); 413 | t.deepEqual( 414 | serializedHex, 415 | "0x2200000010000000160000001c000000020000001234020000004678020000002312" 416 | ); 417 | }); 418 | 419 | test("normalize and serialize empty witness args", t => { 420 | const value = {}; 421 | const normalizedValue = normalizers.NormalizeWitnessArgs(value); 422 | const serializedValue = CKB.SerializeWitnessArgs(normalizedValue); 423 | const serializedHex = new Reader(serializedValue).serializeJson(); 424 | t.deepEqual(serializedHex, "0x10000000100000001000000010000000"); 425 | }); 426 | 427 | test("normalize and serialize only one witness args", t => { 428 | const value = { 429 | lock: "0x1234" 430 | }; 431 | const normalizedValue = normalizers.NormalizeWitnessArgs(value); 432 | const serializedValue = CKB.SerializeWitnessArgs(normalizedValue); 433 | const serializedHex = new Reader(serializedValue).serializeJson(); 434 | t.deepEqual(serializedHex, "0x16000000100000001600000016000000020000001234"); 435 | }); 436 | -------------------------------------------------------------------------------- /tests/transformers.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const { transformers, Reader } = require("../build/ckb-js-toolkit.node.js"); 3 | 4 | test("transform script", t => { 5 | const s = transformers.TransformScript({ 6 | code_hash: 7 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 8 | args: Reader.fromRawString("args1234"), 9 | hash_type: { 10 | serializeJson: () => "data" 11 | } 12 | }); 13 | 14 | t.deepEqual(s, { 15 | code_hash: 16 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 17 | args: "0x6172677331323334", 18 | hash_type: "data" 19 | }); 20 | }); 21 | 22 | test("transform camel case script", t => { 23 | const s = transformers.TransformScript({ 24 | codeHash: 25 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 26 | args: Reader.fromRawString("args1234"), 27 | hashType: { 28 | serializeJson: () => "data" 29 | } 30 | }); 31 | 32 | t.deepEqual(s, { 33 | code_hash: 34 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 35 | args: "0x6172677331323334", 36 | hash_type: "data" 37 | }); 38 | }); 39 | 40 | test("transform plain script", t => { 41 | const s = transformers.TransformScript({ 42 | code_hash: 43 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 44 | args: "0x1234", 45 | hash_type: "data" 46 | }); 47 | 48 | t.deepEqual(s, { 49 | code_hash: 50 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 51 | args: "0x1234", 52 | hash_type: "data" 53 | }); 54 | }); 55 | 56 | test("transform invalid script", t => { 57 | t.throws(() => { 58 | transformers.TransformScript({ 59 | code_hash: 60 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 61 | args: "0xgghh", 62 | hash_type: "data" 63 | }); 64 | }); 65 | }); 66 | 67 | test("transform invalid script but do not validate", t => { 68 | const s = transformers.TransformScript( 69 | { 70 | code_hash: 71 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 72 | args: "0xgghh", 73 | hash_type: "data" 74 | }, 75 | { validation: false } 76 | ); 77 | 78 | t.deepEqual(s, { 79 | code_hash: 80 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 81 | args: "0xgghh", 82 | hash_type: "data" 83 | }); 84 | }); 85 | 86 | test("transform outpoint", t => { 87 | const o = transformers.TransformOutPoint({ 88 | tx_hash: 89 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 90 | index: "0x0" 91 | }); 92 | 93 | t.deepEqual(o, { 94 | tx_hash: 95 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 96 | index: "0x0" 97 | }); 98 | }); 99 | 100 | test("transform outpoint more fields", t => { 101 | const o = transformers.TransformOutPoint({ 102 | tx_hash: { 103 | serializeJson: () => 104 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 105 | anotherfield: "not used" 106 | }, 107 | index: "0x10", 108 | unneeded: "unneeded field" 109 | }); 110 | 111 | t.deepEqual(o, { 112 | tx_hash: 113 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 114 | index: "0x10" 115 | }); 116 | }); 117 | 118 | test("correct cellinput", t => { 119 | const v = transformers.TransformCellInput({ 120 | since: "0x0", 121 | previous_output: { 122 | tx_hash: 123 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 124 | index: new Reader("0x10") 125 | } 126 | }); 127 | 128 | t.deepEqual(v, { 129 | since: "0x0", 130 | previous_output: { 131 | tx_hash: 132 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 133 | index: "0x10" 134 | } 135 | }); 136 | }); 137 | 138 | test("correct cellinput with serialize function using this", t => { 139 | const v = transformers.TransformCellInput({ 140 | since: "0x0", 141 | previous_output: { 142 | value: { 143 | tx_hash: 144 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 145 | index: "0x10" 146 | }, 147 | serializeJson: function() { 148 | return this.value; 149 | } 150 | } 151 | }); 152 | 153 | t.deepEqual(v, { 154 | since: "0x0", 155 | previous_output: { 156 | tx_hash: 157 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 158 | index: "0x10" 159 | } 160 | }); 161 | }); 162 | 163 | test("invalid cellinput", t => { 164 | t.throws(() => { 165 | transformers.TransformCellInput({ 166 | since: "0x0", 167 | previous_output: { 168 | tx_hash: 169 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 170 | index: new Reader("0x00") 171 | } 172 | }); 173 | }); 174 | }); 175 | 176 | test("celloutput with type", t => { 177 | const v = transformers.TransformCellOutput({ 178 | capacity: "0x10", 179 | lock: { 180 | value: { 181 | code_hash: 182 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 183 | args: new Reader("0x1234"), 184 | hash_type: "data" 185 | }, 186 | serializeJson: function() { 187 | return this.value; 188 | } 189 | }, 190 | type: { 191 | code_hash: 192 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 193 | args: "0x", 194 | hash_type: "type" 195 | } 196 | }); 197 | 198 | t.deepEqual(v, { 199 | capacity: "0x10", 200 | lock: { 201 | code_hash: 202 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 203 | args: "0x1234", 204 | hash_type: "data" 205 | }, 206 | type: { 207 | code_hash: 208 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 209 | args: "0x", 210 | hash_type: "type" 211 | } 212 | }); 213 | }); 214 | 215 | test("celloutput", t => { 216 | const v = transformers.TransformCellOutput({ 217 | capacity: "0x1024", 218 | lock: { 219 | code_hash: 220 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 221 | args: "0x", 222 | hash_type: "type" 223 | } 224 | }); 225 | 226 | t.deepEqual(v, { 227 | capacity: "0x1024", 228 | lock: { 229 | code_hash: 230 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 231 | args: "0x", 232 | hash_type: "type" 233 | } 234 | }); 235 | }); 236 | 237 | test("celloutput invalid lock but skip validation", t => { 238 | const v = transformers.TransformCellOutput( 239 | { 240 | capacity: "0x1024", 241 | type: { 242 | code_hash: 243 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 244 | args: "0x", 245 | hash_type: "type" 246 | }, 247 | unused: "value" 248 | }, 249 | { validation: false } 250 | ); 251 | 252 | t.deepEqual(v, { 253 | capacity: "0x1024", 254 | type: { 255 | code_hash: 256 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 257 | args: "0x", 258 | hash_type: "type" 259 | } 260 | }); 261 | }); 262 | 263 | test("celloutput invalid lock", t => { 264 | t.throws(() => { 265 | transformers.TransformCellOutput({ 266 | capacity: "0x1024", 267 | type: { 268 | code_hash: 269 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 270 | args: "0x", 271 | hash_type: "type" 272 | } 273 | }); 274 | }); 275 | }); 276 | 277 | test("correct celldep", t => { 278 | const v = transformers.TransformCellDep({ 279 | dep_type: { 280 | serializeJson: () => { 281 | return "code"; 282 | } 283 | }, 284 | out_point: { 285 | tx_hash: 286 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 287 | index: "0x0" 288 | } 289 | }); 290 | 291 | t.deepEqual(v, { 292 | dep_type: "code", 293 | out_point: { 294 | tx_hash: 295 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 296 | index: "0x0" 297 | } 298 | }); 299 | }); 300 | 301 | class DummyValueHolder { 302 | constructor(value) { 303 | this.value = value; 304 | } 305 | 306 | serializeJson() { 307 | return this.value; 308 | } 309 | } 310 | 311 | test("correct transaction", t => { 312 | const v = transformers.TransformTransaction({ 313 | version: "0x0", 314 | cell_deps: [ 315 | { 316 | dep_type: "code", 317 | out_point: { 318 | tx_hash: { 319 | value: "0xa98c57135830e1b91345948df6c4b887082819", 320 | serializeJson: function() { 321 | return this.value + "9a786b26f09f7dec4bc27a7300"; 322 | } 323 | }, 324 | index: "0x0" 325 | }, 326 | redundant_key: "unused value" 327 | } 328 | ], 329 | header_deps: [ 330 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 331 | ], 332 | inputs: [ 333 | { 334 | since: new DummyValueHolder("0x10"), 335 | previous_output: { 336 | tx_hash: 337 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 338 | index: { 339 | serializeJson: () => { 340 | return "0x2"; 341 | } 342 | } 343 | } 344 | } 345 | ], 346 | outputs: [ 347 | { 348 | capacity: "0x1234", 349 | lock: { 350 | code_hash: 351 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 352 | args: new Reader("0x1234"), 353 | hash_type: "data" 354 | } 355 | } 356 | ], 357 | outputs_data: ["0xabcdef"], 358 | witnesses: [Reader.fromRawString("1111")] 359 | }); 360 | 361 | t.deepEqual(v, { 362 | version: "0x0", 363 | cell_deps: [ 364 | { 365 | dep_type: "code", 366 | out_point: { 367 | tx_hash: 368 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 369 | index: "0x0" 370 | } 371 | } 372 | ], 373 | header_deps: [ 374 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 375 | ], 376 | inputs: [ 377 | { 378 | since: "0x10", 379 | previous_output: { 380 | tx_hash: 381 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 382 | index: "0x2" 383 | } 384 | } 385 | ], 386 | outputs: [ 387 | { 388 | capacity: "0x1234", 389 | lock: { 390 | code_hash: 391 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 392 | args: "0x1234", 393 | hash_type: "data" 394 | } 395 | } 396 | ], 397 | outputs_data: ["0xabcdef"], 398 | witnesses: ["0x31313131"] 399 | }); 400 | }); 401 | 402 | test("correct header", t => { 403 | const v = transformers.TransformHeader({ 404 | compact_target: "0x1a2d3494", 405 | number: "0xfb1bc", 406 | parent_hash: 407 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 408 | nonce: "0x449b385049af131a0000001584a00100", 409 | timestamp: "0x170aba663c3", 410 | transactions_root: new Reader( 411 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a" 412 | ), 413 | proposals_hash: 414 | "0x0000000000000000000000000000000000000000000000000000000000000000", 415 | uncles_hash: 416 | "0x0000000000000000000000000000000000000000000000000000000000000000", 417 | version: "0x0", 418 | epoch: "0x7080612000287", 419 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 420 | }); 421 | 422 | t.deepEqual(v, { 423 | compact_target: "0x1a2d3494", 424 | number: "0xfb1bc", 425 | parent_hash: 426 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 427 | nonce: "0x449b385049af131a0000001584a00100", 428 | timestamp: "0x170aba663c3", 429 | transactions_root: 430 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 431 | proposals_hash: 432 | "0x0000000000000000000000000000000000000000000000000000000000000000", 433 | uncles_hash: 434 | "0x0000000000000000000000000000000000000000000000000000000000000000", 435 | version: "0x0", 436 | epoch: "0x7080612000287", 437 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 438 | }); 439 | }); 440 | 441 | test("invalid header", t => { 442 | t.throws(() => { 443 | transformers.TransformHeader({ 444 | compact_target: "0x1a2d3494", 445 | number: "0xfb1bc", 446 | parent_hash: 447 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 448 | nonce: "0x449b385049af131a0000001584a00100", 449 | timestamp: new Reader("0x170aba663c3"), 450 | transactions_root: 451 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 452 | proposals_hash: 453 | "0x0000000000000000000000000000000000000000000000000000000000000000", 454 | uncles_hash: 455 | "0x0000000000000000000000000000000000000000000000000000000000000000", 456 | version: "0x0", 457 | epoch: "0x7080612000287a", 458 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 459 | }); 460 | }); 461 | }); 462 | 463 | test("correct block", t => { 464 | const v = transformers.TransformBlock({ 465 | header: { 466 | compact_target: "0x1a2d3494", 467 | number: "0xfb1bc", 468 | parent_hash: 469 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 470 | nonce: "0x449b385049af131a0000001584a00100", 471 | timestamp: "0x170aba663c3", 472 | transactions_root: 473 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 474 | proposals_hash: 475 | "0x0000000000000000000000000000000000000000000000000000000000000000", 476 | uncles_hash: 477 | "0x0000000000000000000000000000000000000000000000000000000000000000", 478 | version: "0x0", 479 | epoch: "0x7080612000287", 480 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 481 | }, 482 | transactions: [ 483 | { 484 | version: "0x0", 485 | cell_deps: [ 486 | { 487 | dep_type: "code", 488 | out_point: { 489 | tx_hash: new Reader( 490 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300" 491 | ), 492 | index: "0x0" 493 | } 494 | } 495 | ], 496 | header_deps: [ 497 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 498 | ], 499 | inputs: [ 500 | { 501 | since: "0x10", 502 | previous_output: { 503 | tx_hash: 504 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 505 | index: "0x2" 506 | } 507 | } 508 | ], 509 | outputs: [ 510 | { 511 | capacity: "0x1234", 512 | lock: { 513 | code_hash: 514 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 515 | args: "0x1234", 516 | hash_type: { 517 | serializeJson: () => { 518 | return "data"; 519 | } 520 | } 521 | } 522 | } 523 | ], 524 | outputs_data: ["0xabcdef"], 525 | witnesses: ["0x1111"] 526 | } 527 | ], 528 | uncles: [], 529 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 530 | }); 531 | 532 | t.deepEqual(v, { 533 | header: { 534 | compact_target: "0x1a2d3494", 535 | number: "0xfb1bc", 536 | parent_hash: 537 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 538 | nonce: "0x449b385049af131a0000001584a00100", 539 | timestamp: "0x170aba663c3", 540 | transactions_root: 541 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 542 | proposals_hash: 543 | "0x0000000000000000000000000000000000000000000000000000000000000000", 544 | uncles_hash: 545 | "0x0000000000000000000000000000000000000000000000000000000000000000", 546 | version: "0x0", 547 | epoch: "0x7080612000287", 548 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 549 | }, 550 | transactions: [ 551 | { 552 | version: "0x0", 553 | cell_deps: [ 554 | { 555 | dep_type: "code", 556 | out_point: { 557 | tx_hash: 558 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 559 | index: "0x0" 560 | } 561 | } 562 | ], 563 | header_deps: [ 564 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 565 | ], 566 | inputs: [ 567 | { 568 | since: "0x10", 569 | previous_output: { 570 | tx_hash: 571 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 572 | index: "0x2" 573 | } 574 | } 575 | ], 576 | outputs: [ 577 | { 578 | capacity: "0x1234", 579 | lock: { 580 | code_hash: 581 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 582 | args: "0x1234", 583 | hash_type: "data" 584 | } 585 | } 586 | ], 587 | outputs_data: ["0xabcdef"], 588 | witnesses: ["0x1111"] 589 | } 590 | ], 591 | uncles: [], 592 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 593 | }); 594 | }); 595 | 596 | test("correct cellbase witness", t => { 597 | const v = transformers.TransformCellbaseWitness({ 598 | lock: { 599 | code_hash: 600 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 601 | args: "0x1234", 602 | hash_type: "data", 603 | unneeded1: "unneeded1" 604 | }, 605 | message: "0x1234abcdef", 606 | unneeded2: 2 607 | }); 608 | 609 | t.deepEqual(v, { 610 | lock: { 611 | code_hash: 612 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 613 | args: "0x1234", 614 | hash_type: "data" 615 | }, 616 | message: "0x1234abcdef" 617 | }); 618 | }); 619 | 620 | test("correct witness args", t => { 621 | const v = transformers.TransformWitnessArgs({ 622 | lock: "0x1234", 623 | input_type: "0x4678", 624 | output_type: "0x2312" 625 | }); 626 | 627 | t.deepEqual(v, { 628 | lock: "0x1234", 629 | input_type: "0x4678", 630 | output_type: "0x2312" 631 | }); 632 | }); 633 | 634 | test("empty witness args", t => { 635 | const v = transformers.TransformWitnessArgs({}); 636 | 637 | t.deepEqual(v, {}); 638 | }); 639 | 640 | test("only one witness args", t => { 641 | const v = transformers.TransformWitnessArgs({ 642 | lock: "0x1234", 643 | unneeded: "unneeded123" 644 | }); 645 | 646 | t.deepEqual(v, { 647 | lock: "0x1234" 648 | }); 649 | }); 650 | -------------------------------------------------------------------------------- /tests/validators.js: -------------------------------------------------------------------------------- 1 | const test = require("ava"); 2 | const { validators } = require("../build/ckb-js-toolkit.node.js"); 3 | 4 | test("correct script should pass validation", t => { 5 | validators.ValidateScript({ 6 | code_hash: 7 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 8 | args: "0x1234", 9 | hash_type: "data" 10 | }); 11 | t.pass(); 12 | }); 13 | 14 | test("correct script with empty args", t => { 15 | validators.ValidateScript({ 16 | code_hash: 17 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 18 | args: "0x", 19 | hash_type: "type" 20 | }); 21 | t.pass(); 22 | }); 23 | 24 | test("script that is not object", t => { 25 | t.throws(() => { 26 | validators.ValidateScript("i am a script, trust me"); 27 | }); 28 | }); 29 | 30 | test("script with invalid code hash", t => { 31 | t.throws(() => { 32 | validators.ValidateScript({ 33 | code_hash: "0xa98c57135830e1b913", 34 | args: "0x", 35 | hash_type: "type" 36 | }); 37 | }); 38 | }); 39 | 40 | test("script with invalid args", t => { 41 | t.throws(() => { 42 | validators.ValidateScript({ 43 | code_hash: 44 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 45 | args: "0xthisisnothex", 46 | hash_type: "type" 47 | }); 48 | }); 49 | }); 50 | 51 | test("script with invalid hash type", t => { 52 | t.throws(() => { 53 | validators.ValidateScript({ 54 | code_hash: 55 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 56 | args: "0x", 57 | hash_type: "code" 58 | }); 59 | }); 60 | }); 61 | 62 | test("script with data variants", t => { 63 | validators.ValidateScript({ 64 | code_hash: 65 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 66 | args: "0x1234", 67 | hash_type: "data2" 68 | }); 69 | t.pass(); 70 | }); 71 | 72 | test("script with invalid data variants", t => { 73 | t.throws(() => { 74 | validators.ValidateScript({ 75 | code_hash: 76 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 77 | args: "0x1234", 78 | hash_type: "dataf" 79 | }); 80 | }); 81 | }); 82 | 83 | test("correct outpoint", t => { 84 | validators.ValidateOutPoint({ 85 | tx_hash: 86 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 87 | index: "0x0" 88 | }); 89 | t.pass(); 90 | }); 91 | 92 | test("correct outpoint with positive number", t => { 93 | validators.ValidateOutPoint({ 94 | tx_hash: 95 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 96 | index: "0x101" 97 | }); 98 | t.pass(); 99 | }); 100 | 101 | test("outpoint with zero leaded invalid number", t => { 102 | t.throws(() => { 103 | validators.ValidateOutPoint({ 104 | tx_hash: 105 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 106 | index: "0x010" 107 | }); 108 | }); 109 | }); 110 | 111 | test("outpoint with invalid hex number", t => { 112 | t.throws(() => { 113 | validators.ValidateOutPoint({ 114 | tx_hash: 115 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 116 | index: "0xgg1" 117 | }); 118 | }); 119 | }); 120 | 121 | test("correct cellinput", t => { 122 | validators.ValidateCellInput({ 123 | since: "0x10", 124 | previous_output: { 125 | tx_hash: 126 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 127 | index: "0x0" 128 | } 129 | }); 130 | t.pass(); 131 | }); 132 | 133 | test("cellinput with invalid since", t => { 134 | t.throws(() => { 135 | validators.ValidateCellInput({ 136 | since: "0x0001", 137 | previous_output: { 138 | tx_hash: "0xa98c57135830e1b91345948df", 139 | index: "0x0" 140 | } 141 | }); 142 | }); 143 | }); 144 | 145 | test("cellinput with invalid outpoint", t => { 146 | t.throws(() => { 147 | validators.ValidateCellInput({ 148 | since: "0x0", 149 | previous_output: { 150 | tx_hash: "0xa98c57135830e1b91345948df", 151 | index: "0x0" 152 | } 153 | }); 154 | }); 155 | }); 156 | 157 | test("cellinput with invalid outpoint but skip nested validation", t => { 158 | validators.ValidateCellInput( 159 | { 160 | since: "0x0", 161 | previous_output: { 162 | tx_hash: "0xa98c57135830e1b91345948df", 163 | index: "0x0" 164 | } 165 | }, 166 | { nestedValidation: false } 167 | ); 168 | t.pass(); 169 | }); 170 | 171 | test("correct celloutput", t => { 172 | validators.ValidateCellOutput({ 173 | capacity: "0x10", 174 | lock: { 175 | code_hash: 176 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 177 | args: "0x1234", 178 | hash_type: "data" 179 | } 180 | }); 181 | t.pass(); 182 | }); 183 | 184 | test("correct celloutput with type", t => { 185 | validators.ValidateCellOutput({ 186 | capacity: "0x10", 187 | lock: { 188 | code_hash: 189 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 190 | args: "0x1234", 191 | hash_type: "data" 192 | }, 193 | type: { 194 | code_hash: 195 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 196 | args: "0x", 197 | hash_type: "type" 198 | } 199 | }); 200 | t.pass(); 201 | }); 202 | 203 | test("celloutput with invalid capacity", t => { 204 | t.throws(() => { 205 | validators.ValidateCellOutput({ 206 | capacity: "0xggg", 207 | lock: { 208 | code_hash: 209 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 210 | args: "0x1234", 211 | hash_type: "data" 212 | } 213 | }); 214 | }); 215 | }); 216 | 217 | test("celloutput with invalid lock", t => { 218 | t.throws(() => { 219 | validators.ValidateCellOutput({ 220 | capacity: "0x10", 221 | lock: { 222 | invalid: "lock" 223 | } 224 | }); 225 | }); 226 | }); 227 | 228 | test("celloutput with invalid lock but skips validation", t => { 229 | validators.ValidateCellOutput( 230 | { 231 | capacity: "0x10", 232 | lock: { 233 | invalid: "lock" 234 | } 235 | }, 236 | { nestedValidation: false } 237 | ); 238 | t.pass(); 239 | }); 240 | 241 | test("correct celldep", t => { 242 | validators.ValidateCellDep({ 243 | dep_type: "code", 244 | out_point: { 245 | tx_hash: 246 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 247 | index: "0x0" 248 | } 249 | }); 250 | t.pass(); 251 | }); 252 | 253 | test("celldep with invalid dep type", t => { 254 | t.throws(() => { 255 | validators.ValidateCellDep({ 256 | dep_type: "data", 257 | out_point: { 258 | tx_hash: 259 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 260 | index: "0x0" 261 | } 262 | }); 263 | }); 264 | }); 265 | 266 | test("celldep with invalid out point", t => { 267 | t.throws(() => { 268 | validators.ValidateCellDep({ 269 | dep_type: "dep_group", 270 | out_point: "invalid out point" 271 | }); 272 | }); 273 | }); 274 | 275 | test("celldep with invalid out point but skips validation", t => { 276 | validators.ValidateCellDep( 277 | { 278 | dep_type: "dep_group", 279 | out_point: "invalid out point" 280 | }, 281 | { nestedValidation: false } 282 | ); 283 | t.pass(); 284 | }); 285 | 286 | test("correct raw transaction", t => { 287 | validators.ValidateRawTransaction({ 288 | version: "0x0", 289 | cell_deps: [ 290 | { 291 | dep_type: "code", 292 | out_point: { 293 | tx_hash: 294 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 295 | index: "0x0" 296 | } 297 | } 298 | ], 299 | header_deps: [ 300 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 301 | ], 302 | inputs: [ 303 | { 304 | since: "0x10", 305 | previous_output: { 306 | tx_hash: 307 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 308 | index: "0x2" 309 | } 310 | } 311 | ], 312 | outputs: [ 313 | { 314 | capacity: "0x1234", 315 | lock: { 316 | code_hash: 317 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 318 | args: "0x1234", 319 | hash_type: "data" 320 | } 321 | } 322 | ], 323 | outputs_data: ["0xabcdef"] 324 | }); 325 | t.pass(); 326 | }); 327 | 328 | test("invalid raw transaction", t => { 329 | t.throws(() => { 330 | validators.ValidateRawTransaction({ 331 | version: "0x0", 332 | cell_deps: "invalid", 333 | header_deps: [ 334 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 335 | ], 336 | inputs: [ 337 | { 338 | since: "0x10", 339 | previous_output: { 340 | tx_hash: 341 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 342 | index: "0x2" 343 | } 344 | } 345 | ], 346 | outputs: [ 347 | { 348 | capacity: "0x1234", 349 | lock: { 350 | code_hash: 351 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 352 | args: "0x1234", 353 | hash_type: "data" 354 | } 355 | } 356 | ], 357 | outputs_data: ["0xabcdef"] 358 | }); 359 | }); 360 | }); 361 | 362 | test("correct transaction", t => { 363 | validators.ValidateTransaction({ 364 | version: "0x0", 365 | cell_deps: [ 366 | { 367 | dep_type: "code", 368 | out_point: { 369 | tx_hash: 370 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 371 | index: "0x0" 372 | } 373 | } 374 | ], 375 | header_deps: [ 376 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 377 | ], 378 | inputs: [ 379 | { 380 | since: "0x10", 381 | previous_output: { 382 | tx_hash: 383 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 384 | index: "0x2" 385 | } 386 | } 387 | ], 388 | outputs: [ 389 | { 390 | capacity: "0x1234", 391 | lock: { 392 | code_hash: 393 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 394 | args: "0x1234", 395 | hash_type: "data" 396 | } 397 | } 398 | ], 399 | outputs_data: ["0xabcdef"], 400 | witnesses: ["0x1111"] 401 | }); 402 | t.pass(); 403 | }); 404 | 405 | test("correct header", t => { 406 | validators.ValidateHeader({ 407 | compact_target: "0x1a2d3494", 408 | number: "0xfb1bc", 409 | parent_hash: 410 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 411 | nonce: "0x449b385049af131a0000001584a00100", 412 | timestamp: "0x170aba663c3", 413 | transactions_root: 414 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 415 | proposals_hash: 416 | "0x0000000000000000000000000000000000000000000000000000000000000000", 417 | uncles_hash: 418 | "0x0000000000000000000000000000000000000000000000000000000000000000", 419 | version: "0x0", 420 | epoch: "0x7080612000287", 421 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 422 | }); 423 | t.pass(); 424 | }); 425 | 426 | test("invalid header", t => { 427 | t.throws(() => { 428 | validators.ValidateHeader({ 429 | compact_target: "0x1a2d3494", 430 | number: "0xfb1bc", 431 | parent_hash: 432 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 433 | nonce: "0x449b385049af131a0000001584a0", 434 | timestamp: "0x170aba663c3", 435 | transactions_root: 436 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 437 | proposals_hash: 438 | "0x0000000000000000000000000000000000000000000000000000000000000000", 439 | uncles_hash: 440 | "0x0000000000000000000000000000000000000000000000000000000000000000", 441 | version: "0x0", 442 | epoch: "0x7080612000287", 443 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 444 | }); 445 | }); 446 | }); 447 | 448 | test("invalid raw header", t => { 449 | t.throws(() => { 450 | validators.ValidateRawHeader({ 451 | compact_target: "0x1a2d3494", 452 | number: "0xfb1bc", 453 | parent_hash: 454 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 455 | nonce: "0x449b385049af131a0000001584a00100", 456 | timestamp: "0x170aba663c3", 457 | transactions_root: 458 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 459 | proposals_hash: 460 | "0x0000000000000000000000000000000000000000000000000000000000000000", 461 | uncles_hash: 462 | "0x0000000000000000000000000000000000000000000000000000000000000000", 463 | version: "0x0", 464 | epoch: "0x7080612000287", 465 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 466 | }); 467 | }); 468 | }); 469 | 470 | test("validate uncle block", t => { 471 | validators.ValidateUncleBlock({ 472 | header: { 473 | compact_target: "0x1a2d3494", 474 | number: "0xfb1bc", 475 | parent_hash: 476 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 477 | nonce: "0x449b385049af131a0000001584a00100", 478 | timestamp: "0x170aba663c3", 479 | transactions_root: 480 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 481 | proposals_hash: 482 | "0x0000000000000000000000000000000000000000000000000000000000000000", 483 | uncles_hash: 484 | "0x0000000000000000000000000000000000000000000000000000000000000000", 485 | version: "0x0", 486 | epoch: "0x7080612000287", 487 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 488 | }, 489 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 490 | }); 491 | t.pass(); 492 | }); 493 | 494 | test("validate invalid uncle block", t => { 495 | t.throws(() => { 496 | validators.ValidateUncleBlock({ 497 | header: { 498 | compact_target: "0x1a2d3494", 499 | number: "0xfb1bc", 500 | parent_hash: 501 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 502 | nonce: "0x449b385049af131a0000001584a00100", 503 | timestamp: "0x170aba663c3", 504 | transactions_root: 505 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 506 | proposals_hash: 507 | "0x0000000000000000000000000000000000000000000000000000000000000000", 508 | uncles_hash: 509 | "0x0000000000000000000000000000000000000000000000000000000000000000", 510 | version: "0x0", 511 | epoch: "0x7080612000287", 512 | dao: 513 | "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 514 | }, 515 | proposals: ["0x12345678901234567890", "0xabcdeabcdeab"] 516 | }); 517 | }); 518 | }); 519 | 520 | test("validate invalid uncle block but skips nested validation", t => { 521 | validators.ValidateUncleBlock( 522 | { 523 | header: 123123, 524 | proposals: ["0x12345678901234567890"] 525 | }, 526 | { nestedValidation: false } 527 | ); 528 | t.pass(); 529 | }); 530 | 531 | test("validate block", t => { 532 | validators.ValidateBlock({ 533 | header: { 534 | compact_target: "0x1a2d3494", 535 | number: "0xfb1bc", 536 | parent_hash: 537 | "0x3134874027b9b2b17391d2fa545344b10bd8b8c49d9ea47d55a447d01142b21b", 538 | nonce: "0x449b385049af131a0000001584a00100", 539 | timestamp: "0x170aba663c3", 540 | transactions_root: 541 | "0x68a83c880eb942396d22020aa83343906986f66418e9b8a4488f2866ecc4e86a", 542 | proposals_hash: 543 | "0x0000000000000000000000000000000000000000000000000000000000000000", 544 | uncles_hash: 545 | "0x0000000000000000000000000000000000000000000000000000000000000000", 546 | version: "0x0", 547 | epoch: "0x7080612000287", 548 | dao: "0x40b4d9a3ddc9e730736c7342a2f023001240f362253b780000b6ca2f1e790107" 549 | }, 550 | transactions: [ 551 | { 552 | version: "0x0", 553 | cell_deps: [ 554 | { 555 | dep_type: "code", 556 | out_point: { 557 | tx_hash: 558 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7300", 559 | index: "0x0" 560 | } 561 | } 562 | ], 563 | header_deps: [ 564 | "0xb39d53656421d1532dd995a0924441ca8f43052bc2b7740a0e814a488a8214d6" 565 | ], 566 | inputs: [ 567 | { 568 | since: "0x10", 569 | previous_output: { 570 | tx_hash: 571 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7301", 572 | index: "0x2" 573 | } 574 | } 575 | ], 576 | outputs: [ 577 | { 578 | capacity: "0x1234", 579 | lock: { 580 | code_hash: 581 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a7302", 582 | args: "0x1234", 583 | hash_type: "data" 584 | } 585 | } 586 | ], 587 | outputs_data: ["0xabcdef"], 588 | witnesses: ["0x1111"] 589 | } 590 | ], 591 | uncles: [], 592 | proposals: ["0x12345678901234567890", "0xabcdeabcdeabcdeabcde"] 593 | }); 594 | t.pass(); 595 | }); 596 | 597 | test("correct cellbase witness", t => { 598 | validators.ValidateCellbaseWitness({ 599 | lock: { 600 | code_hash: 601 | "0xa98c57135830e1b91345948df6c4b8870828199a786b26f09f7dec4bc27a73da", 602 | args: "0x1234", 603 | hash_type: "data" 604 | }, 605 | message: "0x1234abcdef" 606 | }); 607 | t.pass(); 608 | }); 609 | 610 | test("correct witness args", t => { 611 | validators.ValidateWitnessArgs({ 612 | lock: "0x1234", 613 | input_type: "0x4678", 614 | output_type: "0x2312" 615 | }); 616 | t.pass(); 617 | }); 618 | 619 | test("empty witness args", t => { 620 | validators.ValidateWitnessArgs({}); 621 | t.pass(); 622 | }); 623 | 624 | test("only one witness args", t => { 625 | validators.ValidateWitnessArgs({ 626 | lock: "0x1234" 627 | }); 628 | t.pass(); 629 | }); 630 | 631 | test("invalid witness args", t => { 632 | t.throws(() => { 633 | validators.ValidateWitnessArgs({ 634 | lock: "0x1234", 635 | invalidkey: "0x1232" 636 | }); 637 | }); 638 | }); 639 | 640 | test("invalid witness args content", t => { 641 | t.throws(() => { 642 | validators.ValidateWitnessArgs({ 643 | lock: "0x1234gg" 644 | }); 645 | }); 646 | }); 647 | --------------------------------------------------------------------------------