├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── config.js ├── config.json ├── default.nix ├── package.json ├── shell.nix ├── src ├── check.ts ├── cli.ts ├── index.ts ├── parachain.ts ├── rpc.ts ├── runner.ts ├── spawn.ts ├── spec.ts └── types.d.ts ├── test ├── test-utils │ ├── constants.ts │ ├── para-node.ts │ ├── providers.ts │ ├── setup-para-tests.ts │ └── substrate-rpc.ts ├── tests-no-ci │ └── test-balance-genesis-2-parachains.ts └── tests │ └── test-balance-genesis.ts ├── tsconfig.json ├── yarn.lock └── yarn.nix /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.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: CI 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [14.x, 16.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: install 28 | run: yarn install 29 | 30 | - name: Lint 31 | run: yarn lint 32 | 33 | - name: Build 34 | run: yarn build 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | *.wasm 4 | *.log 5 | rococo-local.json 6 | rococo-local-raw.json 7 | configLocal.json 8 | bin 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | README.md 4 | *.json 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Shawn Tabrizi 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 | # ⚠️ Deprecated ⚠️ 2 | 3 | This project has been deprecated. Please use one of the following alternatives: 4 | - [zombienet](https://github.com/paritytech/zombienet) 5 | - [parachain-launch](https://github.com/open-web3-stack/parachain-launch) 6 | 7 | # polkadot-launch 8 | 9 | Simple CLI tool to launch a local [Polkadot](https://github.com/paritytech/polkadot/) test network. 10 | 11 | ## Install 12 | 13 | ``` 14 | yarn global add polkadot-launch 15 | ``` 16 | 17 | Or, if you use `npm`: 18 | 19 | ```bash 20 | npm i polkadot-launch -g 21 | ``` 22 | 23 | ## Binary Files 24 | 25 | To use polkadot-launch, you need to have binary files for a `polkadot` relay chain and a 26 | `polkadot-parachain` in the bin folder. 27 | 28 | > If you are on an Apple M1 ARM chip, make sure you are using the `stable-aarch64-apple-darwin` toolchain to compile the binaries. 29 | 30 | You can generate these files by cloning the `rococo-v1` branch of these projects in the same root as the polkadot-launch repo 31 | and building them with the specific flags below: 32 | 33 | ```bash 34 | git clone https://github.com/paritytech/polkadot 35 | cd polkadot 36 | cargo build --release 37 | cp ./target/release/polkadot ../polkadot-launch/bin/polkadot-relaychain 38 | ``` 39 | 40 | and 41 | 42 | ``` 43 | git clone https://github.com/paritytech/cumulus 44 | cd cumulus 45 | cargo build --release -p polkadot-parachain 46 | cp ./target/release/polkadot-parachain ../polkadot-launch/bin/polkadot-parachain 47 | ``` 48 | 49 | ## Use 50 | 51 | ```bash 52 | polkadot-launch config.json 53 | ``` 54 | 55 | ### Configuration File 56 | 57 | The required configuration file defines the properties of the network you want to set up. 58 | You may use a json or a js file. 59 | 60 | You can see the examples: 61 | - [config.json](config.json) 62 | - [config.js](config.js) 63 | 64 | You may find the .js alternative more convenient if you need comments, trailing commas or if you prefer do dedup some portions of the config. 65 | 66 | #### `relaychain` 67 | 68 | - `bin`: The path of the [Polkadot relay chain binary](https://github.com/paritytech/polkadot/) used 69 | to setup your test network. For example `/target/release/polkadot`. 70 | - `chain`: The chain you want to use to generate your spec (probably `rococo-local`). 71 | - `nodes`: An array of nodes that will be validators on the relay chain. 72 | - `name`: Must be one of `alice`, `bob`, `charlie`, or `dave`. 73 | - `wsPort`: The websocket port for this node. 74 | - `port`: The TCP port for this node. 75 | - `nodeKey`: a secret key used for generating libp2p peer identifier. Optional. 76 | - `basePath`: The directory used for the blockchain db and other outputs. When unspecified, we use 77 | `--tmp`. 78 | - `flags`: Any additional command line flags you want to add when starting your node. 79 | - `genesis`: A JSON object of the properties you want to modify from the genesis configuration. 80 | Non-specified properties will be unchanged from the original genesis configuration. 81 | 82 | These variable are fed directly into the Polkadot binary and used to spawn a node: 83 | 84 | ```bash 85 | \ 86 | --chain=-raw.json \ 87 | --tmp \ 88 | --ws-port= \ 89 | --port= \ 90 | -- \ 91 | ``` 92 | 93 | An example of `genesis` is: 94 | 95 | ```json 96 | "genesis": { 97 | "runtime": { 98 | "runtime_genesis_config": { 99 | "configuration": { 100 | "config": { 101 | "validation_upgrade_frequency": 10, 102 | "validation_upgrade_delay": 10 103 | } 104 | }, 105 | "palletCollective": { 106 | "members": ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", "5DbKjhNLpqX3zqZdNBc9BGb4fHU1cRBaDhJUskrvkwfraDi6"] 107 | } 108 | }, 109 | "session_length_in_blocks": 10 110 | } 111 | } 112 | ``` 113 | 114 | All `genesis` properties can be found in the chainspec output: 115 | 116 | ```bash 117 | ./polkadot build-spec --chain=rococo-local --disable-default-bootnode 118 | ``` 119 | 120 | #### `parachains` 121 | 122 | `parachains` is an array of objects that consists of: 123 | 124 | - `bin`: The path of the [collator node 125 | binary](https://github.com/substrate-developer-hub/substrate-parachain-template) used to create 126 | blocks for your parachain. For example 127 | `/target/release/polkadot-parachain`. 128 | - `id`: The id to assign to this parachain. Must be unique. 129 | - `wsPort`: The websocket port for this node. 130 | - `port`: The TCP port for this node. 131 | - `balance`: (Optional) Configure a starting amount of balance on the relay chain for this chain's 132 | account ID. 133 | - `chain`: (Optional) Configure an alternative chain specification to be used for launching the 134 | parachain. 135 | - `basePath`: The directory used for the blockchain db and other outputs. When unspecified, we use 136 | `--tmp`. 137 | - `flags`: Any additional command line flags you want to add when starting your node. 138 | 139 | These variables are fed directly into the collator binary and used to spawn a node: 140 | 141 | ```bash 142 | \ 143 | --tmp \ 144 | --ws-port= \ 145 | --port= \ 146 | --parachain-id= \ 147 | --validator \ 148 | --chain= 149 | -- \ 150 | --chain=-raw.json \ 151 | ``` 152 | 153 | #### `simpleParachains` 154 | 155 | This is similar to `parachains` but for "simple" collators like the adder-collator, a very simple 156 | collator that lives in the polkadot repo and is meant just for simple testing. It supports a subset 157 | of configuration values: 158 | 159 | - `bin`: The path to the collator binary. 160 | - `id`: The id to assign to this parachain. Must be unique. 161 | - `port`: The TCP port for this node. 162 | - `balance`: (Optional) Configure a starting amount of balance on the relay chain for this chain's 163 | account ID. 164 | 165 | #### `hrmpChannels` 166 | 167 | Open HRMP channels between the specified parachains so that it's possible to send messages between 168 | those. Keep in mind that an HRMP channel is unidirectional and in case you need to communicate both 169 | ways you need to open channels in both directions. 170 | 171 | ```json 172 | "hrmpChannels": [ 173 | { 174 | "sender": "200", 175 | "recipient": "300", 176 | "maxCapacity": 8, 177 | "maxMessageSize": 512 178 | } 179 | ] 180 | ``` 181 | 182 | #### `types` 183 | 184 | These are the Polkadot JS types you might need to include so that Polkadot JS will be able to 185 | interface properly with your runtime. 186 | 187 | ```json 188 | "types": { 189 | "HrmpChannelId": { 190 | "sender": "u32", 191 | "receiver": "u32" 192 | } 193 | } 194 | ``` 195 | 196 | Or you can specify a path to the type definition json file instead: 197 | 198 | ```json 199 | "types": "./typedefs.json" 200 | ``` 201 | 202 | #### `finalization` 203 | 204 | A simple boolean flag for whether you want to make sure all of the transactions submitted in 205 | polkadot-launch wait for finalization. 206 | 207 | ## How Does It Work? 208 | 209 | This tool just automates the steps needed to spin up multiple relay chain nodes and parachain nodes 210 | in order to create a local test network. 211 | 212 | You can add the `-v` or `--verbose` flag to see what processes it is invoking and with which arguments. 213 | 214 | - [`child_process`](https://nodejs.org/api/child_process.html) is used to execute commands on your 215 | node: 216 | - We build a fresh chain spec using the `chain` parameter specified in your config. 217 | - Includes the authorities you specified. 218 | - Includes changes to the `paras`. 219 | - Includes parachains you have added. 220 | - `wasm` is generated using the ` export-genesis-wasm` subcommand. 221 | - `header` is retrieved by calling `api.rpc.chain.getHeader(genesis_hash)`. 222 | - The final file is named `-raw.json`. 223 | - We spawn new node instances using the information provided in your config. Each node produces a 224 | `.log` file in your working directory that you can use to track the output. For example: 225 | ```bash 226 | tail -f alice.log # Alice validator on the relay chain 227 | # or 228 | tail -f 9988.log # Collator for Parachain ID 200 on wsPort 9988 229 | ``` 230 | - [`polkadot-js api`](https://polkadot.js.org/api/) is used to connect to these spawned nodes over 231 | their WebSocket endpoint. 232 | 233 | ## Development 234 | 235 | To work on this project, you will need [`yarn`](https://yarnpkg.com/). 236 | 237 | Install all NodeJS dependencies with: 238 | 239 | ```bash 240 | yarn 241 | ``` 242 | 243 | Start the application with: 244 | 245 | ```bash 246 | yarn start config.json 247 | ``` 248 | 249 | When you have finished your changes, make a [pull 250 | request](https://github.com/paritytech/polkadot-launch/pulls) to this repo. 251 | 252 | ## Get Help 253 | 254 | Open an [issue](https://github.com/paritytech/polkadot-launch/issues) if you have problems or 255 | feature requests! 256 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | // Collator flags 2 | var flags = ["--force-authoring", "--", "--execution=wasm"]; 3 | 4 | var config = { 5 | relaychain: { 6 | bin: "./bin/polkadot", 7 | chain: "rococo-local", 8 | nodes: [ 9 | { 10 | name: "alice", 11 | wsPort: 9944, 12 | port: 30444, 13 | }, 14 | { 15 | name: "bob", 16 | wsPort: 9955, 17 | port: 30555, 18 | }, 19 | { 20 | name: "charlie", 21 | wsPort: 9966, 22 | port: 30666, 23 | }, 24 | { 25 | name: "dave", 26 | wsPort: 9977, 27 | port: 30777, 28 | }, 29 | ], 30 | genesis: { 31 | runtime: { 32 | runtime_genesis_config: { 33 | configuration: { 34 | config: { 35 | validation_upgrade_frequency: 10, 36 | validation_upgrade_delay: 10, 37 | }, 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | parachains: [ 44 | { 45 | bin: "./bin/polkadot-parachain", 46 | id: "200", 47 | balance: "1000000000000000000000", 48 | nodes: [ 49 | { 50 | wsPort: 9988, 51 | port: 31200, 52 | name: "alice", 53 | flags, 54 | }, 55 | ], 56 | }, 57 | { 58 | bin: "./bin/polkadot-parachain", 59 | id: "300", 60 | balance: "1000000000000000000000", 61 | nodes: [ 62 | { 63 | wsPort: 9999, 64 | port: 31300, 65 | name: "alice", 66 | flags, 67 | }, 68 | ], 69 | }, 70 | ], 71 | simpleParachains: [ 72 | { 73 | bin: "./bin/adder-collator", 74 | id: "400", 75 | port: "31400", 76 | name: "alice", 77 | balance: "1000000000000000000000", 78 | }, 79 | ], 80 | hrmpChannels: [ 81 | { 82 | sender: 200, 83 | recipient: 300, 84 | maxCapacity: 8, 85 | maxMessageSize: 512, 86 | }, 87 | ], 88 | types: {}, 89 | finalization: false, 90 | }; 91 | 92 | module.exports = config; 93 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "relaychain": { 3 | "bin": "./bin/polkadot", 4 | "chain": "rococo-local", 5 | "nodes": [ 6 | { 7 | "name": "alice", 8 | "wsPort": 9944, 9 | "port": 30444 10 | }, 11 | { 12 | "name": "bob", 13 | "wsPort": 9955, 14 | "port": 30555 15 | }, 16 | { 17 | "name": "charlie", 18 | "wsPort": 9966, 19 | "port": 30666 20 | }, 21 | { 22 | "name": "dave", 23 | "wsPort": 9977, 24 | "port": 30777 25 | } 26 | ], 27 | "genesis": { 28 | "runtime": { 29 | "runtime_genesis_config": { 30 | "configuration": { 31 | "config": { 32 | "validation_upgrade_frequency": 10, 33 | "validation_upgrade_delay": 10 34 | } 35 | } 36 | } 37 | } 38 | } 39 | }, 40 | "parachains": [ 41 | { 42 | "bin": "./bin/polkadot-parachain", 43 | "id": "200", 44 | "balance": "1000000000000000000000", 45 | "nodes": [ 46 | { 47 | "wsPort": 9988, 48 | "port": 31200, 49 | "name": "alice", 50 | "flags": ["--", "--execution=wasm"] 51 | } 52 | ] 53 | }, 54 | { 55 | "bin": "./bin/polkadot-parachain", 56 | "id": "300", 57 | "balance": "1000000000000000000000", 58 | "nodes": [ 59 | { 60 | "wsPort": 9999, 61 | "port": 31300, 62 | "name": "alice", 63 | "flags": ["--", "--execution=wasm"] 64 | } 65 | ] 66 | } 67 | ], 68 | "simpleParachains": [ 69 | { 70 | "bin": "./bin/adder-collator", 71 | "id": "400", 72 | "port": "31400", 73 | "name": "alice", 74 | "balance": "1000000000000000000000" 75 | } 76 | ], 77 | "hrmpChannels": [ 78 | { 79 | "sender": 200, 80 | "recipient": 300, 81 | "maxCapacity": 8, 82 | "maxMessageSize": 512 83 | } 84 | ], 85 | "types": {}, 86 | "finalization": false 87 | } 88 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | pkgs.mkYarnPackage { 3 | name = "polkadot-launch"; 4 | src = builtins.filterSource 5 | (path: type: type != "directory" || baseNameOf path != "bin") 6 | ./.; 7 | buildPhase = '' 8 | yarn build 9 | ''; 10 | postInstall = '' 11 | chmod +x $out/bin/polkadot-launch 12 | ''; 13 | packageJSON = ./package.json; 14 | yarnLock = ./yarn.lock; 15 | yarnNix = ./yarn.nix; 16 | } 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "polkadot-launch", 3 | "version": "2.3.0", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "author": "Shawn Tabrizi ", 7 | "license": "MIT", 8 | "scripts": { 9 | "prepare": "tsc", 10 | "build": "tsc", 11 | "start": "yarn build && node dist/cli.js", 12 | "lint": "prettier -v && prettier --check .", 13 | "lint:write": "prettier --write .", 14 | "para-test": "mocha -r ts-node/register 'test/tests/**/test-*.ts'", 15 | "para-test-no-ci": "mocha -r ts-node/register 'test/tests-no-ci/**/test-*.ts'" 16 | }, 17 | "dependencies": { 18 | "@polkadot/api": "^8.9.1", 19 | "@polkadot/api-augment": "^8.9.1", 20 | "@polkadot/keyring": "^9.5.1", 21 | "@polkadot/types": "^8.9.1", 22 | "@polkadot/util": "^9.5.1", 23 | "@polkadot/util-crypto": "^9.5.1", 24 | "@types/chai": "^4.2.22", 25 | "@types/mocha": "^9.0.0", 26 | "chai": "^4.3.4", 27 | "ethers": "^5.4.7", 28 | "filter-console": "^0.1.1", 29 | "libp2p-crypto": "^0.20.0", 30 | "mocha": "^9.1.2", 31 | "peer-id": "^0.15.3", 32 | "tcp-port-used": "^1.0.2", 33 | "ts-node": "^10.3.0", 34 | "web3": "^1.6.0", 35 | "web3-core": "^1.6.0", 36 | "web3-eth": "^1.6.0", 37 | "yargs": "^15.4.1" 38 | }, 39 | "files": [ 40 | "dist" 41 | ], 42 | "bin": { 43 | "polkadot-launch": "dist/cli.js" 44 | }, 45 | "devDependencies": { 46 | "@types/node": "^16.4.12", 47 | "@types/tcp-port-used": "^1.0.0", 48 | "prettier": "^2.4.1", 49 | "typescript": "^4.1.5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | with pkgs; mkShell { 3 | buildInputs = [ 4 | nodePackages.typescript 5 | (yarn.override { nodejs = nodejs-14_x; }) 6 | ]; 7 | } 8 | -------------------------------------------------------------------------------- /src/check.ts: -------------------------------------------------------------------------------- 1 | // This function checks that the `config.json` file has all the expected properties. 2 | // It displays a unique error message and returns `false` for any detected issues. 3 | import { LaunchConfig } from "./types"; 4 | export function checkConfig(config: LaunchConfig) { 5 | if (!config) { 6 | console.error("⚠ Missing config"); 7 | return false; 8 | } 9 | 10 | if (!config.relaychain) { 11 | console.error("⚠ Missing `relaychain` object"); 12 | return false; 13 | } 14 | 15 | if (!config.relaychain.bin) { 16 | console.error("⚠ Missing `relaychain.bin`"); 17 | return false; 18 | } 19 | 20 | if (!config.relaychain.chain) { 21 | console.error("⚠ Missing `relaychain.chain`"); 22 | return false; 23 | } 24 | 25 | if (config.relaychain.nodes.length == 0) { 26 | console.error("⚠ No relaychain nodes defined"); 27 | return false; 28 | } 29 | 30 | for (const node of config.relaychain.nodes) { 31 | if (node.flags && node.flags.constructor !== Array) { 32 | console.error("⚠ Relay chain flags should be an array."); 33 | return false; 34 | } 35 | } 36 | 37 | if (!config.parachains) { 38 | console.error("⚠ Missing `parachains` object"); 39 | return false; 40 | } 41 | 42 | if (config.parachains.length >= config.relaychain.nodes.length) { 43 | console.error( 44 | "⚠ Must have the same or greater number of relaychain nodes than parachains." 45 | ); 46 | return false; 47 | } 48 | 49 | for (let parachain of config.parachains) { 50 | if (!parachain.nodes) { 51 | console.error("⚠ Missing parachain nodes"); 52 | return false; 53 | } 54 | } 55 | 56 | for (let parachain of config.parachains) { 57 | for (let node of parachain.nodes) { 58 | if (node.flags && node.flags.constructor !== Array) { 59 | console.error("⚠ Parachain flags should be an array."); 60 | return false; 61 | } 62 | } 63 | } 64 | 65 | // Allow the config to not contain `simpleParachains` 66 | if (!config.simpleParachains) { 67 | config.simpleParachains = []; 68 | } 69 | 70 | return true; 71 | } 72 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { killAll } from "./spawn"; 4 | import { resolve, dirname } from "path"; 5 | import fs from "fs"; 6 | import { LaunchConfig } from "./types"; 7 | import { run } from "./runner"; 8 | 9 | // Special care is needed to handle paths to various files (binaries, spec, config, etc...) 10 | // The user passes the path to `config.json`, and we use that as the starting point for any other 11 | // relative path. So the `config.json` file is what we will be our starting point. 12 | const { argv } = require("yargs"); 13 | 14 | const config_file = argv._[0] ? argv._[0] : null; 15 | if (!config_file) { 16 | console.error("Missing config file argument..."); 17 | process.exit(); 18 | } 19 | let config_path = resolve(process.cwd(), config_file); 20 | let config_dir = dirname(config_path); 21 | if (!fs.existsSync(config_path)) { 22 | console.error("Config file does not exist: ", config_path); 23 | process.exit(); 24 | } 25 | let config: LaunchConfig = require(config_path); 26 | 27 | // Kill all processes when exiting. 28 | process.on("exit", function () { 29 | killAll(); 30 | }); 31 | 32 | // Handle ctrl+c to trigger `exit`. 33 | process.on("SIGINT", function () { 34 | process.exit(2); 35 | }); 36 | 37 | run(config_dir, config); 38 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./check"; 2 | export * from "./parachain"; 3 | export * from "./rpc"; 4 | export * from "./runner"; 5 | export * from "./spawn"; 6 | export * from "./spec"; 7 | -------------------------------------------------------------------------------- /src/parachain.ts: -------------------------------------------------------------------------------- 1 | import { bnToHex, stringToHex, hexStripPrefix } from "@polkadot/util"; 2 | import { encodeAddress } from "@polkadot/util-crypto"; 3 | 4 | export function parachainAccount(id: string) { 5 | let prefix = stringToHex("para"); 6 | let encoded_id = bnToHex(parseInt(id), { isLe: true }); 7 | let address_bytes = (prefix + hexStripPrefix(encoded_id)).padEnd(64 + 2, "0"); 8 | let address = encodeAddress(address_bytes); 9 | 10 | return address; 11 | } 12 | -------------------------------------------------------------------------------- /src/rpc.ts: -------------------------------------------------------------------------------- 1 | import "@polkadot/api-augment"; 2 | import { ApiPromise, WsProvider } from "@polkadot/api"; 3 | import { Keyring } from "@polkadot/api"; 4 | import { cryptoWaitReady } from "@polkadot/util-crypto"; 5 | 6 | const filterConsole = require("filter-console"); 7 | 8 | // Hide some warning messages that are coming from Polkadot JS API. 9 | // TODO: Make configurable. 10 | filterConsole([ 11 | `code: '1006' reason: 'connection failed'`, 12 | `Unhandled promise rejections`, 13 | `UnhandledPromiseRejectionWarning:`, 14 | `Unknown types found`, 15 | ]); 16 | 17 | // Connect to a local Substrate node. This function wont resolve until connected. 18 | // TODO: Add a timeout where we know something went wrong so we don't wait forever. 19 | export async function connect(port: number, types: any) { 20 | const provider = new WsProvider("ws://127.0.0.1:" + port); 21 | const api = await ApiPromise.create({ 22 | provider, 23 | types, 24 | throwOnConnect: false, 25 | }); 26 | return api; 27 | } 28 | 29 | // Get the genesis header of a node. Used for registering a parachain on the relay chain. 30 | export async function getHeader(api: ApiPromise) { 31 | let genesis_hash = await api.rpc.chain.getBlockHash(0); 32 | let genesis_header = await api.rpc.chain.getHeader(genesis_hash); 33 | return genesis_header.toHex(); 34 | } 35 | 36 | // Submit an extrinsic to the relay chain to register a parachain. 37 | // Uses the Alice account which is known to be Sudo for the relay chain. 38 | export async function registerParachain( 39 | api: ApiPromise, 40 | id: string, 41 | wasm: string, 42 | header: string, 43 | finalization: boolean = false 44 | ) { 45 | return new Promise(async (resolvePromise, reject) => { 46 | await cryptoWaitReady(); 47 | 48 | const keyring = new Keyring({ type: "sr25519" }); 49 | const alice = keyring.addFromUri("//Alice"); 50 | 51 | let paraGenesisArgs = { 52 | genesis_head: header, 53 | validation_code: wasm, 54 | parachain: true, 55 | }; 56 | let genesis = api.createType("ParaGenesisArgs", paraGenesisArgs); 57 | 58 | const nonce = Number((await api.query.system.account(alice.address)).nonce); 59 | 60 | console.log( 61 | `--- Submitting extrinsic to register parachain ${id}. (nonce: ${nonce}) ---` 62 | ); 63 | const unsub = await api.tx.sudo 64 | .sudo(api.tx.parasSudoWrapper.sudoScheduleParaInitialize(id, genesis)) 65 | .signAndSend(alice, { nonce: nonce, era: 0 }, (result) => { 66 | console.log(`Current status is ${result.status}`); 67 | if (result.status.isInBlock) { 68 | console.log( 69 | `Transaction included at blockHash ${result.status.asInBlock}` 70 | ); 71 | if (finalization) { 72 | console.log("Waiting for finalization..."); 73 | } else { 74 | unsub(); 75 | resolvePromise(); 76 | } 77 | } else if (result.status.isFinalized) { 78 | console.log( 79 | `Transaction finalized at blockHash ${result.status.asFinalized}` 80 | ); 81 | unsub(); 82 | resolvePromise(); 83 | } else if (result.isError) { 84 | console.log(`Transaction Error`); 85 | reject(`Transaction Error`); 86 | } 87 | }); 88 | }); 89 | } 90 | 91 | // Set the balance of an account on the relay chain. 92 | export async function setBalance( 93 | api: ApiPromise, 94 | who: string, 95 | value: string, 96 | finalization: boolean = false 97 | ) { 98 | return new Promise(async (resolvePromise, reject) => { 99 | await cryptoWaitReady(); 100 | 101 | const keyring = new Keyring({ type: "sr25519" }); 102 | const alice = keyring.addFromUri("//Alice"); 103 | 104 | const nonce = Number((await api.query.system.account(alice.address)).nonce); 105 | 106 | console.log( 107 | `--- Submitting extrinsic to set balance of ${who} to ${value}. (nonce: ${nonce}) ---` 108 | ); 109 | const unsub = await api.tx.sudo 110 | .sudo(api.tx.balances.setBalance(who, value, 0)) 111 | .signAndSend(alice, { nonce: nonce, era: 0 }, (result) => { 112 | console.log(`Current status is ${result.status}`); 113 | if (result.status.isInBlock) { 114 | console.log( 115 | `Transaction included at blockHash ${result.status.asInBlock}` 116 | ); 117 | if (finalization) { 118 | console.log("Waiting for finalization..."); 119 | } else { 120 | unsub(); 121 | resolvePromise(); 122 | } 123 | } else if (result.status.isFinalized) { 124 | console.log( 125 | `Transaction finalized at blockHash ${result.status.asFinalized}` 126 | ); 127 | unsub(); 128 | resolvePromise(); 129 | } else if (result.isError) { 130 | console.log(`Transaction Error`); 131 | reject(`Transaction Error`); 132 | } 133 | }); 134 | }); 135 | } 136 | 137 | export async function sendHrmpMessage( 138 | api: ApiPromise, 139 | recipient: string, 140 | data: string, 141 | finalization: boolean = false 142 | ) { 143 | return new Promise(async (resolvePromise, reject) => { 144 | await cryptoWaitReady(); 145 | 146 | const keyring = new Keyring({ type: "sr25519" }); 147 | const alice = keyring.addFromUri("//Alice"); 148 | 149 | const nonce = Number((await api.query.system.account(alice.address)).nonce); 150 | 151 | let hrmpMessage = { 152 | recipient: recipient, 153 | data: data, 154 | }; 155 | let message = api.createType("OutboundHrmpMessage", hrmpMessage); 156 | 157 | console.log(`--- Sending a message to ${recipient}. (nonce: ${nonce}) ---`); 158 | const unsub = await api.tx.sudo 159 | .sudo(api.tx.messageBroker.sudoSendHrmpMessage(message)) 160 | .signAndSend(alice, { nonce: nonce, era: 0 }, (result) => { 161 | console.log(`Current status is ${result.status}`); 162 | if (result.status.isInBlock) { 163 | console.log( 164 | `Transaction included at blockHash ${result.status.asInBlock}` 165 | ); 166 | if (finalization) { 167 | console.log("Waiting for finalization..."); 168 | } else { 169 | unsub(); 170 | resolvePromise(); 171 | } 172 | } else if (result.status.isFinalized) { 173 | console.log( 174 | `Transaction finalized at blockHash ${result.status.asFinalized}` 175 | ); 176 | unsub(); 177 | resolvePromise(); 178 | } else if (result.isError) { 179 | console.log(`Transaction Error`); 180 | reject(`Transaction Error`); 181 | } 182 | }); 183 | }); 184 | } 185 | -------------------------------------------------------------------------------- /src/runner.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import "@polkadot/api-augment"; 4 | import { 5 | startNode, 6 | startCollator, 7 | generateChainSpec, 8 | generateChainSpecRaw, 9 | exportGenesisWasm, 10 | exportGenesisState, 11 | startSimpleCollator, 12 | getParachainIdFromSpec, 13 | } from "./spawn"; 14 | import { connect, setBalance } from "./rpc"; 15 | import { checkConfig } from "./check"; 16 | import { 17 | clearAuthorities, 18 | addAuthority, 19 | changeGenesisConfig, 20 | addGenesisParachain, 21 | addGenesisHrmpChannel, 22 | addBootNodes, 23 | } from "./spec"; 24 | import { parachainAccount } from "./parachain"; 25 | import { ApiPromise } from "@polkadot/api"; 26 | import { randomAsHex } from "@polkadot/util-crypto"; 27 | 28 | import { resolve } from "path"; 29 | import fs from "fs"; 30 | import type { 31 | LaunchConfig, 32 | ResolvedParachainConfig, 33 | ResolvedSimpleParachainConfig, 34 | HrmpChannelsConfig, 35 | ResolvedLaunchConfig, 36 | } from "./types"; 37 | import { keys as libp2pKeys } from "libp2p-crypto"; 38 | import { hexAddPrefix, hexStripPrefix, hexToU8a } from "@polkadot/util"; 39 | import PeerId from "peer-id"; 40 | 41 | function loadTypeDef(types: string | object): object { 42 | if (typeof types === "string") { 43 | // Treat types as a json file path 44 | try { 45 | const rawdata = fs.readFileSync(types, { encoding: "utf-8" }); 46 | return JSON.parse(rawdata); 47 | } catch { 48 | console.error("failed to load parachain typedef file"); 49 | process.exit(1); 50 | } 51 | } else { 52 | return types; 53 | } 54 | } 55 | 56 | // keep track of registered parachains 57 | let registeredParachains: { [key: string]: boolean } = {}; 58 | 59 | export async function run(config_dir: string, rawConfig: LaunchConfig) { 60 | // We need to reset that variable when running a new network 61 | registeredParachains = {}; 62 | // Verify that the `config.json` has all the expected properties. 63 | if (!checkConfig(rawConfig)) { 64 | return; 65 | } 66 | const config = await resolveParachainId(config_dir, rawConfig); 67 | var bootnodes = await generateNodeKeys(config); 68 | 69 | const relay_chain_bin = resolve(config_dir, config.relaychain.bin); 70 | if (!fs.existsSync(relay_chain_bin)) { 71 | console.error("Relay chain binary does not exist: ", relay_chain_bin); 72 | process.exit(); 73 | } 74 | const chain = config.relaychain.chain; 75 | await generateChainSpec(relay_chain_bin, chain); 76 | // -- Start Chain Spec Modify -- 77 | clearAuthorities(`${chain}.json`); 78 | for (const node of config.relaychain.nodes) { 79 | await addAuthority(`${chain}.json`, node.name); 80 | } 81 | if (config.relaychain.genesis) { 82 | await changeGenesisConfig(`${chain}.json`, config.relaychain.genesis); 83 | } 84 | await addParachainsToGenesis( 85 | config_dir, 86 | `${chain}.json`, 87 | config.parachains, 88 | config.simpleParachains 89 | ); 90 | if (config.hrmpChannels) { 91 | await addHrmpChannelsToGenesis(`${chain}.json`, config.hrmpChannels); 92 | } 93 | addBootNodes(`${chain}.json`, bootnodes); 94 | // -- End Chain Spec Modify -- 95 | await generateChainSpecRaw(relay_chain_bin, chain); 96 | const spec = resolve(`${chain}-raw.json`); 97 | 98 | // First we launch each of the validators for the relay chain. 99 | for (const node of config.relaychain.nodes) { 100 | const { name, wsPort, rpcPort, port, flags, basePath, nodeKey } = node; 101 | console.log( 102 | `Starting Relaychain Node ${name}... wsPort: ${wsPort} rpcPort: ${rpcPort} port: ${port} nodeKey: ${nodeKey}` 103 | ); 104 | // We spawn a `child_process` starting a node, and then wait until we 105 | // able to connect to it using PolkadotJS in order to know its running. 106 | startNode( 107 | relay_chain_bin, 108 | name, 109 | wsPort, 110 | rpcPort, 111 | port, 112 | nodeKey!, // by the time the control flow gets here it should be assigned. 113 | spec, 114 | flags, 115 | basePath 116 | ); 117 | } 118 | 119 | // Connect to the first relay chain node to submit the extrinsic. 120 | let relayChainApi: ApiPromise = await connect( 121 | config.relaychain.nodes[0].wsPort, 122 | loadTypeDef(config.types) 123 | ); 124 | 125 | // Then launch each parachain 126 | for (const parachain of config.parachains) { 127 | const { resolvedId, balance, chain: paraChain } = parachain; 128 | 129 | const bin = resolve(config_dir, parachain.bin); 130 | if (!fs.existsSync(bin)) { 131 | console.error("Parachain binary does not exist: ", bin); 132 | process.exit(); 133 | } 134 | let account = parachainAccount(resolvedId); 135 | 136 | for (const node of parachain.nodes) { 137 | const { wsPort, port, flags, name, basePath, rpcPort } = node; 138 | console.log( 139 | `Starting a Collator for parachain ${resolvedId}: ${account}, Collator port : ${port} wsPort : ${wsPort} rpcPort : ${rpcPort}` 140 | ); 141 | await startCollator(bin, wsPort, rpcPort, port, { 142 | name, 143 | spec, 144 | flags, 145 | chain: paraChain, 146 | basePath, 147 | onlyOneParachainNode: parachain.nodes.length === 1, 148 | }); 149 | } 150 | 151 | // Allow time for the TX to complete, avoiding nonce issues. 152 | // TODO: Handle nonce directly instead of this. 153 | if (balance) { 154 | await setBalance(relayChainApi, account, balance, config.finalization); 155 | } 156 | } 157 | 158 | // Then launch each simple parachain (e.g. an adder-collator) 159 | if (config.simpleParachains) { 160 | for (const simpleParachain of config.simpleParachains) { 161 | const { id, resolvedId, port, balance } = simpleParachain; 162 | const bin = resolve(config_dir, simpleParachain.bin); 163 | if (!fs.existsSync(bin)) { 164 | console.error("Simple parachain binary does not exist: ", bin); 165 | process.exit(); 166 | } 167 | 168 | let account = parachainAccount(resolvedId); 169 | console.log(`Starting Parachain ${resolvedId}: ${account}`); 170 | const skipIdArg = !id; 171 | await startSimpleCollator(bin, resolvedId, spec, port, skipIdArg); 172 | 173 | // Allow time for the TX to complete, avoiding nonce issues. 174 | // TODO: Handle nonce directly instead of this. 175 | if (balance) { 176 | await setBalance(relayChainApi, account, balance, config.finalization); 177 | } 178 | } 179 | } 180 | 181 | // We don't need the PolkadotJs API anymore 182 | await relayChainApi.disconnect(); 183 | 184 | console.log("🚀 POLKADOT LAUNCH COMPLETE 🚀"); 185 | } 186 | 187 | interface GenesisParachain { 188 | isSimple: boolean; 189 | resolvedId: string; 190 | chain?: string; 191 | bin: string; 192 | } 193 | 194 | async function addParachainsToGenesis( 195 | config_dir: string, 196 | spec: string, 197 | parachains: ResolvedParachainConfig[], 198 | simpleParachains: ResolvedSimpleParachainConfig[] 199 | ) { 200 | console.log("\n⛓ Adding Genesis Parachains"); 201 | 202 | // Collect all paras into a single list 203 | let x: GenesisParachain[] = parachains.map((p) => { 204 | return { isSimple: false, ...p }; 205 | }); 206 | let y: GenesisParachain[] = simpleParachains.map((p) => { 207 | return { isSimple: true, ...p }; 208 | }); 209 | let paras = x.concat(y); 210 | 211 | for (const parachain of paras) { 212 | const { resolvedId, chain } = parachain; 213 | const bin = resolve(config_dir, parachain.bin); 214 | if (!fs.existsSync(bin)) { 215 | console.error("Parachain binary does not exist: ", bin); 216 | process.exit(); 217 | } 218 | // If it isn't registered yet, register the parachain in genesis 219 | if (!registeredParachains[resolvedId]) { 220 | // Get the information required to register the parachain in genesis. 221 | let genesisState: string; 222 | let genesisWasm: string; 223 | try { 224 | genesisState = await exportGenesisState(bin, chain); 225 | genesisWasm = await exportGenesisWasm(bin, chain); 226 | } catch (err) { 227 | console.error(err); 228 | process.exit(1); 229 | } 230 | 231 | await addGenesisParachain( 232 | spec, 233 | resolvedId, 234 | genesisState, 235 | genesisWasm, 236 | true 237 | ); 238 | registeredParachains[resolvedId] = true; 239 | } 240 | } 241 | } 242 | 243 | async function addHrmpChannelsToGenesis( 244 | spec: string, 245 | hrmpChannels: HrmpChannelsConfig[] 246 | ) { 247 | console.log("⛓ Adding Genesis HRMP Channels"); 248 | for (const hrmpChannel of hrmpChannels) { 249 | await addGenesisHrmpChannel(spec, hrmpChannel); 250 | } 251 | } 252 | 253 | // Resolves parachain id from chain spec if not specified 254 | async function resolveParachainId( 255 | config_dir: string, 256 | config: LaunchConfig 257 | ): Promise { 258 | console.log(`\n🧹 Resolving parachain id...`); 259 | const resolvedConfig = config as ResolvedLaunchConfig; 260 | for (const parachain of resolvedConfig.parachains) { 261 | if (parachain.id) { 262 | parachain.resolvedId = parachain.id; 263 | } else { 264 | const bin = resolve(config_dir, parachain.bin); 265 | const paraId = await getParachainIdFromSpec(bin, parachain.chain); 266 | console.log(` ✓ Read parachain id for ${parachain.bin}: ${paraId}`); 267 | parachain.resolvedId = paraId.toString(); 268 | } 269 | } 270 | for (const parachain of resolvedConfig.simpleParachains) { 271 | parachain.resolvedId = parachain.id; 272 | } 273 | return resolvedConfig; 274 | } 275 | 276 | async function generateNodeKeys( 277 | config: ResolvedLaunchConfig 278 | ): Promise { 279 | var bootnodes = []; 280 | for (const node of config.relaychain.nodes) { 281 | if (!node.nodeKey) { 282 | node.nodeKey = hexStripPrefix(randomAsHex(32)); 283 | } 284 | 285 | let pair = await libp2pKeys.generateKeyPairFromSeed( 286 | "Ed25519", 287 | hexToU8a(hexAddPrefix(node.nodeKey!)), 288 | 1024 289 | ); 290 | let peerId: PeerId = await PeerId.createFromPrivKey(pair.bytes); 291 | bootnodes.push( 292 | `/ip4/127.0.0.1/tcp/${node.port}/p2p/${peerId.toB58String()}` 293 | ); 294 | } 295 | 296 | return bootnodes; 297 | } 298 | -------------------------------------------------------------------------------- /src/spawn.ts: -------------------------------------------------------------------------------- 1 | import { 2 | spawn, 3 | ChildProcessWithoutNullStreams, 4 | execFile as ex, 5 | } from "child_process"; 6 | import util from "util"; 7 | import fs from "fs"; 8 | import { CollatorOptions } from "./types"; 9 | 10 | // This tracks all the processes that we spawn from this file. 11 | // Used to clean up processes when exiting this program. 12 | const p: { [key: string]: ChildProcessWithoutNullStreams } = {}; 13 | 14 | const execFile = util.promisify(ex); 15 | 16 | // Output the chainspec of a node. 17 | export async function generateChainSpec(bin: string, chain: string) { 18 | return new Promise(function (resolve, reject) { 19 | let args = ["build-spec", "--chain=" + chain, "--disable-default-bootnode"]; 20 | verbose(bin, args); 21 | p["spec"] = spawn(bin, args); 22 | let spec = fs.createWriteStream(`${chain}.json`); 23 | 24 | // `pipe` since it deals with flushing and we need to guarantee that the data is flushed 25 | // before we resolve the promise. 26 | p["spec"].stdout.pipe(spec); 27 | 28 | p["spec"].stderr.pipe(process.stderr); 29 | 30 | p["spec"].on("close", () => { 31 | resolve(); 32 | }); 33 | 34 | p["spec"].on("error", (err) => { 35 | reject(err); 36 | }); 37 | }); 38 | } 39 | 40 | // Output the chainspec of a node using `--raw` from a JSON file. 41 | export async function generateChainSpecRaw(bin: string, chain: string) { 42 | console.log(); // Add a newline in output 43 | return new Promise(function (resolve, reject) { 44 | let args = ["build-spec", "--chain=" + chain + ".json", "--raw"]; 45 | 46 | p["spec"] = spawn(bin, args); 47 | verbose(bin, args); 48 | let spec = fs.createWriteStream(`${chain}-raw.json`); 49 | 50 | // `pipe` since it deals with flushing and we need to guarantee that the data is flushed 51 | // before we resolve the promise. 52 | p["spec"].stdout.pipe(spec); 53 | p["spec"].stderr.pipe(process.stderr); 54 | 55 | p["spec"].on("close", () => { 56 | resolve(); 57 | }); 58 | 59 | p["spec"].on("error", (err) => { 60 | reject(err); 61 | }); 62 | }); 63 | } 64 | 65 | export async function getParachainIdFromSpec( 66 | bin: string, 67 | chain?: string 68 | ): Promise { 69 | const data = await new Promise(function (resolve, reject) { 70 | let args = ["build-spec"]; 71 | if (chain) { 72 | args.push("--chain=" + chain); 73 | } 74 | 75 | let data = ""; 76 | 77 | p["spec"] = spawn(bin, args); 78 | verbose(bin, args); 79 | p["spec"].stdout.on("data", (chunk) => { 80 | data += chunk; 81 | }); 82 | 83 | p["spec"].stderr.pipe(process.stderr); 84 | 85 | p["spec"].on("close", () => { 86 | resolve(data); 87 | }); 88 | 89 | p["spec"].on("error", (err) => { 90 | reject(err); 91 | }); 92 | }); 93 | 94 | const spec = JSON.parse(data); 95 | 96 | // Some parachains are still using snake_case format 97 | return spec.paraId || spec.para_id; 98 | } 99 | 100 | // Spawn a new relay chain node. 101 | // `name` must be `alice`, `bob`, `charlie`, etc... (hardcoded in Substrate). 102 | export function startNode( 103 | bin: string, 104 | name: string, 105 | wsPort: number, 106 | rpcPort: number | undefined, 107 | port: number, 108 | nodeKey: string, 109 | spec: string, 110 | flags?: string[], 111 | basePath?: string 112 | ) { 113 | // TODO: Make DB directory configurable rather than just `tmp` 114 | let args = [ 115 | "--chain=" + spec, 116 | "--ws-port=" + wsPort, 117 | "--port=" + port, 118 | "--node-key=" + nodeKey, 119 | "--" + name.toLowerCase(), 120 | ]; 121 | if (rpcPort) { 122 | args.push("--rpc-port=" + rpcPort); 123 | } 124 | 125 | if (basePath) { 126 | args.push("--base-path=" + basePath); 127 | } else { 128 | args.push("--tmp"); 129 | } 130 | 131 | if (flags) { 132 | // Add any additional flags to the CLI 133 | args = args.concat(flags); 134 | console.log(`Added ${flags}`); 135 | } 136 | verbose(bin, args); 137 | p[name] = spawn(bin, args); 138 | 139 | let log = fs.createWriteStream(`${name}.log`); 140 | 141 | p[name].stdout.pipe(log); 142 | p[name].stderr.pipe(log); 143 | } 144 | 145 | // Export the genesis wasm for a parachain and return it as a hex encoded string starting with 0x. 146 | // Used for registering the parachain on the relay chain. 147 | export async function exportGenesisWasm( 148 | bin: string, 149 | chain?: string 150 | ): Promise { 151 | let args = ["export-genesis-wasm"]; 152 | 153 | if (chain) { 154 | args.push("--chain=" + chain); 155 | } 156 | 157 | // wasm files are typically large and `exec` requires us to supply the maximum buffer size in 158 | // advance. Hopefully, this generous limit will be enough. 159 | let opts = { maxBuffer: 10 * 1024 * 1024 }; 160 | verbose(bin, args); 161 | let { stdout, stderr } = await execFile(bin, args, opts); 162 | if (stderr) { 163 | console.error(stderr); 164 | } 165 | return stdout.trim(); 166 | } 167 | 168 | /// Export the genesis state aka genesis head. 169 | export async function exportGenesisState( 170 | bin: string, 171 | chain?: string 172 | ): Promise { 173 | let args = ["export-genesis-state"]; 174 | 175 | if (chain) { 176 | args.push("--chain=" + chain); 177 | } 178 | 179 | // wasm files are typically large and `exec` requires us to supply the maximum buffer size in 180 | // advance. Hopefully, this generous limit will be enough. 181 | let opts = { maxBuffer: 5 * 1024 * 1024 }; 182 | verbose(bin, args); 183 | let { stdout, stderr } = await execFile(bin, args, opts); 184 | if (stderr) { 185 | console.error(stderr); 186 | } 187 | return stdout.trim(); 188 | } 189 | 190 | // Start a collator node for a parachain. 191 | export function startCollator( 192 | bin: string, 193 | wsPort: number, 194 | rpcPort: number | undefined, 195 | port: number, 196 | options: CollatorOptions 197 | ) { 198 | return new Promise(function (resolve) { 199 | // TODO: Make DB directory configurable rather than just `tmp` 200 | let args = ["--ws-port=" + wsPort, "--port=" + port]; 201 | const { basePath, name, onlyOneParachainNode, flags, spec, chain } = 202 | options; 203 | 204 | if (rpcPort) { 205 | args.push("--rpc-port=" + rpcPort); 206 | console.log(`Added --rpc-port=" + ${rpcPort}`); 207 | } 208 | args.push("--collator"); 209 | 210 | if (basePath) { 211 | args.push("--base-path=" + basePath); 212 | } else { 213 | args.push("--tmp"); 214 | } 215 | 216 | if (chain) { 217 | args.push("--chain=" + chain); 218 | } 219 | 220 | if (name) { 221 | args.push(`--${name.toLowerCase()}`); 222 | console.log(`Added --${name.toLowerCase()}`); 223 | } 224 | 225 | if (onlyOneParachainNode) { 226 | args.push("--force-authoring"); 227 | console.log(`Added --force-authoring`); 228 | } 229 | 230 | let flags_collator = null; 231 | let flags_parachain = null; 232 | let split_index = flags ? flags.findIndex((value) => value == "--") : -1; 233 | 234 | if (split_index < 0) { 235 | flags_parachain = flags; 236 | } else { 237 | flags_parachain = flags ? flags.slice(0, split_index) : null; 238 | flags_collator = flags ? flags.slice(split_index + 1) : null; 239 | } 240 | 241 | if (flags_parachain) { 242 | // Add any additional flags to the CLI 243 | args = args.concat(flags_parachain); 244 | console.log(`Added ${flags_parachain} to parachain`); 245 | } 246 | 247 | // Arguments for the relay chain node part of the collator binary. 248 | args = args.concat(["--", "--chain=" + spec]); 249 | 250 | if (flags_collator) { 251 | // Add any additional flags to the CLI 252 | args = args.concat(flags_collator); 253 | console.log(`Added ${flags_collator} to collator`); 254 | } 255 | verbose(bin, args); 256 | p[wsPort] = spawn(bin, args); 257 | 258 | let log = fs.createWriteStream(`${wsPort}.log`); 259 | 260 | p[wsPort].stdout.pipe(log); 261 | p[wsPort].stderr.on("data", function (chunk) { 262 | let message = chunk.toString(); 263 | let ready = 264 | message.includes("Running JSON-RPC WS server:") || 265 | message.includes("Listening for new connections"); 266 | if (ready) { 267 | resolve(); 268 | } 269 | log.write(message); 270 | }); 271 | }); 272 | } 273 | 274 | export function startSimpleCollator( 275 | bin: string, 276 | id: string, 277 | spec: string, 278 | port: string, 279 | skip_id_arg?: boolean 280 | ) { 281 | return new Promise(function (resolve) { 282 | let args = [ 283 | "--tmp", 284 | "--port=" + port, 285 | "--chain=" + spec, 286 | "--execution=wasm", 287 | ]; 288 | 289 | if (!skip_id_arg) { 290 | args.push("--parachain-id=" + id); 291 | console.log(`Added --parachain-id=${id}`); 292 | } 293 | verbose(bin, args); 294 | p[port] = spawn(bin, args); 295 | 296 | let log = fs.createWriteStream(`${port}.log`); 297 | 298 | p[port].stdout.pipe(log); 299 | p[port].stderr.on("data", function (chunk) { 300 | let message = chunk.toString(); 301 | let ready = 302 | message.includes("Running JSON-RPC WS server:") || 303 | message.includes("Listening for new connections"); 304 | if (ready) { 305 | resolve(); 306 | } 307 | log.write(message); 308 | }); 309 | }); 310 | } 311 | 312 | // Purge the chain for any node. 313 | // You shouldn't need to use this function since every node starts with `--tmp` 314 | // TODO: Make DB directory configurable rather than just `tmp` 315 | export function purgeChain(bin: string, spec: string) { 316 | console.log("Purging Chain..."); 317 | let args = ["purge-chain"]; 318 | 319 | if (spec) { 320 | args.push("--chain=" + spec); 321 | } 322 | 323 | // Avoid prompt to confirm. 324 | args.push("-y"); 325 | verbose(bin, args); 326 | p["purge"] = spawn(bin, args); 327 | 328 | p["purge"].stdout.on("data", function (chunk) { 329 | let message = chunk.toString(); 330 | console.log(message); 331 | }); 332 | 333 | p["purge"].stderr.on("data", function (chunk) { 334 | let message = chunk.toString(); 335 | console.log(message); 336 | }); 337 | } 338 | 339 | function verbose(bin: string, args: string[]) { 340 | if (process.argv.includes("--verbose") || process.argv.includes("-v")) { 341 | for (var arg in args) { 342 | bin += " "; 343 | bin += args[arg]; 344 | } 345 | console.log("\x1b[33m " + bin + " \x1b[0m"); 346 | } 347 | } 348 | 349 | // Kill all processes spawned and tracked by this file. 350 | export function killAll() { 351 | console.log("\nKilling all processes..."); 352 | for (const key of Object.keys(p)) { 353 | p[key].kill(); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /src/spec.ts: -------------------------------------------------------------------------------- 1 | import "@polkadot/api-augment"; 2 | import { Keyring } from "@polkadot/api"; 3 | import { cryptoWaitReady } from "@polkadot/util-crypto"; 4 | import { encodeAddress } from "@polkadot/util-crypto"; 5 | import { ChainSpec, HrmpChannelsConfig } from "./types"; 6 | const fs = require("fs"); 7 | 8 | function nameCase(string: string) { 9 | return string.charAt(0).toUpperCase() + string.slice(1); 10 | } 11 | 12 | // Get authority keys from within chainSpec data 13 | function getAuthorityKeys(chainSpec: ChainSpec) { 14 | // Check runtime_genesis_config key for rococo compatibility. 15 | const runtimeConfig = 16 | chainSpec.genesis.runtime.runtime_genesis_config || 17 | chainSpec.genesis.runtime; 18 | if (runtimeConfig && runtimeConfig.session) { 19 | return runtimeConfig.session.keys; 20 | } 21 | 22 | // For retro-compatibility with substrate pre Polkadot 0.9.5 23 | if (runtimeConfig && runtimeConfig.palletSession) { 24 | return runtimeConfig.palletSession.keys; 25 | } 26 | 27 | console.error(" ⚠ session not found in runtimeConfig"); 28 | process.exit(1); 29 | } 30 | 31 | // Remove all existing keys from `session.keys` 32 | export function clearAuthorities(spec: string) { 33 | let rawdata = fs.readFileSync(spec); 34 | let chainSpec; 35 | try { 36 | chainSpec = JSON.parse(rawdata); 37 | } catch { 38 | console.error(" ⚠ failed to parse the chain spec"); 39 | process.exit(1); 40 | } 41 | 42 | let keys = getAuthorityKeys(chainSpec); 43 | keys.length = 0; 44 | 45 | let data = JSON.stringify(chainSpec, null, 2); 46 | fs.writeFileSync(spec, data); 47 | console.log(`\n🧹 Starting with a fresh authority set...`); 48 | } 49 | 50 | // Add additional authorities to chain spec in `session.keys` 51 | export async function addAuthority(spec: string, name: string) { 52 | await cryptoWaitReady(); 53 | 54 | const sr_keyring = new Keyring({ type: "sr25519" }); 55 | const sr_account = sr_keyring.createFromUri(`//${nameCase(name)}`); 56 | const sr_stash = sr_keyring.createFromUri(`//${nameCase(name)}//stash`); 57 | 58 | const ed_keyring = new Keyring({ type: "ed25519" }); 59 | const ed_account = ed_keyring.createFromUri(`//${nameCase(name)}`); 60 | 61 | const ec_keyring = new Keyring({ type: "ecdsa" }); 62 | const ec_account = ec_keyring.createFromUri(`//${nameCase(name)}`); 63 | 64 | let key = [ 65 | sr_stash.address, 66 | sr_stash.address, 67 | { 68 | grandpa: ed_account.address, 69 | babe: sr_account.address, 70 | im_online: sr_account.address, 71 | parachain_validator: sr_account.address, 72 | authority_discovery: sr_account.address, 73 | para_validator: sr_account.address, 74 | para_assignment: sr_account.address, 75 | beefy: encodeAddress(ec_account.publicKey), 76 | }, 77 | ]; 78 | 79 | let rawdata = fs.readFileSync(spec); 80 | let chainSpec = JSON.parse(rawdata); 81 | 82 | let keys = getAuthorityKeys(chainSpec); 83 | keys.push(key); 84 | 85 | let data = JSON.stringify(chainSpec, null, 2); 86 | fs.writeFileSync(spec, data); 87 | console.log(` 👤 Added Genesis Authority ${name}`); 88 | } 89 | 90 | // Add parachains to the chain spec at genesis. 91 | export async function addGenesisParachain( 92 | spec: string, 93 | para_id: string, 94 | head: string, 95 | wasm: string, 96 | parachain: boolean 97 | ) { 98 | let rawdata = fs.readFileSync(spec); 99 | let chainSpec = JSON.parse(rawdata); 100 | 101 | // Check runtime_genesis_config key for rococo compatibility. 102 | const runtimeConfig = 103 | chainSpec.genesis.runtime.runtime_genesis_config || 104 | chainSpec.genesis.runtime; 105 | let paras = undefined; 106 | if (runtimeConfig.paras) { 107 | paras = runtimeConfig.paras.paras; 108 | } 109 | // For retro-compatibility with substrate pre Polkadot 0.9.5 110 | else if (runtimeConfig.parachainsParas) { 111 | paras = runtimeConfig.parachainsParas.paras; 112 | } 113 | if (paras) { 114 | let new_para = [ 115 | parseInt(para_id), 116 | { 117 | genesis_head: head, 118 | validation_code: wasm, 119 | parachain: parachain, 120 | }, 121 | ]; 122 | 123 | paras.push(new_para); 124 | 125 | let data = JSON.stringify(chainSpec, null, 2); 126 | 127 | fs.writeFileSync(spec, data); 128 | console.log(` ✓ Added Genesis Parachain ${para_id}`); 129 | } else { 130 | console.error(" ⚠ paras not found in runtimeConfig"); 131 | process.exit(1); 132 | } 133 | } 134 | 135 | // Update the `genesis` object in the chain specification. 136 | export async function addGenesisHrmpChannel( 137 | spec: string, 138 | hrmpChannel: HrmpChannelsConfig 139 | ) { 140 | let rawdata = fs.readFileSync(spec); 141 | let chainSpec = JSON.parse(rawdata); 142 | 143 | let newHrmpChannel = [ 144 | hrmpChannel.sender, 145 | hrmpChannel.recipient, 146 | hrmpChannel.maxCapacity, 147 | hrmpChannel.maxMessageSize, 148 | ]; 149 | 150 | // Check runtime_genesis_config key for rococo compatibility. 151 | const runtimeConfig = 152 | chainSpec.genesis.runtime.runtime_genesis_config || 153 | chainSpec.genesis.runtime; 154 | 155 | let hrmp = undefined; 156 | 157 | if (runtimeConfig.hrmp) { 158 | hrmp = runtimeConfig.hrmp; 159 | } 160 | // For retro-compatibility with substrate pre Polkadot 0.9.5 161 | else if (runtimeConfig.parachainsHrmp) { 162 | hrmp = runtimeConfig.parachainsHrmp; 163 | } 164 | 165 | if (hrmp && hrmp.preopenHrmpChannels) { 166 | hrmp.preopenHrmpChannels.push(newHrmpChannel); 167 | 168 | let data = JSON.stringify(chainSpec, null, 2); 169 | fs.writeFileSync(spec, data); 170 | console.log( 171 | ` ✓ Added HRMP channel ${hrmpChannel.sender} -> ${hrmpChannel.recipient}` 172 | ); 173 | } else { 174 | console.error(" ⚠ hrmp not found in runtimeConfig"); 175 | process.exit(1); 176 | } 177 | } 178 | 179 | // Update the runtime config in the genesis. 180 | // It will try to match keys which exist within the configuration and update the value. 181 | export async function changeGenesisConfig(spec: string, updates: any) { 182 | let rawdata = fs.readFileSync(spec); 183 | let chainSpec = JSON.parse(rawdata); 184 | 185 | console.log(`\n⚙ Updating Relay Chain Genesis Configuration`); 186 | 187 | if (chainSpec.genesis) { 188 | let config = chainSpec.genesis; 189 | findAndReplaceConfig(updates, config); 190 | 191 | let data = JSON.stringify(chainSpec, null, 2); 192 | fs.writeFileSync(spec, data); 193 | } 194 | } 195 | 196 | // Look at the key + values from `obj1` and try to replace them in `obj2`. 197 | function findAndReplaceConfig(obj1: any, obj2: any) { 198 | // Look at keys of obj1 199 | Object.keys(obj1).forEach((key) => { 200 | // See if obj2 also has this key 201 | if (obj2.hasOwnProperty(key)) { 202 | // If it goes deeper, recurse... 203 | if ( 204 | obj1[key] !== null && 205 | obj1[key] !== undefined && 206 | obj1[key].constructor === Object 207 | ) { 208 | findAndReplaceConfig(obj1[key], obj2[key]); 209 | } else { 210 | obj2[key] = obj1[key]; 211 | console.log( 212 | ` ✓ Updated Genesis Configuration [ ${key}: ${obj2[key]} ]` 213 | ); 214 | } 215 | } else { 216 | console.error(` ⚠ Bad Genesis Configuration [ ${key}: ${obj1[key]} ]`); 217 | } 218 | }); 219 | } 220 | 221 | export async function addBootNodes(spec: any, addresses: any) { 222 | let rawdata = fs.readFileSync(spec); 223 | let chainSpec = JSON.parse(rawdata); 224 | chainSpec.bootNodes = addresses; 225 | let data = JSON.stringify(chainSpec, null, 2); 226 | fs.writeFileSync(spec, data); 227 | console.log(`Added Boot Nodes: ${addresses}`); 228 | } 229 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface CollatorOptions { 2 | name?: string; 3 | spec?: string; 4 | flags?: string[]; 5 | basePath?: string; 6 | chain?: string; 7 | onlyOneParachainNode?: boolean; 8 | } 9 | 10 | export interface LaunchConfig { 11 | relaychain: RelayChainConfig; 12 | parachains: ParachainConfig[]; 13 | simpleParachains: SimpleParachainConfig[]; 14 | hrmpChannels: HrmpChannelsConfig[]; 15 | types: any; 16 | finalization: boolean; 17 | } 18 | export interface ParachainNodeConfig { 19 | rpcPort?: number; 20 | wsPort: number; 21 | port: number; 22 | basePath?: string; 23 | name?: string; 24 | flags: string[]; 25 | } 26 | export interface ParachainConfig { 27 | bin: string; 28 | id?: string; 29 | balance: string; 30 | chain?: string; 31 | nodes: ParachainNodeConfig[]; 32 | } 33 | export interface SimpleParachainConfig { 34 | bin: string; 35 | id: string; 36 | port: string; 37 | balance: string; 38 | } 39 | export interface HrmpChannelsConfig { 40 | sender: number; 41 | recipient: number; 42 | maxCapacity: number; 43 | maxMessageSize: number; 44 | } 45 | interface ObjectJSON { 46 | [key: string]: ObjectJSON | number | string; 47 | } 48 | export interface RelayChainConfig { 49 | bin: string; 50 | chain: string; 51 | nodes: { 52 | name: string; 53 | basePath?: string; 54 | wsPort: number; 55 | rpcPort?: number; 56 | nodeKey?: string; 57 | port: number; 58 | flags?: string[]; 59 | }[]; 60 | genesis?: JSON | ObjectJSON; 61 | } 62 | 63 | export interface ChainSpec { 64 | name: string; 65 | id: string; 66 | chainType: string; 67 | bootNodes: string[]; 68 | telemetryEndpoints: null; 69 | protocolId: string; 70 | properties: null; 71 | forkBlocks: null; 72 | badBlocks: null; 73 | consensusEngine: null; 74 | lightSyncState: null; 75 | genesis: { 76 | runtime: any; // this can change depending on the versions 77 | raw: { 78 | top: { 79 | [key: string]: string; 80 | }; 81 | }; 82 | }; 83 | } 84 | 85 | export interface ResolvedParachainConfig extends ParachainConfig { 86 | resolvedId: string; 87 | } 88 | export interface ResolvedSimpleParachainConfig extends SimpleParachainConfig { 89 | resolvedId: string; 90 | } 91 | export interface ResolvedLaunchConfig extends LaunchConfig { 92 | parachains: ResolvedParachainConfig[]; 93 | simpleParachains: ResolvedSimpleParachainConfig[]; 94 | } 95 | -------------------------------------------------------------------------------- /test/test-utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const SPECS_PATH = `./moonbeam-test-specs`; 2 | 3 | export const DEBUG_MODE = process.env.DEBUG_MODE || false; 4 | export const DISPLAY_LOG = process.env.PARACHAIN_LOG || false; 5 | export const PARACHAIN_LOG = process.env.PARACHAIN_LOG || "info"; 6 | 7 | export const BINARY_PATH = 8 | process.env.BINARY_PATH || `../bin/polkadot-parachain`; 9 | export const RELAY_BINARY_PATH = 10 | process.env.RELAY_BINARY_PATH || `../bin/polkadot-relaychain`; 11 | export const SPAWNING_TIME = 20000; 12 | export const ETHAPI_CMD = process.env.ETHAPI_CMD || ""; 13 | 14 | export const RELAY_CHAIN_NODE_NAMES = [ 15 | "Alice", 16 | "Bob", 17 | "Charlie", 18 | "Dave", 19 | "Eve", 20 | "Ferdie", 21 | "One", 22 | ]; 23 | 24 | // Test variables 25 | export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 26 | export const TREASURY_ACCOUNT = "0x6d6f646c70632f74727372790000000000000000"; 27 | export const GENESIS_ACCOUNT = "0x6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b"; 28 | export const GENESIS_ACCOUNT_PRIVATE_KEY = 29 | "0x99B3C12287537E38C90A9219D4CB074A89A16E9CDB20BF85728EBD97C343E342"; 30 | export const TEST_ACCOUNT = "0x1111111111111111111111111111111111111111"; 31 | export const ALITH = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; 32 | export const ALITH_PRIV_KEY = 33 | "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"; 34 | export const BALTATHAR = "0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0"; 35 | export const BALTATHAR_PRIV_KEY = 36 | "0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b"; 37 | export const CHARLETH = "0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc"; 38 | export const CHARLETH_PRIV_KEY = 39 | "0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b"; 40 | export const DOROTHY = "0x773539d4Ac0e786233D90A233654ccEE26a613D9"; 41 | export const DOROTHY_PRIV_KEY = 42 | "0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68"; 43 | export const RANDOM_PRIV_KEY = 44 | "0x66d8d3bdfc9d678c1ea6dc3e15a81cb98dcd4d456f5ce0519479df1fba70cc5e"; 45 | export const ETHAN_PRIVKEY = 46 | "0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4"; 47 | export const ETHAN = "0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB"; 48 | export const RANDOM_ADDRESS = "0x39Cccb8cc2A821eB5cDADc656fF4229398AbA190"; 49 | export const GLMR = 1_000_000_000_000_000_000n; 50 | export const DEFAULT_GENESIS_BALANCE = 2n ** 80n; 51 | export const DEFAULT_GENESIS_STAKING = 1_000n * GLMR; 52 | export const DEFAULT_GENESIS_MAPPING = 100n * GLMR; 53 | export const PROPOSAL_AMOUNT = 1000n * GLMR; 54 | export const VOTE_AMOUNT = 10n * GLMR; 55 | export const MIN_GLMR_STAKING = 1000n * GLMR; 56 | export const MIN_GLMR_NOMINATOR = 5n * GLMR; 57 | export const MIN_GLMR_NOMINATOR_PLUS_ONE = 6n * GLMR; 58 | export const GENESIS_ACCOUNT_BALANCE = DEFAULT_GENESIS_BALANCE; 59 | // This is Alice 60 | export const COLLATOR_ACCOUNT = "0xf24ff3a9cf04c71dbc94d0b566f7a27b94566cac"; 61 | export const COLLATOR_ACCOUNT_BALANCE = 62 | DEFAULT_GENESIS_BALANCE - DEFAULT_GENESIS_STAKING - DEFAULT_GENESIS_MAPPING; 63 | 64 | // Prefunded accounts. 65 | export const ALITH_ADDRESS = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; 66 | export const ALITH_PRIVATE_KEY = 67 | "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"; 68 | 69 | export const BALTATHAR_ADDRESS = "0x3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0"; 70 | export const BALTATHAR_PRIVATE_KEY = 71 | "0x8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b"; 72 | 73 | export const CHARLETH_ADDRESS = "0x798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc"; 74 | export const CHARLETH_PRIVATE_KEY = 75 | "0x0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b"; 76 | 77 | export const DOROTHY_ADDRESS = "0x773539d4Ac0e786233D90A233654ccEE26a613D9"; 78 | export const DOROTHY_PRIVATE_KEY = 79 | "0x39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68"; 80 | 81 | export const ETHAN_ADDRESS = "0xFf64d3F6efE2317EE2807d223a0Bdc4c0c49dfDB"; 82 | export const ETHAN_PRIVATE_KEY = 83 | "0x7dce9bc8babb68fec1409be38c8e1a52650206a7ed90ff956ae8a6d15eeaaef4"; 84 | 85 | export const FAITH_ADDRESS = "0xC0F0f4ab324C46e55D02D0033343B4Be8A55532d"; 86 | export const FAITH_PRIVATE_KEY = 87 | "0xb9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df"; 88 | 89 | export const GERALD_ADDRESS = "0x7BF369283338E12C90514468aa3868A551AB2929"; 90 | export const GERALD_PRIVATE_KEY = 91 | "0x96b8a38e12e1a31dee1eab2fffdf9d9990045f5b37e44d8cc27766ef294acf18"; 92 | 93 | // Current gas per second 94 | export const GAS_PER_SECOND = 40_000_000; 95 | // The real computation is 1_000_000_000_000 / 40_000_000, but we simplify to avoid bigint. 96 | export const GAS_PER_WEIGHT = 1_000_000 / 40; 97 | 98 | // Our weight limit is 500ms. 99 | export const BLOCK_TX_LIMIT = GAS_PER_SECOND * 0.5; 100 | 101 | // Current implementation is limiting block transactions to 75% of the block gas limit 102 | export const BLOCK_TX_GAS_LIMIT = BLOCK_TX_LIMIT * 0.75; 103 | // 125_000_000 Weight per extrinsics 104 | export const EXTRINSIC_BASE_COST = 125_000_000 / GAS_PER_WEIGHT; 105 | 106 | // Maximum extrinsic weight is taken from the max allowed transaction weight per block, 107 | // minus the block initialization (10%) and minus the extrinsic base cost. 108 | export const EXTRINSIC_GAS_LIMIT = 109 | BLOCK_TX_GAS_LIMIT - BLOCK_TX_LIMIT * 0.1 - EXTRINSIC_BASE_COST; 110 | -------------------------------------------------------------------------------- /test/test-utils/para-node.ts: -------------------------------------------------------------------------------- 1 | import tcpPortUsed from "tcp-port-used"; 2 | import path from "path"; 3 | import { killAll, run } from "../../src"; //"polkadot-launch"; 4 | import { 5 | BINARY_PATH, 6 | RELAY_BINARY_PATH, 7 | RELAY_CHAIN_NODE_NAMES, 8 | } from "./constants"; 9 | const debug = require("debug")("test:para-node"); 10 | 11 | export async function findAvailablePorts(parachainCount: number = 1) { 12 | // 2 nodes per prachain, and as many relaychain nodes 13 | const relayCount = parachainCount + 1; 14 | const paraNodeCount = parachainCount * 2; // * 2; 15 | const nodeCount = relayCount + paraNodeCount; 16 | const portCount = nodeCount * 3; 17 | const availablePorts = await Promise.all( 18 | new Array(portCount).fill(0).map(async (_, index) => { 19 | let selectedPort = 0; 20 | let endingPort = 65535; 21 | const portDistance: number = Math.floor((endingPort - 1024) / portCount); 22 | let port = 1024 + index * portDistance + (process.pid % portDistance); 23 | while (!selectedPort && port < endingPort) { 24 | try { 25 | const inUse = await tcpPortUsed.check(port, "127.0.0.1"); 26 | if (!inUse) { 27 | selectedPort = port; 28 | } 29 | } catch (e) { 30 | console.log("caught err ", e); 31 | } 32 | port++; 33 | } 34 | if (!selectedPort) { 35 | throw new Error(`No available port`); 36 | } 37 | return selectedPort; 38 | }) 39 | ); 40 | 41 | return new Array(nodeCount).fill(0).map((_, index) => ({ 42 | p2pPort: availablePorts[index * 3 + 0], 43 | rpcPort: availablePorts[index * 3 + 1], 44 | wsPort: availablePorts[index * 3 + 2], 45 | })); 46 | } 47 | 48 | // Stores if the node has already started. 49 | // It is used when a test file contains multiple describeDevMoonbeam. Those are 50 | // executed within the same PID and so would generate a race condition if started 51 | // at the same time. 52 | let nodeStarted = false; 53 | 54 | export type ParachainOptions = { 55 | chain: "rococo-local"; 56 | relaychain?: 57 | | "rococo-local" 58 | | "westend-local" 59 | | "kusama-local" 60 | | "polkadot-local"; 61 | numberOfParachains?: number; 62 | }; 63 | 64 | export interface ParachainPorts { 65 | parachainId: number; 66 | ports: NodePorts[]; 67 | } 68 | 69 | export interface NodePorts { 70 | p2pPort: number; 71 | rpcPort: number; 72 | wsPort: number; 73 | } 74 | 75 | export interface HrmpChannel { 76 | sender: number; 77 | recipient: number; 78 | maxCapacity: number; 79 | maxMessageSize: number; 80 | } 81 | 82 | // This will start a parachain node, only 1 at a time (check every 100ms). 83 | // This will prevent race condition on the findAvailablePorts which uses the PID of the process 84 | // Returns ports for the 3rd parachain node 85 | export async function startParachainNodes(options: ParachainOptions): Promise<{ 86 | relayPorts: NodePorts[]; 87 | paraPorts: ParachainPorts[]; 88 | }> { 89 | while (nodeStarted) { 90 | // Wait 100ms to see if the node is free 91 | await new Promise((resolve) => { 92 | setTimeout(resolve, 100); 93 | }); 94 | } 95 | const relaychain = options.relaychain || "rococo-local"; 96 | // For now we only support one, two or three parachains 97 | const numberOfParachains = 98 | (options.numberOfParachains && 99 | options.numberOfParachains < 4 && 100 | options.numberOfParachains > 0 && 101 | options.numberOfParachains) || 102 | 1; 103 | const parachainArray = new Array(numberOfParachains).fill(0); 104 | nodeStarted = true; 105 | // Each node will have 3 ports. There are 2 nodes per parachain, and as many relaychain nodes. 106 | // So numberOfPorts = 3 * 2 * parachainCount 107 | const ports = await findAvailablePorts(numberOfParachains); 108 | 109 | //Build hrmpChannels, all connected to first parachain 110 | const hrmpChannels: HrmpChannel[] = []; 111 | new Array(numberOfParachains - 1).fill(0).forEach((_, i) => { 112 | hrmpChannels.push({ 113 | sender: 1000, 114 | recipient: 1000 * (i + 2), 115 | maxCapacity: 8, 116 | maxMessageSize: 512, 117 | }); 118 | hrmpChannels.push({ 119 | sender: 1000 * (i + 2), 120 | recipient: 1000, 121 | maxCapacity: 8, 122 | maxMessageSize: 512, 123 | }); 124 | }); 125 | 126 | // Build launchConfig 127 | const launchConfig = { 128 | relaychain: { 129 | bin: RELAY_BINARY_PATH, 130 | chain: relaychain, 131 | nodes: new Array(numberOfParachains + 1).fill(0).map((_, i) => { 132 | return { 133 | name: RELAY_CHAIN_NODE_NAMES[i], 134 | port: ports[i].p2pPort, 135 | rpcPort: ports[i].rpcPort, 136 | wsPort: ports[i].wsPort, 137 | }; 138 | }), 139 | genesis: { 140 | runtime: { 141 | runtime_genesis_config: { 142 | parachainsConfiguration: { 143 | config: { 144 | validation_upgrade_frequency: 10, 145 | validation_upgrade_delay: 10, 146 | }, 147 | }, 148 | }, 149 | }, 150 | }, 151 | }, 152 | parachains: parachainArray.map((_, i) => { 153 | return { 154 | bin: BINARY_PATH, 155 | id: (1000 * (i + 1)).toString(), 156 | balance: "1000000000000000000000", 157 | nodes: [ 158 | { 159 | port: ports[i * 2 + numberOfParachains + 1].p2pPort, 160 | rpcPort: ports[i * 2 + numberOfParachains + 1].rpcPort, 161 | wsPort: ports[i * 2 + numberOfParachains + 1].wsPort, 162 | name: "alice", 163 | flags: [ 164 | "--log=info,rpc=trace,evm=trace,ethereum=trace", 165 | "--unsafe-rpc-external", 166 | "--rpc-cors=all", 167 | "--", 168 | "--execution=wasm", 169 | ], 170 | }, 171 | { 172 | port: ports[i * 2 + numberOfParachains + 2].p2pPort, 173 | rpcPort: ports[i * 2 + numberOfParachains + 2].rpcPort, 174 | wsPort: ports[i * 2 + numberOfParachains + 2].wsPort, 175 | name: "bob", 176 | flags: [ 177 | "--log=info,rpc=trace,evm=trace,ethereum=trace", 178 | "--unsafe-rpc-external", 179 | "--rpc-cors=all", 180 | "--", 181 | "--execution=wasm", 182 | ], 183 | }, 184 | ], 185 | }; 186 | }), 187 | simpleParachains: [], 188 | hrmpChannels: hrmpChannels, 189 | finalization: true, 190 | types: {}, 191 | }; 192 | 193 | const onProcessExit = function () { 194 | killAll(); 195 | }; 196 | const onProcessInterrupt = function () { 197 | process.exit(2); 198 | }; 199 | 200 | process.once("exit", onProcessExit); 201 | process.once("SIGINT", onProcessInterrupt); 202 | 203 | await run(path.join(__dirname, "../"), launchConfig); 204 | 205 | return { 206 | relayPorts: new Array(numberOfParachains + 1).fill(0).map((_, i) => { 207 | return { 208 | p2pPort: ports[i].p2pPort, 209 | rpcPort: ports[i].rpcPort, 210 | wsPort: ports[i].wsPort, 211 | }; 212 | }), 213 | 214 | paraPorts: parachainArray.map((_, i) => { 215 | return { 216 | parachainId: 1000 * (i + 1), 217 | ports: [ 218 | { 219 | p2pPort: ports[i * 2 + numberOfParachains + 1].p2pPort, 220 | rpcPort: ports[i * 2 + numberOfParachains + 1].rpcPort, 221 | wsPort: ports[i * 2 + numberOfParachains + 1].wsPort, 222 | }, 223 | { 224 | p2pPort: ports[i * 2 + numberOfParachains + 2].p2pPort, 225 | rpcPort: ports[i * 2 + numberOfParachains + 2].rpcPort, 226 | wsPort: ports[i * 2 + numberOfParachains + 2].wsPort, 227 | }, 228 | ], 229 | }; 230 | }), 231 | }; 232 | } 233 | 234 | export async function stopParachainNodes() { 235 | killAll(); 236 | await new Promise((resolve) => { 237 | // TODO: improve, make killAll async https://github.com/paritytech/polkadot-launch/issues/139 238 | console.log("Waiting 10 seconds for processes to shut down..."); 239 | setTimeout(resolve, 10000); 240 | nodeStarted = false; 241 | console.log("... done"); 242 | }); 243 | } 244 | -------------------------------------------------------------------------------- /test/test-utils/providers.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import "@polkadot/api-augment"; 3 | import { ApiPromise, WsProvider } from "@polkadot/api"; 4 | import { JsonRpcResponse } from "web3-core-helpers"; 5 | import { ethers } from "ethers"; 6 | import { GENESIS_ACCOUNT_PRIVATE_KEY } from "./constants"; 7 | import { Subscription as Web3Subscription } from "web3-core-subscriptions"; 8 | import { BlockHeader } from "web3-eth"; 9 | import { Log } from "web3-core"; 10 | 11 | export async function customWeb3Request( 12 | web3: Web3, 13 | method: string, 14 | params: any[] 15 | ) { 16 | return new Promise((resolve, reject) => { 17 | (web3.currentProvider as any).send( 18 | { 19 | jsonrpc: "2.0", 20 | id: 1, 21 | method, 22 | params, 23 | }, 24 | (error: Error | null, result: JsonRpcResponse) => { 25 | if (error) { 26 | reject( 27 | `Failed to send custom request (${method} (${params.join(",")})): ${ 28 | error.message || error.toString() 29 | }` 30 | ); 31 | } 32 | resolve(result); 33 | } 34 | ); 35 | }); 36 | } 37 | 38 | // Extra type because web3 is not well typed 39 | export interface Subscription extends Web3Subscription { 40 | once: ( 41 | type: "data" | "connected", 42 | handler: (data: T) => void 43 | ) => Subscription; 44 | } 45 | 46 | // Little helper to hack web3 that are not complete. 47 | export function web3Subscribe( 48 | web3: Web3, 49 | type: "newBlockHeaders" 50 | ): Subscription; 51 | export function web3Subscribe( 52 | web3: Web3, 53 | type: "pendingTransactions" 54 | ): Subscription; 55 | export function web3Subscribe( 56 | web3: Web3, 57 | type: "logs", 58 | params: {} 59 | ): Subscription; 60 | export function web3Subscribe( 61 | web3: Web3, 62 | type: "newBlockHeaders" | "pendingTransactions" | "logs", 63 | params?: any 64 | ) { 65 | return (web3.eth as any).subscribe(...[].slice.call(arguments, 1)); 66 | } 67 | 68 | export type EnhancedWeb3 = Web3 & { 69 | customRequest: (method: string, params: any[]) => Promise; 70 | }; 71 | 72 | export const provideWeb3Api = async ( 73 | port: number, 74 | protocol: "ws" | "http" = "http" 75 | ) => { 76 | const web3 = 77 | protocol == "ws" 78 | ? new Web3(`ws://localhost:${port}`) // TODO: restore support for 79 | : new Web3(`http://localhost:${port}`); 80 | 81 | // Adding genesis account for convenience 82 | web3.eth.accounts.wallet.add(GENESIS_ACCOUNT_PRIVATE_KEY); 83 | 84 | // Hack to add customRequest method. 85 | (web3 as any).customRequest = (method: string, params: any[]) => 86 | customWeb3Request(web3, method, params); 87 | 88 | return web3 as EnhancedWeb3; 89 | }; 90 | 91 | export const providePolkadotApi = async (port: number) => { 92 | return await ApiPromise.create({ 93 | initWasm: false, 94 | provider: new WsProvider(`ws://localhost:${port}`), 95 | }); 96 | }; 97 | 98 | export const provideEthersApi = async (port: number) => { 99 | return new ethers.providers.JsonRpcProvider(`http://localhost:${port}`); 100 | }; 101 | -------------------------------------------------------------------------------- /test/test-utils/setup-para-tests.ts: -------------------------------------------------------------------------------- 1 | import "@polkadot/api-augment"; 2 | import { ApiPromise } from "@polkadot/api"; 3 | import { ethers } from "ethers"; 4 | import { 5 | provideWeb3Api, 6 | provideEthersApi, 7 | providePolkadotApi, 8 | EnhancedWeb3, 9 | } from "./providers"; 10 | import { DEBUG_MODE } from "./constants"; 11 | import { HttpProvider } from "web3-core"; 12 | import { 13 | NodePorts, 14 | ParachainOptions, 15 | ParachainPorts, 16 | startParachainNodes, 17 | stopParachainNodes, 18 | } from "./para-node"; 19 | const debug = require("debug")("test:setup"); 20 | 21 | export interface ParaTestContext { 22 | createWeb3: (protocol?: "ws" | "http") => Promise; 23 | createEthers: () => Promise; 24 | createPolkadotApiParachains: () => Promise; 25 | createPolkadotApiRelaychains: () => Promise; 26 | 27 | // We also provided singleton providers for simplicity 28 | web3: EnhancedWeb3; 29 | ethers: ethers.providers.JsonRpcProvider; 30 | polkadotApiParaone: ApiPromise; 31 | } 32 | 33 | export interface ParachainApis { 34 | parachainId: number; 35 | apis: ApiPromise[]; 36 | } 37 | 38 | export interface InternalParaTestContext extends ParaTestContext { 39 | _polkadotApiParachains: ParachainApis[]; 40 | _polkadotApiRelaychains: ApiPromise[]; 41 | _web3Providers: HttpProvider[]; 42 | } 43 | 44 | export function describeParachain( 45 | title: string, 46 | options: ParachainOptions, 47 | cb: (context: InternalParaTestContext) => void 48 | ) { 49 | describe(title, function () { 50 | // Set timeout to 5000 for all tests. 51 | this.timeout(300000); 52 | 53 | // The context is initialized empty to allow passing a reference 54 | // and to be filled once the node information is retrieved 55 | let context: InternalParaTestContext = {} as InternalParaTestContext; 56 | 57 | // Making sure the Moonbeam node has started 58 | before("Starting Moonbeam Test Node", async function () { 59 | this.timeout(300000); 60 | const init = !DEBUG_MODE 61 | ? await startParachainNodes(options) 62 | : { 63 | paraPorts: [ 64 | { 65 | parachainId: 1000, 66 | ports: [ 67 | { 68 | p2pPort: 19931, 69 | wsPort: 19933, 70 | rpcPort: 19932, 71 | }, 72 | ], 73 | }, 74 | ], 75 | relayPorts: [], 76 | }; 77 | // Context is given prior to this assignement, so doing 78 | // context = init.context will fail because it replace the variable; 79 | 80 | context._polkadotApiParachains = []; 81 | context._polkadotApiRelaychains = []; 82 | context._web3Providers = []; 83 | 84 | context.createWeb3 = async (protocol: "ws" | "http" = "http") => { 85 | const provider = 86 | protocol == "ws" 87 | ? await provideWeb3Api(init.paraPorts[0].ports[0].wsPort, "ws") 88 | : await provideWeb3Api(init.paraPorts[0].ports[0].rpcPort, "http"); 89 | context._web3Providers.push((provider as any)._provider); 90 | return provider; 91 | }; 92 | context.createEthers = async () => 93 | provideEthersApi(init.paraPorts[0].ports[0].rpcPort); 94 | context.createPolkadotApiParachains = async () => { 95 | const apiPromises = await Promise.all( 96 | init.paraPorts.map(async (parachain: ParachainPorts) => { 97 | return { 98 | parachainId: parachain.parachainId, 99 | apis: await Promise.all( 100 | parachain.ports.map(async (ports: NodePorts) => { 101 | return providePolkadotApi(ports.wsPort); 102 | }) 103 | ), 104 | }; 105 | }) 106 | ); 107 | // We keep track of the polkadotApis to close them at the end of the test 108 | context._polkadotApiParachains = apiPromises; 109 | await Promise.all( 110 | apiPromises.map(async (promises) => 111 | Promise.all(promises.apis.map((promise) => promise.isReady)) 112 | ) 113 | ); 114 | // Necessary hack to allow polkadotApi to finish its internal metadata loading 115 | // apiPromise.isReady unfortunately doesn't wait for those properly 116 | await new Promise((resolve) => { 117 | setTimeout(resolve, 100); 118 | }); 119 | 120 | return apiPromises[0].apis[0]; 121 | }; 122 | context.createPolkadotApiRelaychains = async () => { 123 | const apiPromises = await Promise.all( 124 | init.relayPorts.map(async (ports: NodePorts) => { 125 | return await providePolkadotApi(ports.wsPort); 126 | }) 127 | ); 128 | // We keep track of the polkadotApis to close them at the end of the test 129 | context._polkadotApiRelaychains = apiPromises; 130 | await Promise.all(apiPromises.map((promise) => promise.isReady)); 131 | // Necessary hack to allow polkadotApi to finish its internal metadata loading 132 | // apiPromise.isReady unfortunately doesn't wait for those properly 133 | await new Promise((resolve) => { 134 | setTimeout(resolve, 100); 135 | }); 136 | 137 | return apiPromises[0]; 138 | }; 139 | 140 | context.polkadotApiParaone = await context.createPolkadotApiParachains(); 141 | await context.createPolkadotApiRelaychains(); 142 | context.web3 = await context.createWeb3(); 143 | context.ethers = await context.createEthers(); 144 | debug( 145 | `Setup ready [${ 146 | //@ts-ignore 147 | /:([0-9]+)$/.exec((context.web3.currentProvider as any).host)[1] 148 | }] for ${ 149 | //@ts-ignore 150 | this.currentTest.title 151 | }` 152 | ); 153 | }); 154 | 155 | after(async function () { 156 | await Promise.all(context._web3Providers.map((p) => p.disconnect())); 157 | await Promise.all( 158 | context._polkadotApiParachains.map( 159 | async (ps) => await Promise.all(ps.apis.map((p) => p.disconnect())) 160 | ) 161 | ); 162 | await Promise.all( 163 | context._polkadotApiRelaychains.map((p) => p.disconnect()) 164 | ); 165 | 166 | if (!DEBUG_MODE) { 167 | await stopParachainNodes(); 168 | await new Promise((resolve) => { 169 | // TODO: Replace Sleep by actually checking the process has ended 170 | setTimeout(resolve, 1000); 171 | }); 172 | } 173 | }); 174 | 175 | cb(context); 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /test/test-utils/substrate-rpc.ts: -------------------------------------------------------------------------------- 1 | import "@polkadot/api-augment"; 2 | import { ApiPromise } from "@polkadot/api"; 3 | import { 4 | AddressOrPair, 5 | ApiTypes, 6 | SubmittableExtrinsic, 7 | } from "@polkadot/api/types"; 8 | import { GenericExtrinsic } from "@polkadot/types"; 9 | import { AnyTuple } from "@polkadot/types/types"; 10 | import { Event } from "@polkadot/types/interfaces"; 11 | import { u8aToHex } from "@polkadot/util"; 12 | // import { DevTestContext } from "./setup-dev-tests"; 13 | const debug = require("debug")("test:substrateEvents"); 14 | 15 | // LAUNCH BASED NETWORK TESTING (PARA TESTS) 16 | 17 | export async function waitOneBlock( 18 | api: ApiPromise, 19 | numberOfBlocks: number = 1 20 | ) { 21 | return new Promise(async (res) => { 22 | let count = 0; 23 | let unsub = await api.derive.chain.subscribeNewHeads(async (header) => { 24 | console.log( 25 | `One block elapsed : #${header.number}: author : ${header.author}` 26 | ); 27 | count += 1; 28 | if (count === 1 + numberOfBlocks) { 29 | unsub(); 30 | res(); 31 | } 32 | }); 33 | }); 34 | } 35 | 36 | // Log relay/parachain new blocks and events 37 | export async function logEvents(api: ApiPromise, name: string) { 38 | api.derive.chain.subscribeNewHeads(async (header) => { 39 | debug( 40 | `------------- ${name} BLOCK#${header.number}: author ${header.author}, hash ${header.hash}` 41 | ); 42 | (await api.query.system.events.at(header.hash)).forEach((e, i) => { 43 | debug( 44 | `${name} Event :`, 45 | i, 46 | header.hash.toHex(), 47 | (e.toHuman() as any).event.section, 48 | (e.toHuman() as any).event.method 49 | ); 50 | }); 51 | }); 52 | } 53 | 54 | async function lookForExtrinsicAndEvents( 55 | api: ApiPromise, 56 | extrinsicHash: Uint8Array 57 | ) { 58 | // We retrieve the block (including the extrinsics) 59 | const signedBlock = await api.rpc.chain.getBlock(); 60 | 61 | // We retrieve the events for that block 62 | const allRecords = await api.query.system.events.at( 63 | signedBlock.block.header.hash 64 | ); 65 | 66 | const extrinsicIndex = signedBlock.block.extrinsics.findIndex((ext) => { 67 | return ext.hash.toHex() == u8aToHex(extrinsicHash); 68 | }); 69 | if (extrinsicIndex < 0) { 70 | console.log( 71 | `Extrinsic ${extrinsicHash} is missing in the block ${signedBlock.block.header.hash}` 72 | ); 73 | } 74 | const extrinsic = signedBlock.block.extrinsics[extrinsicIndex]; 75 | 76 | // We retrieve the events associated with the extrinsic 77 | const events = allRecords 78 | .filter( 79 | ({ phase }) => 80 | phase.isApplyExtrinsic && 81 | phase.asApplyExtrinsic.toNumber() == extrinsicIndex 82 | ) 83 | .map(({ event }) => event); 84 | return { events, extrinsic }; 85 | } 86 | 87 | async function tryLookingForEvents( 88 | api: ApiPromise, 89 | extrinsicHash: Uint8Array 90 | ): Promise<{ extrinsic: GenericExtrinsic; events: Event[] }> { 91 | await waitOneBlock(api); 92 | let { extrinsic, events } = await lookForExtrinsicAndEvents( 93 | api, 94 | extrinsicHash 95 | ); 96 | if (events.length > 0) { 97 | return { 98 | extrinsic, 99 | events, 100 | }; 101 | } else { 102 | return await tryLookingForEvents(api, extrinsicHash); 103 | } 104 | } 105 | 106 | export const createBlockWithExtrinsicParachain = async < 107 | Call extends SubmittableExtrinsic, 108 | ApiType extends ApiTypes 109 | >( 110 | api: ApiPromise, 111 | sender: AddressOrPair, 112 | polkadotCall: Call 113 | ): Promise<{ extrinsic: GenericExtrinsic; events: Event[] }> => { 114 | console.log("-------------- EXTRINSIC CALL -------------------------------"); 115 | // This should return a Uint8Array 116 | const extrinsicHash = (await polkadotCall.signAndSend( 117 | sender 118 | )) as unknown as Uint8Array; 119 | 120 | // We create the block which is containing the extrinsic 121 | //const blockResult = await context.createBlock(); 122 | return await tryLookingForEvents(api, extrinsicHash); 123 | }; 124 | -------------------------------------------------------------------------------- /test/tests-no-ci/test-balance-genesis-2-parachains.ts: -------------------------------------------------------------------------------- 1 | import Keyring from "@polkadot/keyring"; 2 | import { expect } from "chai"; 3 | 4 | import { describeParachain } from "../test-utils/setup-para-tests"; 5 | 6 | describeParachain( 7 | "Balance genesis", 8 | { chain: "rococo-local", numberOfParachains: 2 }, 9 | (context) => { 10 | it("should be accessible through web3", async function () { 11 | const keyring = new Keyring({ type: "sr25519" }); 12 | const aliceRelay = keyring.addFromUri("//Alice"); 13 | 14 | //check parachain id 15 | expect( 16 | ( 17 | (await context._polkadotApiRelaychains[0].query.paras.parachains()) as any 18 | )[0].toString() 19 | ).to.eq("1000"); 20 | expect( 21 | ( 22 | (await context._polkadotApiRelaychains[0].query.paras.parachains()) as any 23 | )[1].toString() 24 | ).to.eq("2000"); 25 | expect( 26 | ( 27 | await context.polkadotApiParaone.query.system.account( 28 | aliceRelay.addressRaw 29 | ) 30 | ).data.free.toHuman() 31 | ).to.eq("1,152,921,504,606,846,976"); 32 | }); 33 | } 34 | ); 35 | -------------------------------------------------------------------------------- /test/tests/test-balance-genesis.ts: -------------------------------------------------------------------------------- 1 | import Keyring from "@polkadot/keyring"; 2 | import { expect } from "chai"; 3 | 4 | import { describeParachain } from "../test-utils/setup-para-tests"; 5 | 6 | describeParachain("Balance genesis", { chain: "rococo-local" }, (context) => { 7 | it("should be accessible through web3", async function () { 8 | const keyring = new Keyring({ type: "sr25519" }); 9 | const aliceRelay = keyring.addFromUri("//Alice"); 10 | 11 | //check parachain id 12 | expect( 13 | ( 14 | (await context._polkadotApiRelaychains[0].query.paras.parachains()) as any 15 | )[0].toString() 16 | ).to.eq("1000"); 17 | // check genesis balance 18 | expect( 19 | ( 20 | await context.polkadotApiParaone.query.system.account( 21 | aliceRelay.addressRaw 22 | ) 23 | ).data.free.toHuman() 24 | ).to.eq("1,152,921,504,606,846,976"); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "target": "ES2020", 6 | "module": "commonjs", 7 | "lib": [ 8 | "esnext", 9 | "dom" 10 | ], 11 | "outDir": "dist", 12 | "resolveJsonModule": true, 13 | "strict": true, 14 | "declaration": true 15 | }, 16 | "exclude": [ 17 | "node_modules", 18 | "dist", 19 | "test" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /yarn.nix: -------------------------------------------------------------------------------- 1 | { fetchurl, fetchgit, linkFarm, runCommandNoCC, gnutar }: rec { 2 | offline_cache = linkFarm "offline" packages; 3 | packages = [ 4 | { 5 | name = "_babel_runtime___runtime_7.15.4.tgz"; 6 | path = fetchurl { 7 | name = "_babel_runtime___runtime_7.15.4.tgz"; 8 | url = "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz"; 9 | sha1 = "fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"; 10 | }; 11 | } 12 | { 13 | name = "_polkadot_api_derive___api_derive_6.3.1.tgz"; 14 | path = fetchurl { 15 | name = "_polkadot_api_derive___api_derive_6.3.1.tgz"; 16 | url = "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-6.3.1.tgz"; 17 | sha1 = "88618243e15f82368256c7f8d068a4539f9327a3"; 18 | }; 19 | } 20 | { 21 | name = "_polkadot_api___api_6.3.1.tgz"; 22 | path = fetchurl { 23 | name = "_polkadot_api___api_6.3.1.tgz"; 24 | url = "https://registry.yarnpkg.com/@polkadot/api/-/api-6.3.1.tgz"; 25 | sha1 = "18859dec2cdd30b54e6c04bc23a9d906b485c5e8"; 26 | }; 27 | } 28 | { 29 | name = "_polkadot_keyring___keyring_7.4.1.tgz"; 30 | path = fetchurl { 31 | name = "_polkadot_keyring___keyring_7.4.1.tgz"; 32 | url = "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-7.4.1.tgz"; 33 | sha1 = "cda3f371cc2a9bf4b8847bad41c4c14edfb05745"; 34 | }; 35 | } 36 | { 37 | name = "_polkadot_networks___networks_7.4.1.tgz"; 38 | path = fetchurl { 39 | name = "_polkadot_networks___networks_7.4.1.tgz"; 40 | url = "https://registry.yarnpkg.com/@polkadot/networks/-/networks-7.4.1.tgz"; 41 | sha1 = "02b4a1a159e64b90a08d0f3a0206858b64846a3b"; 42 | }; 43 | } 44 | { 45 | name = "_polkadot_rpc_core___rpc_core_6.3.1.tgz"; 46 | path = fetchurl { 47 | name = "_polkadot_rpc_core___rpc_core_6.3.1.tgz"; 48 | url = "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-6.3.1.tgz"; 49 | sha1 = "96eebcea74c1334b128b34a341406ac6ade34e2d"; 50 | }; 51 | } 52 | { 53 | name = "_polkadot_rpc_provider___rpc_provider_6.3.1.tgz"; 54 | path = fetchurl { 55 | name = "_polkadot_rpc_provider___rpc_provider_6.3.1.tgz"; 56 | url = "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-6.3.1.tgz"; 57 | sha1 = "8a9d11a0ad40783228e56f642bc0fe418227528c"; 58 | }; 59 | } 60 | { 61 | name = "_polkadot_types_known___types_known_6.3.1.tgz"; 62 | path = fetchurl { 63 | name = "_polkadot_types_known___types_known_6.3.1.tgz"; 64 | url = "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-6.3.1.tgz"; 65 | sha1 = "dae6d8532272d8fc3c4ea53181a18d7d117b7113"; 66 | }; 67 | } 68 | { 69 | name = "_polkadot_types___types_6.3.1.tgz"; 70 | path = fetchurl { 71 | name = "_polkadot_types___types_6.3.1.tgz"; 72 | url = "https://registry.yarnpkg.com/@polkadot/types/-/types-6.3.1.tgz"; 73 | sha1 = "98f14278806b68b784113b6aac361a9e4bd1b005"; 74 | }; 75 | } 76 | { 77 | name = "_polkadot_util_crypto___util_crypto_7.4.1.tgz"; 78 | path = fetchurl { 79 | name = "_polkadot_util_crypto___util_crypto_7.4.1.tgz"; 80 | url = "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-7.4.1.tgz"; 81 | sha1 = "76760df995e9feb7deef69d85cab6c13e9ceb977"; 82 | }; 83 | } 84 | { 85 | name = "_polkadot_util___util_7.4.1.tgz"; 86 | path = fetchurl { 87 | name = "_polkadot_util___util_7.4.1.tgz"; 88 | url = "https://registry.yarnpkg.com/@polkadot/util/-/util-7.4.1.tgz"; 89 | sha1 = "f5aa9b60e5ca5c5b8f0d188beb7cbd47dd6c4041"; 90 | }; 91 | } 92 | { 93 | name = "_polkadot_wasm_crypto_asmjs___wasm_crypto_asmjs_4.2.1.tgz"; 94 | path = fetchurl { 95 | name = "_polkadot_wasm_crypto_asmjs___wasm_crypto_asmjs_4.2.1.tgz"; 96 | url = "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-4.2.1.tgz"; 97 | sha1 = "6b7eae1c011709f8042dfd30872a5fc5e9e021c0"; 98 | }; 99 | } 100 | { 101 | name = "_polkadot_wasm_crypto_wasm___wasm_crypto_wasm_4.2.1.tgz"; 102 | path = fetchurl { 103 | name = "_polkadot_wasm_crypto_wasm___wasm_crypto_wasm_4.2.1.tgz"; 104 | url = "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-4.2.1.tgz"; 105 | sha1 = "2a86f9b405e7195c3f523798c6ce4afffd19737e"; 106 | }; 107 | } 108 | { 109 | name = "_polkadot_wasm_crypto___wasm_crypto_4.2.1.tgz"; 110 | path = fetchurl { 111 | name = "_polkadot_wasm_crypto___wasm_crypto_4.2.1.tgz"; 112 | url = "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-4.2.1.tgz"; 113 | sha1 = "4d09402f5ac71a90962fb58cbe4b1707772a4fb6"; 114 | }; 115 | } 116 | { 117 | name = "_polkadot_x_fetch___x_fetch_7.4.1.tgz"; 118 | path = fetchurl { 119 | name = "_polkadot_x_fetch___x_fetch_7.4.1.tgz"; 120 | url = "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-7.4.1.tgz"; 121 | sha1 = "70dc3f648981f24b32afbcfb5b59e2000c72f4b2"; 122 | }; 123 | } 124 | { 125 | name = "_polkadot_x_global___x_global_7.4.1.tgz"; 126 | path = fetchurl { 127 | name = "_polkadot_x_global___x_global_7.4.1.tgz"; 128 | url = "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-7.4.1.tgz"; 129 | sha1 = "66f7f8a5d0208832773a4606c56d10e7927552fc"; 130 | }; 131 | } 132 | { 133 | name = "_polkadot_x_randomvalues___x_randomvalues_7.4.1.tgz"; 134 | path = fetchurl { 135 | name = "_polkadot_x_randomvalues___x_randomvalues_7.4.1.tgz"; 136 | url = "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-7.4.1.tgz"; 137 | sha1 = "e48d6c7fa869f5f871b2d18aa8b864c9802e9aeb"; 138 | }; 139 | } 140 | { 141 | name = "_polkadot_x_textdecoder___x_textdecoder_7.4.1.tgz"; 142 | path = fetchurl { 143 | name = "_polkadot_x_textdecoder___x_textdecoder_7.4.1.tgz"; 144 | url = "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-7.4.1.tgz"; 145 | sha1 = "e0e0bc375d5aa7fad8929a7ea1c279884c57ad26"; 146 | }; 147 | } 148 | { 149 | name = "_polkadot_x_textencoder___x_textencoder_7.4.1.tgz"; 150 | path = fetchurl { 151 | name = "_polkadot_x_textencoder___x_textencoder_7.4.1.tgz"; 152 | url = "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-7.4.1.tgz"; 153 | sha1 = "0411213c6ab3f6f80af074f49ed12174c3e28775"; 154 | }; 155 | } 156 | { 157 | name = "_polkadot_x_ws___x_ws_7.4.1.tgz"; 158 | path = fetchurl { 159 | name = "_polkadot_x_ws___x_ws_7.4.1.tgz"; 160 | url = "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-7.4.1.tgz"; 161 | sha1 = "94b310e3385dabf550adba99a2a06cbf03a737cb"; 162 | }; 163 | } 164 | { 165 | name = "_protobufjs_aspromise___aspromise_1.1.2.tgz"; 166 | path = fetchurl { 167 | name = "_protobufjs_aspromise___aspromise_1.1.2.tgz"; 168 | url = "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz"; 169 | sha1 = "9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"; 170 | }; 171 | } 172 | { 173 | name = "_protobufjs_base64___base64_1.1.2.tgz"; 174 | path = fetchurl { 175 | name = "_protobufjs_base64___base64_1.1.2.tgz"; 176 | url = "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz"; 177 | sha1 = "4c85730e59b9a1f1f349047dbf24296034bb2735"; 178 | }; 179 | } 180 | { 181 | name = "_protobufjs_codegen___codegen_2.0.4.tgz"; 182 | path = fetchurl { 183 | name = "_protobufjs_codegen___codegen_2.0.4.tgz"; 184 | url = "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz"; 185 | sha1 = "7ef37f0d010fb028ad1ad59722e506d9262815cb"; 186 | }; 187 | } 188 | { 189 | name = "_protobufjs_eventemitter___eventemitter_1.1.0.tgz"; 190 | path = fetchurl { 191 | name = "_protobufjs_eventemitter___eventemitter_1.1.0.tgz"; 192 | url = "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz"; 193 | sha1 = "355cbc98bafad5978f9ed095f397621f1d066b70"; 194 | }; 195 | } 196 | { 197 | name = "_protobufjs_fetch___fetch_1.1.0.tgz"; 198 | path = fetchurl { 199 | name = "_protobufjs_fetch___fetch_1.1.0.tgz"; 200 | url = "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz"; 201 | sha1 = "ba99fb598614af65700c1619ff06d454b0d84c45"; 202 | }; 203 | } 204 | { 205 | name = "_protobufjs_float___float_1.0.2.tgz"; 206 | path = fetchurl { 207 | name = "_protobufjs_float___float_1.0.2.tgz"; 208 | url = "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz"; 209 | sha1 = "5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"; 210 | }; 211 | } 212 | { 213 | name = "_protobufjs_inquire___inquire_1.1.0.tgz"; 214 | path = fetchurl { 215 | name = "_protobufjs_inquire___inquire_1.1.0.tgz"; 216 | url = "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz"; 217 | sha1 = "ff200e3e7cf2429e2dcafc1140828e8cc638f089"; 218 | }; 219 | } 220 | { 221 | name = "_protobufjs_path___path_1.1.2.tgz"; 222 | path = fetchurl { 223 | name = "_protobufjs_path___path_1.1.2.tgz"; 224 | url = "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz"; 225 | sha1 = "6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"; 226 | }; 227 | } 228 | { 229 | name = "_protobufjs_pool___pool_1.1.0.tgz"; 230 | path = fetchurl { 231 | name = "_protobufjs_pool___pool_1.1.0.tgz"; 232 | url = "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz"; 233 | sha1 = "09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"; 234 | }; 235 | } 236 | { 237 | name = "_protobufjs_utf8___utf8_1.1.0.tgz"; 238 | path = fetchurl { 239 | name = "_protobufjs_utf8___utf8_1.1.0.tgz"; 240 | url = "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz"; 241 | sha1 = "a777360b5b39a1a2e5106f8e858f2fd2d060c570"; 242 | }; 243 | } 244 | { 245 | name = "_types_bn.js___bn.js_4.11.6.tgz"; 246 | path = fetchurl { 247 | name = "_types_bn.js___bn.js_4.11.6.tgz"; 248 | url = "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz"; 249 | sha1 = "c306c70d9358aaea33cd4eda092a742b9505967c"; 250 | }; 251 | } 252 | { 253 | name = "_types_long___long_4.0.1.tgz"; 254 | path = fetchurl { 255 | name = "_types_long___long_4.0.1.tgz"; 256 | url = "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz"; 257 | sha1 = "459c65fa1867dafe6a8f322c4c51695663cc55e9"; 258 | }; 259 | } 260 | { 261 | name = "_types_node_fetch___node_fetch_2.5.12.tgz"; 262 | path = fetchurl { 263 | name = "_types_node_fetch___node_fetch_2.5.12.tgz"; 264 | url = "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz"; 265 | sha1 = "8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66"; 266 | }; 267 | } 268 | { 269 | name = "_types_node___node_16.10.3.tgz"; 270 | path = fetchurl { 271 | name = "_types_node___node_16.10.3.tgz"; 272 | url = "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz"; 273 | sha1 = "7a8f2838603ea314d1d22bb3171d899e15c57bd5"; 274 | }; 275 | } 276 | { 277 | name = "_types_node___node_16.11.8.tgz"; 278 | path = fetchurl { 279 | name = "_types_node___node_16.11.8.tgz"; 280 | url = "https://registry.yarnpkg.com/@types/node/-/node-16.11.8.tgz"; 281 | sha1 = "a1aeb23f0aa33cb111e64ccaa1687b2ae0423b69"; 282 | }; 283 | } 284 | { 285 | name = "_types_websocket___websocket_1.0.4.tgz"; 286 | path = fetchurl { 287 | name = "_types_websocket___websocket_1.0.4.tgz"; 288 | url = "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.4.tgz"; 289 | sha1 = "1dc497280d8049a5450854dd698ee7e6ea9e60b8"; 290 | }; 291 | } 292 | { 293 | name = "ansi_regex___ansi_regex_5.0.1.tgz"; 294 | path = fetchurl { 295 | name = "ansi_regex___ansi_regex_5.0.1.tgz"; 296 | url = "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz"; 297 | sha1 = "082cb2c89c9fe8659a311a53bd6a4dc5301db304"; 298 | }; 299 | } 300 | { 301 | name = "ansi_styles___ansi_styles_4.3.0.tgz"; 302 | path = fetchurl { 303 | name = "ansi_styles___ansi_styles_4.3.0.tgz"; 304 | url = "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz"; 305 | sha1 = "edd803628ae71c04c85ae7a0906edad34b648937"; 306 | }; 307 | } 308 | { 309 | name = "asn1.js___asn1.js_5.4.1.tgz"; 310 | path = fetchurl { 311 | name = "asn1.js___asn1.js_5.4.1.tgz"; 312 | url = "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz"; 313 | sha1 = "11a980b84ebb91781ce35b0fdc2ee294e3783f07"; 314 | }; 315 | } 316 | { 317 | name = "asynckit___asynckit_0.4.0.tgz"; 318 | path = fetchurl { 319 | name = "asynckit___asynckit_0.4.0.tgz"; 320 | url = "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz"; 321 | sha1 = "c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"; 322 | }; 323 | } 324 | { 325 | name = "base_x___base_x_3.0.8.tgz"; 326 | path = fetchurl { 327 | name = "base_x___base_x_3.0.8.tgz"; 328 | url = "https://registry.yarnpkg.com/base-x/-/base-x-3.0.8.tgz"; 329 | sha1 = "1e1106c2537f0162e8b52474a557ebb09000018d"; 330 | }; 331 | } 332 | { 333 | name = "base64_js___base64_js_1.5.1.tgz"; 334 | path = fetchurl { 335 | name = "base64_js___base64_js_1.5.1.tgz"; 336 | url = "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz"; 337 | sha1 = "1b1b440160a5bf7ad40b650f095963481903930a"; 338 | }; 339 | } 340 | { 341 | name = "bindings___bindings_1.5.0.tgz"; 342 | path = fetchurl { 343 | name = "bindings___bindings_1.5.0.tgz"; 344 | url = "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz"; 345 | sha1 = "10353c9e945334bc0511a6d90b38fbc7c9c504df"; 346 | }; 347 | } 348 | { 349 | name = "blakejs___blakejs_1.1.1.tgz"; 350 | path = fetchurl { 351 | name = "blakejs___blakejs_1.1.1.tgz"; 352 | url = "https://registry.yarnpkg.com/blakejs/-/blakejs-1.1.1.tgz"; 353 | sha1 = "bf313053978b2cd4c444a48795710be05c785702"; 354 | }; 355 | } 356 | { 357 | name = "bn.js___bn.js_4.12.0.tgz"; 358 | path = fetchurl { 359 | name = "bn.js___bn.js_4.12.0.tgz"; 360 | url = "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz"; 361 | sha1 = "775b3f278efbb9718eec7361f483fb36fbbfea88"; 362 | }; 363 | } 364 | { 365 | name = "brorand___brorand_1.1.0.tgz"; 366 | path = fetchurl { 367 | name = "brorand___brorand_1.1.0.tgz"; 368 | url = "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz"; 369 | sha1 = "12c25efe40a45e3c323eb8675a0a0ce57b22371f"; 370 | }; 371 | } 372 | { 373 | name = "bufferutil___bufferutil_4.0.4.tgz"; 374 | path = fetchurl { 375 | name = "bufferutil___bufferutil_4.0.4.tgz"; 376 | url = "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.4.tgz"; 377 | sha1 = "ab81373d313a6ead0d734e98c448c722734ae7bb"; 378 | }; 379 | } 380 | { 381 | name = "camelcase___camelcase_5.3.1.tgz"; 382 | path = fetchurl { 383 | name = "camelcase___camelcase_5.3.1.tgz"; 384 | url = "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz"; 385 | sha1 = "e3c9b31569e106811df242f715725a1f4c494320"; 386 | }; 387 | } 388 | { 389 | name = "camelcase___camelcase_6.2.0.tgz"; 390 | path = fetchurl { 391 | name = "camelcase___camelcase_6.2.0.tgz"; 392 | url = "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz"; 393 | sha1 = "924af881c9d525ac9d87f40d964e5cea982a1809"; 394 | }; 395 | } 396 | { 397 | name = "cipher_base___cipher_base_1.0.4.tgz"; 398 | path = fetchurl { 399 | name = "cipher_base___cipher_base_1.0.4.tgz"; 400 | url = "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz"; 401 | sha1 = "8760e4ecc272f4c363532f926d874aae2c1397de"; 402 | }; 403 | } 404 | { 405 | name = "class_is___class_is_1.1.0.tgz"; 406 | path = fetchurl { 407 | name = "class_is___class_is_1.1.0.tgz"; 408 | url = "https://registry.yarnpkg.com/class-is/-/class-is-1.1.0.tgz"; 409 | sha1 = "9d3c0fba0440d211d843cec3dedfa48055005825"; 410 | }; 411 | } 412 | { 413 | name = "cliui___cliui_6.0.0.tgz"; 414 | path = fetchurl { 415 | name = "cliui___cliui_6.0.0.tgz"; 416 | url = "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz"; 417 | sha1 = "511d702c0c4e41ca156d7d0e96021f23e13225b1"; 418 | }; 419 | } 420 | { 421 | name = "color_convert___color_convert_2.0.1.tgz"; 422 | path = fetchurl { 423 | name = "color_convert___color_convert_2.0.1.tgz"; 424 | url = "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz"; 425 | sha1 = "72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"; 426 | }; 427 | } 428 | { 429 | name = "color_name___color_name_1.1.4.tgz"; 430 | path = fetchurl { 431 | name = "color_name___color_name_1.1.4.tgz"; 432 | url = "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz"; 433 | sha1 = "c2a09a87acbde69543de6f63fa3995c826c536a2"; 434 | }; 435 | } 436 | { 437 | name = "combined_stream___combined_stream_1.0.8.tgz"; 438 | path = fetchurl { 439 | name = "combined_stream___combined_stream_1.0.8.tgz"; 440 | url = "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz"; 441 | sha1 = "c3d45a8b34fd730631a110a8a2520682b31d5a7f"; 442 | }; 443 | } 444 | { 445 | name = "create_hash___create_hash_1.2.0.tgz"; 446 | path = fetchurl { 447 | name = "create_hash___create_hash_1.2.0.tgz"; 448 | url = "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz"; 449 | sha1 = "889078af11a63756bcfb59bd221996be3a9ef196"; 450 | }; 451 | } 452 | { 453 | name = "cuint___cuint_0.2.2.tgz"; 454 | path = fetchurl { 455 | name = "cuint___cuint_0.2.2.tgz"; 456 | url = "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz"; 457 | sha1 = "408086d409550c2631155619e9fa7bcadc3b991b"; 458 | }; 459 | } 460 | { 461 | name = "d___d_1.0.1.tgz"; 462 | path = fetchurl { 463 | name = "d___d_1.0.1.tgz"; 464 | url = "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz"; 465 | sha1 = "8698095372d58dbee346ffd0c7093f99f8f9eb5a"; 466 | }; 467 | } 468 | { 469 | name = "debug___debug_2.6.9.tgz"; 470 | path = fetchurl { 471 | name = "debug___debug_2.6.9.tgz"; 472 | url = "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz"; 473 | sha1 = "5d128515df134ff327e90a4c93f4e077a536341f"; 474 | }; 475 | } 476 | { 477 | name = "decamelize___decamelize_1.2.0.tgz"; 478 | path = fetchurl { 479 | name = "decamelize___decamelize_1.2.0.tgz"; 480 | url = "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz"; 481 | sha1 = "f6534d15148269b20352e7bee26f501f9a191290"; 482 | }; 483 | } 484 | { 485 | name = "delayed_stream___delayed_stream_1.0.0.tgz"; 486 | path = fetchurl { 487 | name = "delayed_stream___delayed_stream_1.0.0.tgz"; 488 | url = "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz"; 489 | sha1 = "df3ae199acadfb7d440aaae0b29e2272b24ec619"; 490 | }; 491 | } 492 | { 493 | name = "ed2curve___ed2curve_0.3.0.tgz"; 494 | path = fetchurl { 495 | name = "ed2curve___ed2curve_0.3.0.tgz"; 496 | url = "https://registry.yarnpkg.com/ed2curve/-/ed2curve-0.3.0.tgz"; 497 | sha1 = "322b575152a45305429d546b071823a93129a05d"; 498 | }; 499 | } 500 | { 501 | name = "elliptic___elliptic_6.5.4.tgz"; 502 | path = fetchurl { 503 | name = "elliptic___elliptic_6.5.4.tgz"; 504 | url = "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz"; 505 | sha1 = "da37cebd31e79a1367e941b592ed1fbebd58abbb"; 506 | }; 507 | } 508 | { 509 | name = "emoji_regex___emoji_regex_8.0.0.tgz"; 510 | path = fetchurl { 511 | name = "emoji_regex___emoji_regex_8.0.0.tgz"; 512 | url = "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz"; 513 | sha1 = "e818fd69ce5ccfcb404594f842963bf53164cc37"; 514 | }; 515 | } 516 | { 517 | name = "err_code___err_code_3.0.1.tgz"; 518 | path = fetchurl { 519 | name = "err_code___err_code_3.0.1.tgz"; 520 | url = "https://registry.yarnpkg.com/err-code/-/err-code-3.0.1.tgz"; 521 | sha1 = "a444c7b992705f2b120ee320b09972eef331c920"; 522 | }; 523 | } 524 | { 525 | name = "es5_ext___es5_ext_0.10.53.tgz"; 526 | path = fetchurl { 527 | name = "es5_ext___es5_ext_0.10.53.tgz"; 528 | url = "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz"; 529 | sha1 = "93c5a3acfdbef275220ad72644ad02ee18368de1"; 530 | }; 531 | } 532 | { 533 | name = "es6_iterator___es6_iterator_2.0.3.tgz"; 534 | path = fetchurl { 535 | name = "es6_iterator___es6_iterator_2.0.3.tgz"; 536 | url = "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz"; 537 | sha1 = "a7de889141a05a94b0854403b2d0a0fbfa98f3b7"; 538 | }; 539 | } 540 | { 541 | name = "es6_symbol___es6_symbol_3.1.3.tgz"; 542 | path = fetchurl { 543 | name = "es6_symbol___es6_symbol_3.1.3.tgz"; 544 | url = "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz"; 545 | sha1 = "bad5d3c1bcdac28269f4cb331e431c78ac705d18"; 546 | }; 547 | } 548 | { 549 | name = "eventemitter3___eventemitter3_4.0.7.tgz"; 550 | path = fetchurl { 551 | name = "eventemitter3___eventemitter3_4.0.7.tgz"; 552 | url = "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz"; 553 | sha1 = "2de9b68f6528d5644ef5c59526a1b4a07306169f"; 554 | }; 555 | } 556 | { 557 | name = "events___events_3.3.0.tgz"; 558 | path = fetchurl { 559 | name = "events___events_3.3.0.tgz"; 560 | url = "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz"; 561 | sha1 = "31a95ad0a924e2d2c419a813aeb2c4e878ea7400"; 562 | }; 563 | } 564 | { 565 | name = "ext___ext_1.6.0.tgz"; 566 | path = fetchurl { 567 | name = "ext___ext_1.6.0.tgz"; 568 | url = "https://registry.yarnpkg.com/ext/-/ext-1.6.0.tgz"; 569 | sha1 = "3871d50641e874cc172e2b53f919842d19db4c52"; 570 | }; 571 | } 572 | { 573 | name = "file_uri_to_path___file_uri_to_path_1.0.0.tgz"; 574 | path = fetchurl { 575 | name = "file_uri_to_path___file_uri_to_path_1.0.0.tgz"; 576 | url = "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz"; 577 | sha1 = "553a7b8446ff6f684359c445f1e37a05dacc33dd"; 578 | }; 579 | } 580 | { 581 | name = "filter_console___filter_console_0.1.1.tgz"; 582 | path = fetchurl { 583 | name = "filter_console___filter_console_0.1.1.tgz"; 584 | url = "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz"; 585 | sha1 = "6242be28982bba7415bcc6db74a79f4a294fa67c"; 586 | }; 587 | } 588 | { 589 | name = "find_up___find_up_4.1.0.tgz"; 590 | path = fetchurl { 591 | name = "find_up___find_up_4.1.0.tgz"; 592 | url = "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz"; 593 | sha1 = "97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"; 594 | }; 595 | } 596 | { 597 | name = "form_data___form_data_3.0.1.tgz"; 598 | path = fetchurl { 599 | name = "form_data___form_data_3.0.1.tgz"; 600 | url = "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz"; 601 | sha1 = "ebd53791b78356a99af9a300d4282c4d5eb9755f"; 602 | }; 603 | } 604 | { 605 | name = "get_caller_file___get_caller_file_2.0.5.tgz"; 606 | path = fetchurl { 607 | name = "get_caller_file___get_caller_file_2.0.5.tgz"; 608 | url = "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz"; 609 | sha1 = "4f94412a82db32f36e3b0b9741f8a97feb031f7e"; 610 | }; 611 | } 612 | { 613 | name = "hash_base___hash_base_3.1.0.tgz"; 614 | path = fetchurl { 615 | name = "hash_base___hash_base_3.1.0.tgz"; 616 | url = "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz"; 617 | sha1 = "55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"; 618 | }; 619 | } 620 | { 621 | name = "hash.js___hash.js_1.1.7.tgz"; 622 | path = fetchurl { 623 | name = "hash.js___hash.js_1.1.7.tgz"; 624 | url = "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz"; 625 | sha1 = "0babca538e8d4ee4a0f8988d68866537a003cf42"; 626 | }; 627 | } 628 | { 629 | name = "hmac_drbg___hmac_drbg_1.0.1.tgz"; 630 | path = fetchurl { 631 | name = "hmac_drbg___hmac_drbg_1.0.1.tgz"; 632 | url = "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz"; 633 | sha1 = "d2745701025a6c775a6c545793ed502fc0c649a1"; 634 | }; 635 | } 636 | { 637 | name = "inherits___inherits_2.0.4.tgz"; 638 | path = fetchurl { 639 | name = "inherits___inherits_2.0.4.tgz"; 640 | url = "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz"; 641 | sha1 = "0fa2c64f932917c3433a0ded55363aae37416b7c"; 642 | }; 643 | } 644 | { 645 | name = "ip_regex___ip_regex_4.3.0.tgz"; 646 | path = fetchurl { 647 | name = "ip_regex___ip_regex_4.3.0.tgz"; 648 | url = "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz"; 649 | sha1 = "687275ab0f57fa76978ff8f4dddc8a23d5990db5"; 650 | }; 651 | } 652 | { 653 | name = "is_fullwidth_code_point___is_fullwidth_code_point_3.0.0.tgz"; 654 | path = fetchurl { 655 | name = "is_fullwidth_code_point___is_fullwidth_code_point_3.0.0.tgz"; 656 | url = "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"; 657 | sha1 = "f116f8064fe90b3f7844a38997c0b75051269f1d"; 658 | }; 659 | } 660 | { 661 | name = "is_typedarray___is_typedarray_1.0.0.tgz"; 662 | path = fetchurl { 663 | name = "is_typedarray___is_typedarray_1.0.0.tgz"; 664 | url = "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz"; 665 | sha1 = "e479c80858df0c1b11ddda6940f96011fcda4a9a"; 666 | }; 667 | } 668 | { 669 | name = "iso_random_stream___iso_random_stream_2.0.0.tgz"; 670 | path = fetchurl { 671 | name = "iso_random_stream___iso_random_stream_2.0.0.tgz"; 672 | url = "https://registry.yarnpkg.com/iso-random-stream/-/iso-random-stream-2.0.0.tgz"; 673 | sha1 = "3f0118166d5443148bbc134345fb100002ad0f1d"; 674 | }; 675 | } 676 | { 677 | name = "js_sha3___js_sha3_0.8.0.tgz"; 678 | path = fetchurl { 679 | name = "js_sha3___js_sha3_0.8.0.tgz"; 680 | url = "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz"; 681 | sha1 = "b9b7a5da73afad7dedd0f8c463954cbde6818840"; 682 | }; 683 | } 684 | { 685 | name = "keypair___keypair_1.0.4.tgz"; 686 | path = fetchurl { 687 | name = "keypair___keypair_1.0.4.tgz"; 688 | url = "https://registry.yarnpkg.com/keypair/-/keypair-1.0.4.tgz"; 689 | sha1 = "a749a45f388593f3950f18b3757d32a93bd8ce83"; 690 | }; 691 | } 692 | { 693 | name = "libp2p_crypto___libp2p_crypto_0.19.7.tgz"; 694 | path = fetchurl { 695 | name = "libp2p_crypto___libp2p_crypto_0.19.7.tgz"; 696 | url = "https://registry.yarnpkg.com/libp2p-crypto/-/libp2p-crypto-0.19.7.tgz"; 697 | sha1 = "e96a95bd430e672a695209fe0fbd2bcbd348bc35"; 698 | }; 699 | } 700 | { 701 | name = "libp2p_crypto___libp2p_crypto_0.20.0.tgz"; 702 | path = fetchurl { 703 | name = "libp2p_crypto___libp2p_crypto_0.20.0.tgz"; 704 | url = "https://registry.yarnpkg.com/libp2p-crypto/-/libp2p-crypto-0.20.0.tgz"; 705 | sha1 = "3881ccff5f1f51f48c74050d685535fb1a728488"; 706 | }; 707 | } 708 | { 709 | name = "locate_path___locate_path_5.0.0.tgz"; 710 | path = fetchurl { 711 | name = "locate_path___locate_path_5.0.0.tgz"; 712 | url = "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz"; 713 | sha1 = "1afba396afd676a6d42504d0a67a3a7eb9f62aa0"; 714 | }; 715 | } 716 | { 717 | name = "long___long_4.0.0.tgz"; 718 | path = fetchurl { 719 | name = "long___long_4.0.0.tgz"; 720 | url = "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz"; 721 | sha1 = "9a7b71cfb7d361a194ea555241c92f7468d5bf28"; 722 | }; 723 | } 724 | { 725 | name = "md5.js___md5.js_1.3.5.tgz"; 726 | path = fetchurl { 727 | name = "md5.js___md5.js_1.3.5.tgz"; 728 | url = "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz"; 729 | sha1 = "b5d07b8e3216e3e27cd728d72f70d1e6a342005f"; 730 | }; 731 | } 732 | { 733 | name = "mime_db___mime_db_1.50.0.tgz"; 734 | path = fetchurl { 735 | name = "mime_db___mime_db_1.50.0.tgz"; 736 | url = "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz"; 737 | sha1 = "abd4ac94e98d3c0e185016c67ab45d5fde40c11f"; 738 | }; 739 | } 740 | { 741 | name = "mime_types___mime_types_2.1.33.tgz"; 742 | path = fetchurl { 743 | name = "mime_types___mime_types_2.1.33.tgz"; 744 | url = "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz"; 745 | sha1 = "1fa12a904472fafd068e48d9e8401f74d3f70edb"; 746 | }; 747 | } 748 | { 749 | name = "minimalistic_assert___minimalistic_assert_1.0.1.tgz"; 750 | path = fetchurl { 751 | name = "minimalistic_assert___minimalistic_assert_1.0.1.tgz"; 752 | url = "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz"; 753 | sha1 = "2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"; 754 | }; 755 | } 756 | { 757 | name = "minimalistic_crypto_utils___minimalistic_crypto_utils_1.0.1.tgz"; 758 | path = fetchurl { 759 | name = "minimalistic_crypto_utils___minimalistic_crypto_utils_1.0.1.tgz"; 760 | url = "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz"; 761 | sha1 = "f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"; 762 | }; 763 | } 764 | { 765 | name = "minimist___minimist_1.2.5.tgz"; 766 | path = fetchurl { 767 | name = "minimist___minimist_1.2.5.tgz"; 768 | url = "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz"; 769 | sha1 = "67d66014b66a6a8aaa0c083c5fd58df4e4e97602"; 770 | }; 771 | } 772 | { 773 | name = "ms___ms_2.0.0.tgz"; 774 | path = fetchurl { 775 | name = "ms___ms_2.0.0.tgz"; 776 | url = "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz"; 777 | sha1 = "5608aeadfc00be6c2901df5f9861788de0d597c8"; 778 | }; 779 | } 780 | { 781 | name = "multiformats___multiformats_9.4.10.tgz"; 782 | path = fetchurl { 783 | name = "multiformats___multiformats_9.4.10.tgz"; 784 | url = "https://registry.yarnpkg.com/multiformats/-/multiformats-9.4.10.tgz"; 785 | sha1 = "d654d06b28cc066506e4e59b246d65267fb6b93b"; 786 | }; 787 | } 788 | { 789 | name = "nan___nan_2.15.0.tgz"; 790 | path = fetchurl { 791 | name = "nan___nan_2.15.0.tgz"; 792 | url = "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz"; 793 | sha1 = "3f34a473ff18e15c1b5626b62903b5ad6e665fee"; 794 | }; 795 | } 796 | { 797 | name = "next_tick___next_tick_1.0.0.tgz"; 798 | path = fetchurl { 799 | name = "next_tick___next_tick_1.0.0.tgz"; 800 | url = "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz"; 801 | sha1 = "ca86d1fe8828169b0120208e3dc8424b9db8342c"; 802 | }; 803 | } 804 | { 805 | name = "noble_ed25519___noble_ed25519_1.2.6.tgz"; 806 | path = fetchurl { 807 | name = "noble_ed25519___noble_ed25519_1.2.6.tgz"; 808 | url = "https://registry.yarnpkg.com/noble-ed25519/-/noble-ed25519-1.2.6.tgz"; 809 | sha1 = "a55b75c61da000498abb43ffd81caaa370bfed22"; 810 | }; 811 | } 812 | { 813 | name = "noble_secp256k1___noble_secp256k1_1.2.14.tgz"; 814 | path = fetchurl { 815 | name = "noble_secp256k1___noble_secp256k1_1.2.14.tgz"; 816 | url = "https://registry.yarnpkg.com/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz"; 817 | sha1 = "39429c941d51211ca40161569cee03e61d72599e"; 818 | }; 819 | } 820 | { 821 | name = "node_addon_api___node_addon_api_2.0.2.tgz"; 822 | path = fetchurl { 823 | name = "node_addon_api___node_addon_api_2.0.2.tgz"; 824 | url = "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.2.tgz"; 825 | sha1 = "432cfa82962ce494b132e9d72a15b29f71ff5d32"; 826 | }; 827 | } 828 | { 829 | name = "node_fetch___node_fetch_2.6.5.tgz"; 830 | path = fetchurl { 831 | name = "node_fetch___node_fetch_2.6.5.tgz"; 832 | url = "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz"; 833 | sha1 = "42735537d7f080a7e5f78b6c549b7146be1742fd"; 834 | }; 835 | } 836 | { 837 | name = "node_forge___node_forge_0.10.0.tgz"; 838 | path = fetchurl { 839 | name = "node_forge___node_forge_0.10.0.tgz"; 840 | url = "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz"; 841 | sha1 = "32dea2afb3e9926f02ee5ce8794902691a676bf3"; 842 | }; 843 | } 844 | { 845 | name = "node_gyp_build___node_gyp_build_4.3.0.tgz"; 846 | path = fetchurl { 847 | name = "node_gyp_build___node_gyp_build_4.3.0.tgz"; 848 | url = "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz"; 849 | sha1 = "9f256b03e5826150be39c764bf51e993946d71a3"; 850 | }; 851 | } 852 | { 853 | name = "p_limit___p_limit_2.3.0.tgz"; 854 | path = fetchurl { 855 | name = "p_limit___p_limit_2.3.0.tgz"; 856 | url = "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz"; 857 | sha1 = "3dd33c647a214fdfffd835933eb086da0dc21db1"; 858 | }; 859 | } 860 | { 861 | name = "p_locate___p_locate_4.1.0.tgz"; 862 | path = fetchurl { 863 | name = "p_locate___p_locate_4.1.0.tgz"; 864 | url = "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz"; 865 | sha1 = "a3428bb7088b3a60292f66919278b7c297ad4f07"; 866 | }; 867 | } 868 | { 869 | name = "p_try___p_try_2.2.0.tgz"; 870 | path = fetchurl { 871 | name = "p_try___p_try_2.2.0.tgz"; 872 | url = "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz"; 873 | sha1 = "cb2868540e313d61de58fafbe35ce9004d5540e6"; 874 | }; 875 | } 876 | { 877 | name = "path_exists___path_exists_4.0.0.tgz"; 878 | path = fetchurl { 879 | name = "path_exists___path_exists_4.0.0.tgz"; 880 | url = "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz"; 881 | sha1 = "513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"; 882 | }; 883 | } 884 | { 885 | name = "peer_id___peer_id_0.15.3.tgz"; 886 | path = fetchurl { 887 | name = "peer_id___peer_id_0.15.3.tgz"; 888 | url = "https://registry.yarnpkg.com/peer-id/-/peer-id-0.15.3.tgz"; 889 | sha1 = "c093486bcc11399ba63672990382946cfcf0e6f3"; 890 | }; 891 | } 892 | { 893 | name = "pem_jwk___pem_jwk_2.0.0.tgz"; 894 | path = fetchurl { 895 | name = "pem_jwk___pem_jwk_2.0.0.tgz"; 896 | url = "https://registry.yarnpkg.com/pem-jwk/-/pem-jwk-2.0.0.tgz"; 897 | sha1 = "1c5bb264612fc391340907f5c1de60c06d22f085"; 898 | }; 899 | } 900 | { 901 | name = "prettier___prettier_2.4.1.tgz"; 902 | path = fetchurl { 903 | name = "prettier___prettier_2.4.1.tgz"; 904 | url = "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz"; 905 | sha1 = "671e11c89c14a4cfc876ce564106c4a6726c9f5c"; 906 | }; 907 | } 908 | { 909 | name = "protobufjs___protobufjs_6.11.2.tgz"; 910 | path = fetchurl { 911 | name = "protobufjs___protobufjs_6.11.2.tgz"; 912 | url = "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.2.tgz"; 913 | sha1 = "de39fabd4ed32beaa08e9bb1e30d08544c1edf8b"; 914 | }; 915 | } 916 | { 917 | name = "readable_stream___readable_stream_3.6.0.tgz"; 918 | path = fetchurl { 919 | name = "readable_stream___readable_stream_3.6.0.tgz"; 920 | url = "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz"; 921 | sha1 = "337bbda3adc0706bd3e024426a286d4b4b2c9198"; 922 | }; 923 | } 924 | { 925 | name = "regenerator_runtime___regenerator_runtime_0.13.9.tgz"; 926 | path = fetchurl { 927 | name = "regenerator_runtime___regenerator_runtime_0.13.9.tgz"; 928 | url = "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz"; 929 | sha1 = "8925742a98ffd90814988d7566ad30ca3b263b52"; 930 | }; 931 | } 932 | { 933 | name = "require_directory___require_directory_2.1.1.tgz"; 934 | path = fetchurl { 935 | name = "require_directory___require_directory_2.1.1.tgz"; 936 | url = "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz"; 937 | sha1 = "8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"; 938 | }; 939 | } 940 | { 941 | name = "require_main_filename___require_main_filename_2.0.0.tgz"; 942 | path = fetchurl { 943 | name = "require_main_filename___require_main_filename_2.0.0.tgz"; 944 | url = "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz"; 945 | sha1 = "d0b329ecc7cc0f61649f62215be69af54aa8989b"; 946 | }; 947 | } 948 | { 949 | name = "ripemd160___ripemd160_2.0.2.tgz"; 950 | path = fetchurl { 951 | name = "ripemd160___ripemd160_2.0.2.tgz"; 952 | url = "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz"; 953 | sha1 = "a1c1a6f624751577ba5d07914cbc92850585890c"; 954 | }; 955 | } 956 | { 957 | name = "rxjs___rxjs_7.4.0.tgz"; 958 | path = fetchurl { 959 | name = "rxjs___rxjs_7.4.0.tgz"; 960 | url = "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz"; 961 | sha1 = "a12a44d7eebf016f5ff2441b87f28c9a51cebc68"; 962 | }; 963 | } 964 | { 965 | name = "safe_buffer___safe_buffer_5.2.1.tgz"; 966 | path = fetchurl { 967 | name = "safe_buffer___safe_buffer_5.2.1.tgz"; 968 | url = "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz"; 969 | sha1 = "1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"; 970 | }; 971 | } 972 | { 973 | name = "safer_buffer___safer_buffer_2.1.2.tgz"; 974 | path = fetchurl { 975 | name = "safer_buffer___safer_buffer_2.1.2.tgz"; 976 | url = "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz"; 977 | sha1 = "44fa161b0187b9549dd84bb91802f9bd8385cd6a"; 978 | }; 979 | } 980 | { 981 | name = "scryptsy___scryptsy_2.1.0.tgz"; 982 | path = fetchurl { 983 | name = "scryptsy___scryptsy_2.1.0.tgz"; 984 | url = "https://registry.yarnpkg.com/scryptsy/-/scryptsy-2.1.0.tgz"; 985 | sha1 = "8d1e8d0c025b58fdd25b6fa9a0dc905ee8faa790"; 986 | }; 987 | } 988 | { 989 | name = "secp256k1___secp256k1_4.0.2.tgz"; 990 | path = fetchurl { 991 | name = "secp256k1___secp256k1_4.0.2.tgz"; 992 | url = "https://registry.yarnpkg.com/secp256k1/-/secp256k1-4.0.2.tgz"; 993 | sha1 = "15dd57d0f0b9fdb54ac1fa1694f40e5e9a54f4a1"; 994 | }; 995 | } 996 | { 997 | name = "set_blocking___set_blocking_2.0.0.tgz"; 998 | path = fetchurl { 999 | name = "set_blocking___set_blocking_2.0.0.tgz"; 1000 | url = "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz"; 1001 | sha1 = "045f9782d011ae9a6803ddd382b24392b3d890f7"; 1002 | }; 1003 | } 1004 | { 1005 | name = "sha.js___sha.js_2.4.11.tgz"; 1006 | path = fetchurl { 1007 | name = "sha.js___sha.js_2.4.11.tgz"; 1008 | url = "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz"; 1009 | sha1 = "37a5cf0b81ecbc6943de109ba2960d1b26584ae7"; 1010 | }; 1011 | } 1012 | { 1013 | name = "string_width___string_width_4.2.3.tgz"; 1014 | path = fetchurl { 1015 | name = "string_width___string_width_4.2.3.tgz"; 1016 | url = "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz"; 1017 | sha1 = "269c7117d27b05ad2e536830a8ec895ef9c6d010"; 1018 | }; 1019 | } 1020 | { 1021 | name = "string_decoder___string_decoder_1.3.0.tgz"; 1022 | path = fetchurl { 1023 | name = "string_decoder___string_decoder_1.3.0.tgz"; 1024 | url = "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz"; 1025 | sha1 = "42f114594a46cf1a8e30b0a84f56c78c3edac21e"; 1026 | }; 1027 | } 1028 | { 1029 | name = "strip_ansi___strip_ansi_6.0.1.tgz"; 1030 | path = fetchurl { 1031 | name = "strip_ansi___strip_ansi_6.0.1.tgz"; 1032 | url = "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz"; 1033 | sha1 = "9e26c63d30f53443e9489495b2105d37b67a85d9"; 1034 | }; 1035 | } 1036 | { 1037 | name = "tr46___tr46_0.0.3.tgz"; 1038 | path = fetchurl { 1039 | name = "tr46___tr46_0.0.3.tgz"; 1040 | url = "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz"; 1041 | sha1 = "8184fd347dac9cdc185992f3a6622e14b9d9ab6a"; 1042 | }; 1043 | } 1044 | { 1045 | name = "tslib___tslib_2.1.0.tgz"; 1046 | path = fetchurl { 1047 | name = "tslib___tslib_2.1.0.tgz"; 1048 | url = "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz"; 1049 | sha1 = "da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"; 1050 | }; 1051 | } 1052 | { 1053 | name = "tweetnacl___tweetnacl_1.0.3.tgz"; 1054 | path = fetchurl { 1055 | name = "tweetnacl___tweetnacl_1.0.3.tgz"; 1056 | url = "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz"; 1057 | sha1 = "ac0af71680458d8a6378d0d0d050ab1407d35596"; 1058 | }; 1059 | } 1060 | { 1061 | name = "type___type_1.2.0.tgz"; 1062 | path = fetchurl { 1063 | name = "type___type_1.2.0.tgz"; 1064 | url = "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz"; 1065 | sha1 = "848dd7698dafa3e54a6c479e759c4bc3f18847a0"; 1066 | }; 1067 | } 1068 | { 1069 | name = "type___type_2.5.0.tgz"; 1070 | path = fetchurl { 1071 | name = "type___type_2.5.0.tgz"; 1072 | url = "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz"; 1073 | sha1 = "0a2e78c2e77907b252abe5f298c1b01c63f0db3d"; 1074 | }; 1075 | } 1076 | { 1077 | name = "typedarray_to_buffer___typedarray_to_buffer_3.1.5.tgz"; 1078 | path = fetchurl { 1079 | name = "typedarray_to_buffer___typedarray_to_buffer_3.1.5.tgz"; 1080 | url = "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz"; 1081 | sha1 = "a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"; 1082 | }; 1083 | } 1084 | { 1085 | name = "typescript___typescript_4.4.3.tgz"; 1086 | path = fetchurl { 1087 | name = "typescript___typescript_4.4.3.tgz"; 1088 | url = "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz"; 1089 | sha1 = "bdc5407caa2b109efd4f82fe130656f977a29324"; 1090 | }; 1091 | } 1092 | { 1093 | name = "uint8arrays___uint8arrays_3.0.0.tgz"; 1094 | path = fetchurl { 1095 | name = "uint8arrays___uint8arrays_3.0.0.tgz"; 1096 | url = "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.0.0.tgz"; 1097 | sha1 = "260869efb8422418b6f04e3fac73a3908175c63b"; 1098 | }; 1099 | } 1100 | { 1101 | name = "ursa_optional___ursa_optional_0.10.2.tgz"; 1102 | path = fetchurl { 1103 | name = "ursa_optional___ursa_optional_0.10.2.tgz"; 1104 | url = "https://registry.yarnpkg.com/ursa-optional/-/ursa-optional-0.10.2.tgz"; 1105 | sha1 = "bd74e7d60289c22ac2a69a3c8dea5eb2817f9681"; 1106 | }; 1107 | } 1108 | { 1109 | name = "utf_8_validate___utf_8_validate_5.0.6.tgz"; 1110 | path = fetchurl { 1111 | name = "utf_8_validate___utf_8_validate_5.0.6.tgz"; 1112 | url = "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.6.tgz"; 1113 | sha1 = "e1b3e0a5cc8648a3b44c1799fbb170d1aaaffe80"; 1114 | }; 1115 | } 1116 | { 1117 | name = "util_deprecate___util_deprecate_1.0.2.tgz"; 1118 | path = fetchurl { 1119 | name = "util_deprecate___util_deprecate_1.0.2.tgz"; 1120 | url = "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz"; 1121 | sha1 = "450d4dc9fa70de732762fbd2d4a28981419a0ccf"; 1122 | }; 1123 | } 1124 | { 1125 | name = "webidl_conversions___webidl_conversions_3.0.1.tgz"; 1126 | path = fetchurl { 1127 | name = "webidl_conversions___webidl_conversions_3.0.1.tgz"; 1128 | url = "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz"; 1129 | sha1 = "24534275e2a7bc6be7bc86611cc16ae0a5654871"; 1130 | }; 1131 | } 1132 | { 1133 | name = "websocket___websocket_1.0.34.tgz"; 1134 | path = fetchurl { 1135 | name = "websocket___websocket_1.0.34.tgz"; 1136 | url = "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz"; 1137 | sha1 = "2bdc2602c08bf2c82253b730655c0ef7dcab3111"; 1138 | }; 1139 | } 1140 | { 1141 | name = "whatwg_url___whatwg_url_5.0.0.tgz"; 1142 | path = fetchurl { 1143 | name = "whatwg_url___whatwg_url_5.0.0.tgz"; 1144 | url = "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz"; 1145 | sha1 = "966454e8765462e37644d3626f6742ce8b70965d"; 1146 | }; 1147 | } 1148 | { 1149 | name = "which_module___which_module_2.0.0.tgz"; 1150 | path = fetchurl { 1151 | name = "which_module___which_module_2.0.0.tgz"; 1152 | url = "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz"; 1153 | sha1 = "d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"; 1154 | }; 1155 | } 1156 | { 1157 | name = "wrap_ansi___wrap_ansi_6.2.0.tgz"; 1158 | path = fetchurl { 1159 | name = "wrap_ansi___wrap_ansi_6.2.0.tgz"; 1160 | url = "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz"; 1161 | sha1 = "e9393ba07102e6c91a3b221478f0257cd2856e53"; 1162 | }; 1163 | } 1164 | { 1165 | name = "xxhashjs___xxhashjs_0.2.2.tgz"; 1166 | path = fetchurl { 1167 | name = "xxhashjs___xxhashjs_0.2.2.tgz"; 1168 | url = "https://registry.yarnpkg.com/xxhashjs/-/xxhashjs-0.2.2.tgz"; 1169 | sha1 = "8a6251567621a1c46a5ae204da0249c7f8caa9d8"; 1170 | }; 1171 | } 1172 | { 1173 | name = "y18n___y18n_4.0.3.tgz"; 1174 | path = fetchurl { 1175 | name = "y18n___y18n_4.0.3.tgz"; 1176 | url = "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz"; 1177 | sha1 = "b5f259c82cd6e336921efd7bfd8bf560de9eeedf"; 1178 | }; 1179 | } 1180 | { 1181 | name = "yaeti___yaeti_0.0.6.tgz"; 1182 | path = fetchurl { 1183 | name = "yaeti___yaeti_0.0.6.tgz"; 1184 | url = "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz"; 1185 | sha1 = "f26f484d72684cf42bedfb76970aa1608fbf9577"; 1186 | }; 1187 | } 1188 | { 1189 | name = "yargs_parser___yargs_parser_18.1.3.tgz"; 1190 | path = fetchurl { 1191 | name = "yargs_parser___yargs_parser_18.1.3.tgz"; 1192 | url = "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz"; 1193 | sha1 = "be68c4975c6b2abf469236b0c870362fab09a7b0"; 1194 | }; 1195 | } 1196 | { 1197 | name = "yargs___yargs_15.4.1.tgz"; 1198 | path = fetchurl { 1199 | name = "yargs___yargs_15.4.1.tgz"; 1200 | url = "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz"; 1201 | sha1 = "0d87a16de01aee9d8bec2bfbf74f67851730f4f8"; 1202 | }; 1203 | } 1204 | ]; 1205 | } 1206 | --------------------------------------------------------------------------------