فهرست منبع

Merge branch 'iznik' into joystream-types-upgrade

Leszek Wiesner 4 سال پیش
والد
کامیت
ed11564ef3
90فایلهای تغییر یافته به همراه8002 افزوده شده و 5129 حذف شده
  1. 12 2
      .github/workflows/joystream-cli.yml
  2. 390 17
      cli/README.md
  3. 0 4671
      cli/package-lock.json
  4. 19 9
      cli/package.json
  5. 37 4
      cli/src/Api.ts
  6. 30 0
      cli/src/Types.ts
  7. 11 3
      cli/src/base/AccountsCommandBase.ts
  8. 60 5
      cli/src/base/ApiCommandBase.ts
  9. 5 4
      cli/src/base/StateAwareCommandBase.ts
  10. 3 0
      cli/src/base/WorkingGroupsCommandBase.ts
  11. 20 11
      cli/src/commands/api/setUri.ts
  12. 9 1
      cli/src/commands/working-groups/opening.ts
  13. 0 1
      cli/src/commands/working-groups/openings.ts
  14. 10 1
      cli/src/commands/working-groups/overview.ts
  15. 13 0
      cli/src/promptOptions/addWorkerOpening.ts
  16. 11 6
      devops/ansible/build-and-run-tests-single-node-playbook.yml
  17. 17 0
      devops/ansible/build-and-run-tests-two-nodes-playbook.yml
  18. 13 0
      devops/ansible/build-image-playbook.yml
  19. 0 15
      devops/ansible/install-dependencies-playbook.yml
  20. 0 0
      devops/ansible/roles/build_docker_image/tasks/main.yml
  21. 35 0
      devops/ansible/roles/install_dependencies/tasks/main.yml
  22. 22 0
      devops/ansible/roles/run_tests_single_node/tasks/main.yml
  23. 18 0
      devops/ansible/roles/run_tests_two_nodes/tasks/main.yml
  24. 0 24
      devops/ansible/run-tests-single-node-playbook.yml
  25. 0 24
      devops/ansible/run-tests-two-nodes-playbook.yml
  26. 1 1
      pioneer/packages/apps/public/locales/en/ui.json
  27. 12 1
      pioneer/packages/joy-proposals/src/Proposal/ProposalPreview.tsx
  28. 50 94
      pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx
  29. 3 0
      pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx
  30. 1 1
      pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx
  31. 91 41
      pioneer/packages/joy-roles/src/elements.tsx
  32. 1 1
      pioneer/packages/joy-roles/src/flows/apply.tsx
  33. 1 1
      pioneer/packages/joy-roles/src/index.tsx
  34. 1 1
      pioneer/packages/joy-roles/src/tabs.stories.tsx
  35. 2 0
      pioneer/packages/joy-roles/src/tabs/MyRoles.tsx
  36. 4 1
      pioneer/packages/joy-roles/src/tabs/Opportunities.tsx
  37. 11 5
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.stories.tsx
  38. 1 1
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx
  39. 12 6
      pioneer/packages/joy-roles/src/transport.mock.ts
  40. 33 13
      pioneer/packages/joy-roles/src/transport.substrate.ts
  41. 1 0
      pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx
  42. 41 8
      pioneer/packages/joy-utils/src/transport/proposals.ts
  43. 10 0
      pioneer/packages/joy-utils/src/types/proposals.ts
  44. 26 10
      runtime/CHANGELOG.md
  45. 0 0
      scripts/build-joystream-testing-node-docker-image.sh
  46. 1 1
      storage-node/package.json
  47. 1 1
      storage-node/packages/cli/package.json
  48. 15 16
      storage-node/packages/colossus/bin/cli.js
  49. 18 0
      storage-node/packages/colossus/lib/sync.js
  50. 1 1
      storage-node/packages/colossus/package.json
  51. 7 0
      storage-node/packages/colossus/paths/asset/v0/{id}.js
  52. 1 1
      storage-node/packages/discovery/package.json
  53. 1 1
      storage-node/packages/helios/package.json
  54. 158 115
      storage-node/packages/runtime-api/index.js
  55. 2 1
      storage-node/packages/runtime-api/package.json
  56. 1 1
      storage-node/packages/storage/package.json
  57. 1 1
      storage-node/packages/util/package.json
  58. 9 0
      storage-node/packages/util/sleep.js
  59. 3 2
      tests/network-tests/package.json
  60. 0 0
      tests/network-tests/src/iznik/tap-parallel-not-ok
  61. 65 0
      tests/network-tests/src/iznik/tests/council/electingCouncilTest.ts
  62. 140 0
      tests/network-tests/src/iznik/tests/fixtures/councilElectionModule.ts
  63. 3 0
      tests/network-tests/src/iznik/tests/fixtures/interfaces/fixture.ts
  64. 106 0
      tests/network-tests/src/iznik/tests/fixtures/membershipModule.ts
  65. 1037 0
      tests/network-tests/src/iznik/tests/fixtures/proposalsModule.ts
  66. 1195 0
      tests/network-tests/src/iznik/tests/fixtures/workingGroupModule.ts
  67. 56 0
      tests/network-tests/src/iznik/tests/membership/membershipCreationTest.ts
  68. 79 0
      tests/network-tests/src/iznik/tests/proposals/contentWorkingGroupMintCapacityProposalTest.ts
  69. 75 0
      tests/network-tests/src/iznik/tests/proposals/electionParametersProposalTest.ts
  70. 304 0
      tests/network-tests/src/iznik/tests/proposals/manageLeaderRoleTest.ts
  71. 75 0
      tests/network-tests/src/iznik/tests/proposals/setLeadProposalTest.ts
  72. 78 0
      tests/network-tests/src/iznik/tests/proposals/spendingProposalTest.ts
  73. 69 0
      tests/network-tests/src/iznik/tests/proposals/textProposalTest.ts
  74. 77 0
      tests/network-tests/src/iznik/tests/proposals/updateRuntime.ts
  75. 76 0
      tests/network-tests/src/iznik/tests/proposals/validatorCountProposalTest.ts
  76. 96 0
      tests/network-tests/src/iznik/tests/proposals/workingGroupMintCapacityProposalTest.ts
  77. 154 0
      tests/network-tests/src/iznik/tests/workingGroup/atLeastValueBugTest.ts
  78. 280 0
      tests/network-tests/src/iznik/tests/workingGroup/manageWorkerAsLeadTest.ts
  79. 209 0
      tests/network-tests/src/iznik/tests/workingGroup/manageWorkerAsWorkerTest.ts
  80. 204 0
      tests/network-tests/src/iznik/tests/workingGroup/workerApplicationHappyCaseTest.ts
  81. 204 0
      tests/network-tests/src/iznik/tests/workingGroup/workerApplicationRejectionCaseTest.ts
  82. 243 0
      tests/network-tests/src/iznik/tests/workingGroup/workerPayoutTest.ts
  83. 1551 0
      tests/network-tests/src/iznik/utils/apiWrapper.ts
  84. 8 0
      tests/network-tests/src/iznik/utils/closeApi.ts
  85. 5 0
      tests/network-tests/src/iznik/utils/config.ts
  86. 66 0
      tests/network-tests/src/iznik/utils/sender.ts
  87. 7 0
      tests/network-tests/src/iznik/utils/setTestTimeout.ts
  88. 63 0
      tests/network-tests/src/iznik/utils/utils.ts
  89. 1 1
      types/package.json
  90. 220 4
      yarn.lock

+ 12 - 2
.github/workflows/joystream-cli.yml

@@ -17,7 +17,12 @@ jobs:
     - name: checks
       run: |
         yarn install --frozen-lockfile
-        yarn workspace joystream-cli checks
+        yarn workspace @joystream/cli checks
+    - name: yarn pack test
+      run: |
+        yarn workspace @joystream/cli pack --filename cli-pack-test.tgz
+        tar zxvf ./cli/cli-pack-test.tgz -C cli
+        cd ./cli/package && yarn link
 
   cli_build_osx:
     name: MacOS Checks
@@ -34,4 +39,9 @@ jobs:
     - name: checks
       run: |
         yarn install --frozen-lockfile --network-timeout 120000
-        yarn workspace joystream-cli checks
+        yarn workspace @joystream/cli checks
+    - name: yarn pack test
+      run: |
+        yarn workspace @joystream/cli pack --filename cli-pack-test.tgz
+        tar zxvf ./cli/cli-pack-test.tgz -C cli
+        cd ./cli/package && yarn link

+ 390 - 17
cli/README.md

@@ -1,16 +1,17 @@
-joystream-cli
+@joystream/cli
 =============
 
 Command Line Interface for Joystream community and governance activities
 
 [![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
-[![Version](https://img.shields.io/npm/v/joystream-cli.svg)](https://npmjs.org/package/joystream-cli)
-[![Downloads/week](https://img.shields.io/npm/dw/joystream-cli.svg)](https://npmjs.org/package/joystream-cli)
-[![License](https://img.shields.io/npm/l/joystream-cli.svg)](https://github.com/Joystream/cli/blob/master/package.json)
+[![Version](https://img.shields.io/npm/v/@joystream/cli.svg)](https://npmjs.org/package/@joystream/cli)
+[![Downloads/week](https://img.shields.io/npm/dw/@joystream/cli.svg)](https://npmjs.org/package/@joystream/cli)
+[![License](https://img.shields.io/npm/l/@joystream/cli.svg)](https://github.com/Joystream/joystream/blob/master/cli/package.json)
 
 <!-- toc -->
 * [Development](#development)
 * [Usage](#usage)
+* [First steps](#first-steps)
 * [Commands](#commands)
 <!-- tocstop -->
 
@@ -19,19 +20,17 @@ Command Line Interface for Joystream community and governance activities
 To run a command in developemnt environment (without installing the package):
 
 1. Navigate into the CLI root directory
-1. Either execute any command like this:
+1. Execute any command like this:
 
     ```
         $ ./bin/run COMMAND
     ```
 
-    Or use:
+Alternatively:
 
-    ```
-        $ npm link
-    ```
-
-    And then execute any command like this:
+1. Navigate into the CLI root directory
+1. Execute `yarn link` (if that doesn't work, consider `sudo yarn link`)
+1. Execute command from any location like this:
 
     ```
         $ joystream-cli COMMAND
@@ -41,17 +40,29 @@ To run a command in developemnt environment (without installing the package):
 # Usage
 <!-- usage -->
 ```sh-session
-$ npm install -g joystream-cli
+$ npm install -g @joystream/cli
 $ joystream-cli COMMAND
 running command...
 $ joystream-cli (-v|--version|version)
-joystream-cli/0.0.0 linux-x64 node-v13.12.0
+@joystream/cli/0.1.0 linux-x64 node-v13.12.0
 $ joystream-cli --help [COMMAND]
 USAGE
   $ joystream-cli COMMAND
 ...
 ```
 <!-- usagestop -->
+
+# First steps
+<!-- first-steps -->
+When using the CLI for the first time there are a few common steps you might want to take in order to configure the CLI:
+
+1. Set the correct node endpoint. You can do this by executing `api:setUri` or any command that requires an api connection. To verify the current endpoint you can execute `api:getUri`.
+1. In order to use the accounts/keys that you may already have access to within Pioneer, you need to dowload the backup json file(s) ([https://testnet.joystream.org/#/accounts](https://testnet.joystream.org/#/accounts)) and import them into the CLI by executing `account:import /path/to/backup.json`.
+1. By executing `account:choose` you can choose one of the imported accounts, that will then serve as context for the next commands (you can check currently selected account using `account:info`). If you just want to use the development _Alice_ or _Bob_ account, you can access them without importing by providing an additional flag: `account:choose --showSpecial`.
+1. The context should now be fully set up! Feel free to use the `--help` flag to investigate the available commands or take a look at the sections below.
+1. You may also find it useful to get the first part of the command (before the colon) autocompleted when you press `[Tab]` while typing the name in the console. Executing `autocomplete` command will provide the instructions on how to set this up (see documentation below).
+<!-- first-steps -->
+
 # Commands
 <!-- commands -->
 * [`joystream-cli account:choose`](#joystream-cli-accountchoose)
@@ -63,9 +74,27 @@ USAGE
 * [`joystream-cli account:transferTokens RECIPIENT AMOUNT`](#joystream-cli-accounttransfertokens-recipient-amount)
 * [`joystream-cli api:getUri`](#joystream-cli-apigeturi)
 * [`joystream-cli api:inspect`](#joystream-cli-apiinspect)
-* [`joystream-cli api:setUri URI`](#joystream-cli-apiseturi-uri)
+* [`joystream-cli api:setUri [URI]`](#joystream-cli-apiseturi-uri)
+* [`joystream-cli autocomplete [SHELL]`](#joystream-cli-autocomplete-shell)
 * [`joystream-cli council:info`](#joystream-cli-councilinfo)
 * [`joystream-cli help [COMMAND]`](#joystream-cli-help-command)
+* [`joystream-cli working-groups:application WGAPPLICATIONID`](#joystream-cli-working-groupsapplication-wgapplicationid)
+* [`joystream-cli working-groups:createOpening`](#joystream-cli-working-groupscreateopening)
+* [`joystream-cli working-groups:decreaseWorkerStake WORKERID`](#joystream-cli-working-groupsdecreaseworkerstake-workerid)
+* [`joystream-cli working-groups:evictWorker WORKERID`](#joystream-cli-working-groupsevictworker-workerid)
+* [`joystream-cli working-groups:fillOpening WGOPENINGID`](#joystream-cli-working-groupsfillopening-wgopeningid)
+* [`joystream-cli working-groups:increaseStake`](#joystream-cli-working-groupsincreasestake)
+* [`joystream-cli working-groups:leaveRole`](#joystream-cli-working-groupsleaverole)
+* [`joystream-cli working-groups:opening WGOPENINGID`](#joystream-cli-working-groupsopening-wgopeningid)
+* [`joystream-cli working-groups:openings`](#joystream-cli-working-groupsopenings)
+* [`joystream-cli working-groups:overview`](#joystream-cli-working-groupsoverview)
+* [`joystream-cli working-groups:slashWorker WORKERID`](#joystream-cli-working-groupsslashworker-workerid)
+* [`joystream-cli working-groups:startAcceptingApplications WGOPENINGID`](#joystream-cli-working-groupsstartacceptingapplications-wgopeningid)
+* [`joystream-cli working-groups:startReviewPeriod WGOPENINGID`](#joystream-cli-working-groupsstartreviewperiod-wgopeningid)
+* [`joystream-cli working-groups:terminateApplication WGAPPLICATIONID`](#joystream-cli-working-groupsterminateapplication-wgapplicationid)
+* [`joystream-cli working-groups:updateRewardAccount [ACCOUNTADDRESS]`](#joystream-cli-working-groupsupdaterewardaccount-accountaddress)
+* [`joystream-cli working-groups:updateRoleAccount [ACCOUNTADDRESS]`](#joystream-cli-working-groupsupdateroleaccount-accountaddress)
+* [`joystream-cli working-groups:updateWorkerReward WORKERID`](#joystream-cli-working-groupsupdateworkerreward-workerid)
 
 ## `joystream-cli account:choose`
 
@@ -74,6 +103,9 @@ Choose default account to use in the CLI
 ```
 USAGE
   $ joystream-cli account:choose
+
+OPTIONS
+  --showSpecial  Whether to show special (DEV chain) accounts
 ```
 
 _See code: [src/commands/account/choose.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/choose.ts)_
@@ -219,20 +251,43 @@ EXAMPLES
 
 _See code: [src/commands/api/inspect.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/api/inspect.ts)_
 
-## `joystream-cli api:setUri URI`
+## `joystream-cli api:setUri [URI]`
 
 Set api WS provider uri
 
 ```
 USAGE
-  $ joystream-cli api:setUri URI
+  $ joystream-cli api:setUri [URI]
 
 ARGUMENTS
-  URI  Uri of the node api WS provider
+  URI  Uri of the node api WS provider (if skipped, a prompt will be displayed)
 ```
 
 _See code: [src/commands/api/setUri.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/api/setUri.ts)_
 
+## `joystream-cli autocomplete [SHELL]`
+
+display autocomplete installation instructions
+
+```
+USAGE
+  $ joystream-cli autocomplete [SHELL]
+
+ARGUMENTS
+  SHELL  shell type
+
+OPTIONS
+  -r, --refresh-cache  Refresh cache (ignores displaying instructions)
+
+EXAMPLES
+  $ joystream-cli autocomplete
+  $ joystream-cli autocomplete bash
+  $ joystream-cli autocomplete zsh
+  $ joystream-cli autocomplete --refresh-cache
+```
+
+_See code: [@oclif/plugin-autocomplete](https://github.com/oclif/plugin-autocomplete/blob/v0.2.0/src/commands/autocomplete/index.ts)_
+
 ## `joystream-cli council:info`
 
 Get current council and council elections information
@@ -260,4 +315,322 @@ OPTIONS
 ```
 
 _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v2.2.3/src/commands/help.ts)_
+
+## `joystream-cli working-groups:application WGAPPLICATIONID`
+
+Shows an overview of given application by Working Group Application ID
+
+```
+USAGE
+  $ joystream-cli working-groups:application WGAPPLICATIONID
+
+ARGUMENTS
+  WGAPPLICATIONID  Working Group Application ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/application.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/application.ts)_
+
+## `joystream-cli working-groups:createOpening`
+
+Create working group opening (requires lead access)
+
+```
+USAGE
+  $ joystream-cli working-groups:createOpening
+
+OPTIONS
+  -c, --createDraftOnly      If provided - the extrinsic will not be executed. Use this flag if you only want to create
+                             a draft.
+
+  -d, --useDraft             Whether to create the opening from existing draft.
+                             If provided without --draftName - the list of choices will be displayed.
+
+  -g, --group=group          (required) [default: storageProviders] The working group context in which the command
+                             should be executed
+                             Available values are: storageProviders.
+
+  -n, --draftName=draftName  Name of the draft to create the opening from.
+
+  -s, --skipPrompts          Whether to skip all prompts when adding from draft (will use all default values)
+```
+
+_See code: [src/commands/working-groups/createOpening.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/createOpening.ts)_
+
+## `joystream-cli working-groups:decreaseWorkerStake WORKERID`
+
+Decreases given worker stake by an amount that will be returned to the worker role account. Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:decreaseWorkerStake WORKERID
+
+ARGUMENTS
+  WORKERID  Worker ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/decreaseWorkerStake.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/decreaseWorkerStake.ts)_
+
+## `joystream-cli working-groups:evictWorker WORKERID`
+
+Evicts given worker. Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:evictWorker WORKERID
+
+ARGUMENTS
+  WORKERID  Worker ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/evictWorker.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/evictWorker.ts)_
+
+## `joystream-cli working-groups:fillOpening WGOPENINGID`
+
+Allows filling working group opening that's currently in review. Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:fillOpening WGOPENINGID
+
+ARGUMENTS
+  WGOPENINGID  Working Group Opening ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/fillOpening.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/fillOpening.ts)_
+
+## `joystream-cli working-groups:increaseStake`
+
+Increases current role (lead/worker) stake. Requires active role account to be selected.
+
+```
+USAGE
+  $ joystream-cli working-groups:increaseStake
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/increaseStake.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/increaseStake.ts)_
+
+## `joystream-cli working-groups:leaveRole`
+
+Leave the worker or lead role associated with currently selected account.
+
+```
+USAGE
+  $ joystream-cli working-groups:leaveRole
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/leaveRole.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/leaveRole.ts)_
+
+## `joystream-cli working-groups:opening WGOPENINGID`
+
+Shows an overview of given working group opening by Working Group Opening ID
+
+```
+USAGE
+  $ joystream-cli working-groups:opening WGOPENINGID
+
+ARGUMENTS
+  WGOPENINGID  Working Group Opening ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/opening.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/opening.ts)_
+
+## `joystream-cli working-groups:openings`
+
+Shows an overview of given working group openings
+
+```
+USAGE
+  $ joystream-cli working-groups:openings
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/openings.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/openings.ts)_
+
+## `joystream-cli working-groups:overview`
+
+Shows an overview of given working group (current lead and workers)
+
+```
+USAGE
+  $ joystream-cli working-groups:overview
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/overview.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/overview.ts)_
+
+## `joystream-cli working-groups:slashWorker WORKERID`
+
+Slashes given worker stake. Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:slashWorker WORKERID
+
+ARGUMENTS
+  WORKERID  Worker ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/slashWorker.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/slashWorker.ts)_
+
+## `joystream-cli working-groups:startAcceptingApplications WGOPENINGID`
+
+Changes the status of pending opening to "Accepting applications". Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:startAcceptingApplications WGOPENINGID
+
+ARGUMENTS
+  WGOPENINGID  Working Group Opening ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/startAcceptingApplications.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/startAcceptingApplications.ts)_
+
+## `joystream-cli working-groups:startReviewPeriod WGOPENINGID`
+
+Changes the status of active opening to "In review". Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:startReviewPeriod WGOPENINGID
+
+ARGUMENTS
+  WGOPENINGID  Working Group Opening ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/startReviewPeriod.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/startReviewPeriod.ts)_
+
+## `joystream-cli working-groups:terminateApplication WGAPPLICATIONID`
+
+Terminates given working group application. Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:terminateApplication WGAPPLICATIONID
+
+ARGUMENTS
+  WGAPPLICATIONID  Working Group Application ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/terminateApplication.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/terminateApplication.ts)_
+
+## `joystream-cli working-groups:updateRewardAccount [ACCOUNTADDRESS]`
+
+Updates the worker/lead reward account (requires current role account to be selected)
+
+```
+USAGE
+  $ joystream-cli working-groups:updateRewardAccount [ACCOUNTADDRESS]
+
+ARGUMENTS
+  ACCOUNTADDRESS  New reward account address (if omitted, one of the existing CLI accounts can be selected)
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/updateRewardAccount.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/updateRewardAccount.ts)_
+
+## `joystream-cli working-groups:updateRoleAccount [ACCOUNTADDRESS]`
+
+Updates the worker/lead role account. Requires member controller account to be selected
+
+```
+USAGE
+  $ joystream-cli working-groups:updateRoleAccount [ACCOUNTADDRESS]
+
+ARGUMENTS
+  ACCOUNTADDRESS  New role account address (if omitted, one of the existing CLI accounts can be selected)
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/updateRoleAccount.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/updateRoleAccount.ts)_
+
+## `joystream-cli working-groups:updateWorkerReward WORKERID`
+
+Change given worker's reward (amount only). Requires lead access.
+
+```
+USAGE
+  $ joystream-cli working-groups:updateWorkerReward WORKERID
+
+ARGUMENTS
+  WORKERID  Worker ID
+
+OPTIONS
+  -g, --group=group  (required) [default: storageProviders] The working group context in which the command should be
+                     executed
+                     Available values are: storageProviders.
+```
+
+_See code: [src/commands/working-groups/updateWorkerReward.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/working-groups/updateWorkerReward.ts)_
 <!-- commandsstop -->

+ 0 - 4671
cli/package-lock.json

@@ -1,4671 +0,0 @@
-{
-  "name": "joystream-cli",
-  "version": "0.0.0",
-  "lockfileVersion": 1,
-  "requires": true,
-  "dependencies": {
-    "@babel/code-frame": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
-      "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
-      "dev": true,
-      "requires": {
-        "@babel/highlight": "^7.8.3"
-      }
-    },
-    "@babel/generator": {
-      "version": "7.9.4",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.4.tgz",
-      "integrity": "sha512-rjP8ahaDy/ouhrvCoU1E5mqaitWrxwuNGU+dy1EpaoK48jZay4MdkskKGIMHLZNewg8sAsqpGSREJwP0zH3YQA==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.9.0",
-        "jsesc": "^2.5.1",
-        "lodash": "^4.17.13",
-        "source-map": "^0.5.0"
-      }
-    },
-    "@babel/helper-function-name": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz",
-      "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-get-function-arity": "^7.8.3",
-        "@babel/template": "^7.8.3",
-        "@babel/types": "^7.8.3"
-      }
-    },
-    "@babel/helper-get-function-arity": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz",
-      "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.8.3"
-      }
-    },
-    "@babel/helper-split-export-declaration": {
-      "version": "7.8.3",
-      "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz",
-      "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==",
-      "dev": true,
-      "requires": {
-        "@babel/types": "^7.8.3"
-      }
-    },
-    "@babel/helper-validator-identifier": {
-      "version": "7.9.0",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz",
-      "integrity": "sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw==",
-      "dev": true
-    },
-    "@babel/highlight": {
-      "version": "7.9.0",
-      "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
-      "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-validator-identifier": "^7.9.0",
-        "chalk": "^2.0.0",
-        "js-tokens": "^4.0.0"
-      }
-    },
-    "@babel/parser": {
-      "version": "7.9.4",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.4.tgz",
-      "integrity": "sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==",
-      "dev": true
-    },
-    "@babel/runtime": {
-      "version": "7.9.2",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz",
-      "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==",
-      "requires": {
-        "regenerator-runtime": "^0.13.4"
-      }
-    },
-    "@babel/template": {
-      "version": "7.8.6",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz",
-      "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==",
-      "dev": true,
-      "requires": {
-        "@babel/code-frame": "^7.8.3",
-        "@babel/parser": "^7.8.6",
-        "@babel/types": "^7.8.6"
-      }
-    },
-    "@babel/traverse": {
-      "version": "7.9.0",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.0.tgz",
-      "integrity": "sha512-jAZQj0+kn4WTHO5dUZkZKhbFrqZE7K5LAQ5JysMnmvGij+wOdr+8lWqPeW0BcF4wFwrEXXtdGO7wcV6YPJcf3w==",
-      "dev": true,
-      "requires": {
-        "@babel/code-frame": "^7.8.3",
-        "@babel/generator": "^7.9.0",
-        "@babel/helper-function-name": "^7.8.3",
-        "@babel/helper-split-export-declaration": "^7.8.3",
-        "@babel/parser": "^7.9.0",
-        "@babel/types": "^7.9.0",
-        "debug": "^4.1.0",
-        "globals": "^11.1.0",
-        "lodash": "^4.17.13"
-      }
-    },
-    "@babel/types": {
-      "version": "7.9.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.0.tgz",
-      "integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==",
-      "dev": true,
-      "requires": {
-        "@babel/helper-validator-identifier": "^7.9.0",
-        "lodash": "^4.17.13",
-        "to-fast-properties": "^2.0.0"
-      }
-    },
-    "@joystream/types": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/@joystream/types/-/types-0.6.0.tgz",
-      "integrity": "sha512-b+6U36GHJLlBPxVqMVQRTZzVxu7BGsjqlC/XJfl/vdx8TOy3P8TIB/3olLU64EPB3cVNadg2p9jqYSsvh9XVAQ==",
-      "requires": {
-        "@polkadot/types": "^0.96.1",
-        "@types/vfile": "^4.0.0",
-        "ajv": "^6.11.0"
-      }
-    },
-    "@nodelib/fs.scandir": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
-      "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "2.0.3",
-        "run-parallel": "^1.1.9"
-      }
-    },
-    "@nodelib/fs.stat": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
-      "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
-      "dev": true
-    },
-    "@nodelib/fs.walk": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
-      "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.scandir": "2.1.3",
-        "fastq": "^1.6.0"
-      }
-    },
-    "@oclif/command": {
-      "version": "1.5.19",
-      "resolved": "https://registry.npmjs.org/@oclif/command/-/command-1.5.19.tgz",
-      "integrity": "sha512-6+iaCMh/JXJaB2QWikqvGE9//wLEVYYwZd5sud8aLoLKog1Q75naZh2vlGVtg5Mq/NqpqGQvdIjJb3Bm+64AUQ==",
-      "requires": {
-        "@oclif/config": "^1",
-        "@oclif/errors": "^1.2.2",
-        "@oclif/parser": "^3.8.3",
-        "@oclif/plugin-help": "^2",
-        "debug": "^4.1.1",
-        "semver": "^5.6.0"
-      }
-    },
-    "@oclif/config": {
-      "version": "1.14.0",
-      "resolved": "https://registry.npmjs.org/@oclif/config/-/config-1.14.0.tgz",
-      "integrity": "sha512-KsOP/mx9lzTah+EtGqLUXN3PDL0J3zb9/dTneFyiUK2K6T7vFEGhV6OasmqTh4uMZHGYTGrNPV8x/Yw6qZNL6A==",
-      "requires": {
-        "@oclif/errors": "^1.0.0",
-        "@oclif/parser": "^3.8.0",
-        "debug": "^4.1.1",
-        "tslib": "^1.9.3"
-      }
-    },
-    "@oclif/dev-cli": {
-      "version": "1.22.2",
-      "resolved": "https://registry.npmjs.org/@oclif/dev-cli/-/dev-cli-1.22.2.tgz",
-      "integrity": "sha512-c7633R37RxrQIpwqPKxjNRm6/jb1yuG8fd16hmNz9Nw+/MUhEtQtKHSCe9ScH8n5M06l6LEo4ldk9LEGtpaWwA==",
-      "dev": true,
-      "requires": {
-        "@oclif/command": "^1.5.13",
-        "@oclif/config": "^1.12.12",
-        "@oclif/errors": "^1.2.2",
-        "@oclif/plugin-help": "^2.1.6",
-        "cli-ux": "^5.2.1",
-        "debug": "^4.1.1",
-        "fs-extra": "^7.0.1",
-        "github-slugger": "^1.2.1",
-        "lodash": "^4.17.11",
-        "normalize-package-data": "^2.5.0",
-        "qqjs": "^0.3.10",
-        "tslib": "^1.9.3"
-      }
-    },
-    "@oclif/errors": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/@oclif/errors/-/errors-1.2.2.tgz",
-      "integrity": "sha512-Eq8BFuJUQcbAPVofDxwdE0bL14inIiwt5EaKRVY9ZDIG11jwdXZqiQEECJx0VfnLyUZdYfRd/znDI/MytdJoKg==",
-      "requires": {
-        "clean-stack": "^1.3.0",
-        "fs-extra": "^7.0.0",
-        "indent-string": "^3.2.0",
-        "strip-ansi": "^5.0.0",
-        "wrap-ansi": "^4.0.0"
-      }
-    },
-    "@oclif/linewrap": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@oclif/linewrap/-/linewrap-1.0.0.tgz",
-      "integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw=="
-    },
-    "@oclif/parser": {
-      "version": "3.8.4",
-      "resolved": "https://registry.npmjs.org/@oclif/parser/-/parser-3.8.4.tgz",
-      "integrity": "sha512-cyP1at3l42kQHZtqDS3KfTeyMvxITGwXwH1qk9ktBYvqgMp5h4vHT+cOD74ld3RqJUOZY/+Zi9lb4Tbza3BtuA==",
-      "requires": {
-        "@oclif/linewrap": "^1.0.0",
-        "chalk": "^2.4.2",
-        "tslib": "^1.9.3"
-      }
-    },
-    "@oclif/plugin-help": {
-      "version": "2.2.3",
-      "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-2.2.3.tgz",
-      "integrity": "sha512-bGHUdo5e7DjPJ0vTeRBMIrfqTRDBfyR5w0MP41u0n3r7YG5p14lvMmiCXxi6WDaP2Hw5nqx3PnkAIntCKZZN7g==",
-      "requires": {
-        "@oclif/command": "^1.5.13",
-        "chalk": "^2.4.1",
-        "indent-string": "^4.0.0",
-        "lodash.template": "^4.4.0",
-        "string-width": "^3.0.0",
-        "strip-ansi": "^5.0.0",
-        "widest-line": "^2.0.1",
-        "wrap-ansi": "^4.0.0"
-      },
-      "dependencies": {
-        "indent-string": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
-          "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
-        },
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        }
-      }
-    },
-    "@oclif/screen": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-1.0.4.tgz",
-      "integrity": "sha512-60CHpq+eqnTxLZQ4PGHYNwUX572hgpMHGPtTWMjdTMsAvlm69lZV/4ly6O3sAYkomo4NggGcomrDpBe34rxUqw=="
-    },
-    "@oclif/test": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/@oclif/test/-/test-1.2.5.tgz",
-      "integrity": "sha512-8Y+Ix4A3Zhm87aL0ldVonDK7vFWyLfnFHzP3goYaLyIeh/60KL37lMxfmbp/kBN6/Y0Ru17iR1pdDi/hTDClLQ==",
-      "dev": true,
-      "requires": {
-        "fancy-test": "^1.4.3"
-      }
-    },
-    "@polkadot/api": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-0.96.1.tgz",
-      "integrity": "sha512-FeYyMfJL0NACJBIuG7C7mp7f9J/WOGUERF/hUP3RlIz4Ld2X0vRjEoOgiG0VIS89I4K31XaNmSjIchH244WtHg==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/api-derive": "^0.96.1",
-        "@polkadot/api-metadata": "^0.96.1",
-        "@polkadot/keyring": "^1.7.0-beta.5",
-        "@polkadot/rpc-core": "^0.96.1",
-        "@polkadot/rpc-provider": "^0.96.1",
-        "@polkadot/types": "^0.96.1",
-        "@polkadot/util-crypto": "^1.7.0-beta.5"
-      }
-    },
-    "@polkadot/api-derive": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-0.96.1.tgz",
-      "integrity": "sha512-PGWdUvlD2acUKOgaJcYWuMTfSuQKUpwgwjer5SomHLFn4ZPOz8iDa7mYtrgmxQctRv1zsuck2X01uhxdEdtJZw==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/api": "^0.96.1",
-        "@polkadot/types": "^0.96.1"
-      }
-    },
-    "@polkadot/api-metadata": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/api-metadata/-/api-metadata-0.96.1.tgz",
-      "integrity": "sha512-I9F3twpSCgx4ny25a3moGrhf2vHKFnjooO3W9NaAxIj/us4q4Gqo4+czQajqt8vaJqrNMq/PE7lzVz1NhYDrZQ==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/types": "^0.96.1",
-        "@polkadot/util": "^1.7.0-beta.5",
-        "@polkadot/util-crypto": "^1.7.0-beta.5"
-      }
-    },
-    "@polkadot/jsonrpc": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/jsonrpc/-/jsonrpc-0.96.1.tgz",
-      "integrity": "sha512-UHpcUGIvkG4dJ5gUhDyfJ1xfr/VcBlJ5lIlGamGsnNacMuIVmmEsftgxtPlJLWHuoA1EBEHY4cbPSv9CUJ0IFw==",
-      "requires": {
-        "@babel/runtime": "^7.7.1"
-      }
-    },
-    "@polkadot/keyring": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-1.8.1.tgz",
-      "integrity": "sha512-KeDbfP8biY3bXEhMv1ANp9d3kCuXj2oxseuDK0jvxRo7CehVME9UwAMGQK3Y9NCUuYWd+xTO2To0ZOqR7hdmuQ==",
-      "requires": {
-        "@babel/runtime": "^7.7.7",
-        "@polkadot/util": "^1.8.1",
-        "@polkadot/util-crypto": "^1.8.1"
-      }
-    },
-    "@polkadot/rpc-core": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-0.96.1.tgz",
-      "integrity": "sha512-ygSaJpz/QPEq1p35wYRzONuP2PCtkAJ9eS8swQqUIezTo2ZPUOyBhmnJ3nxj11R8YnQClq4Id0QdsJmH1ClYgw==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/jsonrpc": "^0.96.1",
-        "@polkadot/rpc-provider": "^0.96.1",
-        "@polkadot/types": "^0.96.1",
-        "@polkadot/util": "^1.7.0-beta.5",
-        "rxjs": "^6.5.3"
-      }
-    },
-    "@polkadot/rpc-provider": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-0.96.1.tgz",
-      "integrity": "sha512-cUhp8FMCYHrXrBTbxZrok/hPIgtOXEUhIXn5/zrffg1Qpbzju/y/bXx7c1Kxl1JF7Bg0vSBRZEGJTn/x0irWRQ==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/api-metadata": "^0.96.1",
-        "@polkadot/util": "^1.7.0-beta.5",
-        "@polkadot/util-crypto": "^1.7.0-beta.5",
-        "eventemitter3": "^4.0.0",
-        "isomorphic-fetch": "^2.2.1",
-        "websocket": "^1.0.30"
-      }
-    },
-    "@polkadot/ts": {
-      "version": "0.1.91",
-      "resolved": "https://registry.npmjs.org/@polkadot/ts/-/ts-0.1.91.tgz",
-      "integrity": "sha512-UB8zOFZXb/ih03izzAQ1r1DRpiUXBofxAlXjcx4530jopfiNsiU1LZ2J/uS3dVV1QXaGRhkgm8SIJDLsSMRYIQ==",
-      "dev": true,
-      "requires": {
-        "@types/chrome": "^0.0.92"
-      }
-    },
-    "@polkadot/types": {
-      "version": "0.96.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-0.96.1.tgz",
-      "integrity": "sha512-b8AZBNmMjB0+34Oxue3AYc0gIjDHYCdVGtDpel0omHkLMcEquSvrCniLm+p7g4cfArICiZPFmS9In/OWWdRUVA==",
-      "requires": {
-        "@babel/runtime": "^7.7.1",
-        "@polkadot/util": "^1.7.0-beta.5",
-        "@polkadot/util-crypto": "^1.7.0-beta.5",
-        "@types/memoizee": "^0.4.3",
-        "memoizee": "^0.4.14"
-      }
-    },
-    "@polkadot/util": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-1.8.1.tgz",
-      "integrity": "sha512-sFpr+JLCG9d+epjboXsmJ1qcKa96r8ZYzXmVo8+aPzI/9jKKyez6Unox/dnfnpKppZB2nJuLcsxQm6nocp2Caw==",
-      "requires": {
-        "@babel/runtime": "^7.7.7",
-        "@types/bn.js": "^4.11.6",
-        "bn.js": "^4.11.8",
-        "camelcase": "^5.3.1",
-        "chalk": "^3.0.0",
-        "ip-regex": "^4.1.0",
-        "moment": "^2.24.0"
-      },
-      "dependencies": {
-        "ansi-styles": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
-          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
-          "requires": {
-            "@types/color-name": "^1.1.1",
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
-          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
-        },
-        "supports-color": {
-          "version": "7.1.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
-          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
-      }
-    },
-    "@polkadot/util-crypto": {
-      "version": "1.8.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-1.8.1.tgz",
-      "integrity": "sha512-ypUs10hV1HPvYc0ZsEu+LTGSEh0rkr0as/FUh7+Z9v3Bxibn3aO+EOxJPQuDbZZ59FSMRmc9SeOSa0wn9ddrnw==",
-      "requires": {
-        "@babel/runtime": "^7.7.7",
-        "@polkadot/util": "^1.8.1",
-        "@polkadot/wasm-crypto": "^0.14.1",
-        "@types/bip39": "^2.4.2",
-        "@types/bs58": "^4.0.0",
-        "@types/pbkdf2": "^3.0.0",
-        "@types/secp256k1": "^3.5.0",
-        "@types/xxhashjs": "^0.2.1",
-        "base-x": "3.0.5",
-        "bip39": "^2.5.0",
-        "blakejs": "^1.1.0",
-        "bs58": "^4.0.1",
-        "js-sha3": "^0.8.0",
-        "secp256k1": "^3.8.0",
-        "tweetnacl": "^1.0.1",
-        "xxhashjs": "^0.2.2"
-      }
-    },
-    "@polkadot/wasm-crypto": {
-      "version": "0.14.1",
-      "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-0.14.1.tgz",
-      "integrity": "sha512-Xng7L2Z8TNZa/5g6pot4O06Jf0ohQRZdvfl8eQL+E/L2mcqJYC1IjkMxJBSBuQEV7hisWzh9mHOy5WCcgPk29Q=="
-    },
-    "@types/bip39": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/@types/bip39/-/bip39-2.4.2.tgz",
-      "integrity": "sha512-Vo9lqOIRq8uoIzEVrV87ZvcIM0PN9t0K3oYZ/CS61fIYKCBdOIM7mlWzXuRvSXrDtVa1uUO2w1cdfufxTC0bzg==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@types/bn.js": {
-      "version": "4.11.6",
-      "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
-      "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@types/bs58": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
-      "integrity": "sha512-yfAgiWgVLjFCmRv8zAcOIHywYATEwiTVccTLnRp6UxTNavT55M9d/uhK3T03St/+8/z/wW+CRjGKUNmEqoHHCA==",
-      "requires": {
-        "base-x": "^3.0.6"
-      },
-      "dependencies": {
-        "base-x": {
-          "version": "3.0.8",
-          "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.8.tgz",
-          "integrity": "sha512-Rl/1AWP4J/zRrk54hhlxH4drNxPJXYUaKffODVI53/dAsV4t9fBxyxYKAVPU1XBHxYwOWP9h9H0hM2MVw4YfJA==",
-          "requires": {
-            "safe-buffer": "^5.0.1"
-          }
-        }
-      }
-    },
-    "@types/chai": {
-      "version": "4.2.11",
-      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.11.tgz",
-      "integrity": "sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw==",
-      "dev": true
-    },
-    "@types/chrome": {
-      "version": "0.0.92",
-      "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.92.tgz",
-      "integrity": "sha512-bTv1EljZ03bexRJwS5FwSZmrudtw+QNbzwUY2sxVtXWgtxk752G4I2owhZ+Mlzbf3VKvG+rBYSw/FnvzuZ4xOA==",
-      "dev": true,
-      "requires": {
-        "@types/filesystem": "*"
-      }
-    },
-    "@types/color-name": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
-      "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
-    },
-    "@types/eslint-visitor-keys": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz",
-      "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==",
-      "dev": true
-    },
-    "@types/events": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
-      "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
-      "dev": true
-    },
-    "@types/filesystem": {
-      "version": "0.0.29",
-      "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.29.tgz",
-      "integrity": "sha512-85/1KfRedmfPGsbK8YzeaQUyV1FQAvMPMTuWFQ5EkLd2w7szhNO96bk3Rh/SKmOfd9co2rCLf0Voy4o7ECBOvw==",
-      "dev": true,
-      "requires": {
-        "@types/filewriter": "*"
-      }
-    },
-    "@types/filewriter": {
-      "version": "0.0.28",
-      "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.28.tgz",
-      "integrity": "sha1-wFTor02d11205jq8dviFFocU1LM=",
-      "dev": true
-    },
-    "@types/glob": {
-      "version": "7.1.1",
-      "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz",
-      "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==",
-      "dev": true,
-      "requires": {
-        "@types/events": "*",
-        "@types/minimatch": "*",
-        "@types/node": "*"
-      }
-    },
-    "@types/inquirer": {
-      "version": "6.5.0",
-      "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-6.5.0.tgz",
-      "integrity": "sha512-rjaYQ9b9y/VFGOpqBEXRavc3jh0a+e6evAbI31tMda8VlPaSy0AZJfXsvmIe3wklc7W6C3zCSfleuMXR7NOyXw==",
-      "requires": {
-        "@types/through": "*",
-        "rxjs": "^6.4.0"
-      }
-    },
-    "@types/json-schema": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
-      "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==",
-      "dev": true
-    },
-    "@types/lodash": {
-      "version": "4.14.149",
-      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz",
-      "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==",
-      "dev": true
-    },
-    "@types/memoizee": {
-      "version": "0.4.3",
-      "resolved": "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.3.tgz",
-      "integrity": "sha512-N6QT0c9ZbEKl33n1wyoTxZs4cpN+YXjs0Aqy5Qim8ipd9PBNIPqOh/p5Pixc4601tqr5GErsdxUbfqviDfubNw=="
-    },
-    "@types/minimatch": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
-      "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
-      "dev": true
-    },
-    "@types/mocha": {
-      "version": "5.2.7",
-      "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz",
-      "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==",
-      "dev": true
-    },
-    "@types/node": {
-      "version": "10.17.18",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.18.tgz",
-      "integrity": "sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg=="
-    },
-    "@types/pbkdf2": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.0.0.tgz",
-      "integrity": "sha512-6J6MHaAlBJC/eVMy9jOwj9oHaprfutukfW/Dyt0NEnpQ/6HN6YQrpvLwzWdWDeWZIdenjGHlbYDzyEODO5Z+2Q==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@types/proper-lockfile": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/@types/proper-lockfile/-/proper-lockfile-4.1.1.tgz",
-      "integrity": "sha512-HAjVfDa73pFgivViHyDu8HHHcds+W4MgOuZZAdyFJrHS8ngtCXmhl4hc2YXqSOwO6Bsa+iF2Sgxb2+gv874VOQ==",
-      "requires": {
-        "@types/retry": "*"
-      }
-    },
-    "@types/retry": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
-      "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="
-    },
-    "@types/secp256k1": {
-      "version": "3.5.3",
-      "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-3.5.3.tgz",
-      "integrity": "sha512-NGcsPDR0P+Q71O63e2ayshmiZGAwCOa/cLJzOIuhOiDvmbvrCIiVtEpqdCJGogG92Bnr6tw/6lqVBsRMEl15OQ==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@types/sinon": {
-      "version": "9.0.0",
-      "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.0.tgz",
-      "integrity": "sha512-v2TkYHkts4VXshMkcmot/H+ERZ2SevKa10saGaJPGCJ8vh3lKrC4u663zYEeRZxep+VbG6YRDtQ6gVqw9dYzPA==",
-      "dev": true,
-      "requires": {
-        "@types/sinonjs__fake-timers": "*"
-      }
-    },
-    "@types/sinonjs__fake-timers": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz",
-      "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==",
-      "dev": true
-    },
-    "@types/slug": {
-      "version": "0.9.1",
-      "resolved": "https://registry.npmjs.org/@types/slug/-/slug-0.9.1.tgz",
-      "integrity": "sha512-zR/u8WFQ4/6uCIikjI00a5uB084XjgEGNRAvM4a1BL39Bw9yEiDQFiPS2DgJ8lPDkR2Qd/vZ26dCR9XqlKbDqQ=="
-    },
-    "@types/through": {
-      "version": "0.0.30",
-      "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz",
-      "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@types/unist": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz",
-      "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ=="
-    },
-    "@types/vfile": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-4.0.0.tgz",
-      "integrity": "sha512-eleP0/Cz8uVWxARDLi3Axq2+fDdN4ibAXoC6Pv8p6s7znXaUL7XvhgeIhjCiNMnvlLNP+tmCLd+RuCryGgmtEg==",
-      "requires": {
-        "vfile": "*"
-      }
-    },
-    "@types/xxhashjs": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/@types/xxhashjs/-/xxhashjs-0.2.1.tgz",
-      "integrity": "sha512-Akm13wkwsQylVnBokl/aiKLtSxndSjfgTjdvmSxXNehYy4NymwdfdJHwGhpV54wcYfmOByOp3ak8AGdUlvp0sA==",
-      "requires": {
-        "@types/node": "*"
-      }
-    },
-    "@typescript-eslint/eslint-plugin": {
-      "version": "2.26.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.26.0.tgz",
-      "integrity": "sha512-4yUnLv40bzfzsXcTAtZyTjbiGUXMrcIJcIMioI22tSOyAxpdXiZ4r7YQUU8Jj6XXrLz9d5aMHPQf5JFR7h27Nw==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/experimental-utils": "2.26.0",
-        "functional-red-black-tree": "^1.0.1",
-        "regexpp": "^3.0.0",
-        "tsutils": "^3.17.1"
-      },
-      "dependencies": {
-        "regexpp": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz",
-          "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==",
-          "dev": true
-        }
-      }
-    },
-    "@typescript-eslint/experimental-utils": {
-      "version": "2.26.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.26.0.tgz",
-      "integrity": "sha512-RELVoH5EYd+JlGprEyojUv9HeKcZqF7nZUGSblyAw1FwOGNnmQIU8kxJ69fttQvEwCsX5D6ECJT8GTozxrDKVQ==",
-      "dev": true,
-      "requires": {
-        "@types/json-schema": "^7.0.3",
-        "@typescript-eslint/typescript-estree": "2.26.0",
-        "eslint-scope": "^5.0.0",
-        "eslint-utils": "^2.0.0"
-      },
-      "dependencies": {
-        "eslint-scope": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
-          "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
-          "dev": true,
-          "requires": {
-            "esrecurse": "^4.1.0",
-            "estraverse": "^4.1.1"
-          }
-        },
-        "eslint-utils": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz",
-          "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==",
-          "dev": true,
-          "requires": {
-            "eslint-visitor-keys": "^1.1.0"
-          }
-        }
-      }
-    },
-    "@typescript-eslint/parser": {
-      "version": "2.26.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.26.0.tgz",
-      "integrity": "sha512-+Xj5fucDtdKEVGSh9353wcnseMRkPpEAOY96EEenN7kJVrLqy/EVwtIh3mxcUz8lsFXW1mT5nN5vvEam/a5HiQ==",
-      "dev": true,
-      "requires": {
-        "@types/eslint-visitor-keys": "^1.0.0",
-        "@typescript-eslint/experimental-utils": "2.26.0",
-        "@typescript-eslint/typescript-estree": "2.26.0",
-        "eslint-visitor-keys": "^1.1.0"
-      }
-    },
-    "@typescript-eslint/typescript-estree": {
-      "version": "2.26.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.26.0.tgz",
-      "integrity": "sha512-3x4SyZCLB4zsKsjuhxDLeVJN6W29VwBnYpCsZ7vIdPel9ZqLfIZJgJXO47MNUkurGpQuIBALdPQKtsSnWpE1Yg==",
-      "dev": true,
-      "requires": {
-        "debug": "^4.1.1",
-        "eslint-visitor-keys": "^1.1.0",
-        "glob": "^7.1.6",
-        "is-glob": "^4.0.1",
-        "lodash": "^4.17.15",
-        "semver": "^6.3.0",
-        "tsutils": "^3.17.1"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "acorn": {
-      "version": "6.4.1",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz",
-      "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==",
-      "dev": true
-    },
-    "acorn-jsx": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.2.0.tgz",
-      "integrity": "sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ==",
-      "dev": true
-    },
-    "ajv": {
-      "version": "6.12.0",
-      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
-      "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
-      "requires": {
-        "fast-deep-equal": "^3.1.1",
-        "fast-json-stable-stringify": "^2.0.0",
-        "json-schema-traverse": "^0.4.1",
-        "uri-js": "^4.2.2"
-      }
-    },
-    "ansi-escapes": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
-      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ=="
-    },
-    "ansi-regex": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
-      "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
-    },
-    "ansi-styles": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
-      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
-      "requires": {
-        "color-convert": "^1.9.0"
-      }
-    },
-    "ansicolors": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.3.2.tgz",
-      "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk="
-    },
-    "append-transform": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
-      "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
-      "dev": true,
-      "requires": {
-        "default-require-extensions": "^2.0.0"
-      }
-    },
-    "archy": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
-      "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
-      "dev": true
-    },
-    "arg": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
-      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
-      "dev": true
-    },
-    "argparse": {
-      "version": "1.0.10",
-      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
-      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
-      "requires": {
-        "sprintf-js": "~1.0.2"
-      }
-    },
-    "array-union": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
-      "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
-      "dev": true
-    },
-    "assertion-error": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
-      "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
-      "dev": true
-    },
-    "astral-regex": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
-      "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
-      "dev": true
-    },
-    "balanced-match": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
-      "dev": true
-    },
-    "base-x": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.5.tgz",
-      "integrity": "sha512-C3picSgzPSLE+jW3tcBzJoGwitOtazb5B+5YmAxZm2ybmTi9LNgAtDO/jjVEBZwHoXmDBZ9m/IELj3elJVRBcA==",
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "base64-js": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
-      "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==",
-      "dev": true
-    },
-    "bindings": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
-      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
-      "requires": {
-        "file-uri-to-path": "1.0.0"
-      }
-    },
-    "bip39": {
-      "version": "2.6.0",
-      "resolved": "https://registry.npmjs.org/bip39/-/bip39-2.6.0.tgz",
-      "integrity": "sha512-RrnQRG2EgEoqO24ea+Q/fftuPUZLmrEM3qNhhGsA3PbaXaCW791LTzPuVyx/VprXQcTbPJ3K3UeTna8ZnVl2sg==",
-      "requires": {
-        "create-hash": "^1.1.0",
-        "pbkdf2": "^3.0.9",
-        "randombytes": "^2.0.1",
-        "safe-buffer": "^5.0.1",
-        "unorm": "^1.3.3"
-      }
-    },
-    "bip66": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz",
-      "integrity": "sha1-AfqHSHhcpwlV1QESF9GzE5lpyiI=",
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "bl": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
-      "integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
-      "dev": true,
-      "requires": {
-        "buffer": "^5.5.0",
-        "inherits": "^2.0.4",
-        "readable-stream": "^3.4.0"
-      }
-    },
-    "blakejs": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.1.0.tgz",
-      "integrity": "sha1-ad+S75U6qIylGjLfarHFShVfx6U="
-    },
-    "bn.js": {
-      "version": "4.11.8",
-      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
-      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
-    },
-    "brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
-      "requires": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "braces": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
-      "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "dev": true,
-      "requires": {
-        "fill-range": "^7.0.1"
-      }
-    },
-    "brorand": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
-      "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8="
-    },
-    "browser-stdout": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
-      "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
-      "dev": true
-    },
-    "browserify-aes": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
-      "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
-      "requires": {
-        "buffer-xor": "^1.0.3",
-        "cipher-base": "^1.0.0",
-        "create-hash": "^1.1.0",
-        "evp_bytestokey": "^1.0.3",
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "bs58": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
-      "integrity": "sha1-vhYedsNU9veIrkBx9j806MTwpCo=",
-      "requires": {
-        "base-x": "^3.0.2"
-      }
-    },
-    "buffer": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
-      "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
-      "dev": true,
-      "requires": {
-        "base64-js": "^1.0.2",
-        "ieee754": "^1.1.4"
-      }
-    },
-    "buffer-from": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
-      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
-      "dev": true
-    },
-    "buffer-xor": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
-      "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk="
-    },
-    "caching-transform": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz",
-      "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==",
-      "dev": true,
-      "requires": {
-        "hasha": "^3.0.0",
-        "make-dir": "^2.0.0",
-        "package-hash": "^3.0.0",
-        "write-file-atomic": "^2.4.2"
-      },
-      "dependencies": {
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        },
-        "write-file-atomic": {
-          "version": "2.4.3",
-          "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
-          "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
-          "dev": true,
-          "requires": {
-            "graceful-fs": "^4.1.11",
-            "imurmurhash": "^0.1.4",
-            "signal-exit": "^3.0.2"
-          }
-        }
-      }
-    },
-    "callsites": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
-      "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
-      "dev": true
-    },
-    "camelcase": {
-      "version": "5.3.1",
-      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
-      "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
-    },
-    "cardinal": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz",
-      "integrity": "sha1-fMEFXYItISlU0HsIXeolHMe8VQU=",
-      "requires": {
-        "ansicolors": "~0.3.2",
-        "redeyed": "~2.1.0"
-      }
-    },
-    "chai": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
-      "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
-      "dev": true,
-      "requires": {
-        "assertion-error": "^1.1.0",
-        "check-error": "^1.0.2",
-        "deep-eql": "^3.0.1",
-        "get-func-name": "^2.0.0",
-        "pathval": "^1.1.0",
-        "type-detect": "^4.0.5"
-      }
-    },
-    "chalk": {
-      "version": "2.4.2",
-      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
-      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
-      "requires": {
-        "ansi-styles": "^3.2.1",
-        "escape-string-regexp": "^1.0.5",
-        "supports-color": "^5.3.0"
-      }
-    },
-    "chardet": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
-      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
-    },
-    "check-error": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
-      "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
-      "dev": true
-    },
-    "chownr": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
-      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
-      "dev": true
-    },
-    "cipher-base": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
-      "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
-      "requires": {
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "clean-regexp": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz",
-      "integrity": "sha1-jffHquUf02h06PjQW5GAvBGj/tc=",
-      "dev": true,
-      "requires": {
-        "escape-string-regexp": "^1.0.5"
-      }
-    },
-    "clean-stack": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz",
-      "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE="
-    },
-    "cli-cursor": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
-      "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
-      "requires": {
-        "restore-cursor": "^3.1.0"
-      }
-    },
-    "cli-progress": {
-      "version": "3.6.1",
-      "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.6.1.tgz",
-      "integrity": "sha512-OVRgcyeI0viJW47MnyS10Jw/0RTpk7wwNbrCOPyXT0TVi2o3Q/u+Os8vQUFYhvkdXSbguSdFvMv1ia+UuwgIQQ==",
-      "requires": {
-        "colors": "^1.1.2",
-        "string-width": "^4.2.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
-          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
-        },
-        "emoji-regex": {
-          "version": "8.0.0",
-          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-        },
-        "is-fullwidth-code-point": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
-        },
-        "string-width": {
-          "version": "4.2.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
-          "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
-          "requires": {
-            "emoji-regex": "^8.0.0",
-            "is-fullwidth-code-point": "^3.0.0",
-            "strip-ansi": "^6.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
-          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
-          "requires": {
-            "ansi-regex": "^5.0.0"
-          }
-        }
-      }
-    },
-    "cli-ux": {
-      "version": "5.4.5",
-      "resolved": "https://registry.npmjs.org/cli-ux/-/cli-ux-5.4.5.tgz",
-      "integrity": "sha512-5A6FuU0wPUlfCWUjtizUvNIbXElp6jN9QUJsDibs6F9cVX1kTgaMR3m6KT0R3iriEXpMrmPKV6yYS8XICNuQ6Q==",
-      "requires": {
-        "@oclif/command": "^1.5.1",
-        "@oclif/errors": "^1.2.1",
-        "@oclif/linewrap": "^1.0.0",
-        "@oclif/screen": "^1.0.3",
-        "ansi-escapes": "^3.1.0",
-        "ansi-styles": "^3.2.1",
-        "cardinal": "^2.1.1",
-        "chalk": "^2.4.1",
-        "clean-stack": "^2.0.0",
-        "cli-progress": "^3.4.0",
-        "extract-stack": "^1.0.0",
-        "fs-extra": "^7.0.1",
-        "hyperlinker": "^1.0.0",
-        "indent-string": "^4.0.0",
-        "is-wsl": "^1.1.0",
-        "js-yaml": "^3.13.1",
-        "lodash": "^4.17.11",
-        "natural-orderby": "^2.0.1",
-        "password-prompt": "^1.1.2",
-        "semver": "^5.6.0",
-        "string-width": "^3.1.0",
-        "strip-ansi": "^5.1.0",
-        "supports-color": "^5.5.0",
-        "supports-hyperlinks": "^1.0.1",
-        "treeify": "^1.1.0",
-        "tslib": "^1.9.3"
-      },
-      "dependencies": {
-        "clean-stack": {
-          "version": "2.2.0",
-          "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
-          "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="
-        },
-        "indent-string": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
-          "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="
-        },
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        }
-      }
-    },
-    "cli-width": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
-      "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk="
-    },
-    "cliui": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
-      "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
-      "dev": true,
-      "requires": {
-        "string-width": "^3.1.0",
-        "strip-ansi": "^5.2.0",
-        "wrap-ansi": "^5.1.0"
-      },
-      "dependencies": {
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "dev": true,
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        },
-        "wrap-ansi": {
-          "version": "5.1.0",
-          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
-          "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
-          "dev": true,
-          "requires": {
-            "ansi-styles": "^3.2.0",
-            "string-width": "^3.0.0",
-            "strip-ansi": "^5.0.0"
-          }
-        }
-      }
-    },
-    "color-convert": {
-      "version": "1.9.3",
-      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
-      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "requires": {
-        "color-name": "1.1.3"
-      }
-    },
-    "color-name": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
-    },
-    "colors": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
-      "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
-    },
-    "commander": {
-      "version": "2.15.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
-      "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
-      "dev": true
-    },
-    "commondir": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
-      "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
-      "dev": true
-    },
-    "concat-map": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
-      "dev": true
-    },
-    "content-type": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
-      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
-      "dev": true
-    },
-    "convert-source-map": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
-      "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
-      "dev": true,
-      "requires": {
-        "safe-buffer": "~5.1.1"
-      },
-      "dependencies": {
-        "safe-buffer": {
-          "version": "5.1.2",
-          "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-          "dev": true
-        }
-      }
-    },
-    "cp-file": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz",
-      "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "make-dir": "^2.0.0",
-        "nested-error-stacks": "^2.0.0",
-        "pify": "^4.0.1",
-        "safe-buffer": "^5.0.1"
-      },
-      "dependencies": {
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        }
-      }
-    },
-    "create-hash": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
-      "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
-      "requires": {
-        "cipher-base": "^1.0.1",
-        "inherits": "^2.0.1",
-        "md5.js": "^1.3.4",
-        "ripemd160": "^2.0.1",
-        "sha.js": "^2.4.0"
-      }
-    },
-    "create-hmac": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
-      "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
-      "requires": {
-        "cipher-base": "^1.0.3",
-        "create-hash": "^1.1.0",
-        "inherits": "^2.0.1",
-        "ripemd160": "^2.0.0",
-        "safe-buffer": "^5.0.1",
-        "sha.js": "^2.4.8"
-      }
-    },
-    "cross-spawn": {
-      "version": "6.0.5",
-      "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
-      "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
-      "requires": {
-        "nice-try": "^1.0.4",
-        "path-key": "^2.0.1",
-        "semver": "^5.5.0",
-        "shebang-command": "^1.2.0",
-        "which": "^1.2.9"
-      }
-    },
-    "cuint": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
-      "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs="
-    },
-    "d": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
-      "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
-      "requires": {
-        "es5-ext": "^0.10.50",
-        "type": "^1.0.1"
-      }
-    },
-    "debug": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
-      "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
-      "requires": {
-        "ms": "^2.1.1"
-      }
-    },
-    "decamelize": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
-      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
-      "dev": true
-    },
-    "deep-eql": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
-      "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
-      "dev": true,
-      "requires": {
-        "type-detect": "^4.0.0"
-      }
-    },
-    "deep-is": {
-      "version": "0.1.3",
-      "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
-      "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
-      "dev": true
-    },
-    "default-require-extensions": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
-      "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
-      "dev": true,
-      "requires": {
-        "strip-bom": "^3.0.0"
-      },
-      "dependencies": {
-        "strip-bom": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
-          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
-          "dev": true
-        }
-      }
-    },
-    "detect-indent": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.0.0.tgz",
-      "integrity": "sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==",
-      "dev": true
-    },
-    "diff": {
-      "version": "3.5.0",
-      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
-      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
-      "dev": true
-    },
-    "dir-glob": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
-      "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
-      "dev": true,
-      "requires": {
-        "path-type": "^4.0.0"
-      }
-    },
-    "doctrine": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
-      "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
-      "dev": true,
-      "requires": {
-        "esutils": "^2.0.2"
-      }
-    },
-    "drbg.js": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz",
-      "integrity": "sha1-Pja2xCs3BDgjzbwzLVjzHiRFSAs=",
-      "requires": {
-        "browserify-aes": "^1.0.6",
-        "create-hash": "^1.1.2",
-        "create-hmac": "^1.1.4"
-      }
-    },
-    "elliptic": {
-      "version": "6.5.2",
-      "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
-      "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
-      "requires": {
-        "bn.js": "^4.4.0",
-        "brorand": "^1.0.1",
-        "hash.js": "^1.0.0",
-        "hmac-drbg": "^1.0.0",
-        "inherits": "^2.0.1",
-        "minimalistic-assert": "^1.0.0",
-        "minimalistic-crypto-utils": "^1.0.0"
-      }
-    },
-    "emoji-regex": {
-      "version": "7.0.3",
-      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
-      "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
-    },
-    "encoding": {
-      "version": "0.1.12",
-      "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
-      "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
-      "requires": {
-        "iconv-lite": "~0.4.13"
-      }
-    },
-    "end-of-stream": {
-      "version": "1.4.4",
-      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
-      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
-      "dev": true,
-      "requires": {
-        "once": "^1.4.0"
-      }
-    },
-    "error-ex": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
-      "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
-      "dev": true,
-      "requires": {
-        "is-arrayish": "^0.2.1"
-      }
-    },
-    "es5-ext": {
-      "version": "0.10.53",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz",
-      "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==",
-      "requires": {
-        "es6-iterator": "~2.0.3",
-        "es6-symbol": "~3.1.3",
-        "next-tick": "~1.0.0"
-      },
-      "dependencies": {
-        "next-tick": {
-          "version": "1.0.0",
-          "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
-          "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
-        }
-      }
-    },
-    "es6-error": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
-      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
-      "dev": true
-    },
-    "es6-iterator": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
-      "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "^0.10.35",
-        "es6-symbol": "^3.1.1"
-      }
-    },
-    "es6-symbol": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
-      "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
-      "requires": {
-        "d": "^1.0.1",
-        "ext": "^1.1.2"
-      }
-    },
-    "es6-weak-map": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
-      "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
-      "requires": {
-        "d": "1",
-        "es5-ext": "^0.10.46",
-        "es6-iterator": "^2.0.3",
-        "es6-symbol": "^3.1.1"
-      }
-    },
-    "escape-string-regexp": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
-      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
-    },
-    "eslint": {
-      "version": "5.16.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
-      "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
-      "dev": true,
-      "requires": {
-        "@babel/code-frame": "^7.0.0",
-        "ajv": "^6.9.1",
-        "chalk": "^2.1.0",
-        "cross-spawn": "^6.0.5",
-        "debug": "^4.0.1",
-        "doctrine": "^3.0.0",
-        "eslint-scope": "^4.0.3",
-        "eslint-utils": "^1.3.1",
-        "eslint-visitor-keys": "^1.0.0",
-        "espree": "^5.0.1",
-        "esquery": "^1.0.1",
-        "esutils": "^2.0.2",
-        "file-entry-cache": "^5.0.1",
-        "functional-red-black-tree": "^1.0.1",
-        "glob": "^7.1.2",
-        "globals": "^11.7.0",
-        "ignore": "^4.0.6",
-        "import-fresh": "^3.0.0",
-        "imurmurhash": "^0.1.4",
-        "inquirer": "^6.2.2",
-        "js-yaml": "^3.13.0",
-        "json-stable-stringify-without-jsonify": "^1.0.1",
-        "levn": "^0.3.0",
-        "lodash": "^4.17.11",
-        "minimatch": "^3.0.4",
-        "mkdirp": "^0.5.1",
-        "natural-compare": "^1.4.0",
-        "optionator": "^0.8.2",
-        "path-is-inside": "^1.0.2",
-        "progress": "^2.0.0",
-        "regexpp": "^2.0.1",
-        "semver": "^5.5.1",
-        "strip-ansi": "^4.0.0",
-        "strip-json-comments": "^2.0.1",
-        "table": "^5.2.3",
-        "text-table": "^0.2.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
-          "dev": true
-        },
-        "cli-cursor": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
-          "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
-          "dev": true,
-          "requires": {
-            "restore-cursor": "^2.0.0"
-          }
-        },
-        "figures": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
-          "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
-          "dev": true,
-          "requires": {
-            "escape-string-regexp": "^1.0.5"
-          }
-        },
-        "ignore": {
-          "version": "4.0.6",
-          "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
-          "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
-          "dev": true
-        },
-        "inquirer": {
-          "version": "6.5.2",
-          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
-          "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
-          "dev": true,
-          "requires": {
-            "ansi-escapes": "^3.2.0",
-            "chalk": "^2.4.2",
-            "cli-cursor": "^2.1.0",
-            "cli-width": "^2.0.0",
-            "external-editor": "^3.0.3",
-            "figures": "^2.0.0",
-            "lodash": "^4.17.12",
-            "mute-stream": "0.0.7",
-            "run-async": "^2.2.0",
-            "rxjs": "^6.4.0",
-            "string-width": "^2.1.0",
-            "strip-ansi": "^5.1.0",
-            "through": "^2.3.6"
-          },
-          "dependencies": {
-            "ansi-regex": {
-              "version": "4.1.0",
-              "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
-              "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
-              "dev": true
-            },
-            "strip-ansi": {
-              "version": "5.2.0",
-              "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
-              "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
-              "dev": true,
-              "requires": {
-                "ansi-regex": "^4.1.0"
-              }
-            }
-          }
-        },
-        "mimic-fn": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
-          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
-          "dev": true
-        },
-        "mute-stream": {
-          "version": "0.0.7",
-          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
-          "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
-          "dev": true
-        },
-        "onetime": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
-          "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
-          "dev": true,
-          "requires": {
-            "mimic-fn": "^1.0.0"
-          }
-        },
-        "restore-cursor": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
-          "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
-          "dev": true,
-          "requires": {
-            "onetime": "^2.0.0",
-            "signal-exit": "^3.0.2"
-          }
-        },
-        "strip-ansi": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^3.0.0"
-          }
-        }
-      }
-    },
-    "eslint-ast-utils": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-ast-utils/-/eslint-ast-utils-1.1.0.tgz",
-      "integrity": "sha512-otzzTim2/1+lVrlH19EfQQJEhVJSu0zOb9ygb3iapN6UlyaDtyRq4b5U1FuW0v1lRa9Fp/GJyHkSwm6NqABgCA==",
-      "dev": true,
-      "requires": {
-        "lodash.get": "^4.4.2",
-        "lodash.zip": "^4.2.0"
-      }
-    },
-    "eslint-config-oclif": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-oclif/-/eslint-config-oclif-3.1.0.tgz",
-      "integrity": "sha512-Tqgy43cNXsSdhTLWW4RuDYGFhV240sC4ISSv/ZiUEg/zFxExSEUpRE6J+AGnkKY9dYwIW4C9b2YSUVv8z/miMA==",
-      "dev": true,
-      "requires": {
-        "eslint-config-xo-space": "^0.20.0",
-        "eslint-plugin-mocha": "^5.2.0",
-        "eslint-plugin-node": "^7.0.1",
-        "eslint-plugin-unicorn": "^6.0.1"
-      }
-    },
-    "eslint-config-oclif-typescript": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-0.1.0.tgz",
-      "integrity": "sha512-BjXNJcH2F02MdaSFml9vJskviUFVkLHbTPGM5tinIt98H6klFNKP7/lQ+fB/Goc2wB45usEuuw6+l/fwAv9i7g==",
-      "dev": true,
-      "requires": {
-        "@typescript-eslint/eslint-plugin": "^2.6.1",
-        "@typescript-eslint/parser": "^2.6.1",
-        "eslint-config-oclif": "^3.1.0",
-        "eslint-config-xo-space": "^0.20.0",
-        "eslint-plugin-mocha": "^5.2.0",
-        "eslint-plugin-node": "^7.0.1",
-        "eslint-plugin-unicorn": "^6.0.1"
-      }
-    },
-    "eslint-config-xo": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.24.2.tgz",
-      "integrity": "sha512-ivQ7qISScW6gfBp+p31nQntz1rg34UCybd3uvlngcxt5Utsf4PMMi9QoAluLFcPUM5Tvqk4JGraR9qu3msKPKQ==",
-      "dev": true
-    },
-    "eslint-config-xo-space": {
-      "version": "0.20.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-xo-space/-/eslint-config-xo-space-0.20.0.tgz",
-      "integrity": "sha512-bOsoZA8M6v1HviDUIGVq1fLVnSu3mMZzn85m2tqKb73tSzu4GKD4Jd2Py4ZKjCgvCbRRByEB5HPC3fTMnnJ1uw==",
-      "dev": true,
-      "requires": {
-        "eslint-config-xo": "^0.24.0"
-      }
-    },
-    "eslint-plugin-es": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-1.4.1.tgz",
-      "integrity": "sha512-5fa/gR2yR3NxQf+UXkeLeP8FBBl6tSgdrAz1+cF84v1FMM4twGwQoqTnn+QxFLcPOrF4pdKEJKDB/q9GoyJrCA==",
-      "dev": true,
-      "requires": {
-        "eslint-utils": "^1.4.2",
-        "regexpp": "^2.0.1"
-      }
-    },
-    "eslint-plugin-mocha": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz",
-      "integrity": "sha512-3uwlJVLijjEmBeNyH60nzqgA1gacUWLUmcKV8PIGNvj1kwP/CTgAWQHn2ayyJVwziX+KETkr9opNwT1qD/RZ5A==",
-      "dev": true,
-      "requires": {
-        "ramda": "^0.26.1"
-      }
-    },
-    "eslint-plugin-node": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-7.0.1.tgz",
-      "integrity": "sha512-lfVw3TEqThwq0j2Ba/Ckn2ABdwmL5dkOgAux1rvOk6CO7A6yGyPI2+zIxN6FyNkp1X1X/BSvKOceD6mBWSj4Yw==",
-      "dev": true,
-      "requires": {
-        "eslint-plugin-es": "^1.3.1",
-        "eslint-utils": "^1.3.1",
-        "ignore": "^4.0.2",
-        "minimatch": "^3.0.4",
-        "resolve": "^1.8.1",
-        "semver": "^5.5.0"
-      },
-      "dependencies": {
-        "ignore": {
-          "version": "4.0.6",
-          "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
-          "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
-          "dev": true
-        }
-      }
-    },
-    "eslint-plugin-unicorn": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-6.0.1.tgz",
-      "integrity": "sha512-hjy9LhTdtL7pz8WTrzS0CGXRkWK3VAPLDjihofj8JC+uxQLfXm0WwZPPPB7xKmcjRyoH+jruPHOCrHNEINpG/Q==",
-      "dev": true,
-      "requires": {
-        "clean-regexp": "^1.0.0",
-        "eslint-ast-utils": "^1.0.0",
-        "import-modules": "^1.1.0",
-        "lodash.camelcase": "^4.1.1",
-        "lodash.kebabcase": "^4.0.1",
-        "lodash.snakecase": "^4.0.1",
-        "lodash.upperfirst": "^4.2.0",
-        "safe-regex": "^1.1.0"
-      }
-    },
-    "eslint-scope": {
-      "version": "4.0.3",
-      "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
-      "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
-      "dev": true,
-      "requires": {
-        "esrecurse": "^4.1.0",
-        "estraverse": "^4.1.1"
-      }
-    },
-    "eslint-utils": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
-      "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
-      "dev": true,
-      "requires": {
-        "eslint-visitor-keys": "^1.1.0"
-      }
-    },
-    "eslint-visitor-keys": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
-      "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
-      "dev": true
-    },
-    "espree": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
-      "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
-      "dev": true,
-      "requires": {
-        "acorn": "^6.0.7",
-        "acorn-jsx": "^5.0.0",
-        "eslint-visitor-keys": "^1.0.0"
-      }
-    },
-    "esprima": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
-      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
-    },
-    "esquery": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.2.0.tgz",
-      "integrity": "sha512-weltsSqdeWIX9G2qQZz7KlTRJdkkOCTPgLYJUz1Hacf48R4YOwGPHO3+ORfWedqJKbq5WQmsgK90n+pFLIKt/Q==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^5.0.0"
-      },
-      "dependencies": {
-        "estraverse": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.0.0.tgz",
-          "integrity": "sha512-j3acdrMzqrxmJTNj5dbr1YbjacrYgAxVMeF0gK16E3j494mOe7xygM/ZLIguEQ0ETwAg2hlJCtHRGav+y0Ny5A==",
-          "dev": true
-        }
-      }
-    },
-    "esrecurse": {
-      "version": "4.2.1",
-      "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
-      "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
-      "dev": true,
-      "requires": {
-        "estraverse": "^4.1.0"
-      }
-    },
-    "estraverse": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
-      "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
-      "dev": true
-    },
-    "esutils": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
-      "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
-      "dev": true
-    },
-    "event-emitter": {
-      "version": "0.3.5",
-      "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
-      "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
-      "requires": {
-        "d": "1",
-        "es5-ext": "~0.10.14"
-      }
-    },
-    "eventemitter3": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz",
-      "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg=="
-    },
-    "evp_bytestokey": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
-      "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
-      "requires": {
-        "md5.js": "^1.3.4",
-        "safe-buffer": "^5.1.1"
-      }
-    },
-    "execa": {
-      "version": "0.10.0",
-      "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
-      "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
-      "dev": true,
-      "requires": {
-        "cross-spawn": "^6.0.0",
-        "get-stream": "^3.0.0",
-        "is-stream": "^1.1.0",
-        "npm-run-path": "^2.0.0",
-        "p-finally": "^1.0.0",
-        "signal-exit": "^3.0.0",
-        "strip-eof": "^1.0.0"
-      },
-      "dependencies": {
-        "get-stream": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
-          "dev": true
-        }
-      }
-    },
-    "ext": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
-      "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==",
-      "requires": {
-        "type": "^2.0.0"
-      },
-      "dependencies": {
-        "type": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
-          "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
-        }
-      }
-    },
-    "external-editor": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
-      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
-      "requires": {
-        "chardet": "^0.7.0",
-        "iconv-lite": "^0.4.24",
-        "tmp": "^0.0.33"
-      },
-      "dependencies": {
-        "tmp": {
-          "version": "0.0.33",
-          "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
-          "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
-          "requires": {
-            "os-tmpdir": "~1.0.2"
-          }
-        }
-      }
-    },
-    "extract-stack": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/extract-stack/-/extract-stack-1.0.0.tgz",
-      "integrity": "sha1-uXrK+UQe6iMyUpYktzL8WhyBZfo="
-    },
-    "fancy-test": {
-      "version": "1.4.7",
-      "resolved": "https://registry.npmjs.org/fancy-test/-/fancy-test-1.4.7.tgz",
-      "integrity": "sha512-drgNrpNbvXXbPAz0rn7jvzjoEihDKpm1fFF+aZ+FVLatjE3jZSc6WwfgC5x7N/+nhmentMx4TXPQ0OkS0SElVQ==",
-      "dev": true,
-      "requires": {
-        "@types/chai": "*",
-        "@types/lodash": "*",
-        "@types/mocha": "*",
-        "@types/node": "*",
-        "@types/sinon": "*",
-        "lodash": "^4.17.13",
-        "mock-stdin": "^0.3.1",
-        "stdout-stderr": "^0.1.9"
-      }
-    },
-    "fast-deep-equal": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
-      "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
-    },
-    "fast-glob": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.2.tgz",
-      "integrity": "sha512-UDV82o4uQyljznxwMxyVRJgZZt3O5wENYojjzbaGEGZgeOxkLFf+V4cnUD+krzb2F72E18RhamkMZ7AdeggF7A==",
-      "dev": true,
-      "requires": {
-        "@nodelib/fs.stat": "^2.0.2",
-        "@nodelib/fs.walk": "^1.2.3",
-        "glob-parent": "^5.1.0",
-        "merge2": "^1.3.0",
-        "micromatch": "^4.0.2",
-        "picomatch": "^2.2.1"
-      }
-    },
-    "fast-json-stable-stringify": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
-      "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
-    },
-    "fast-levenshtein": {
-      "version": "2.0.6",
-      "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
-      "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
-      "dev": true
-    },
-    "fastq": {
-      "version": "1.7.0",
-      "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.7.0.tgz",
-      "integrity": "sha512-YOadQRnHd5q6PogvAR/x62BGituF2ufiEA6s8aavQANw5YKHERI4AREboX6KotzP8oX2klxYF2wcV/7bn1clfQ==",
-      "dev": true,
-      "requires": {
-        "reusify": "^1.0.4"
-      }
-    },
-    "figures": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz",
-      "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
-      "requires": {
-        "escape-string-regexp": "^1.0.5"
-      }
-    },
-    "file-entry-cache": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
-      "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
-      "dev": true,
-      "requires": {
-        "flat-cache": "^2.0.1"
-      }
-    },
-    "file-uri-to-path": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
-      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
-    },
-    "fill-range": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
-      "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "dev": true,
-      "requires": {
-        "to-regex-range": "^5.0.1"
-      }
-    },
-    "find-cache-dir": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
-      "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
-      "dev": true,
-      "requires": {
-        "commondir": "^1.0.1",
-        "make-dir": "^2.0.0",
-        "pkg-dir": "^3.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
-          "requires": {
-            "locate-path": "^3.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
-          "requires": {
-            "p-locate": "^3.0.0",
-            "path-exists": "^3.0.0"
-          }
-        },
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        },
-        "p-locate": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
-          "requires": {
-            "p-limit": "^2.0.0"
-          }
-        },
-        "path-exists": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
-        },
-        "pkg-dir": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
-          "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
-          "dev": true,
-          "requires": {
-            "find-up": "^3.0.0"
-          }
-        }
-      }
-    },
-    "find-up": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
-      "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
-      "dev": true,
-      "requires": {
-        "locate-path": "^5.0.0",
-        "path-exists": "^4.0.0"
-      }
-    },
-    "flat-cache": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
-      "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
-      "dev": true,
-      "requires": {
-        "flatted": "^2.0.0",
-        "rimraf": "2.6.3",
-        "write": "1.0.3"
-      },
-      "dependencies": {
-        "rimraf": {
-          "version": "2.6.3",
-          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
-          "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
-          "dev": true,
-          "requires": {
-            "glob": "^7.1.3"
-          }
-        }
-      }
-    },
-    "flatted": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz",
-      "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==",
-      "dev": true
-    },
-    "foreground-child": {
-      "version": "1.5.6",
-      "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
-      "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=",
-      "dev": true,
-      "requires": {
-        "cross-spawn": "^4",
-        "signal-exit": "^3.0.0"
-      },
-      "dependencies": {
-        "cross-spawn": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
-          "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
-          "dev": true,
-          "requires": {
-            "lru-cache": "^4.0.1",
-            "which": "^1.2.9"
-          }
-        }
-      }
-    },
-    "fs-constants": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
-      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
-      "dev": true
-    },
-    "fs-extra": {
-      "version": "7.0.1",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
-      "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
-      "requires": {
-        "graceful-fs": "^4.1.2",
-        "jsonfile": "^4.0.0",
-        "universalify": "^0.1.0"
-      }
-    },
-    "fs.realpath": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
-      "dev": true
-    },
-    "functional-red-black-tree": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
-      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
-      "dev": true
-    },
-    "get-caller-file": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
-      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
-      "dev": true
-    },
-    "get-func-name": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
-      "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
-      "dev": true
-    },
-    "get-stream": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
-      "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
-      "dev": true,
-      "requires": {
-        "pump": "^3.0.0"
-      }
-    },
-    "github-slugger": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.3.0.tgz",
-      "integrity": "sha512-gwJScWVNhFYSRDvURk/8yhcFBee6aFjye2a7Lhb2bUyRulpIoek9p0I9Kt7PT67d/nUlZbFu8L9RLiA0woQN8Q==",
-      "dev": true,
-      "requires": {
-        "emoji-regex": ">=6.0.0 <=6.1.1"
-      },
-      "dependencies": {
-        "emoji-regex": {
-          "version": "6.1.1",
-          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz",
-          "integrity": "sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=",
-          "dev": true
-        }
-      }
-    },
-    "glob": {
-      "version": "7.1.6",
-      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
-      "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
-      "dev": true,
-      "requires": {
-        "fs.realpath": "^1.0.0",
-        "inflight": "^1.0.4",
-        "inherits": "2",
-        "minimatch": "^3.0.4",
-        "once": "^1.3.0",
-        "path-is-absolute": "^1.0.0"
-      }
-    },
-    "glob-parent": {
-      "version": "5.1.1",
-      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
-      "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
-      "dev": true,
-      "requires": {
-        "is-glob": "^4.0.1"
-      }
-    },
-    "globals": {
-      "version": "11.12.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
-      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
-      "dev": true
-    },
-    "globby": {
-      "version": "10.0.2",
-      "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz",
-      "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==",
-      "dev": true,
-      "requires": {
-        "@types/glob": "^7.1.1",
-        "array-union": "^2.1.0",
-        "dir-glob": "^3.0.1",
-        "fast-glob": "^3.0.3",
-        "glob": "^7.1.3",
-        "ignore": "^5.1.1",
-        "merge2": "^1.2.3",
-        "slash": "^3.0.0"
-      }
-    },
-    "graceful-fs": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
-      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
-    },
-    "growl": {
-      "version": "1.10.5",
-      "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
-      "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
-      "dev": true
-    },
-    "has-flag": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
-    },
-    "hash-base": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
-      "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
-      "requires": {
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "hash.js": {
-      "version": "1.1.7",
-      "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
-      "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
-      "requires": {
-        "inherits": "^2.0.3",
-        "minimalistic-assert": "^1.0.1"
-      }
-    },
-    "hasha": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz",
-      "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=",
-      "dev": true,
-      "requires": {
-        "is-stream": "^1.0.1"
-      }
-    },
-    "he": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
-      "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
-      "dev": true
-    },
-    "hmac-drbg": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
-      "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
-      "requires": {
-        "hash.js": "^1.0.3",
-        "minimalistic-assert": "^1.0.0",
-        "minimalistic-crypto-utils": "^1.0.1"
-      }
-    },
-    "hosted-git-info": {
-      "version": "2.8.8",
-      "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz",
-      "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
-      "dev": true
-    },
-    "html-escaper": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
-      "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
-      "dev": true
-    },
-    "http-call": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/http-call/-/http-call-5.3.0.tgz",
-      "integrity": "sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==",
-      "dev": true,
-      "requires": {
-        "content-type": "^1.0.4",
-        "debug": "^4.1.1",
-        "is-retry-allowed": "^1.1.0",
-        "is-stream": "^2.0.0",
-        "parse-json": "^4.0.0",
-        "tunnel-agent": "^0.6.0"
-      },
-      "dependencies": {
-        "is-stream": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
-          "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
-          "dev": true
-        }
-      }
-    },
-    "hyperlinker": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/hyperlinker/-/hyperlinker-1.0.0.tgz",
-      "integrity": "sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ=="
-    },
-    "iconv-lite": {
-      "version": "0.4.24",
-      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
-      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
-      "requires": {
-        "safer-buffer": ">= 2.1.2 < 3"
-      }
-    },
-    "ieee754": {
-      "version": "1.1.13",
-      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
-      "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==",
-      "dev": true
-    },
-    "ignore": {
-      "version": "5.1.4",
-      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
-      "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==",
-      "dev": true
-    },
-    "import-fresh": {
-      "version": "3.2.1",
-      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz",
-      "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==",
-      "dev": true,
-      "requires": {
-        "parent-module": "^1.0.0",
-        "resolve-from": "^4.0.0"
-      }
-    },
-    "import-modules": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/import-modules/-/import-modules-1.1.0.tgz",
-      "integrity": "sha1-dI23nFzEK7lwHvq0JPiU5yYA6dw=",
-      "dev": true
-    },
-    "imurmurhash": {
-      "version": "0.1.4",
-      "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
-      "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
-      "dev": true
-    },
-    "indent-string": {
-      "version": "3.2.0",
-      "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
-      "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok="
-    },
-    "inflight": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
-      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
-      "dev": true,
-      "requires": {
-        "once": "^1.3.0",
-        "wrappy": "1"
-      }
-    },
-    "inherits": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
-    },
-    "inquirer": {
-      "version": "7.1.0",
-      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.1.0.tgz",
-      "integrity": "sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg==",
-      "requires": {
-        "ansi-escapes": "^4.2.1",
-        "chalk": "^3.0.0",
-        "cli-cursor": "^3.1.0",
-        "cli-width": "^2.0.0",
-        "external-editor": "^3.0.3",
-        "figures": "^3.0.0",
-        "lodash": "^4.17.15",
-        "mute-stream": "0.0.8",
-        "run-async": "^2.4.0",
-        "rxjs": "^6.5.3",
-        "string-width": "^4.1.0",
-        "strip-ansi": "^6.0.0",
-        "through": "^2.3.6"
-      },
-      "dependencies": {
-        "ansi-escapes": {
-          "version": "4.3.1",
-          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz",
-          "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==",
-          "requires": {
-            "type-fest": "^0.11.0"
-          }
-        },
-        "ansi-regex": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
-          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
-        },
-        "ansi-styles": {
-          "version": "4.2.1",
-          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
-          "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
-          "requires": {
-            "@types/color-name": "^1.1.1",
-            "color-convert": "^2.0.1"
-          }
-        },
-        "chalk": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
-          "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
-          "requires": {
-            "ansi-styles": "^4.1.0",
-            "supports-color": "^7.1.0"
-          }
-        },
-        "color-convert": {
-          "version": "2.0.1",
-          "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-          "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-          "requires": {
-            "color-name": "~1.1.4"
-          }
-        },
-        "color-name": {
-          "version": "1.1.4",
-          "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-          "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-        },
-        "emoji-regex": {
-          "version": "8.0.0",
-          "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-          "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
-        },
-        "is-fullwidth-code-point": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
-          "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
-        },
-        "string-width": {
-          "version": "4.2.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
-          "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
-          "requires": {
-            "emoji-regex": "^8.0.0",
-            "is-fullwidth-code-point": "^3.0.0",
-            "strip-ansi": "^6.0.0"
-          }
-        },
-        "strip-ansi": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
-          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
-          "requires": {
-            "ansi-regex": "^5.0.0"
-          }
-        },
-        "supports-color": {
-          "version": "7.1.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz",
-          "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==",
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        },
-        "type-fest": {
-          "version": "0.11.0",
-          "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz",
-          "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ=="
-        }
-      }
-    },
-    "ip-regex": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.1.0.tgz",
-      "integrity": "sha512-pKnZpbgCTfH/1NLIlOduP/V+WRXzC2MOz3Qo8xmxk8C5GudJLgK5QyLVXOSWy3ParAH7Eemurl3xjv/WXYFvMA=="
-    },
-    "is-arrayish": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
-      "dev": true
-    },
-    "is-buffer": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
-      "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
-    },
-    "is-extglob": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
-      "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
-      "dev": true
-    },
-    "is-fullwidth-code-point": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-      "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
-    },
-    "is-glob": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
-      "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
-      "dev": true,
-      "requires": {
-        "is-extglob": "^2.1.1"
-      }
-    },
-    "is-number": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
-      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "dev": true
-    },
-    "is-plain-obj": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
-      "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
-      "dev": true
-    },
-    "is-promise": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
-      "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o="
-    },
-    "is-retry-allowed": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz",
-      "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==",
-      "dev": true
-    },
-    "is-stream": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
-    },
-    "is-typedarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
-      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
-    },
-    "is-wsl": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
-      "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
-    },
-    "isexe": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
-    },
-    "isomorphic-fetch": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
-      "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
-      "requires": {
-        "node-fetch": "^1.0.1",
-        "whatwg-fetch": ">=0.10.0"
-      }
-    },
-    "istanbul-lib-coverage": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
-      "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
-      "dev": true
-    },
-    "istanbul-lib-hook": {
-      "version": "2.0.7",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz",
-      "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==",
-      "dev": true,
-      "requires": {
-        "append-transform": "^1.0.0"
-      }
-    },
-    "istanbul-lib-instrument": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
-      "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
-      "dev": true,
-      "requires": {
-        "@babel/generator": "^7.4.0",
-        "@babel/parser": "^7.4.3",
-        "@babel/template": "^7.4.0",
-        "@babel/traverse": "^7.4.3",
-        "@babel/types": "^7.4.0",
-        "istanbul-lib-coverage": "^2.0.5",
-        "semver": "^6.0.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "istanbul-lib-report": {
-      "version": "2.0.8",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
-      "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
-      "dev": true,
-      "requires": {
-        "istanbul-lib-coverage": "^2.0.5",
-        "make-dir": "^2.1.0",
-        "supports-color": "^6.1.0"
-      },
-      "dependencies": {
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        },
-        "supports-color": {
-          "version": "6.1.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
-          "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        }
-      }
-    },
-    "istanbul-lib-source-maps": {
-      "version": "3.0.6",
-      "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
-      "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
-      "dev": true,
-      "requires": {
-        "debug": "^4.1.1",
-        "istanbul-lib-coverage": "^2.0.5",
-        "make-dir": "^2.1.0",
-        "rimraf": "^2.6.3",
-        "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        },
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
-      }
-    },
-    "istanbul-reports": {
-      "version": "2.2.7",
-      "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz",
-      "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==",
-      "dev": true,
-      "requires": {
-        "html-escaper": "^2.0.0"
-      }
-    },
-    "js-sha3": {
-      "version": "0.8.0",
-      "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
-      "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
-    },
-    "js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
-      "dev": true
-    },
-    "js-yaml": {
-      "version": "3.13.1",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
-      "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
-      "requires": {
-        "argparse": "^1.0.7",
-        "esprima": "^4.0.0"
-      }
-    },
-    "jsesc": {
-      "version": "2.5.2",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
-      "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
-      "dev": true
-    },
-    "json-parse-better-errors": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
-      "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
-      "dev": true
-    },
-    "json-schema-traverse": {
-      "version": "0.4.1",
-      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
-      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
-    },
-    "json-stable-stringify-without-jsonify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
-      "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
-      "dev": true
-    },
-    "jsonfile": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
-      "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
-      "requires": {
-        "graceful-fs": "^4.1.6"
-      }
-    },
-    "levn": {
-      "version": "0.3.0",
-      "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
-      "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2"
-      }
-    },
-    "lines-and-columns": {
-      "version": "1.1.6",
-      "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
-      "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
-      "dev": true
-    },
-    "load-json-file": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-6.2.0.tgz",
-      "integrity": "sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.15",
-        "parse-json": "^5.0.0",
-        "strip-bom": "^4.0.0",
-        "type-fest": "^0.6.0"
-      },
-      "dependencies": {
-        "parse-json": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz",
-          "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==",
-          "dev": true,
-          "requires": {
-            "@babel/code-frame": "^7.0.0",
-            "error-ex": "^1.3.1",
-            "json-parse-better-errors": "^1.0.1",
-            "lines-and-columns": "^1.1.6"
-          }
-        }
-      }
-    },
-    "locate-path": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
-      "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
-      "dev": true,
-      "requires": {
-        "p-locate": "^4.1.0"
-      }
-    },
-    "lodash": {
-      "version": "4.17.15",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
-      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
-    },
-    "lodash._reinterpolate": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
-      "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0="
-    },
-    "lodash.camelcase": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
-      "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=",
-      "dev": true
-    },
-    "lodash.flattendeep": {
-      "version": "4.4.0",
-      "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
-      "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
-      "dev": true
-    },
-    "lodash.get": {
-      "version": "4.4.2",
-      "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
-      "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
-      "dev": true
-    },
-    "lodash.kebabcase": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz",
-      "integrity": "sha1-hImxyw0p/4gZXM7KRI/21swpXDY=",
-      "dev": true
-    },
-    "lodash.snakecase": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
-      "integrity": "sha1-OdcUo1NXFHg3rv1ktdy7Fr7Nj40=",
-      "dev": true
-    },
-    "lodash.template": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz",
-      "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==",
-      "requires": {
-        "lodash._reinterpolate": "^3.0.0",
-        "lodash.templatesettings": "^4.0.0"
-      }
-    },
-    "lodash.templatesettings": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz",
-      "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==",
-      "requires": {
-        "lodash._reinterpolate": "^3.0.0"
-      }
-    },
-    "lodash.upperfirst": {
-      "version": "4.3.1",
-      "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz",
-      "integrity": "sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=",
-      "dev": true
-    },
-    "lodash.zip": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/lodash.zip/-/lodash.zip-4.2.0.tgz",
-      "integrity": "sha1-7GZi5IlkCO1KtsVCo5kLcswIACA=",
-      "dev": true
-    },
-    "lru-cache": {
-      "version": "4.1.5",
-      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
-      "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
-      "dev": true,
-      "requires": {
-        "pseudomap": "^1.0.2",
-        "yallist": "^2.1.2"
-      }
-    },
-    "lru-queue": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
-      "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=",
-      "requires": {
-        "es5-ext": "~0.10.2"
-      }
-    },
-    "make-dir": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz",
-      "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==",
-      "dev": true,
-      "requires": {
-        "semver": "^6.0.0"
-      },
-      "dependencies": {
-        "semver": {
-          "version": "6.3.0",
-          "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-          "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
-          "dev": true
-        }
-      }
-    },
-    "make-error": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
-      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
-      "dev": true
-    },
-    "md5.js": {
-      "version": "1.3.5",
-      "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
-      "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
-      "requires": {
-        "hash-base": "^3.0.0",
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.1.2"
-      }
-    },
-    "memoizee": {
-      "version": "0.4.14",
-      "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz",
-      "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==",
-      "requires": {
-        "d": "1",
-        "es5-ext": "^0.10.45",
-        "es6-weak-map": "^2.0.2",
-        "event-emitter": "^0.3.5",
-        "is-promise": "^2.1",
-        "lru-queue": "0.1",
-        "next-tick": "1",
-        "timers-ext": "^0.1.5"
-      }
-    },
-    "merge-source-map": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
-      "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
-      "dev": true,
-      "requires": {
-        "source-map": "^0.6.1"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
-      }
-    },
-    "merge2": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz",
-      "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==",
-      "dev": true
-    },
-    "micromatch": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
-      "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
-      "dev": true,
-      "requires": {
-        "braces": "^3.0.1",
-        "picomatch": "^2.0.5"
-      }
-    },
-    "mimic-fn": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
-      "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
-    },
-    "minimalistic-assert": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
-      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
-    },
-    "minimalistic-crypto-utils": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
-      "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo="
-    },
-    "minimatch": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
-      "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
-      "dev": true,
-      "requires": {
-        "brace-expansion": "^1.1.7"
-      }
-    },
-    "minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
-      "dev": true
-    },
-    "mkdirp": {
-      "version": "0.5.5",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
-      "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
-      "dev": true,
-      "requires": {
-        "minimist": "^1.2.5"
-      }
-    },
-    "mkdirp-classic": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz",
-      "integrity": "sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g==",
-      "dev": true
-    },
-    "mocha": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz",
-      "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==",
-      "dev": true,
-      "requires": {
-        "browser-stdout": "1.3.1",
-        "commander": "2.15.1",
-        "debug": "3.1.0",
-        "diff": "3.5.0",
-        "escape-string-regexp": "1.0.5",
-        "glob": "7.1.2",
-        "growl": "1.10.5",
-        "he": "1.1.1",
-        "minimatch": "3.0.4",
-        "mkdirp": "0.5.1",
-        "supports-color": "5.4.0"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
-          "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
-          "dev": true,
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "glob": {
-          "version": "7.1.2",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
-          "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
-          "dev": true,
-          "requires": {
-            "fs.realpath": "^1.0.0",
-            "inflight": "^1.0.4",
-            "inherits": "2",
-            "minimatch": "^3.0.4",
-            "once": "^1.3.0",
-            "path-is-absolute": "^1.0.0"
-          }
-        },
-        "minimist": {
-          "version": "0.0.8",
-          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
-          "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
-          "dev": true
-        },
-        "mkdirp": {
-          "version": "0.5.1",
-          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
-          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
-          "dev": true,
-          "requires": {
-            "minimist": "0.0.8"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
-          "dev": true
-        },
-        "supports-color": {
-          "version": "5.4.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
-          "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==",
-          "dev": true,
-          "requires": {
-            "has-flag": "^3.0.0"
-          }
-        }
-      }
-    },
-    "mock-stdin": {
-      "version": "0.3.1",
-      "resolved": "https://registry.npmjs.org/mock-stdin/-/mock-stdin-0.3.1.tgz",
-      "integrity": "sha1-xlfZZC2QeGQ1xkyl6Zu9TQm9fdM=",
-      "dev": true
-    },
-    "moment": {
-      "version": "2.24.0",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
-      "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
-    },
-    "ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
-    },
-    "mute-stream": {
-      "version": "0.0.8",
-      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
-      "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
-    },
-    "nan": {
-      "version": "2.14.0",
-      "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
-      "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
-    },
-    "natural-compare": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
-      "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
-      "dev": true
-    },
-    "natural-orderby": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-2.0.3.tgz",
-      "integrity": "sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q=="
-    },
-    "nested-error-stacks": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz",
-      "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==",
-      "dev": true
-    },
-    "next-tick": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
-      "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
-    },
-    "nice-try": {
-      "version": "1.0.5",
-      "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
-      "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
-    },
-    "node-fetch": {
-      "version": "1.7.3",
-      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
-      "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
-      "requires": {
-        "encoding": "^0.1.11",
-        "is-stream": "^1.0.1"
-      }
-    },
-    "normalize-package-data": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
-      "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
-      "dev": true,
-      "requires": {
-        "hosted-git-info": "^2.1.4",
-        "resolve": "^1.10.0",
-        "semver": "2 || 3 || 4 || 5",
-        "validate-npm-package-license": "^3.0.1"
-      }
-    },
-    "npm-run-path": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
-      "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
-      "dev": true,
-      "requires": {
-        "path-key": "^2.0.0"
-      }
-    },
-    "nyc": {
-      "version": "14.1.1",
-      "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz",
-      "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==",
-      "dev": true,
-      "requires": {
-        "archy": "^1.0.0",
-        "caching-transform": "^3.0.2",
-        "convert-source-map": "^1.6.0",
-        "cp-file": "^6.2.0",
-        "find-cache-dir": "^2.1.0",
-        "find-up": "^3.0.0",
-        "foreground-child": "^1.5.6",
-        "glob": "^7.1.3",
-        "istanbul-lib-coverage": "^2.0.5",
-        "istanbul-lib-hook": "^2.0.7",
-        "istanbul-lib-instrument": "^3.3.0",
-        "istanbul-lib-report": "^2.0.8",
-        "istanbul-lib-source-maps": "^3.0.6",
-        "istanbul-reports": "^2.2.4",
-        "js-yaml": "^3.13.1",
-        "make-dir": "^2.1.0",
-        "merge-source-map": "^1.1.0",
-        "resolve-from": "^4.0.0",
-        "rimraf": "^2.6.3",
-        "signal-exit": "^3.0.2",
-        "spawn-wrap": "^1.4.2",
-        "test-exclude": "^5.2.3",
-        "uuid": "^3.3.2",
-        "yargs": "^13.2.2",
-        "yargs-parser": "^13.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
-          "requires": {
-            "locate-path": "^3.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
-          "requires": {
-            "p-locate": "^3.0.0",
-            "path-exists": "^3.0.0"
-          }
-        },
-        "make-dir": {
-          "version": "2.1.0",
-          "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
-          "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
-          "dev": true,
-          "requires": {
-            "pify": "^4.0.1",
-            "semver": "^5.6.0"
-          }
-        },
-        "p-locate": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
-          "requires": {
-            "p-limit": "^2.0.0"
-          }
-        },
-        "path-exists": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
-        }
-      }
-    },
-    "once": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
-      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
-      "dev": true,
-      "requires": {
-        "wrappy": "1"
-      }
-    },
-    "onetime": {
-      "version": "5.1.0",
-      "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
-      "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
-      "requires": {
-        "mimic-fn": "^2.1.0"
-      }
-    },
-    "optionator": {
-      "version": "0.8.3",
-      "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
-      "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
-      "dev": true,
-      "requires": {
-        "deep-is": "~0.1.3",
-        "fast-levenshtein": "~2.0.6",
-        "levn": "~0.3.0",
-        "prelude-ls": "~1.1.2",
-        "type-check": "~0.3.2",
-        "word-wrap": "~1.2.3"
-      }
-    },
-    "os-homedir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
-      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
-      "dev": true
-    },
-    "os-tmpdir": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
-      "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
-    },
-    "p-finally": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
-      "dev": true
-    },
-    "p-limit": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
-      "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
-      "dev": true,
-      "requires": {
-        "p-try": "^2.0.0"
-      }
-    },
-    "p-locate": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
-      "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
-      "dev": true,
-      "requires": {
-        "p-limit": "^2.2.0"
-      }
-    },
-    "p-try": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
-      "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
-      "dev": true
-    },
-    "package-hash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz",
-      "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==",
-      "dev": true,
-      "requires": {
-        "graceful-fs": "^4.1.15",
-        "hasha": "^3.0.0",
-        "lodash.flattendeep": "^4.4.0",
-        "release-zalgo": "^1.0.0"
-      }
-    },
-    "parent-module": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
-      "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
-      "dev": true,
-      "requires": {
-        "callsites": "^3.0.0"
-      }
-    },
-    "parse-json": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
-      "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
-      "dev": true,
-      "requires": {
-        "error-ex": "^1.3.1",
-        "json-parse-better-errors": "^1.0.1"
-      }
-    },
-    "password-prompt": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/password-prompt/-/password-prompt-1.1.2.tgz",
-      "integrity": "sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA==",
-      "requires": {
-        "ansi-escapes": "^3.1.0",
-        "cross-spawn": "^6.0.5"
-      }
-    },
-    "path-exists": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
-      "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
-      "dev": true
-    },
-    "path-is-absolute": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
-      "dev": true
-    },
-    "path-is-inside": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
-      "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
-      "dev": true
-    },
-    "path-key": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
-      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
-    },
-    "path-parse": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
-      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
-      "dev": true
-    },
-    "path-type": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
-      "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
-      "dev": true
-    },
-    "pathval": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
-      "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
-      "dev": true
-    },
-    "pbkdf2": {
-      "version": "3.0.17",
-      "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
-      "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==",
-      "requires": {
-        "create-hash": "^1.1.2",
-        "create-hmac": "^1.1.4",
-        "ripemd160": "^2.0.1",
-        "safe-buffer": "^5.0.1",
-        "sha.js": "^2.4.8"
-      }
-    },
-    "picomatch": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
-      "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
-      "dev": true
-    },
-    "pify": {
-      "version": "4.0.1",
-      "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
-      "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
-      "dev": true
-    },
-    "pkg-dir": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
-      "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
-      "dev": true,
-      "requires": {
-        "find-up": "^4.0.0"
-      }
-    },
-    "prelude-ls": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
-      "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
-      "dev": true
-    },
-    "progress": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
-      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
-      "dev": true
-    },
-    "proper-lockfile": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.1.tgz",
-      "integrity": "sha512-1w6rxXodisVpn7QYvLk706mzprPTAPCYAqxMvctmPN3ekuRk/kuGkGc82pangZiAt4R3lwSuUzheTTn0/Yb7Zg==",
-      "requires": {
-        "graceful-fs": "^4.1.11",
-        "retry": "^0.12.0",
-        "signal-exit": "^3.0.2"
-      }
-    },
-    "pseudomap": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
-      "dev": true
-    },
-    "pump": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
-      "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
-      "dev": true,
-      "requires": {
-        "end-of-stream": "^1.1.0",
-        "once": "^1.3.1"
-      }
-    },
-    "punycode": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
-      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
-    },
-    "qqjs": {
-      "version": "0.3.11",
-      "resolved": "https://registry.npmjs.org/qqjs/-/qqjs-0.3.11.tgz",
-      "integrity": "sha512-pB2X5AduTl78J+xRSxQiEmga1jQV0j43jOPs/MTgTLApGFEOn6NgdE2dEjp7nvDtjkIOZbvFIojAiYUx6ep3zg==",
-      "dev": true,
-      "requires": {
-        "chalk": "^2.4.1",
-        "debug": "^4.1.1",
-        "execa": "^0.10.0",
-        "fs-extra": "^6.0.1",
-        "get-stream": "^5.1.0",
-        "glob": "^7.1.2",
-        "globby": "^10.0.1",
-        "http-call": "^5.1.2",
-        "load-json-file": "^6.2.0",
-        "pkg-dir": "^4.2.0",
-        "tar-fs": "^2.0.0",
-        "tmp": "^0.1.0",
-        "write-json-file": "^4.1.1"
-      },
-      "dependencies": {
-        "fs-extra": {
-          "version": "6.0.1",
-          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
-          "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
-          "dev": true,
-          "requires": {
-            "graceful-fs": "^4.1.2",
-            "jsonfile": "^4.0.0",
-            "universalify": "^0.1.0"
-          }
-        }
-      }
-    },
-    "ramda": {
-      "version": "0.26.1",
-      "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
-      "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
-      "dev": true
-    },
-    "randombytes": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
-      "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
-      "requires": {
-        "safe-buffer": "^5.1.0"
-      }
-    },
-    "read-pkg": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
-      "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
-      "dev": true,
-      "requires": {
-        "load-json-file": "^4.0.0",
-        "normalize-package-data": "^2.3.2",
-        "path-type": "^3.0.0"
-      },
-      "dependencies": {
-        "load-json-file": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
-          "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
-          "dev": true,
-          "requires": {
-            "graceful-fs": "^4.1.2",
-            "parse-json": "^4.0.0",
-            "pify": "^3.0.0",
-            "strip-bom": "^3.0.0"
-          }
-        },
-        "path-type": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
-          "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
-          "dev": true,
-          "requires": {
-            "pify": "^3.0.0"
-          }
-        },
-        "pify": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-          "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
-          "dev": true
-        },
-        "strip-bom": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
-          "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
-          "dev": true
-        }
-      }
-    },
-    "read-pkg-up": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz",
-      "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==",
-      "dev": true,
-      "requires": {
-        "find-up": "^3.0.0",
-        "read-pkg": "^3.0.0"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
-          "requires": {
-            "locate-path": "^3.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
-          "requires": {
-            "p-locate": "^3.0.0",
-            "path-exists": "^3.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
-          "requires": {
-            "p-limit": "^2.0.0"
-          }
-        },
-        "path-exists": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
-        }
-      }
-    },
-    "readable-stream": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
-      "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
-      "dev": true,
-      "requires": {
-        "inherits": "^2.0.3",
-        "string_decoder": "^1.1.1",
-        "util-deprecate": "^1.0.1"
-      }
-    },
-    "redeyed": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-2.1.1.tgz",
-      "integrity": "sha1-iYS1gV2ZyyIEacme7v/jiRPmzAs=",
-      "requires": {
-        "esprima": "~4.0.0"
-      }
-    },
-    "regenerator-runtime": {
-      "version": "0.13.5",
-      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz",
-      "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA=="
-    },
-    "regexpp": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
-      "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
-      "dev": true
-    },
-    "release-zalgo": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
-      "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=",
-      "dev": true,
-      "requires": {
-        "es6-error": "^4.0.1"
-      }
-    },
-    "replace-ext": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz",
-      "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs="
-    },
-    "require-directory": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
-      "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
-      "dev": true
-    },
-    "require-main-filename": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
-      "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
-      "dev": true
-    },
-    "resolve": {
-      "version": "1.15.1",
-      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz",
-      "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==",
-      "dev": true,
-      "requires": {
-        "path-parse": "^1.0.6"
-      }
-    },
-    "resolve-from": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
-      "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
-      "dev": true
-    },
-    "restore-cursor": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
-      "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
-      "requires": {
-        "onetime": "^5.1.0",
-        "signal-exit": "^3.0.2"
-      }
-    },
-    "ret": {
-      "version": "0.1.15",
-      "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
-      "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
-      "dev": true
-    },
-    "retry": {
-      "version": "0.12.0",
-      "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
-      "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs="
-    },
-    "reusify": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
-      "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
-      "dev": true
-    },
-    "rimraf": {
-      "version": "2.7.1",
-      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
-      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
-      "dev": true,
-      "requires": {
-        "glob": "^7.1.3"
-      }
-    },
-    "ripemd160": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
-      "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
-      "requires": {
-        "hash-base": "^3.0.0",
-        "inherits": "^2.0.1"
-      }
-    },
-    "run-async": {
-      "version": "2.4.0",
-      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.0.tgz",
-      "integrity": "sha512-xJTbh/d7Lm7SBhc1tNvTpeCHaEzoyxPrqNlvSdMfBTYwaY++UJFyXUOxAtsRUXjlqOfj8luNaR9vjCh4KeV+pg==",
-      "requires": {
-        "is-promise": "^2.1.0"
-      }
-    },
-    "run-parallel": {
-      "version": "1.1.9",
-      "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
-      "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
-      "dev": true
-    },
-    "rxjs": {
-      "version": "6.5.5",
-      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.5.tgz",
-      "integrity": "sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ==",
-      "requires": {
-        "tslib": "^1.9.0"
-      }
-    },
-    "safe-buffer": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
-      "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
-    },
-    "safe-regex": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
-      "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
-      "dev": true,
-      "requires": {
-        "ret": "~0.1.10"
-      }
-    },
-    "safer-buffer": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
-      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
-    },
-    "secp256k1": {
-      "version": "3.8.0",
-      "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.0.tgz",
-      "integrity": "sha512-k5ke5avRZbtl9Tqx/SA7CbY3NF6Ro+Sj9cZxezFzuBlLDmyqPiL8hJJ+EmzD8Ig4LUDByHJ3/iPOVoRixs/hmw==",
-      "requires": {
-        "bindings": "^1.5.0",
-        "bip66": "^1.1.5",
-        "bn.js": "^4.11.8",
-        "create-hash": "^1.2.0",
-        "drbg.js": "^1.0.1",
-        "elliptic": "^6.5.2",
-        "nan": "^2.14.0",
-        "safe-buffer": "^5.1.2"
-      }
-    },
-    "semver": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
-      "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="
-    },
-    "set-blocking": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
-      "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
-      "dev": true
-    },
-    "sha.js": {
-      "version": "2.4.11",
-      "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
-      "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
-      "requires": {
-        "inherits": "^2.0.1",
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "shebang-command": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
-      "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
-      "requires": {
-        "shebang-regex": "^1.0.0"
-      }
-    },
-    "shebang-regex": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
-      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
-    },
-    "signal-exit": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
-      "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
-    },
-    "slash": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
-      "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
-      "dev": true
-    },
-    "slice-ansi": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
-      "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
-      "dev": true,
-      "requires": {
-        "ansi-styles": "^3.2.0",
-        "astral-regex": "^1.0.0",
-        "is-fullwidth-code-point": "^2.0.0"
-      }
-    },
-    "slug": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/slug/-/slug-2.1.1.tgz",
-      "integrity": "sha512-yNGhDdS0DR0JyxnPC84qIx/Vd01RHVY4guJeBqBNdBoOLNWnzw5zkWJvxVSmsuUb92bikdnQFnw3PfGY8uZ82g==",
-      "requires": {
-        "unicode": ">= 0.3.1"
-      }
-    },
-    "sort-keys": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-4.0.0.tgz",
-      "integrity": "sha512-hlJLzrn/VN49uyNkZ8+9b+0q9DjmmYcYOnbMQtpkLrYpPwRApDPZfmqbUfJnAA3sb/nRib+nDot7Zi/1ER1fuA==",
-      "dev": true,
-      "requires": {
-        "is-plain-obj": "^2.0.0"
-      }
-    },
-    "source-map": {
-      "version": "0.5.7",
-      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
-      "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
-      "dev": true
-    },
-    "source-map-support": {
-      "version": "0.5.16",
-      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz",
-      "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==",
-      "dev": true,
-      "requires": {
-        "buffer-from": "^1.0.0",
-        "source-map": "^0.6.0"
-      },
-      "dependencies": {
-        "source-map": {
-          "version": "0.6.1",
-          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
-          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
-          "dev": true
-        }
-      }
-    },
-    "spawn-wrap": {
-      "version": "1.4.3",
-      "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
-      "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==",
-      "dev": true,
-      "requires": {
-        "foreground-child": "^1.5.6",
-        "mkdirp": "^0.5.0",
-        "os-homedir": "^1.0.1",
-        "rimraf": "^2.6.2",
-        "signal-exit": "^3.0.2",
-        "which": "^1.3.0"
-      }
-    },
-    "spdx-correct": {
-      "version": "3.1.0",
-      "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
-      "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
-      "dev": true,
-      "requires": {
-        "spdx-expression-parse": "^3.0.0",
-        "spdx-license-ids": "^3.0.0"
-      }
-    },
-    "spdx-exceptions": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
-      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
-      "dev": true
-    },
-    "spdx-expression-parse": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
-      "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
-      "dev": true,
-      "requires": {
-        "spdx-exceptions": "^2.1.0",
-        "spdx-license-ids": "^3.0.0"
-      }
-    },
-    "spdx-license-ids": {
-      "version": "3.0.5",
-      "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
-      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
-      "dev": true
-    },
-    "sprintf-js": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
-      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
-    },
-    "stdout-stderr": {
-      "version": "0.1.13",
-      "resolved": "https://registry.npmjs.org/stdout-stderr/-/stdout-stderr-0.1.13.tgz",
-      "integrity": "sha512-Xnt9/HHHYfjZ7NeQLvuQDyL1LnbsbddgMFKCuaQKwGCdJm8LnstZIXop+uOY36UR1UXXoHXfMbC1KlVdVd2JLA==",
-      "dev": true,
-      "requires": {
-        "debug": "^4.1.1",
-        "strip-ansi": "^6.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
-          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
-          "dev": true
-        },
-        "strip-ansi": {
-          "version": "6.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
-          "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
-          "dev": true,
-          "requires": {
-            "ansi-regex": "^5.0.0"
-          }
-        }
-      }
-    },
-    "string-width": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
-      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
-      "requires": {
-        "is-fullwidth-code-point": "^2.0.0",
-        "strip-ansi": "^4.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
-        },
-        "strip-ansi": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
-          "requires": {
-            "ansi-regex": "^3.0.0"
-          }
-        }
-      }
-    },
-    "string_decoder": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
-      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
-      "dev": true,
-      "requires": {
-        "safe-buffer": "~5.2.0"
-      }
-    },
-    "strip-ansi": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
-      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
-      "requires": {
-        "ansi-regex": "^4.1.0"
-      }
-    },
-    "strip-bom": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
-      "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
-      "dev": true
-    },
-    "strip-eof": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
-      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
-      "dev": true
-    },
-    "strip-json-comments": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-      "dev": true
-    },
-    "supports-color": {
-      "version": "5.5.0",
-      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
-      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
-      "requires": {
-        "has-flag": "^3.0.0"
-      }
-    },
-    "supports-hyperlinks": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz",
-      "integrity": "sha512-HHi5kVSefKaJkGYXbDuKbUGRVxqnWGn3J2e39CYcNJEfWciGq2zYtOhXLTlvrOZW1QU7VX67w7fMmWafHX9Pfw==",
-      "requires": {
-        "has-flag": "^2.0.0",
-        "supports-color": "^5.0.0"
-      },
-      "dependencies": {
-        "has-flag": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
-          "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
-        }
-      }
-    },
-    "table": {
-      "version": "5.4.6",
-      "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
-      "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
-      "dev": true,
-      "requires": {
-        "ajv": "^6.10.2",
-        "lodash": "^4.17.14",
-        "slice-ansi": "^2.1.0",
-        "string-width": "^3.0.0"
-      },
-      "dependencies": {
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "dev": true,
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        }
-      }
-    },
-    "tar-fs": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz",
-      "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==",
-      "dev": true,
-      "requires": {
-        "chownr": "^1.1.1",
-        "mkdirp-classic": "^0.5.2",
-        "pump": "^3.0.0",
-        "tar-stream": "^2.0.0"
-      }
-    },
-    "tar-stream": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.2.tgz",
-      "integrity": "sha512-UaF6FoJ32WqALZGOIAApXx+OdxhekNMChu6axLJR85zMMjXKWFGjbIRe+J6P4UnRGg9rAwWvbTT0oI7hD/Un7Q==",
-      "dev": true,
-      "requires": {
-        "bl": "^4.0.1",
-        "end-of-stream": "^1.4.1",
-        "fs-constants": "^1.0.0",
-        "inherits": "^2.0.3",
-        "readable-stream": "^3.1.1"
-      }
-    },
-    "test-exclude": {
-      "version": "5.2.3",
-      "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
-      "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==",
-      "dev": true,
-      "requires": {
-        "glob": "^7.1.3",
-        "minimatch": "^3.0.4",
-        "read-pkg-up": "^4.0.0",
-        "require-main-filename": "^2.0.0"
-      }
-    },
-    "text-table": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
-      "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
-      "dev": true
-    },
-    "through": {
-      "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
-    },
-    "timers-ext": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz",
-      "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==",
-      "requires": {
-        "es5-ext": "~0.10.46",
-        "next-tick": "1"
-      }
-    },
-    "tmp": {
-      "version": "0.1.0",
-      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
-      "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
-      "dev": true,
-      "requires": {
-        "rimraf": "^2.6.3"
-      }
-    },
-    "to-fast-properties": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
-      "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
-      "dev": true
-    },
-    "to-regex-range": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
-      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "dev": true,
-      "requires": {
-        "is-number": "^7.0.0"
-      }
-    },
-    "treeify": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz",
-      "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A=="
-    },
-    "ts-node": {
-      "version": "8.8.2",
-      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.8.2.tgz",
-      "integrity": "sha512-duVj6BpSpUpD/oM4MfhO98ozgkp3Gt9qIp3jGxwU2DFvl/3IRaEAvbLa8G60uS7C77457e/m5TMowjedeRxI1Q==",
-      "dev": true,
-      "requires": {
-        "arg": "^4.1.0",
-        "diff": "^4.0.1",
-        "make-error": "^1.1.1",
-        "source-map-support": "^0.5.6",
-        "yn": "3.1.1"
-      },
-      "dependencies": {
-        "diff": {
-          "version": "4.0.2",
-          "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
-          "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
-          "dev": true
-        }
-      }
-    },
-    "tslib": {
-      "version": "1.11.1",
-      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
-      "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA=="
-    },
-    "tsutils": {
-      "version": "3.17.1",
-      "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz",
-      "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==",
-      "dev": true,
-      "requires": {
-        "tslib": "^1.8.1"
-      }
-    },
-    "tunnel-agent": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
-      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
-      "dev": true,
-      "requires": {
-        "safe-buffer": "^5.0.1"
-      }
-    },
-    "tweetnacl": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
-      "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
-    },
-    "type": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
-      "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
-    },
-    "type-check": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
-      "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
-      "dev": true,
-      "requires": {
-        "prelude-ls": "~1.1.2"
-      }
-    },
-    "type-detect": {
-      "version": "4.0.8",
-      "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
-      "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
-      "dev": true
-    },
-    "type-fest": {
-      "version": "0.6.0",
-      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
-      "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
-      "dev": true
-    },
-    "typedarray-to-buffer": {
-      "version": "3.1.5",
-      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
-      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
-      "requires": {
-        "is-typedarray": "^1.0.0"
-      }
-    },
-    "typescript": {
-      "version": "3.8.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz",
-      "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==",
-      "dev": true
-    },
-    "unicode": {
-      "version": "12.1.0",
-      "resolved": "https://registry.npmjs.org/unicode/-/unicode-12.1.0.tgz",
-      "integrity": "sha512-Ty6+Ew21DiYTWLYtd05RF/X4c1ekOvOgANyHbBj0h3MaXpfaGr2Rdmc0hMFuGQLyPLb9cU4ArNxl0bTF5HSzXw=="
-    },
-    "unist-util-stringify-position": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
-      "integrity": "sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==",
-      "requires": {
-        "@types/unist": "^2.0.2"
-      }
-    },
-    "universalify": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
-      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
-    },
-    "unorm": {
-      "version": "1.6.0",
-      "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
-      "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA=="
-    },
-    "uri-js": {
-      "version": "4.2.2",
-      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
-      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
-      "requires": {
-        "punycode": "^2.1.0"
-      }
-    },
-    "util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
-      "dev": true
-    },
-    "uuid": {
-      "version": "3.4.0",
-      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
-      "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
-      "dev": true
-    },
-    "validate-npm-package-license": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
-      "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
-      "dev": true,
-      "requires": {
-        "spdx-correct": "^3.0.0",
-        "spdx-expression-parse": "^3.0.0"
-      }
-    },
-    "vfile": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/vfile/-/vfile-4.1.0.tgz",
-      "integrity": "sha512-BaTPalregj++64xbGK6uIlsurN3BCRNM/P2Pg8HezlGzKd1O9PrwIac6bd9Pdx2uTb0QHoioZ+rXKolbVXEgJg==",
-      "requires": {
-        "@types/unist": "^2.0.0",
-        "is-buffer": "^2.0.0",
-        "replace-ext": "1.0.0",
-        "unist-util-stringify-position": "^2.0.0",
-        "vfile-message": "^2.0.0"
-      }
-    },
-    "vfile-message": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.4.tgz",
-      "integrity": "sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ==",
-      "requires": {
-        "@types/unist": "^2.0.0",
-        "unist-util-stringify-position": "^2.0.0"
-      }
-    },
-    "websocket": {
-      "version": "1.0.31",
-      "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.31.tgz",
-      "integrity": "sha512-VAouplvGKPiKFDTeCCO65vYHsyay8DqoBSlzIO3fayrfOgU94lQN5a1uWVnFrMLceTJw/+fQXR5PGbUVRaHshQ==",
-      "requires": {
-        "debug": "^2.2.0",
-        "es5-ext": "^0.10.50",
-        "nan": "^2.14.0",
-        "typedarray-to-buffer": "^3.1.5",
-        "yaeti": "^0.0.6"
-      },
-      "dependencies": {
-        "debug": {
-          "version": "2.6.9",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-          "requires": {
-            "ms": "2.0.0"
-          }
-        },
-        "ms": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
-        }
-      }
-    },
-    "whatwg-fetch": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
-      "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
-    },
-    "which": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
-      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
-      "requires": {
-        "isexe": "^2.0.0"
-      }
-    },
-    "which-module": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
-      "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
-      "dev": true
-    },
-    "widest-line": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz",
-      "integrity": "sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==",
-      "requires": {
-        "string-width": "^2.1.1"
-      }
-    },
-    "word-wrap": {
-      "version": "1.2.3",
-      "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
-      "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
-      "dev": true
-    },
-    "wrap-ansi": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-4.0.0.tgz",
-      "integrity": "sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==",
-      "requires": {
-        "ansi-styles": "^3.2.0",
-        "string-width": "^2.1.1",
-        "strip-ansi": "^4.0.0"
-      },
-      "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
-        },
-        "strip-ansi": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
-          "requires": {
-            "ansi-regex": "^3.0.0"
-          }
-        }
-      }
-    },
-    "wrappy": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
-      "dev": true
-    },
-    "write": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
-      "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
-      "dev": true,
-      "requires": {
-        "mkdirp": "^0.5.1"
-      }
-    },
-    "write-file-atomic": {
-      "version": "3.0.3",
-      "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
-      "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
-      "dev": true,
-      "requires": {
-        "imurmurhash": "^0.1.4",
-        "is-typedarray": "^1.0.0",
-        "signal-exit": "^3.0.2",
-        "typedarray-to-buffer": "^3.1.5"
-      }
-    },
-    "write-json-file": {
-      "version": "4.3.0",
-      "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-4.3.0.tgz",
-      "integrity": "sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ==",
-      "dev": true,
-      "requires": {
-        "detect-indent": "^6.0.0",
-        "graceful-fs": "^4.1.15",
-        "is-plain-obj": "^2.0.0",
-        "make-dir": "^3.0.0",
-        "sort-keys": "^4.0.0",
-        "write-file-atomic": "^3.0.0"
-      }
-    },
-    "xxhashjs": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
-      "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
-      "requires": {
-        "cuint": "^0.2.2"
-      }
-    },
-    "y18n": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
-      "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
-      "dev": true
-    },
-    "yaeti": {
-      "version": "0.0.6",
-      "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
-      "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
-    },
-    "yallist": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-      "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
-      "dev": true
-    },
-    "yargs": {
-      "version": "13.3.2",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
-      "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
-      "dev": true,
-      "requires": {
-        "cliui": "^5.0.0",
-        "find-up": "^3.0.0",
-        "get-caller-file": "^2.0.1",
-        "require-directory": "^2.1.1",
-        "require-main-filename": "^2.0.0",
-        "set-blocking": "^2.0.0",
-        "string-width": "^3.0.0",
-        "which-module": "^2.0.0",
-        "y18n": "^4.0.0",
-        "yargs-parser": "^13.1.2"
-      },
-      "dependencies": {
-        "find-up": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
-          "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
-          "dev": true,
-          "requires": {
-            "locate-path": "^3.0.0"
-          }
-        },
-        "locate-path": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
-          "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
-          "dev": true,
-          "requires": {
-            "p-locate": "^3.0.0",
-            "path-exists": "^3.0.0"
-          }
-        },
-        "p-locate": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
-          "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
-          "dev": true,
-          "requires": {
-            "p-limit": "^2.0.0"
-          }
-        },
-        "path-exists": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
-          "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
-          "dev": true
-        },
-        "string-width": {
-          "version": "3.1.0",
-          "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
-          "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
-          "dev": true,
-          "requires": {
-            "emoji-regex": "^7.0.1",
-            "is-fullwidth-code-point": "^2.0.0",
-            "strip-ansi": "^5.1.0"
-          }
-        }
-      }
-    },
-    "yargs-parser": {
-      "version": "13.1.2",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
-      "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
-      "dev": true,
-      "requires": {
-        "camelcase": "^5.0.0",
-        "decamelize": "^1.2.0"
-      }
-    },
-    "yn": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
-      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
-      "dev": true
-    }
-  }
-}

+ 19 - 9
cli/package.json

@@ -1,28 +1,31 @@
 {
-  "name": "joystream-cli",
+  "name": "@joystream/cli",
   "description": "Command Line Interface for Joystream community and governance activities",
-  "version": "0.0.0",
+  "version": "0.1.0",
   "author": "Leszek Wiesner",
   "bin": {
     "joystream-cli": "./bin/run"
   },
-  "bugs": "https://github.com/Joystream/substrate-runtime-joystream/issues",
+  "bugs": "https://github.com/Joystream/joystream/issues",
   "dependencies": {
     "@joystream/types": "^0.13.0",
     "@oclif/command": "^1.5.19",
     "@oclif/config": "^1.14.0",
+    "@oclif/plugin-autocomplete": "^0.2.0",
     "@oclif/plugin-help": "^2.2.3",
+    "@oclif/plugin-not-found": "^1.2.4",
+    "@oclif/plugin-warn-if-update-available": "^1.7.0",
     "@polkadot/api": "^0.96.1",
     "@types/inquirer": "^6.5.0",
     "@types/proper-lockfile": "^4.1.1",
     "@types/slug": "^0.9.1",
+    "ajv": "^6.11.0",
     "cli-ux": "^5.4.5",
     "inquirer": "^7.1.0",
     "moment": "^2.24.0",
     "proper-lockfile": "^4.1.1",
     "slug": "^2.1.1",
-    "tslib": "^1.11.1",
-    "ajv": "^6.11.0"
+    "tslib": "^1.11.1"
   },
   "devDependencies": {
     "@oclif/dev-cli": "^1.22.2",
@@ -45,24 +48,31 @@
     "node": ">=12.18.0",
     "yarn": "^1.22.0"
   },
+  "publishConfig": {
+    "access": "public",
+    "registry": "https://registry.npmjs.org"
+  },
   "files": [
     "/bin",
     "/lib",
     "/npm-shrinkwrap.json",
     "/oclif.manifest.json"
   ],
-  "homepage": "https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli",
+  "homepage": "https://github.com/Joystream/joystream/blob/master/cli",
   "keywords": [
     "oclif"
   ],
-  "license": "MIT",
+  "license": "GPL-3.0-only",
   "main": "lib/index.js",
   "oclif": {
     "repositoryPrefix": "<%- repo %>/blob/master/cli/<%- commandPath %>",
     "commands": "./lib/commands",
     "bin": "joystream-cli",
     "plugins": [
-      "@oclif/plugin-help"
+      "@oclif/plugin-help",
+      "@oclif/plugin-autocomplete",
+      "@oclif/plugin-not-found",
+      "@oclif/plugin-warn-if-update-available"
     ],
     "topics": {
       "council": {
@@ -81,7 +91,7 @@
   },
   "repository": {
     "type": "git",
-    "url": "https://github.com/Joystream/substrate-runtime-joystream",
+    "url": "https://github.com/Joystream/joystream",
     "directory": "cli"
   },
   "scripts": {

+ 37 - 4
cli/src/Api.ts

@@ -3,7 +3,7 @@ import { registerJoystreamTypes } from '@joystream/types/'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { QueryableStorageMultiArg } from '@polkadot/api/types'
 import { formatBalance } from '@polkadot/util'
-import { Hash, Balance, Moment } from '@polkadot/types/interfaces'
+import { Hash, Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { Codec } from '@polkadot/types/types'
 import { Option, Vec } from '@polkadot/types'
@@ -20,6 +20,9 @@ import {
   GroupOpeningStage,
   GroupOpening,
   GroupApplication,
+  openingPolicyUnstakingPeriodsKeys,
+  UnstakingPeriods,
+  StakingPolicyUnstakingPeriodKey,
 } from './Types'
 import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'
 import { CLIError } from '@oclif/errors'
@@ -38,6 +41,7 @@ import {
   ApplicationStageKeys,
   ApplicationId,
   OpeningId,
+  StakingPolicy,
 } from '@joystream/types/hiring'
 import { MemberId, Membership } from '@joystream/types/members'
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
@@ -46,7 +50,7 @@ import { LinkageResult } from '@polkadot/types/codec/Linkage'
 
 import { InputValidationLengthConstraint } from '@joystream/types/common'
 
-export const DEFAULT_API_URI = 'wss://rome-rpc-endpoint.joystream.org:9944/'
+export const DEFAULT_API_URI = 'ws://localhost:9944/'
 const DEFAULT_DECIMALS = new u32(12)
 
 // Mapping of working group to api module
@@ -402,11 +406,39 @@ export default class Api {
     const applications = await this.groupOpeningApplications(group, wgOpeningId)
     const stage = await this.parseOpeningStage(opening.stage)
     const type = groupOpening.opening_type
+    const { application_staking_policy: applSP, role_staking_policy: roleSP } = opening
     const stakes = {
-      application: opening.application_staking_policy.unwrapOr(undefined),
-      role: opening.role_staking_policy.unwrapOr(undefined),
+      application: applSP.unwrapOr(undefined),
+      role: roleSP.unwrapOr(undefined),
     }
 
+    const unstakingPeriod = (period: Option<BlockNumber>) => period.unwrapOr(new BN(0)).toNumber()
+    const spUnstakingPeriod = (sp: Option<StakingPolicy>, key: StakingPolicyUnstakingPeriodKey) =>
+      sp.isSome ? unstakingPeriod(sp.unwrap()[key]) : 0
+
+    const unstakingPeriods: Partial<UnstakingPeriods> = {
+      ['review_period_expired_application_stake_unstaking_period_length']: spUnstakingPeriod(
+        applSP,
+        'review_period_expired_unstaking_period_length'
+      ),
+      ['crowded_out_application_stake_unstaking_period_length']: spUnstakingPeriod(
+        applSP,
+        'crowded_out_unstaking_period_length'
+      ),
+      ['review_period_expired_role_stake_unstaking_period_length']: spUnstakingPeriod(
+        roleSP,
+        'review_period_expired_unstaking_period_length'
+      ),
+      ['crowded_out_role_stake_unstaking_period_length']: spUnstakingPeriod(
+        roleSP,
+        'crowded_out_unstaking_period_length'
+      ),
+    }
+
+    openingPolicyUnstakingPeriodsKeys.forEach((key) => {
+      unstakingPeriods[key] = unstakingPeriod(groupOpening.policy_commitment[key])
+    })
+
     return {
       wgOpeningId,
       openingId,
@@ -415,6 +447,7 @@ export default class Api {
       stakes,
       applications,
       type,
+      unstakingPeriods: unstakingPeriods as UnstakingPeriods,
     }
   }
 

+ 30 - 0
cli/src/Types.ts

@@ -142,6 +142,35 @@ export type GroupOpeningStakes = {
   role?: StakingPolicy
 }
 
+export const stakingPolicyUnstakingPeriodKeys = [
+  'crowded_out_unstaking_period_length',
+  'review_period_expired_unstaking_period_length',
+] as const
+
+export type StakingPolicyUnstakingPeriodKey = typeof stakingPolicyUnstakingPeriodKeys[number]
+
+export const openingPolicyUnstakingPeriodsKeys = [
+  'fill_opening_failed_applicant_application_stake_unstaking_period',
+  'fill_opening_failed_applicant_role_stake_unstaking_period',
+  'fill_opening_successful_applicant_application_stake_unstaking_period',
+  'terminate_application_stake_unstaking_period',
+  'terminate_role_stake_unstaking_period',
+  'exit_role_application_stake_unstaking_period',
+  'exit_role_stake_unstaking_period',
+] as const
+
+export type OpeningPolicyUnstakingPeriodsKey = typeof openingPolicyUnstakingPeriodsKeys[number]
+export type UnstakingPeriodsKey =
+  | OpeningPolicyUnstakingPeriodsKey
+  | 'crowded_out_application_stake_unstaking_period_length'
+  | 'crowded_out_role_stake_unstaking_period_length'
+  | 'review_period_expired_application_stake_unstaking_period_length'
+  | 'review_period_expired_role_stake_unstaking_period_length'
+
+export type UnstakingPeriods = {
+  [k in UnstakingPeriodsKey]: number
+}
+
 export type GroupOpening = {
   wgOpeningId: number
   openingId: number
@@ -150,6 +179,7 @@ export type GroupOpening = {
   stakes: GroupOpeningStakes
   applications: GroupApplication[]
   type: OpeningType
+  unstakingPeriods: UnstakingPeriods
 }
 
 // Some helper structs for generating human_readable_text in working group opening extrinsic

+ 11 - 3
cli/src/base/AccountsCommandBase.ts

@@ -140,14 +140,22 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
   async getRequiredSelectedAccount(promptIfMissing = true): Promise<NamedKeyringPair> {
     let selectedAccount: NamedKeyringPair | null = this.getSelectedAccount()
     if (!selectedAccount) {
-      this.warn('No default account selected! Use account:choose to set the default account!')
-      if (!promptIfMissing) this.exit(ExitCodes.NoAccountSelected)
+      if (!promptIfMissing) {
+        this.error('No default account selected! Use account:choose to set the default account.', {
+          exit: ExitCodes.NoAccountSelected,
+        })
+      }
+
       const accounts: NamedKeyringPair[] = this.fetchAccounts()
       if (!accounts.length) {
-        this.error('There are no accounts available!', { exit: ExitCodes.NoAccountFound })
+        this.error('No accounts available! Use account:import in order to import accounts into the CLI.', {
+          exit: ExitCodes.NoAccountFound,
+        })
       }
 
+      this.warn('No default account selected!')
       selectedAccount = await this.promptForAccount(accounts)
+      await this.setSelectedAccount(selectedAccount)
     }
 
     return selectedAccount

+ 60 - 5
cli/src/base/ApiCommandBase.ts

@@ -5,7 +5,7 @@ import Api from '../Api'
 import { getTypeDef, createType, Option, Tuple, Bytes } from '@polkadot/types'
 import { Codec, TypeDef, TypeDefInfo, Constructor } from '@polkadot/types/types'
 import { Vec, Struct, Enum } from '@polkadot/types/codec'
-import { ApiPromise } from '@polkadot/api'
+import { ApiPromise, WsProvider } from '@polkadot/api'
 import { KeyringPair } from '@polkadot/keyring/types'
 import chalk from 'chalk'
 import { SubmittableResultImpl } from '@polkadot/api/types'
@@ -20,6 +20,7 @@ class ExtrinsicFailedError extends Error {}
  */
 export default abstract class ApiCommandBase extends StateAwareCommandBase {
   private api: Api | null = null
+  forceSkipApiUriPrompt = false
 
   getApi(): Api {
     if (!this.api) throw new CLIError('Tried to get API before initialization.', { exit: ExitCodes.ApiError })
@@ -33,10 +34,60 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
   async init() {
     await super.init()
-    const apiUri: string = this.getPreservedState().apiUri
+    let apiUri: string = this.getPreservedState().apiUri
+    if (!apiUri) {
+      this.warn("You haven't provided a node/endpoint for the CLI to connect to yet!")
+      apiUri = await this.promptForApiUri()
+    }
     this.api = await Api.create(apiUri)
   }
 
+  async promptForApiUri(): Promise<string> {
+    let selectedNodeUri = await this.simplePrompt({
+      type: 'list',
+      message: 'Choose a node/endpoint:',
+      choices: [
+        {
+          name: 'Local node (ws://localhost:9944)',
+          value: 'ws://localhost:9944',
+        },
+        {
+          name: 'Current Testnet official Joystream node (wss://rome-rpc-endpoint.joystream.org:9944/)',
+          value: 'wss://rome-rpc-endpoint.joystream.org:9944/',
+        },
+        {
+          name: 'Custom endpoint',
+          value: '',
+        },
+      ],
+    })
+
+    if (!selectedNodeUri) {
+      do {
+        selectedNodeUri = await this.simplePrompt({
+          type: 'input',
+          message: 'Provide a WS endpoint uri',
+        })
+        if (!this.isApiUriValid(selectedNodeUri)) {
+          this.warn('Provided uri seems incorrect! Please try again...')
+        }
+      } while (!this.isApiUriValid(selectedNodeUri))
+    }
+
+    await this.setPreservedState({ apiUri: selectedNodeUri })
+
+    return selectedNodeUri
+  }
+
+  isApiUriValid(uri: string) {
+    try {
+      new WsProvider(uri)
+    } catch (e) {
+      return false
+    }
+    return true
+  }
+
   // This is needed to correctly handle some structs, enums etc.
   // Where the main typeDef doesn't provide enough information
   protected getRawTypeDef(type: string) {
@@ -67,11 +118,15 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
   // Prompt for simple/plain value (provided as string) of given type
   async promptForSimple(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Codec> {
+    // If no default provided - get default value resulting from providing empty string
+    const defaultValueString =
+      paramOptions?.value?.default?.toString() || createType(typeDef.type as any, '').toString()
     const providedValue = await this.simplePrompt({
       message: `Provide value for ${this.paramName(typeDef)}`,
       type: 'input',
-      // If not default provided - show default value resulting from providing empty string
-      default: paramOptions?.value?.default?.toString() || createType(typeDef.type as any, '').toString(),
+      // We want to avoid showing default value like '0x', because it falsely suggests
+      // that user needs to provide the value as hex
+      default: (defaultValueString === '0x' ? '' : defaultValueString) || undefined,
       validate: paramOptions?.validator,
     })
     return createType(typeDef.type as any, providedValue)
@@ -137,8 +192,8 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       const fieldOptions = paramOptions?.nestedOptions && paramOptions.nestedOptions[subtype.name!]
       const fieldDefaultValue = fieldOptions?.value?.default || (structDefault && structDefault.get(subtype.name!))
       const finalFieldOptions: ApiParamOptions = {
-        ...fieldOptions,
         forcedName: subtype.name,
+        ...fieldOptions, // "forcedName" above should be overriden with "fieldOptions.forcedName" if available
         value: fieldDefaultValue && { ...fieldOptions?.value, default: fieldDefaultValue },
       }
       structValues[subtype.name!] = await this.promptForParam(subtype.type, finalFieldOptions)

+ 5 - 4
cli/src/base/StateAwareCommandBase.ts

@@ -2,10 +2,10 @@ import fs from 'fs'
 import path from 'path'
 import ExitCodes from '../ExitCodes'
 import { CLIError } from '@oclif/errors'
-import { DEFAULT_API_URI } from '../Api'
 import lockFile from 'proper-lockfile'
 import DefaultCommandBase from './DefaultCommandBase'
 import os from 'os'
+import _ from 'lodash'
 
 // Type for the state object (which is preserved as json in the state file)
 type StateObject = {
@@ -16,7 +16,7 @@ type StateObject = {
 // State object default values
 const DEFAULT_STATE: StateObject = {
   selectedAccountFilename: '',
-  apiUri: DEFAULT_API_URI,
+  apiUri: '',
 }
 
 // State file path (relative to getAppDataPath())
@@ -48,7 +48,7 @@ export default abstract class StateAwareCommandBase extends DefaultCommandBase {
     if (!packageJson || !packageJson.name) {
       throw new CLIError('Cannot get package name from package.json!')
     }
-    return path.join(systemAppDataPath, packageJson.name)
+    return path.join(systemAppDataPath, _.kebabCase(packageJson.name))
   }
 
   getStateFilePath(): string {
@@ -93,7 +93,8 @@ export default abstract class StateAwareCommandBase extends DefaultCommandBase {
   getPreservedState(): StateObject {
     let preservedState: StateObject
     try {
-      preservedState = require(this.getStateFilePath()) as StateObject
+      // Use readFileSync instead of "require" in order to always get a "fresh" state
+      preservedState = JSON.parse(fs.readFileSync(this.getStateFilePath()).toString()) as StateObject
     } catch (e) {
       throw this.createDataReadError()
     }

+ 3 - 0
cli/src/base/WorkingGroupsCommandBase.ts

@@ -18,6 +18,7 @@ import fs from 'fs'
 import path from 'path'
 import _ from 'lodash'
 import { ApplicationStageKeys } from '@joystream/types/hiring'
+import chalk from 'chalk'
 
 const DEFAULT_GROUP = WorkingGroups.StorageProviders
 const DRAFTS_FOLDER = 'opening-drafts'
@@ -267,5 +268,7 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
       })
     }
     this.group = flags.group as WorkingGroups
+
+    this.log(chalk.white('Group: ' + flags.group))
   }
 }

+ 20 - 11
cli/src/commands/api/setUri.ts

@@ -1,28 +1,37 @@
-import StateAwareCommandBase from '../../base/StateAwareCommandBase'
 import chalk from 'chalk'
-import { WsProvider } from '@polkadot/api'
+import ApiCommandBase from '../../base/ApiCommandBase'
 import ExitCodes from '../../ExitCodes'
 
 type ApiSetUriArgs = { uri: string }
 
-export default class ApiSetUri extends StateAwareCommandBase {
+export default class ApiSetUri extends ApiCommandBase {
   static description = 'Set api WS provider uri'
   static args = [
     {
       name: 'uri',
-      required: true,
-      description: 'Uri of the node api WS provider',
+      required: false,
+      description: 'Uri of the node api WS provider (if skipped, a prompt will be displayed)',
     },
   ]
 
+  async init() {
+    this.forceSkipApiUriPrompt = true
+    super.init()
+  }
+
   async run() {
     const args: ApiSetUriArgs = this.parse(ApiSetUri).args as ApiSetUriArgs
-    try {
-      new WsProvider(args.uri)
-    } catch (e) {
-      this.error('The WS provider uri seems to be incorrect', { exit: ExitCodes.InvalidInput })
+    let newUri = ''
+    if (args.uri) {
+      if (this.isApiUriValid(args.uri)) {
+        await this.setPreservedState({ apiUri: args.uri })
+        newUri = args.uri
+      } else {
+        this.error('Provided uri seems to be incorrect!', { exit: ExitCodes.InvalidInput })
+      }
+    } else {
+      newUri = await this.promptForApiUri()
     }
-    await this.setPreservedState({ apiUri: args.uri })
-    this.log(chalk.greenBright('Api uri successfuly changed! New uri: ') + chalk.white(args.uri))
+    this.log(chalk.greenBright('Api uri successfuly changed! New uri: ') + chalk.white(newUri))
   }
 }

+ 9 - 1
cli/src/commands/working-groups/opening.ts

@@ -1,7 +1,7 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { displayTable, displayCollapsedRow, displayHeader } from '../../helpers/display'
 import _ from 'lodash'
-import { OpeningStatus, GroupOpeningStage, GroupOpeningStakes } from '../../Types'
+import { OpeningStatus, GroupOpeningStage, GroupOpeningStakes, UnstakingPeriodsKey } from '../../Types'
 import { StakingAmountLimitModeKeys, StakingPolicy } from '@joystream/types/hiring'
 import { formatBalance } from '@polkadot/util'
 import chalk from 'chalk'
@@ -65,6 +65,14 @@ export default class WorkingGroupsOpening extends WorkingGroupsCommandBase {
     }
     displayCollapsedRow(openingRow)
 
+    displayHeader('Unstaking periods')
+    const periodsRow: { [k: string]: string } = {}
+    for (const key of Object.keys(opening.unstakingPeriods).sort()) {
+      const displayKey = _.startCase(key) + ':  '
+      periodsRow[displayKey] = opening.unstakingPeriods[key as UnstakingPeriodsKey].toLocaleString() + ' blocks'
+    }
+    displayCollapsedRow(periodsRow)
+
     displayHeader(`Applications (${opening.applications.length})`)
     const applicationsRows = opening.applications.map((a) => ({
       'WG appl. ID': a.wgApplicationId,

+ 0 - 1
cli/src/commands/working-groups/openings.ts

@@ -13,7 +13,6 @@ export default class WorkingGroupsOpenings extends WorkingGroupsCommandBase {
 
     const openingsRows = openings.map((o) => ({
       'WG Opening ID': o.wgOpeningId,
-      'Opening ID': o.openingId,
       Type: o.type.type,
       Stage: `${_.startCase(o.stage.status)}${o.stage.block ? ` (#${o.stage.block})` : ''}`,
       Applications: o.applications.length,

+ 10 - 1
cli/src/commands/working-groups/overview.ts

@@ -25,16 +25,25 @@ export default class WorkingGroupsOverview extends WorkingGroupsCommandBase {
       this.log(chalk.yellow('No lead assigned!'))
     }
 
+    const accounts = this.fetchAccounts()
+
     displayHeader('Members')
     const membersRows = members.map((m) => ({
-      '': lead?.workerId.eq(m.workerId) ? '\u{2B50}' : '', // A nice star for the lead
       'Worker id': m.workerId.toString(),
       'Member id': m.memberId.toString(),
       'Member handle': m.profile.handle.toString(),
       Stake: formatBalance(m.stake),
       Earned: formatBalance(m.reward?.totalRecieved),
       'Role account': shortAddress(m.roleAccount),
+      '':
+        (lead?.workerId.eq(m.workerId) ? '\u{2B50}' : '  ') +
+        ' ' +
+        (accounts.some((a) => a.address === m.roleAccount.toString()) ? '\u{1F511}' : '  '),
     }))
     displayTable(membersRows, 5)
+
+    displayHeader('Legend')
+    this.log('\u{2B50} - Leader')
+    this.log('\u{1F511} - Role key available in CLI')
   }
 }

+ 13 - 0
cli/src/promptOptions/addWorkerOpening.ts

@@ -17,6 +17,19 @@ class OpeningPolicyCommitmentOptions implements ApiParamsOptions {
       locked: true,
     },
   }
+  // Rename fields containing "curator" (solivg minor UI issue related to flat namespace)
+  public terminate_curator_application_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'terminate_application_stake_unstaking_period',
+  }
+  public terminate_curator_role_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'terminate_role_stake_unstaking_period',
+  }
+  public exit_curator_role_application_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'exit_role_application_stake_unstaking_period',
+  }
+  public exit_curator_role_stake_unstaking_period: ApiParamOptions = {
+    forcedName: 'exit_role_stake_unstaking_period',
+  }
 }
 
 class AddWrokerOpeningOptions implements ApiParamsOptions {

+ 11 - 6
devops/ansible/build-and-run-tests-single-node-playbook.yml

@@ -1,12 +1,17 @@
-- name: install dependencies
-  import_playbook: install-dependencies-playbook.yml
-
 - hosts: 127.0.0.1
   user: root
   become: yes
   become_method: sudo
+
   tasks:
+    - name: install dependencies
+      include_role:
+        name: install_dependencies
+
     - name: build node
-      include: build-image-tasklist.yml
-- name: run tests
-  import_playbook: run-tests-single-node-playbook.yml
+      include_role:
+        name: build_docker_image
+        
+    - name: run tests
+      include_role:
+        name: run_tests_single_node

+ 17 - 0
devops/ansible/build-and-run-tests-two-nodes-playbook.yml

@@ -0,0 +1,17 @@
+- hosts: 127.0.0.1
+  user: root
+  become: yes
+  become_method: sudo
+
+  tasks:
+    - name: install dependencies
+      include_role:
+        name: install_dependencies
+
+    - name: build node
+      include_role:
+        name: build_docker_image
+        
+    - name: run tests
+      include_role:
+        name: run_tests_two_nodes

+ 13 - 0
devops/ansible/build-image-playbook.yml

@@ -0,0 +1,13 @@
+- hosts: 127.0.0.1
+  user: root
+  become: yes
+  become_method: sudo
+
+  tasks:
+    - name: install dependencies
+      include_role:
+        name: install_dependencies
+
+    - name: build node
+      include_role:
+        name: build_docker_image

+ 0 - 15
devops/ansible/install-dependencies-playbook.yml

@@ -1,15 +0,0 @@
-- hosts: 127.0.0.1
-  user: root
-  become: yes
-  become_method: sudo
-  tasks:
-    - name: install pip
-      apt: name=python-pip state=present
-    - name: install docker
-      pip: name=docker
-    - name: Install yarn with npm
-      npm:
-        name: yarn
-        global: yes
-    - name: Install docker compose
-      pip: name=docker-compose

+ 0 - 0
devops/ansible/build-image-tasklist.yml → devops/ansible/roles/build_docker_image/tasks/main.yml


+ 35 - 0
devops/ansible/roles/install_dependencies/tasks/main.yml

@@ -0,0 +1,35 @@
+- name: install pip on Debian
+  block:
+    - name: install pip using apt
+      apt: name=python-pip state=present
+  when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
+
+- name: install pip on Mac
+  block:
+    - name: create temporary folder
+      file:
+        path: ../../.tmp
+        state: directory
+    - name: get pip installer using curl
+      get_url: 
+        url: https://bootstrap.pypa.io/get-pip.py
+        dest: ../../.tmp/get-pip.py
+    - name: install pip
+      shell: python ../../.tmp/get-pip.py
+  when: ansible_distribution == 'MacOSX'
+  always: 
+    - name: remove pip installer script
+      file: 
+        path: ../../.tmp/get-pip.py
+        state: absent
+
+- name: install docker
+  pip: name=docker
+
+- name: Install yarn with npm
+  npm:
+    name: yarn
+    global: yes
+
+- name: Install docker compose
+  pip: name=docker-compose

+ 22 - 0
devops/ansible/roles/run_tests_single_node/tasks/main.yml

@@ -0,0 +1,22 @@
+- name: run network
+  block:
+
+    - name: run docker container
+      docker_container:
+        name: "joystream-node-testing"
+        image: "joystream/node-testing"
+        ports:
+          - "9944:9944"
+        entrypoint: ./node --chain=chainspec.json --alice --validator --ws-external --rpc-cors=all
+        state: started
+
+    - name: execute network tests
+      shell: yarn debug >> ../../.tmp/tests.log
+      args:
+        chdir: ../../tests/network-tests/
+        
+  always:
+    - name: stop docker container
+      docker_container:
+        name: "joystream-node-testing"
+        state: absent

+ 18 - 0
devops/ansible/roles/run_tests_two_nodes/tasks/main.yml

@@ -0,0 +1,18 @@
+- name: run network
+  block:
+
+    - name: run two nodes containerized network
+      docker_compose:
+        project_src: ./
+        state: present
+
+    - name: execute network tests
+      shell: yarn test >> ../../.tmp/tests.log
+      args:
+        chdir: ../../tests/network-tests/
+        
+  always:
+    - name: stop containers
+      docker_compose:
+        project_src: ./
+        state: absent

+ 0 - 24
devops/ansible/run-tests-single-node-playbook.yml

@@ -1,24 +0,0 @@
-- hosts: 127.0.0.1
-  user: root
-  become: yes
-  become_method: sudo
-  tasks:
-    - name: run network
-      block:
-        - name: run docker container
-          docker_container:
-            name: "joystream-node-testing"
-            image: "joystream/node-testing"
-            ports:
-              - "9944:9944"
-            entrypoint: ./node --chain=chainspec.json --alice --validator --ws-external --rpc-cors=all
-            state: started
-        - name: execute network tests
-          shell: yarn debug >> ../../.tmp/tests.log
-          args:
-            chdir: ../../tests/network-tests/
-      always:
-        - name: stop docker container
-          docker_container:
-            name: "joystream-node-testing"
-            state: absent

+ 0 - 24
devops/ansible/run-tests-two-nodes-playbook.yml

@@ -1,24 +0,0 @@
-- name: install dependencies
-  import_playbook: install-dependencies-playbook.yml
-
-- hosts: 127.0.0.1
-  user: root
-  become: yes
-  become_method: sudo
-
-  tasks:
-    - name: run network
-      block:
-        - name: run two nodes containerized network
-          docker_compose:
-            project_src: ./
-            state: present
-        - name: execute network tests
-          shell: yarn test >> ../../.tmp/tests.log
-          args:
-            chdir: ../../tests/network-tests/
-      always:
-        - name: stop containers
-          docker_compose:
-            project_src: ./
-            state: absent

+ 1 - 1
pioneer/packages/apps/public/locales/en/ui.json

@@ -632,7 +632,7 @@
   "My Requests": "",
   "Working groups": "",
   "Opportunities": "",
-  "My roles": "",
+  "My roles and applications": "",
   "My channels": "",
   "New channel": "",
   "My videos": "",

+ 12 - 1
pioneer/packages/joy-proposals/src/Proposal/ProposalPreview.tsx

@@ -5,6 +5,7 @@ import { ParsedProposal } from '@polkadot/joy-utils/types/proposals';
 import { getExtendedStatus } from './ProposalDetails';
 import { BlockNumber } from '@polkadot/types/interfaces';
 import styled from 'styled-components';
+import ReactMarkdown from 'react-markdown';
 
 import './Proposal.css';
 
@@ -17,6 +18,12 @@ const ProposalIdBox = styled.div`
   font-size: 1.1em;
 `;
 
+const ProposalDesc = styled.div`
+  padding: 0.5rem 1rem;
+  border: 1px solid #ddd;
+  border-radius: 0.25rem;
+`;
+
 export type ProposalPreviewProps = {
   proposal: ParsedProposal;
   bestNumber?: BlockNumber;
@@ -33,7 +40,11 @@ export default function ProposalPreview ({ proposal, bestNumber }: ProposalPrevi
         <Card.Header>
           <Header as="h1">{proposal.title}</Header>
         </Card.Header>
-        <Card.Description>{proposal.description}</Card.Description>
+        <Card.Description>
+          <ProposalDesc>
+            <ReactMarkdown source={proposal.description} linkTarget='_blank' />
+          </ProposalDesc>
+        </Card.Description>
         <Details proposal={proposal} extendedStatus={extendedStatus} />
       </Card.Content>
     </Card>

+ 50 - 94
pioneer/packages/joy-proposals/src/Proposal/ProposalPreviewList.tsx

@@ -1,54 +1,16 @@
 import React, { useState } from 'react';
-import { Button, Card, Container, Icon } from 'semantic-ui-react';
+import { Button, Card, Container, Icon, Pagination } from 'semantic-ui-react';
 import styled from 'styled-components';
 import { Link, useLocation } from 'react-router-dom';
 
 import ProposalPreview from './ProposalPreview';
-import { ParsedProposal } from '@polkadot/joy-utils/types/proposals';
+import { ParsedProposal, proposalStatusFilters, ProposalStatusFilter, ProposalsBatch } from '@polkadot/joy-utils/types/proposals';
 import { useTransport, usePromise } from '@polkadot/joy-utils/react/hooks';
 import { PromiseComponent } from '@polkadot/joy-utils/react/components';
 import { withCalls } from '@polkadot/react-api';
 import { BlockNumber } from '@polkadot/types/interfaces';
 import { Dropdown } from '@polkadot/react-components';
 
-const filters = ['All', 'Active', 'Canceled', 'Approved', 'Rejected', 'Slashed', 'Expired'] as const;
-
-type ProposalFilter = typeof filters[number];
-
-function filterProposals (filter: ProposalFilter, proposals: ParsedProposal[]) {
-  if (filter === 'All') {
-    return proposals;
-  } else if (filter === 'Active') {
-    return proposals.filter((prop: ParsedProposal) => {
-      const [activeOrFinalized] = Object.keys(prop.status);
-      return activeOrFinalized === 'Active';
-    });
-  }
-
-  return proposals.filter((prop: ParsedProposal) => {
-    if (prop.status.Finalized == null || prop.status.Finalized.proposalStatus == null) {
-      return false;
-    }
-
-    const [finalStatus] = Object.keys(prop.status.Finalized.proposalStatus);
-    return finalStatus === filter;
-  });
-}
-
-function mapFromProposals (proposals: ParsedProposal[]) {
-  const proposalsMap = new Map<ProposalFilter, ParsedProposal[]>();
-
-  proposalsMap.set('All', proposals);
-  proposalsMap.set('Canceled', filterProposals('Canceled', proposals));
-  proposalsMap.set('Active', filterProposals('Active', proposals));
-  proposalsMap.set('Approved', filterProposals('Approved', proposals));
-  proposalsMap.set('Rejected', filterProposals('Rejected', proposals));
-  proposalsMap.set('Slashed', filterProposals('Slashed', proposals));
-  proposalsMap.set('Expired', filterProposals('Expired', proposals));
-
-  return proposalsMap;
-}
-
 type ProposalPreviewListProps = {
   bestNumber?: BlockNumber;
 };
@@ -59,56 +21,35 @@ const FilterContainer = styled.div`
   justify-content: space-between;
   margin-bottom: 1.75rem;
 `;
-const FilterOption = styled.span`
-  display: inline-flex;
-  align-items: center;
-`;
-const ProposalFilterCountBadge = styled.span`
-  background-color: rgba(0, 0, 0, .3);
-  color: #fff;
-
-  border-radius: 10px;
-  height: 19px;
-  min-width: 19px;
-  padding: 0 4px;
-
-  font-size: .8rem;
-  font-weight: 500;
-  line-height: 1;
-
-  display: flex;
-  align-items: center;
-  justify-content: center;
-
-  margin-left: 6px;
-`;
 const StyledDropdown = styled(Dropdown)`
   .dropdown {
     width: 200px;
   }
 `;
+const PaginationBox = styled.div`
+  margin-bottom: 1em;
+`;
 
 function ProposalPreviewList ({ bestNumber }: ProposalPreviewListProps) {
   const { pathname } = useLocation();
   const transport = useTransport();
-  const [proposals, error, loading] = usePromise<ParsedProposal[]>(() => transport.proposals.proposals(), []);
-  const [activeFilter, setActiveFilter] = useState<ProposalFilter>('All');
-
-  const proposalsMap = mapFromProposals(proposals);
-  const filteredProposals = proposalsMap.get(activeFilter) as ParsedProposal[];
-  const sortedProposals = filteredProposals.sort((p1, p2) => p2.id.cmp(p1.id));
+  const [activeFilter, setActiveFilter] = useState<ProposalStatusFilter>('All');
+  const [currentPage, setCurrentPage] = useState<number>(1);
+  const [proposalsBatch, error, loading] = usePromise<ProposalsBatch | undefined>(
+    () => transport.proposals.proposalsBatch(activeFilter, currentPage),
+    undefined,
+    [activeFilter, currentPage]
+  );
 
-  const filterOptions = filters.map(filter => ({
-    text: (
-      <FilterOption>
-        {filter}
-        <ProposalFilterCountBadge>{(proposalsMap.get(filter) as ParsedProposal[]).length}</ProposalFilterCountBadge>
-      </FilterOption>
-    ),
+  const filterOptions = proposalStatusFilters.map(filter => ({
+    text: filter,
     value: filter
   }));
 
-  const _onChangePrefix = (f: ProposalFilter) => setActiveFilter(f);
+  const _onChangePrefix = (f: ProposalStatusFilter) => {
+    setCurrentPage(1);
+    setActiveFilter(f);
+  };
 
   return (
     <Container className="Proposal" fluid>
@@ -117,25 +58,40 @@ function ProposalPreviewList ({ bestNumber }: ProposalPreviewListProps) {
           <Icon name="add" />
           New proposal
         </Button>
-        {!loading && (
-          <StyledDropdown
-            label="Proposal state"
-            options={filterOptions}
-            value={activeFilter}
-            onChange={_onChangePrefix}
-          />
-        )}
+        <StyledDropdown
+          label="Proposal state"
+          options={filterOptions}
+          value={activeFilter}
+          onChange={_onChangePrefix}
+        />
       </FilterContainer>
       <PromiseComponent error={ error } loading={ loading } message="Fetching proposals...">
-        {
-          sortedProposals.length ? (
-            <Card.Group>
-              {sortedProposals.map((prop: ParsedProposal, idx: number) => (
-                <ProposalPreview key={`${prop.title}-${idx}`} proposal={prop} bestNumber={bestNumber} />
-              ))}
-            </Card.Group>
-          ) : `There are currently no ${activeFilter !== 'All' ? activeFilter.toLocaleLowerCase() : 'submitted'} proposals.`
-        }
+        { proposalsBatch && (<>
+          <PaginationBox>
+            { proposalsBatch.totalBatches > 1 && (
+              <Pagination
+                activePage={ currentPage }
+                ellipsisItem={{ content: <Icon name='ellipsis horizontal' />, icon: true }}
+                firstItem={{ content: <Icon name='angle double left' />, icon: true }}
+                lastItem={{ content: <Icon name='angle double right' />, icon: true }}
+                prevItem={{ content: <Icon name='angle left' />, icon: true }}
+                nextItem={{ content: <Icon name='angle right' />, icon: true }}
+                totalPages={ proposalsBatch.totalBatches }
+                onPageChange={ (e, data) => setCurrentPage((data.activePage && parseInt(data.activePage.toString())) || 1) }
+              />
+            ) }
+          </PaginationBox>
+           { proposalsBatch.proposals.length
+             ? (
+               <Card.Group>
+                 {proposalsBatch.proposals.map((prop: ParsedProposal, idx: number) => (
+                   <ProposalPreview key={`${prop.title}-${idx}`} proposal={prop} bestNumber={bestNumber} />
+                 ))}
+               </Card.Group>
+             )
+             : `There are currently no ${activeFilter !== 'All' ? activeFilter.toLocaleLowerCase() : 'submitted'} proposals.`
+           }
+        </>) }
       </PromiseComponent>
     </Container>
   );

+ 3 - 0
pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx

@@ -101,6 +101,9 @@ export const GenericProposalForm: React.FunctionComponent<GenericFormInnerProps>
   const [afterSubmit, setAfterSubmit] = useState(null as (() => () => void) | null);
   const formContainerRef = useRef<HTMLDivElement>(null);
 
+  // Scroll to top on load
+  useEffect(() => { window.scrollTo(0, 0); }, []);
+
   // After-submit effect
   // With current version of Formik, there seems to be no other viable way to handle this (ie. for sendTx)
   useEffect(() => {

+ 1 - 1
pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx

@@ -83,7 +83,7 @@ export const GenericWorkingGroupProposalForm: React.FunctionComponent<FormInnerP
           name="workingGroup"
           placeholder="Select the working group"
           selection
-          options={Object.keys(WorkingGroupDef).map(wgKey => ({ text: wgKey + ' Wroking Group', value: wgKey }))}
+          options={Object.keys(WorkingGroupDef).map(wgKey => ({ text: wgKey + ' Working Group', value: wgKey }))}
           value={values.workingGroup}
           onChange={ handleChange }
         />

+ 91 - 41
pioneer/packages/joy-roles/src/elements.tsx

@@ -1,6 +1,6 @@
 import React, { useEffect, useState } from 'react';
 import moment from 'moment';
-import { Card, Icon, Image, Label, Statistic } from 'semantic-ui-react';
+import { Card, Icon, Image, Label, Statistic, Button } from 'semantic-ui-react';
 import { Link } from 'react-router-dom';
 
 import { Balance } from '@polkadot/types/interfaces';
@@ -10,6 +10,10 @@ import { IMembership, MemberId } from '@joystream/types/members';
 import { GenericAccountId } from '@polkadot/types';
 import { LeadRoleState } from '@joystream/types/content-working-group';
 import { WorkerId } from '@joystream/types/working-group';
+import { WorkingGroups } from './working_groups';
+import { RewardRelationship } from '@joystream/types/recurring-rewards';
+import { formatReward } from '@polkadot/joy-utils/functions/format';
+import styled from 'styled-components';
 
 type BalanceProps = {
   balance?: Balance;
@@ -39,11 +43,13 @@ export function HandleView (props: ProfileProps) {
 
 export type GroupMember = {
   memberId: MemberId;
+  group: WorkingGroups;
+  workerId: number;
   roleAccount: GenericAccountId;
   profile: IMembership;
   title: string;
   stake?: Balance;
-  earned?: Balance;
+  rewardRelationship?: RewardRelationship;
 }
 
 export type GroupLead = {
@@ -53,92 +59,136 @@ export type GroupLead = {
   profile: IMembership;
   title: string;
   stage?: LeadRoleState;
+  stake?: Balance;
+  rewardRelationship?: RewardRelationship;
 }
 
-type inset = {
-  inset?: boolean;
-}
-
-export function GroupLeadView (props: GroupLead & inset) {
-  let fluid = false;
-  if (typeof props.inset !== 'undefined') {
-    fluid = props.inset;
-  }
-
+export function GroupLeadView (props: GroupLead) {
   let avatar = <Identicon value={props.roleAccount.toString()} size={50} />;
   if (typeof props.profile.avatar_uri !== 'undefined' && props.profile.avatar_uri.toString() !== '') {
     avatar = <Image src={props.profile.avatar_uri.toString()} circular className='avatar' />;
   }
 
+  const { stake, rewardRelationship } = props;
+
   return (
-    <Card color='grey' className="staked-card" fluid={fluid}>
+    <Card color='grey' className="staked-card">
       <Card.Content>
         <Image floated='right'>
           {avatar}
         </Image>
         <Card.Header><HandleView profile={props.profile} /></Card.Header>
         <Card.Meta>{props.title}</Card.Meta>
+        <Card.Meta>
+          { props.workerId && (
+            <Label>{ 'Worker ID: ' + props.workerId.toString() }</Label>
+          ) }
+        </Card.Meta>
         <Card.Description>
-          <Label color='teal' ribbon={fluid}>
+          <Label color='teal'>
             <Icon name="shield" />
             { props.title }
             <Label.Detail>{/* ... */}</Label.Detail>
           </Label>
         </Card.Description>
       </Card.Content>
-      {/* <Card.Content extra>
-        <Label>Something about <Label.Detail> the lead </Label.Detail></Label>
-      </Card.Content> */}
+      <GroupMemberDetails {...{ stake, rewardRelationship }} />
     </Card>
   );
 }
 
-export function GroupMemberView (props: GroupMember & inset) {
-  let fluid = false;
-  if (typeof props.inset !== 'undefined') {
-    fluid = props.inset;
-  }
+const StakeAndReward = styled.div`
+  display: grid;
+  grid-template-columns: 1fr;
+  grid-row-gap: 0.25em;
+  margin-bottom: 1em;
+`;
+
+type GroupMemberDetailsProps = {
+  rewardRelationship?: RewardRelationship;
+  stake?: Balance;
+}
 
-  let stake = null;
-  if (typeof props.stake !== 'undefined' && props.stake.toNumber() !== 0) {
-    stake = (
-      <Label color='green' ribbon={fluid}>
+export function GroupMemberDetails (props: GroupMemberDetailsProps) {
+  const [showDetails, setShowDetails] = useState(false);
+  const details: JSX.Element[] = [];
+
+  if (props.stake && props.stake.toNumber() > 0) {
+    details.push(
+      <Label color="green">
         <Icon name="shield" />
         Staked
         <Label.Detail>{formatBalance(props.stake)}</Label.Detail>
       </Label>
     );
+  } else {
+    details.push(
+      <Label>Stake <Label.Detail>NONE</Label.Detail></Label>
+    );
+  }
+
+  if (props.rewardRelationship) {
+    const reward = props.rewardRelationship;
+    details.push(
+      <Label>Reward <Label.Detail>{formatReward(reward)}</Label.Detail></Label>
+    );
+    details.push(
+      <Label>Earned <Label.Detail>{formatBalance(reward.total_reward_received)}</Label.Detail></Label>
+    );
+    details.push(
+      <Label>Missed <Label.Detail>{formatBalance(reward.total_reward_missed)}</Label.Detail></Label>
+    );
+    details.push(
+      <Label>
+        Next payment block:
+        <Label.Detail>{props.rewardRelationship.next_payment_at_block.unwrapOr('NONE').toString()}</Label.Detail>
+      </Label>
+    );
+  } else {
+    details.push(
+      <Label>Reward <Label.Detail>NONE</Label.Detail></Label>
+    );
   }
 
+  return (
+    <Card.Content extra>
+      { showDetails && (
+        <Card.Description>
+          <StakeAndReward>
+            {details.map((detail, index) => <div key={index}>{detail}</div>)}
+          </StakeAndReward>
+        </Card.Description>
+      ) }
+      <Button onClick={ () => setShowDetails(v => !v) } size="tiny" fluid>
+        { showDetails ? 'Hide' : 'Show'} details
+      </Button>
+    </Card.Content>
+  );
+}
+
+export function GroupMemberView (props: GroupMember) {
   let avatar = <Identicon value={props.roleAccount.toString()} size={50} />;
   if (typeof props.profile.avatar_uri !== 'undefined' && props.profile.avatar_uri.toString() !== '') {
     avatar = <Image src={props.profile.avatar_uri.toString()} circular className='avatar' />;
   }
 
-  let earned = null;
-  if (typeof props.earned !== 'undefined' &&
-    props.earned.toNumber() > 0 &&
-    !fluid) {
-    earned = (
-      <Card.Content extra>
-        <Label>Earned <Label.Detail>{formatBalance(props.earned)}</Label.Detail></Label>
-      </Card.Content>
-    );
-  }
+  const { stake, rewardRelationship } = props;
 
   return (
-    <Card color='grey' className="staked-card" fluid={fluid}>
+    <Card color='grey' className="staked-card">
       <Card.Content>
         <Image floated='right'>
           {avatar}
         </Image>
         <Card.Header><HandleView profile={props.profile} /></Card.Header>
         <Card.Meta>{props.title}</Card.Meta>
-        <Card.Description>
-          {stake}
-        </Card.Description>
+        <Card.Meta>
+          <Label>
+            { (props.group === WorkingGroups.ContentCurators ? 'Curator' : 'Worker') + ` ID: ${props.workerId.toString()}` }
+          </Label>
+        </Card.Meta>
       </Card.Content>
-      {earned}
+      <GroupMemberDetails {...{ stake, rewardRelationship }} />
     </Card>
   );
 }

+ 1 - 1
pioneer/packages/joy-roles/src/flows/apply.tsx

@@ -950,7 +950,7 @@ export function DoneStage (props: DoneStageProps) {
       </p>
       <p>
         You can track the progress of your
-        application in the <Link to="#working-group/my-roles">My roles</Link> section. Note that your application is attached
+        application in the <Link to="#working-group/my-roles">My roles and applications</Link> section. Note that your application is attached
         to your role key (see below).  If you have any issues, you can message the group lead in in the <Link to="#forum">Forum</Link> or contact them directly.
       </p>
 

+ 1 - 1
pioneer/packages/joy-roles/src/index.tsx

@@ -73,7 +73,7 @@ export const App: React.FC<Props> = (props: Props) => {
   if (props.myAddress) {
     tabs.push({
       name: 'my-roles',
-      text: t('My roles')
+      text: t('My roles and applications')
     });
   }
 

+ 1 - 1
pioneer/packages/joy-roles/src/tabs.stories.tsx

@@ -27,7 +27,7 @@ export const RolesPage = () => {
   const panes = [
     { menuItem: 'Working groups', render: renderWorkingGroups },
     { menuItem: 'Opportunities', render: renderOpportunitySandbox },
-    { menuItem: 'My roles', render: renderMyRolesSandbox }
+    { menuItem: 'My roles and applications', render: renderMyRolesSandbox }
   ];
 
   return (

+ 2 - 0
pioneer/packages/joy-roles/src/tabs/MyRoles.tsx

@@ -413,6 +413,8 @@ export function Application (props: ApplicationProps) {
         <Label.Detail className="right">
           {openingIcon(props.stage.state)}
           {openingDescription(props.stage.state)}
+          <Icon name="hashtag" style={{ marginLeft: '0.75em' }}/>
+          { props.id }
           <ApplicationLabel>
             {_.startCase(props.meta.group) + (isLeadApplication ? ' Lead' : '')}
           </ApplicationLabel>

+ 4 - 1
pioneer/packages/joy-roles/src/tabs/Opportunities.tsx

@@ -89,6 +89,9 @@ export function OpeningHeader (props: OpeningStage) {
               />
             </Link>
           </Label.Detail>
+          <Label.Detail>
+            <Icon name="hashtag" /> {props.meta.id}
+          </Label.Detail>
         </Label>
         <a>
           <CopyToClipboard text={window.location.origin + '/#/working-groups/opportunities/' + props.meta.group + '/' + props.meta.id}>
@@ -540,7 +543,7 @@ export const OpeningsView = Loadable<OpeningsViewProps>(
     };
     const groupOption = (group: WorkingGroups | null, lead = false) => ({
       value: `${basePath}${group ? `/${group}` : ''}${lead ? '/lead' : ''}`,
-      text: _.startCase(`${group || 'All opportuniries'}`) + (lead ? ' (Lead)' : '')
+      text: _.startCase(`${group || 'All opportunities'}`) + (lead ? ' (Lead)' : '')
     });
     // Can assert "props.openings!" because we're using "Loadable" which prevents them from beeing undefined
     const filteredOpenings = props.openings!.filter(o =>

+ 11 - 5
pioneer/packages/joy-roles/src/tabs/WorkingGroup.stories.tsx

@@ -12,6 +12,7 @@ import { mockProfile } from '../mocks';
 
 import 'semantic-ui-css/semantic.min.css';
 import '@polkadot/joy-roles/index.sass';
+import { WorkingGroups } from '../working_groups';
 
 export default {
   title: 'Roles / Components / Working groups tab',
@@ -29,7 +30,8 @@ export function ContentCuratorsSection () {
       ),
       title: text('Title', 'Curation lead', 'Ben'),
       stake: new u128(number('Stake', 10101, {}, 'Ben')),
-      earned: new u128(number('Earned', 347829, {}, 'Ben'))
+      workerId: 1,
+      group: WorkingGroups.ContentCurators
     },
     {
       memberId: new MemberId(2),
@@ -37,7 +39,8 @@ export function ContentCuratorsSection () {
       profile: mockProfile(text('Handle', 'bwhm0', 'Martin')),
       title: text('Title', 'Content curator', 'Martin'),
       stake: new u128(number('Stake', 10101, {}, 'Martin')),
-      earned: new u128(number('Earned', 347829, {}, 'Martin'))
+      workerId: 2,
+      group: WorkingGroups.ContentCurators
     },
     {
       memberId: new MemberId(3),
@@ -48,7 +51,8 @@ export function ContentCuratorsSection () {
       ),
       title: text('Title', 'Content curator', 'Paul'),
       stake: new u128(number('Stake', 10101, {}, 'Paul')),
-      earned: new u128(number('Earned', 347829, {}, 'Paul'))
+      workerId: 3,
+      group: WorkingGroups.ContentCurators
     },
     {
       memberId: new MemberId(4),
@@ -59,7 +63,8 @@ export function ContentCuratorsSection () {
       ),
       title: text('Title', 'Content curator', 'Alex'),
       stake: new u128(number('Stake', 10101, {}, 'Alex')),
-      earned: new u128(number('Earned', 347829, {}, 'Alex'))
+      workerId: 4,
+      group: WorkingGroups.ContentCurators
     },
     {
       memberId: new MemberId(5),
@@ -70,7 +75,8 @@ export function ContentCuratorsSection () {
       ),
       title: text('Title', 'Content curator', 'Mokhtar'),
       stake: new u128(number('Stake', 10101, {}, 'Mokhtar')),
-      earned: new u128(number('Earned', 347829, {}, 'Mokhtar'))
+      workerId: 5,
+      group: WorkingGroups.ContentCurators
     }
   ];
 

+ 1 - 1
pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx

@@ -97,7 +97,7 @@ const GroupOverview = Loadable<GroupOverviewProps>(
       <GroupOverviewSection>
         <h2>{ groupName }</h2>
         <p>{ description }</p>
-        <Card.Group>
+        <Card.Group style={{ alignItems: 'flex-start' }}>
           { workers!.map((worker, key) => (
             <GroupMemberView key={key} {...worker} />
           )) }

+ 12 - 6
pioneer/packages/joy-roles/src/transport.mock.ts

@@ -57,7 +57,8 @@ export class Transport extends TransportBase implements ITransport {
           ),
           title: 'Content curator',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 1,
+          group: WorkingGroups.ContentCurators
         },
         {
           memberId: new MemberId(2),
@@ -65,7 +66,8 @@ export class Transport extends TransportBase implements ITransport {
           profile: mockProfile('bwhm0'),
           title: 'Content curator',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 2,
+          group: WorkingGroups.ContentCurators
         },
         {
           memberId: new MemberId(3),
@@ -76,7 +78,8 @@ export class Transport extends TransportBase implements ITransport {
           ),
           title: 'Content curator',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 3,
+          group: WorkingGroups.ContentCurators
         },
         {
           memberId: new MemberId(4),
@@ -87,7 +90,8 @@ export class Transport extends TransportBase implements ITransport {
           ),
           title: 'Content curator',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 4,
+          group: WorkingGroups.ContentCurators
         },
         {
           memberId: new MemberId(3),
@@ -98,7 +102,8 @@ export class Transport extends TransportBase implements ITransport {
           ),
           title: 'Content curator',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 5,
+          group: WorkingGroups.ContentCurators
         }
       ]
     });
@@ -119,7 +124,8 @@ export class Transport extends TransportBase implements ITransport {
           ),
           title: 'Storage provider',
           stake: new u128(10101),
-          earned: new u128(347829)
+          workerId: 1,
+          group: WorkingGroups.StorageProviders
         }
       ]
     });

+ 33 - 13
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -190,14 +190,18 @@ export class Transport extends TransportBase implements ITransport {
     return this.stakeValue(stakeProfile.stake_id);
   }
 
-  protected async workerTotalReward (relationshipId: RewardRelationshipId): Promise<Balance> {
-    const relationship = new SingleLinkedMapEntry<RewardRelationship>(
+  protected async rewardRelationshipById (id: RewardRelationshipId): Promise<RewardRelationship | undefined> {
+    const rewardRelationship = new SingleLinkedMapEntry<RewardRelationship>(
       RewardRelationship,
-      await this.cachedApi.query.recurringRewards.rewardRelationships(
-        relationshipId
-      )
-    );
-    return relationship.value.total_reward_received;
+      await this.cachedApi.query.recurringRewards.rewardRelationships(id)
+    ).value;
+
+    return rewardRelationship.isEmpty ? undefined : rewardRelationship;
+  }
+
+  protected async workerTotalReward (relationshipId: RewardRelationshipId): Promise<Balance> {
+    const relationship = await this.rewardRelationshipById(relationshipId);
+    return relationship?.total_reward_received || new u128(0);
   }
 
   protected async curatorMemberId (curator: Curator): Promise<MemberId> {
@@ -210,6 +214,13 @@ export class Transport extends TransportBase implements ITransport {
     return curatorApplication.member_id;
   }
 
+  protected async workerRewardRelationship (worker: GroupWorker): Promise<RewardRelationship | undefined> {
+    const rewardRelationship = worker.reward_relationship.isSome
+      ? await this.rewardRelationshipById(worker.reward_relationship.unwrap())
+      : undefined;
+    return rewardRelationship;
+  }
+
   protected async groupMember (
     group: WorkingGroups,
     id: GroupWorkerId,
@@ -230,18 +241,17 @@ export class Transport extends TransportBase implements ITransport {
       stakeValue = await this.workerStake(worker.role_stake_profile.unwrap());
     }
 
-    let earnedValue: Balance = new u128(0);
-    if (worker.reward_relationship && worker.reward_relationship.isSome) {
-      earnedValue = await this.workerTotalReward(worker.reward_relationship.unwrap());
-    }
+    const rewardRelationship = await this.workerRewardRelationship(worker);
 
     return ({
       roleAccount,
+      group,
       memberId,
+      workerId: id.toNumber(),
       profile,
       title: workerRoleNameByGroup[group],
       stake: stakeValue,
-      earned: earnedValue
+      rewardRelationship
     });
   }
 
@@ -334,6 +344,14 @@ export class Transport extends TransportBase implements ITransport {
         throw new Error(`${group} lead profile not found!`);
       }
 
+      const rewardRelationshipId = currentLead.lead.reward_relationship;
+      const rewardRelationship = rewardRelationshipId.isSome
+        ? await this.rewardRelationshipById(rewardRelationshipId.unwrap())
+        : undefined;
+      const stake = group === WorkingGroups.StorageProviders && (currentLead.lead as Worker).role_stake_profile.isSome
+        ? await this.workerStake((currentLead.lead as Worker).role_stake_profile.unwrap())
+        : undefined;
+
       return {
         lead: {
           memberId: currentLead.memberId,
@@ -341,7 +359,9 @@ export class Transport extends TransportBase implements ITransport {
           roleAccount: currentLead.lead.role_account_id,
           profile,
           title: _.startCase(group) + ' Lead',
-          stage: group === WorkingGroups.ContentCurators ? (currentLead.lead as Lead).stage : undefined
+          stage: group === WorkingGroups.ContentCurators ? (currentLead.lead as Lead).stage : undefined,
+          stake,
+          rewardRelationship
         },
         loaded: true
       };

+ 1 - 0
pioneer/packages/joy-utils/src/react/hooks/usePromise.tsx

@@ -16,6 +16,7 @@ export default function usePromise<T> (
 
   let isSubscribed = true;
   const execute = useCallback(() => {
+    setState({ value: state.value, error: null, isPending: true });
     return promise()
       .then(value => {
         if (isSubscribed) {

+ 41 - 8
pioneer/packages/joy-utils/src/transport/proposals.ts

@@ -6,16 +6,18 @@ import {
   ProposalVotes,
   ParsedPost,
   ParsedDiscussion,
-  DiscussionContraints
+  DiscussionContraints,
+  ProposalStatusFilter,
+  ProposalsBatch
 } from '../types/proposals';
 import { ParsedMember } from '../types/members';
 
 import BaseTransport from './base';
 
 import { ThreadId, PostId } from '@joystream/types/common';
-import { Proposal, ProposalId, VoteKind, DiscussionThread, DiscussionPost, ProposalDetails } from '@joystream/types/proposals';
+import { Proposal, ProposalId, VoteKind, DiscussionThread, DiscussionPost, ProposalDetails, Finalized, ProposalDecisionStatus } from '@joystream/types/proposals';
 import { MemberId } from '@joystream/types/members';
-import { u32, u64, Bytes, Vec } from '@polkadot/types/';
+import { u32, u64, Bytes, Null } from '@polkadot/types/';
 import { BalanceOf } from '@polkadot/types/interfaces';
 
 import { bytesToString } from '../functions/misc';
@@ -30,6 +32,7 @@ import CouncilTransport from './council';
 
 import { blake2AsHex } from '@polkadot/util-crypto';
 import { APIQueryCache } from '../APIQueryCache';
+import { MultipleLinkedMapEntry } from '../LinkedMapEntry';
 
 type ProposalDetailsCacheEntry = {
   type: ProposalType;
@@ -100,9 +103,11 @@ export default class ProposalsTransport extends BaseTransport {
     }
   }
 
-  async proposalById (id: ProposalId): Promise<ParsedProposal> {
+  async proposalById (id: ProposalId, rawProposal?: Proposal): Promise<ParsedProposal> {
     const { type, details } = await this.typeAndDetails(id);
-    const rawProposal = await this.rawProposalById(id);
+    if (!rawProposal) {
+      rawProposal = await this.rawProposalById(id);
+    }
     const proposer = (await this.membersT.expectedMembership(rawProposal.proposerId)).toJSON() as ParsedMember;
     const proposal = rawProposal.toJSON() as {
       title: string;
@@ -133,15 +138,43 @@ export default class ProposalsTransport extends BaseTransport {
     return Array.from({ length: total }, (_, i) => new ProposalId(i + 1));
   }
 
+  async activeProposalsIds () {
+    const result = new MultipleLinkedMapEntry(ProposalId, Null, await this.proposalsEngine.activeProposalIds());
+    // linked_keys will be [0] if there are no active proposals!
+    return result.linked_keys.join('') !== '0' ? result.linked_keys : [];
+  }
+
   async proposals () {
     const ids = await this.proposalsIds();
     return Promise.all(ids.map(id => this.proposalById(id)));
   }
 
-  async activeProposals () {
-    const activeProposalIds = (await this.proposalsEngine.activeProposalIds()) as Vec<ProposalId>;
+  async proposalsBatch (status: ProposalStatusFilter, batchNumber = 1, batchSize = 5): Promise<ProposalsBatch> {
+    const ids = (status === 'Active' ? await this.activeProposalsIds() : await this.proposalsIds())
+      .sort((id1, id2) => id2.cmp(id1)); // Sort by newest
+    let rawProposalsWithIds = (await Promise.all(ids.map(id => this.rawProposalById(id))))
+      .map((proposal, index) => ({ id: ids[index], proposal }));
+
+    if (status !== 'All' && status !== 'Active') {
+      rawProposalsWithIds = rawProposalsWithIds.filter(({ proposal }) => {
+        if (proposal.status.type !== 'Finalized') {
+          return false;
+        }
+        const finalStatus = ((proposal.status.value as Finalized).get('proposalStatus') as ProposalDecisionStatus);
+        return finalStatus.type === status;
+      });
+    }
+
+    const totalBatches = Math.ceil(rawProposalsWithIds.length / batchSize);
+    rawProposalsWithIds = rawProposalsWithIds.slice((batchNumber - 1) * batchSize, batchNumber * batchSize);
+    const proposals = await Promise.all(rawProposalsWithIds.map(({ proposal, id }) => this.proposalById(id, proposal)));
 
-    return Promise.all(activeProposalIds.map(id => this.proposalById(id)));
+    return {
+      batchNumber,
+      batchSize: rawProposalsWithIds.length,
+      totalBatches,
+      proposals
+    };
   }
 
   async proposedBy (member: MemberId) {

+ 10 - 0
pioneer/packages/joy-utils/src/types/proposals.ts

@@ -25,6 +25,9 @@ export const ProposalTypes = [
 
 export type ProposalType = typeof ProposalTypes[number];
 
+export const proposalStatusFilters = ['All', 'Active', 'Canceled', 'Approved', 'Rejected', 'Slashed', 'Expired'] as const;
+export type ProposalStatusFilter = typeof proposalStatusFilters[number];
+
 export type ParsedProposal = {
   id: ProposalId;
   type: ProposalType;
@@ -49,6 +52,13 @@ export type ParsedProposal = {
   cancellationFee: number;
 };
 
+export type ProposalsBatch = {
+  batchNumber: number;
+  batchSize: number;
+  totalBatches: number;
+  proposals: ParsedProposal[];
+};
+
 export type ProposalVote = {
   vote: VoteKind | null;
   member: ParsedMember & { memberId: MemberId };

+ 26 - 10
runtime/CHANGELOG.md

@@ -1,43 +1,59 @@
+### Version 6.21.0 - Constantinople runtime upgrade B (Nicaea) - July 29 2020
+
+- Introduction of general Working Group runtime module
+- Adds a new instance of the working group module - the Storage Working Group which
+  replaces the old actors module for managing the Storge Provider enrollment process
+- New governance proposals to support new working groups
+
 ### Version 6.15.0 - Constantinople runtime upgrade A - June 2020
+
 - Updated runtime to sort out type name clashes between the proposal discussion module
-and forum module, in preparing to roll out proposal discussion system in pioneer.
+  and forum module, in preparing to roll out proposal discussion system in pioneer.
 - Increased ROLE_PARAMETERS_REWARD_MAX_VALUE to 100,000
 
 ### Version 6.13.0 - (Constantinople) runtime upgrade - May 20th 2020
+
 - New proposal system that strengthens the governance structure of the platform
 - Adjusted inflation curve to better reflect a new realistic economic system for the platform
 
 ### Version 6.8.0 (Rome release - new chain) - March 9th 2020
+
 - New versioned and permissioned content mangement system that powers a new media experience.
 - Content Working Group - introduces staked content curator roles to maintain quality of content and ensure that is meets the platform's terms of service.
 - Update of core substrate to pre-release of version 2.0 - [c37bb08](https://github.com/paritytech/substrate/commit/c37bb08535c49a12320af7facfd555ce05cce2e8)
 
 ### Version 5.4.0 (Acropolis) - Athens testnet update 3 - June 22n 2019
+
 - New Forum - v1.0.0
 - Discovery Service to support new Storage Provider implementation.
 - Removing all previously uploaded content
 
 ### Version 5.3.0 - Athens testnet update 2 - April 27th 2019
+
 - Fix to configure an onchain primary storage provider liason to work around incomplete storage server implementation.
 
 ### Version 5.2.0 - Athens testnet update 1 - April 17th 2019
+
 - Fix account locking for staked roles to allow storage providers to submit transaction
 - Update substrate version to `6dfc3e8b057bb00322136251a0f10305fbb1ad8f` from v1 branch
 
 ### Version 5.1.0 - Athens testnet release - April 14th 2019
+
 - Storage Role
 - Update to substrate version at `89bbb7b6d0e076f0eda736b330f5f792aa2e2991`
 
 ### Version 4 - Bug Fixes - March 4th 2019 - `9941dd`
- - Allow illiquid accounts to pay transaction fees. Fixes unstaking and setting memo, by permitting extrinsics which do not require more than a transaction fee to be accepted into mempool.
- - Updated Cargo dependencies to use forked substrate repo `github.com/joystream/substrate`
 
- On-chain runtime upgrade performed with sudo `consensus::setCode()`
+- Allow illiquid accounts to pay transaction fees. Fixes unstaking and setting memo, by permitting extrinsics which do not require more than a transaction fee to be accepted into mempool.
+- Updated Cargo dependencies to use forked substrate repo `github.com/joystream/substrate`
+
+On-chain runtime upgrade performed with sudo `consensus::setCode()`
 
 ### Version 3 - Sparta - March 1st 2019 - `1ca4cc`
-  - Basic substrate node - based on substrate `1ca4cc0a16a357782bb1028bb57376594ca232a0`
-  - Block Authoring - only Aura (enabling GRANDPA in future release)
-  - Council Elections
-  - Council Runtime upgrade proposal
-  - Simple PoS validator staking
-  - Memo (account status message)
+
+- Basic substrate node - based on substrate `1ca4cc0a16a357782bb1028bb57376594ca232a0`
+- Block Authoring - only Aura (enabling GRANDPA in future release)
+- Council Elections
+- Council Runtime upgrade proposal
+- Simple PoS validator staking
+- Memo (account status message)

+ 0 - 0
scripts/build-joystream-testing-node-docker-image.sh


+ 1 - 1
storage-node/package.json

@@ -14,7 +14,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 1 - 1
storage-node/packages/cli/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 15 - 16
storage-node/packages/colossus/bin/cli.js

@@ -142,21 +142,6 @@ function getStorage(runtimeApi) {
   return Storage.create(options)
 }
 
-async function untilChainIsSynched(api) {
-  while (true) {
-    const health = await api.api.rpc.system.health()
-
-    if (health.isSyncing.isTrue) {
-      debug('Waiting for chain to be synced...')
-      await new Promise((resolve) => {
-        setTimeout(resolve, 1 * 30 * 1000)
-      })
-    } else {
-      return
-    }
-  }
-}
-
 async function initApiProduction({ wsProvider, providerId, keyFile, passphrase }) {
   // Load key information
   const { RuntimeApi } = require('@joystream/storage-runtime-api')
@@ -180,7 +165,7 @@ async function initApiProduction({ wsProvider, providerId, keyFile, passphrase }
     throw new Error('Failed to unlock storage provider account')
   }
 
-  await untilChainIsSynched(api)
+  await api.untilChainIsSynced()
 
   if (!(await api.workers.isRoleAccountOfStorageProvider(api.storageProviderId, api.identities.key.address))) {
     throw new Error('storage provider role account and storageProviderId are not associated with a worker')
@@ -230,6 +215,18 @@ async function announcePublicUrl(api, publicUrl) {
     setTimeout(announcePublicUrl, timeoutMs, api, publicUrl)
   }
 
+  const chainIsSyncing = await api.chainIsSyncing()
+  if (chainIsSyncing) {
+    debug('Chain is syncing. Postponing announcing public url.')
+    return reannounce(10 * 60 * 1000)
+  }
+
+  const sufficientBalance = await api.providerHasMinimumBalance(1)
+  if (!sufficientBalance) {
+    debug('Provider role account does not have sufficient balance. Postponing announcing public url.')
+    return reannounce(10 * 60 * 1000)
+  }
+
   debug('announcing public url')
   const { publish } = require('@joystream/service-discovery')
 
@@ -291,11 +288,13 @@ const commands = {
     return startColossus({ api, publicUrl, port })
   },
   discovery: async () => {
+    banner()
     debug('Starting Joystream Discovery Service')
     const { RuntimeApi } = require('@joystream/storage-runtime-api')
     const wsProvider = cli.flags.wsProvider
     const api = await RuntimeApi.create({ provider_url: wsProvider })
     const port = cli.flags.port
+    await api.untilChainIsSynced()
     await startDiscoveryService({ api, port })
   },
 }

+ 18 - 0
storage-node/packages/colossus/lib/sync.js

@@ -92,6 +92,24 @@ async function syncCallback(api, storage) {
 async function syncPeriodic(api, flags, storage) {
   try {
     debug('Starting sync run...')
+
+    const chainIsSyncing = await api.chainIsSyncing()
+    if (chainIsSyncing) {
+      debug('Chain is syncing. Postponing sync run.')
+      return setTimeout(syncPeriodic, flags.syncPeriod, api, flags, storage)
+    }
+
+    const recommendedBalance = await api.providerHasMinimumBalance(300)
+    if (!recommendedBalance) {
+      debug('Warning: Provider role account is running low on balance.')
+    }
+
+    const sufficientBalance = await api.providerHasMinimumBalance(100)
+    if (!sufficientBalance) {
+      debug('Provider role account does not have sufficient balance. Postponing sync run!')
+      return setTimeout(syncPeriodic, flags.syncPeriod, api, flags, storage)
+    }
+
     await syncCallback(api, storage)
     debug('sync run complete')
   } catch (err) {

+ 1 - 1
storage-node/packages/colossus/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 7 - 0
storage-node/packages/colossus/paths/asset/v0/{id}.js

@@ -91,6 +91,13 @@ module.exports = function (storage, runtime) {
         return
       }
 
+      const sufficientBalance = await runtime.providerHasMinimumBalance(3)
+
+      if (!sufficientBalance) {
+        errorHandler(res, 'Insufficient balance to process upload!', 503)
+        return
+      }
+
       // We'll open a write stream to the backend, but reserve the right to
       // abort upload if the filters don't smell right.
       let stream

+ 1 - 1
storage-node/packages/discovery/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 1 - 1
storage-node/packages/helios/package.json

@@ -8,7 +8,7 @@
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 0"
   },
-  "license": "MIT",
+  "license": "GPL-3.0-only",
   "dependencies": {
     "@joystream/storage-runtime-api": "^0.1.0",
     "@types/bn.js": "^4.11.5",

+ 158 - 115
storage-node/packages/runtime-api/index.js

@@ -19,6 +19,7 @@
 'use strict'
 
 const debug = require('debug')('joystream:runtime:base')
+const debugTx = require('debug')('joystream:runtime:base:tx')
 
 const { registerJoystreamTypes } = require('@joystream/types')
 const { ApiPromise, WsProvider } = require('@polkadot/api')
@@ -30,6 +31,7 @@ const { DiscoveryApi } = require('@joystream/storage-runtime-api/discovery')
 const { SystemApi } = require('@joystream/storage-runtime-api/system')
 const AsyncLock = require('async-lock')
 const Promise = require('bluebird')
+const { sleep } = require('@joystream/storage-utils/sleep')
 
 Promise.config({
   cancellation: true,
@@ -85,6 +87,29 @@ class RuntimeApi {
     this.api.disconnect()
   }
 
+  async untilChainIsSynced() {
+    debug('Waiting for chain to be synced before proceeding.')
+    while (true) {
+      const isSyncing = await this.chainIsSyncing()
+      if (isSyncing) {
+        debug('Still waiting for chain to be synced.')
+        await sleep(1 * 30 * 1000)
+      } else {
+        return
+      }
+    }
+  }
+
+  async chainIsSyncing() {
+    const { isSyncing } = await this.api.rpc.system.health()
+    return isSyncing.isTrue
+  }
+
+  async providerHasMinimumBalance(minimumBalance) {
+    const providerAccountId = this.identities.key.address
+    return this.balances.hasMinimumBalanceOf(providerAccountId, minimumBalance)
+  }
+
   executeWithAccountLock(accountId, func) {
     return this.asyncLock.acquire(`${accountId}`, func)
   }
@@ -121,7 +146,7 @@ class RuntimeApi {
         })
       }
       const fullName = `${event.section}.${event.method}`
-      debug(`matched event: ${fullName} =>`, event.data && event.data.join(', '))
+      debugTx(`matched event: ${fullName} =>`, event.data && event.data.join(', '))
       return [fullName, payload]
     })
   }
@@ -135,20 +160,15 @@ class RuntimeApi {
     // const nonce = await this.api.rpc.system.accountNextIndex(accountId)
     const systemNonce = await this.api.query.system.accountNonce(accountId)
 
-    if (cachedNonce) {
-      // we have it cached.. but lets do a look ahead to see if we need to adjust
-      if (systemNonce.gt(cachedNonce)) {
-        return systemNonce
-      } else {
-        return cachedNonce
-      }
-    } else {
-      return systemNonce
-    }
+    const bestNonce = cachedNonce && cachedNonce.gte(systemNonce) ? cachedNonce : systemNonce
+
+    this.nonces[accountId] = bestNonce
+
+    return bestNonce.toNumber()
   }
 
-  incrementAndSaveNonce(accountId, nonce) {
-    this.nonces[accountId] = nonce.addn(1)
+  incrementAndSaveNonce(accountId) {
+    this.nonces[accountId] = this.nonces[accountId].addn(1)
   }
 
   /*
@@ -156,8 +176,11 @@ class RuntimeApi {
    * so that they can be included in the same block. Allows you to use the accountId instead
    * of the key, without requiring an external Signer configured on the underlying ApiPromie
    *
-   * If the subscribed events are given, and a callback as well, then the
-   * callback is invoked with matching events.
+   * If the subscribed events are given, then the matchedEvents will be returned in the resolved
+   * value.
+   * Resolves when a transaction finalizes with a successful dispatch (for both signed and root origins)
+   * Rejects in all other cases.
+   * Will also reject on timeout if the transaction doesn't finalize in time.
    */
   async signAndSend(accountId, tx, subscribed) {
     // Accept both a string or AccountId as argument
@@ -171,119 +194,64 @@ class RuntimeApi {
       throw new Error('Must unlock key before using it to sign!')
     }
 
-    // Functions to be called when the submitted transaction is finalized. They are initialized
-    // after the transaction is submitted to the resolve and reject function of the final promise
-    // returned by signAndSend
-    // on extrinsic success
-    let onFinalizedSuccess
-    // on extrinsic failure
-    let onFinalizedFailed
-
-    // Function assigned when transaction is successfully submitted. Invoking it ubsubscribes from
-    // listening to tx status updates.
-    let unsubscribe
-
-    let lastTxUpdateResult
-
-    const handleTxUpdates = (result) => {
-      const { events = [], status } = result
-
-      if (!result || !status) {
-        return
-      }
-
-      lastTxUpdateResult = result
-
-      if (result.isError) {
-        unsubscribe()
-        debug('Tx Error', status.type)
-        onFinalizedFailed &&
-          onFinalizedFailed({ err: status.type, result, tx: status.isUsurped ? status.asUsurped : undefined })
-      } else if (result.isFinalized) {
-        unsubscribe()
-        const mappedEvents = RuntimeApi.matchingEvents(subscribed, events)
-        const failed = result.findRecord('system', 'ExtrinsicFailed')
-        const success = result.findRecord('system', 'ExtrinsicSuccess')
-        const sudid = result.findRecord('sudo', 'Sudid')
-        const sudoAsDone = result.findRecord('sudo', 'SudoAsDone')
+    const callbacks = {
+      // Functions to be called when the submitted transaction is finalized. They are initialized
+      // after the transaction is submitted to the resolve and reject function of the final promise
+      // returned by signAndSend
+      // on extrinsic success
+      onFinalizedSuccess: null,
+      // on extrinsic failure
+      onFinalizedFailed: null,
+      // Function assigned when transaction is successfully submitted. Invoking it ubsubscribes from
+      // listening to tx status updates.
+      unsubscribe: null,
+    }
 
-        if (failed) {
-          const {
-            event: { data },
-          } = failed
-          const dispatchError = data[0]
-          onFinalizedFailed({
-            err: 'ExtrinsicFailed',
-            mappedEvents,
-            result,
-            block: status.asFinalized,
-            dispatchError, // we get module number/id and index into the Error enum
-          })
-        } else if (success) {
-          // Note: For root origin calls, the dispatch error is logged to the joystream-node
-          // console, we cannot get it in the events
-          if (sudid) {
-            const dispatchSuccess = sudid.event.data[0]
-            if (dispatchSuccess.isTrue) {
-              onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
-            } else {
-              onFinalizedFailed({ err: 'SudoFailed', mappedEvents, result, block: status.asFinalized })
-            }
-          } else if (sudoAsDone) {
-            const dispatchSuccess = sudoAsDone.event.data[0]
-            if (dispatchSuccess.isTrue) {
-              onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
-            } else {
-              onFinalizedFailed({ err: 'SudoAsFailed', mappedEvents, result, block: status.asFinalized })
-            }
-          } else {
-            onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
-          }
-        }
-      }
+    // object used to communicate back information from the tx updates handler
+    const out = {
+      lastResult: undefined,
     }
 
     // synchronize access to nonce
     await this.executeWithAccountLock(accountId, async () => {
       const nonce = await this.selectBestNonce(accountId)
+      const signed = tx.sign(fromKey, { nonce })
+      const txhash = signed.hash
 
       try {
-        unsubscribe = await tx.sign(fromKey, { nonce }).send(handleTxUpdates)
-        debug('TransactionSubmitted')
-        // transaction submitted successfully, increment and save nonce.
-        this.incrementAndSaveNonce(accountId, nonce)
-      } catch (err) {
-        const errstr = err.toString()
-        debug('TransactionRejected:', errstr)
-        // This happens when nonce is already used in finalized transactions, ie. the selected nonce
-        // was less than current account nonce. A few scenarios where this happens (other than incorrect code)
-        // 1. When a past future tx got finalized because we submitted some transactions
-        // using up the nonces upto that point.
-        // 2. Can happen while storage-node is talkig to a joystream-node that is still not fully
-        // synced.
-        // 3. Storage role account holder sent a transaction just ahead of us via another app.
-        if (errstr.indexOf('ExtrinsicStatus:: 1010: Invalid Transaction: Stale') !== -1) {
-          // In case 1 or 3 a quick recovery could work by just incrementing, but since we
-          // cannot detect which case we are in just reset and force re-reading nonce. Even
-          // that may not be sufficient expect after a few more failures..
-          delete this.nonces[accountId]
-        }
+        callbacks.unsubscribe = await signed.send(
+          RuntimeApi.createTxUpdateHandler(callbacks, { nonce, txhash, subscribed }, out)
+        )
+
+        const serialized = JSON.stringify({
+          nonce,
+          txhash,
+          tx: signed.toHex(),
+        })
 
-        // Technically it means a transaction in the mempool with same
-        // nonce and same fees being paid so we cannot replace it, either we didn't correctly
-        // increment the nonce or someone external to this application sent a transaction
-        // with same nonce ahead of us.
-        if (errstr.indexOf('ExtrinsicStatus:: 1014: Priority is too low') !== -1) {
-          delete this.nonces[accountId]
+        // We are depending on the behaviour that at this point the Ready status
+        // Elaboration: when the tx is rejected and therefore the tx isn't added
+        // to the tx pool ready queue status is not updated and
+        // .send() throws, so we don't reach this code.
+        if (out.lastResult.status.isFuture) {
+          debugTx(`Warning: Submitted Tx with future nonce: ${serialized}`)
+        } else {
+          debugTx(`Submitted: ${serialized}`)
         }
 
+        // transaction submitted successfully, increment and save nonce.
+        this.incrementAndSaveNonce(accountId)
+      } catch (err) {
+        const errstr = err.toString()
+        debugTx(`Rejected: ${errstr} txhash: ${txhash} nonce: ${nonce}`)
         throw err
       }
     })
 
+    // Here again we assume that the transaction has been accepted into the tx pool
+    // and status was updated.
     // We cannot get tx updates for a future tx so return now to avoid blocking caller
-    if (lastTxUpdateResult.status.isFuture) {
-      debug('Warning: Submitted extrinsic with future nonce')
+    if (out.lastResult.status.isFuture) {
       return {}
     }
 
@@ -293,14 +261,14 @@ class RuntimeApi {
     // Timeout can also occur if a transaction that was part of batch of transactions submitted
     // gets usurped.
     return new Promise((resolve, reject) => {
-      onFinalizedSuccess = resolve
-      onFinalizedFailed = reject
+      callbacks.onFinalizedSuccess = resolve
+      callbacks.onFinalizedFailed = reject
     }).timeout(TX_TIMEOUT)
   }
 
   /*
    * Sign and send a transaction expect event from
-   * module and return eventProperty from the event.
+   * module and return specific(index) value from event data
    */
   async signAndSendThenGetEventResult(senderAccountId, tx, { module, event, index, type }) {
     if (!module || !event || index === undefined || !type) {
@@ -341,6 +309,81 @@ class RuntimeApi {
 
     return value.data
   }
+
+  static createTxUpdateHandler(callbacks, submittedTx, out = {}) {
+    const { nonce, txhash, subscribed } = submittedTx
+
+    return function handleTxUpdates(result) {
+      const { events = [], status } = result
+      const { unsubscribe, onFinalizedFailed, onFinalizedSuccess } = callbacks
+
+      if (!result || !status) {
+        return
+      }
+
+      out.lastResult = result
+
+      const txinfo = () => {
+        return JSON.stringify({
+          nonce,
+          txhash,
+        })
+      }
+
+      if (result.isError) {
+        unsubscribe()
+
+        debugTx(`Error: ${status.type}`, txinfo())
+
+        onFinalizedFailed &&
+          onFinalizedFailed({ err: status.type, result, tx: status.isUsurped ? status.asUsurped : undefined })
+      } else if (result.isFinalized) {
+        unsubscribe()
+
+        debugTx('Finalized', txinfo())
+
+        const mappedEvents = RuntimeApi.matchingEvents(subscribed, events)
+        const failed = result.findRecord('system', 'ExtrinsicFailed')
+        const success = result.findRecord('system', 'ExtrinsicSuccess')
+        const sudid = result.findRecord('sudo', 'Sudid')
+        const sudoAsDone = result.findRecord('sudo', 'SudoAsDone')
+
+        if (failed) {
+          const {
+            event: { data },
+          } = failed
+          const dispatchError = data[0]
+          onFinalizedFailed({
+            err: 'ExtrinsicFailed',
+            mappedEvents,
+            result,
+            block: status.asFinalized,
+            dispatchError, // we get module number/id and index into the Error enum
+          })
+        } else if (success) {
+          // Note: For root origin calls, the dispatch error is logged to the joystream-node
+          // console, we cannot get it in the events
+          if (sudid) {
+            const dispatchSuccess = sudid.event.data[0]
+            if (dispatchSuccess.isTrue) {
+              onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
+            } else {
+              onFinalizedFailed({ err: 'SudoFailed', mappedEvents, result, block: status.asFinalized })
+            }
+          } else if (sudoAsDone) {
+            const dispatchSuccess = sudoAsDone.event.data[0]
+            if (dispatchSuccess.isTrue) {
+              onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
+            } else {
+              onFinalizedFailed({ err: 'SudoAsFailed', mappedEvents, result, block: status.asFinalized })
+            }
+          } else {
+            onFinalizedSuccess({ mappedEvents, result, block: status.asFinalized })
+          }
+        }
+      }
+    }
+  }
 }
 
 module.exports = {

+ 2 - 1
storage-node/packages/runtime-api/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",
@@ -45,6 +45,7 @@
     "temp": "^0.9.0"
   },
   "dependencies": {
+    "@joystream/storage-utils": "^0.1.0",
     "@joystream/types": "^0.12.0",
     "@polkadot/api": "^0.96.1",
     "async-lock": "^1.2.0",

+ 1 - 1
storage-node/packages/storage/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 1 - 1
storage-node/packages/util/package.json

@@ -12,7 +12,7 @@
     "type": "git",
     "url": "https://github.com/Joystream/joystream.git"
   },
-  "license": "GPL-3.0",
+  "license": "GPL-3.0-only",
   "contributors": [
     {
       "name": "Joystream",

+ 9 - 0
storage-node/packages/util/sleep.js

@@ -0,0 +1,9 @@
+function sleep(ms) {
+  return new Promise((resolve) => {
+    setTimeout(resolve, ms)
+  })
+}
+
+module.exports = {
+  sleep,
+}

+ 3 - 2
tests/network-tests/package.json

@@ -4,10 +4,10 @@
   "license": "GPL-3.0-only",
   "scripts": {
     "build": "tsc --build tsconfig.json",
-    "test": "tap --files src/nicaea/tests/proposals/*Test.ts --files src/nicaea/tests/workingGroup/*Test.ts -T",
+    "test": "tap --files src/iznik/tests/proposals/*Test.ts --files src/iznik/tests/workingGroup/*Test.ts -T",
     "test-migration-constantinople": "tap --files src/rome/tests/romeRuntimeUpgradeTest.ts --files src/constantinople/tests/electingCouncilTest.ts -T",
     "test-migration-nicaea": "tap --files src/constantinople/tests/proposals/updateRuntimeTest.ts --files src/nicaea/tests/electingCouncilTest.ts -T",
-    "debug": "tap --files src/nicaea/tests/workingGroup/*Test.ts -T",
+    "debug": "tap --files src/iznik/tests/proposals/manageLeaderRoleTest.ts -T",
     "lint": "eslint . --quiet --ext .ts",
     "checks": "yarn lint && tsc --noEmit --pretty && prettier ./ --check",
     "format": "prettier ./ --write "
@@ -15,6 +15,7 @@
   "dependencies": {
     "@constantinople/types@npm:@joystream/types": "^0.10.0",
     "@nicaea/types@npm:@joystream/types": "^0.12.0",
+    "@alexandria/types": "link:../../types",
     "@polkadot/api": "^0.96.1",
     "@polkadot/keyring": "^1.7.0-beta.5",
     "@rome/types@npm:@joystream/types": "^0.7.0",

+ 0 - 0
tests/network-tests/src/iznik/tap-parallel-not-ok


+ 65 - 0
tests/network-tests/src/iznik/tests/council/electingCouncilTest.ts

@@ -0,0 +1,65 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import BN from 'bn.js'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { closeApi } from '../../utils/closeApi'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { Utils } from '../../utils/utils'
+
+tap.mocha.describe('Electing council scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+
+  const durationInBlocks = 25
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 140 - 0
tests/network-tests/src/iznik/tests/fixtures/councilElectionModule.ts

@@ -0,0 +1,140 @@
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { KeyringPair } from '@polkadot/keyring/types'
+import BN from 'bn.js'
+import { assert } from 'chai'
+import { Seat } from '@alexandria/types/council'
+import { v4 as uuid } from 'uuid'
+import { Utils } from '../../utils/utils'
+import { Fixture } from './interfaces/fixture'
+
+export class ElectCouncilFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private k: number
+  private sudo: KeyringPair
+  private greaterStake: BN
+  private lesserStake: BN
+
+  public constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    m2KeyPairs: KeyringPair[],
+    k: number,
+    sudo: KeyringPair,
+    greaterStake: BN,
+    lesserStake: BN
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.k = k
+    this.sudo = sudo
+    this.greaterStake = greaterStake
+    this.lesserStake = lesserStake
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    let now = await this.apiWrapper.getBestBlock()
+    const applyForCouncilFee: BN = this.apiWrapper.estimateApplyForCouncilFee(this.greaterStake)
+    const voteForCouncilFee: BN = this.apiWrapper.estimateVoteForCouncilFee(
+      this.sudo.address,
+      this.sudo.address,
+      this.greaterStake
+    )
+    const salt: string[] = []
+    this.m1KeyPairs.forEach(() => {
+      salt.push(''.concat(uuid().replace(/-/g, '')))
+    })
+    const revealVoteFee: BN = this.apiWrapper.estimateRevealVoteFee(this.sudo.address, salt[0])
+
+    // Topping the balances
+    await this.apiWrapper.transferBalanceToAccounts(
+      this.sudo,
+      this.m2KeyPairs,
+      applyForCouncilFee.add(this.greaterStake)
+    )
+    await this.apiWrapper.transferBalanceToAccounts(
+      this.sudo,
+      this.m1KeyPairs,
+      voteForCouncilFee.add(revealVoteFee).add(this.greaterStake)
+    )
+
+    // First K members stake more
+    await this.apiWrapper.sudoStartAnnouncingPerion(this.sudo, now.addn(100))
+    await this.apiWrapper.batchApplyForCouncilElection(this.m2KeyPairs.slice(0, this.k), this.greaterStake)
+    this.m2KeyPairs.slice(0, this.k).forEach((keyPair) =>
+      this.apiWrapper.getCouncilElectionStake(keyPair.address).then((stake) => {
+        assert(
+          stake.eq(this.greaterStake),
+          `${keyPair.address} not applied correctly for council election with stake ${stake} versus expected ${this.greaterStake}`
+        )
+      })
+    )
+
+    // Last members stake less
+    await this.apiWrapper.batchApplyForCouncilElection(this.m2KeyPairs.slice(this.k), this.lesserStake)
+    this.m2KeyPairs.slice(this.k).forEach((keyPair) =>
+      this.apiWrapper.getCouncilElectionStake(keyPair.address).then((stake) => {
+        assert(
+          stake.eq(this.lesserStake),
+          `${keyPair.address} not applied correctrly for council election with stake ${stake} versus expected ${this.lesserStake}`
+        )
+      })
+    )
+
+    // Voting
+    await this.apiWrapper.sudoStartVotingPerion(this.sudo, now.addn(100))
+    await this.apiWrapper.batchVoteForCouncilMember(
+      this.m1KeyPairs.slice(0, this.k),
+      this.m2KeyPairs.slice(0, this.k),
+      salt.slice(0, this.k),
+      this.lesserStake
+    )
+    await this.apiWrapper.batchVoteForCouncilMember(
+      this.m1KeyPairs.slice(this.k),
+      this.m2KeyPairs.slice(this.k),
+      salt.slice(this.k),
+      this.greaterStake
+    )
+
+    // Revealing
+    await this.apiWrapper.sudoStartRevealingPerion(this.sudo, now.addn(100))
+    await this.apiWrapper.batchRevealVote(
+      this.m1KeyPairs.slice(0, this.k),
+      this.m2KeyPairs.slice(0, this.k),
+      salt.slice(0, this.k)
+    )
+    await this.apiWrapper.batchRevealVote(
+      this.m1KeyPairs.slice(this.k),
+      this.m2KeyPairs.slice(this.k),
+      salt.slice(this.k)
+    )
+    now = await this.apiWrapper.getBestBlock()
+
+    // Resolving election
+    // 3 is to ensure the revealing block is in future
+    await this.apiWrapper.sudoStartRevealingPerion(this.sudo, now.addn(3))
+    await Utils.wait(this.apiWrapper.getBlockDuration().muln(2.5).toNumber())
+    const seats: Seat[] = await this.apiWrapper.getCouncil()
+
+    // Preparing collections to increase assertion readability
+    const m2addresses: string[] = this.m2KeyPairs.map((keyPair) => keyPair.address)
+    const m1addresses: string[] = this.m1KeyPairs.map((keyPair) => keyPair.address)
+    const members: string[] = seats.map((seat) => seat.member.toString())
+    const bakers: string[] = seats.map((seat) => seat.backers.map((baker) => baker.member.toString())).flat()
+
+    // Assertions
+    m2addresses.forEach((address) => assert(members.includes(address), `Account ${address} is not in the council`))
+    m1addresses.forEach((address) => assert(bakers.includes(address), `Account ${address} is not in the voters`))
+    seats.forEach((seat) =>
+      assert(
+        Utils.getTotalStake(seat).eq(this.greaterStake.add(this.lesserStake)),
+        `Member ${seat.member} has unexpected stake ${Utils.getTotalStake(seat)}`
+      )
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}

+ 3 - 0
tests/network-tests/src/iznik/tests/fixtures/interfaces/fixture.ts

@@ -0,0 +1,3 @@
+export interface Fixture {
+  runner(expectFailure: boolean): Promise<void>
+}

+ 106 - 0
tests/network-tests/src/iznik/tests/fixtures/membershipModule.ts

@@ -0,0 +1,106 @@
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { KeyringPair } from '@polkadot/keyring/types'
+import BN from 'bn.js'
+import { assert } from 'chai'
+import { Fixture } from './interfaces/fixture'
+
+export class BuyMembershipHappyCaseFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private sudo: KeyringPair
+  private keyPairs: KeyringPair[]
+  private paidTerms: number
+
+  public constructor(apiWrapper: ApiWrapper, sudo: KeyringPair, keyPairs: KeyringPair[], paidTerms: number) {
+    this.apiWrapper = apiWrapper
+    this.sudo = sudo
+    this.keyPairs = keyPairs
+    this.paidTerms = paidTerms
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const membershipFee: BN = await this.apiWrapper.getMembershipFee(this.paidTerms)
+    const membershipTransactionFee: BN = this.apiWrapper.estimateBuyMembershipFee(
+      this.sudo,
+      this.paidTerms,
+      'member_name_which_is_longer_than_expected'
+    )
+    await this.apiWrapper.transferBalanceToAccounts(
+      this.sudo,
+      this.keyPairs,
+      membershipTransactionFee.add(new BN(membershipFee))
+    )
+
+    // Buying membership
+    await Promise.all(
+      this.keyPairs.map(async (keyPair, index) => {
+        await this.apiWrapper.buyMembership(
+          keyPair,
+          this.paidTerms,
+          `new_member_${index}${keyPair.address.substring(0, 8)}`
+        )
+      })
+    )
+
+    // Assertions
+    this.keyPairs.forEach((keyPair) =>
+      this.apiWrapper
+        .getMemberIds(keyPair.address)
+        .then((membership) => assert(membership.length > 0, `Account ${keyPair.address} is not a member`))
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class BuyMembershipWithInsufficienFundsFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private sudo: KeyringPair
+  private aKeyPair: KeyringPair
+  private paidTerms: number
+
+  public constructor(apiWrapper: ApiWrapper, sudo: KeyringPair, aKeyPair: KeyringPair, paidTerms: number) {
+    this.apiWrapper = apiWrapper
+    this.sudo = sudo
+    this.aKeyPair = aKeyPair
+    this.paidTerms = paidTerms
+  }
+
+  public async runner(expectFailure: boolean) {
+    // Fee estimation and transfer
+    const membershipFee: BN = await this.apiWrapper.getMembershipFee(this.paidTerms)
+    const membershipTransactionFee: BN = this.apiWrapper.estimateBuyMembershipFee(
+      this.sudo,
+      this.paidTerms,
+      'member_name_which_is_longer_than_expected'
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.aKeyPair.address, membershipTransactionFee)
+
+    // Balance assertion
+    await this.apiWrapper
+      .getBalance(this.aKeyPair.address)
+      .then((balance) =>
+        assert(
+          balance.toBn() < membershipFee.add(membershipTransactionFee),
+          'Account A already have sufficient balance to purchase membership'
+        )
+      )
+
+    // Buying memebership
+    await this.apiWrapper.buyMembership(
+      this.aKeyPair,
+      this.paidTerms,
+      `late_member_${this.aKeyPair.address.substring(0, 8)}`,
+      true
+    )
+
+    // Assertions
+    this.apiWrapper
+      .getMemberIds(this.aKeyPair.address)
+      .then((membership) => assert(membership.length === 0, 'Account A is a member'))
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}

+ 1037 - 0
tests/network-tests/src/iznik/tests/fixtures/proposalsModule.ts

@@ -0,0 +1,1037 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { v4 as uuid } from 'uuid'
+import BN from 'bn.js'
+import { FillOpeningParameters } from '@alexandria/types/proposals'
+import { Fixture } from './interfaces/fixture'
+import { Bytes, Option, u32 } from '@polkadot/types'
+import { Balance, BlockNumber } from '@polkadot/types/interfaces'
+import { assert } from 'chai'
+import {
+  ActivateOpeningAt,
+  ApplicationId,
+  ApplicationRationingPolicy,
+  OpeningId,
+  StakingPolicy,
+} from '@alexandria/types/hiring'
+import { RewardPolicy, SlashingTerms, WorkingGroupOpeningPolicyCommitment } from '@alexandria/types/working-group'
+import { WorkingGroup } from '@alexandria/types/common'
+
+export class CreateWorkingGroupLeaderOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private applicationStake: BN
+  private roleStake: BN
+  private workingGroup: string
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    applicationStake: BN,
+    roleStake: BN,
+    workingGroup: string
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.applicationStake = applicationStake
+    this.roleStake = roleStake
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing working group lead opening proposal ' + uuid().substring(0, 8)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(100000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeCreateWorkingGroupLeaderOpeningFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Opening construction
+    const activateAtBlock: ActivateOpeningAt = new ActivateOpeningAt('CurrentBlock')
+    const commitment: WorkingGroupOpeningPolicyCommitment = new WorkingGroupOpeningPolicyCommitment({
+      application_rationing_policy: new Option(ApplicationRationingPolicy, {
+        max_active_applicants: new BN(this.m1KeyPairs.length) as u32,
+      }),
+      max_review_period_length: new BN(32) as u32,
+      application_staking_policy: new Option(StakingPolicy, {
+        amount: this.applicationStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_staking_policy: new Option(StakingPolicy, {
+        amount: this.roleStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_slashing_terms: new SlashingTerms({
+        Slashable: {
+          max_count: new BN(1),
+          max_percent_pts_per_time: new BN(100),
+        },
+      }),
+      fill_opening_successful_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+    })
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeCreateWorkingGroupLeaderOpening(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      activateAtBlock,
+      commitment,
+      uuid().substring(0, 8),
+      this.workingGroup
+    )
+    const proposalNumber: BN = await proposalPromise
+    this.result = proposalNumber
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class BeginWorkingGroupLeaderApplicationReviewFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private openingId: BN
+  private workingGroup: string
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    openingId: BN,
+    workingGroup: string
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.openingId = openingId
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing begin working group lead application review proposal ' + uuid().substring(0, 8)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(25000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeBeginWorkingGroupLeaderApplicationReviewFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeBeginWorkingGroupLeaderApplicationReview(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      this.openingId,
+      this.workingGroup
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class FillLeaderOpeningProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private applicantRoleAccountAddress: string
+  private sudo: KeyringPair
+  private firstRewardInterval: BN
+  private rewardInterval: BN
+  private payoutAmount: BN
+  private openingId: BN
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    applicantRoleAccountAddress: string,
+    sudo: KeyringPair,
+    firstRewardInterval: BN,
+    rewardInterval: BN,
+    payoutAmount: BN,
+    openingId: BN,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.applicantRoleAccountAddress = applicantRoleAccountAddress
+    this.sudo = sudo
+    this.firstRewardInterval = firstRewardInterval
+    this.rewardInterval = rewardInterval
+    this.payoutAmount = payoutAmount
+    this.openingId = openingId
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing fill opening proposal ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeFillLeaderOpeningFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const applicationId: BN = (
+      await this.apiWrapper.getActiveApplicationsIdsByRoleAccount(this.applicantRoleAccountAddress, this.workingGroup)
+    )[0]
+    const now = await this.apiWrapper.getBestBlock()
+
+    const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters({
+      opening_id: this.openingId as OpeningId,
+      successful_application_id: applicationId as ApplicationId,
+      reward_policy: new Option(
+        RewardPolicy,
+        new RewardPolicy({
+          amount_per_payout: this.payoutAmount as Balance,
+          next_payment_at_block: now.add(this.firstRewardInterval) as BlockNumber,
+          payout_interval: new Option(u32, this.rewardInterval as u32),
+        })
+      ),
+      working_group: new WorkingGroup(workingGroupString),
+    })
+
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeFillLeaderOpening(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      fillOpeningParameters
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class TerminateLeaderRoleProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private leaderRoleAccountAddress: string
+  private sudo: KeyringPair
+  private slash: boolean
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    leaderRoleAccountAddress: string,
+    sudo: KeyringPair,
+    slash: boolean,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.leaderRoleAccountAddress = leaderRoleAccountAddress
+    this.sudo = sudo
+    this.slash = slash
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing begin working group lead application review proposal ' + uuid().substring(0, 8)
+    const rationale: string = 'Testing leader termination ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(
+      this.leaderRoleAccountAddress,
+      this.workingGroup
+    )
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(100000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeTerminateLeaderRoleFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeTerminateLeaderRole(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      workerId,
+      rationale,
+      this.slash,
+      workingGroupString
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class SetLeaderRewardProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private payoutAmount: BN
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    payoutAmount: BN,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.payoutAmount = payoutAmount
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing set leader reward proposal ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+    const workerId: BN = (await this.apiWrapper.getLeadWorkerId(this.workingGroup))!
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeLeaderRewardFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeLeaderReward(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      workerId,
+      this.payoutAmount,
+      workingGroupString
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class DecreaseLeaderStakeProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private stakeDecrement: BN
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    stakeDecrement: BN,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.stakeDecrement = stakeDecrement
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing decrease leader stake proposal ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+    const workerId: BN = (await this.apiWrapper.getLeadWorkerId(this.workingGroup))!
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeDecreaseLeaderStakeFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeDecreaseLeaderStake(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      workerId,
+      this.stakeDecrement,
+      workingGroupString
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class SlashLeaderProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private slashAmount: BN
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    slashAmount: BN,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.slashAmount = slashAmount
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing slash leader stake proposal ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+    const workerId: BN = (await this.apiWrapper.getLeadWorkerId(this.workingGroup))!
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeSlashLeaderStakeFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeSlashLeaderStake(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      workerId,
+      this.slashAmount,
+      workingGroupString
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class WorkingGroupMintCapacityProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private mintCapacity: BN
+  private workingGroup: WorkingGroups
+
+  private result: BN | undefined
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    mintCapacity: BN,
+    workingGroup: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.sudo = sudo
+    this.mintCapacity = mintCapacity
+    this.workingGroup = workingGroup
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing working group mint capacity proposal ' + uuid().substring(0, 8)
+    const workingGroupString: string = this.apiWrapper.getWorkingGroupString(this.workingGroup)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeWorkingGroupMintCapacityFee()
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeWorkingGroupMintCapacity(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      this.mintCapacity,
+      workingGroupString
+    )
+    this.result = await proposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ElectionParametersProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+
+  constructor(apiWrapper: ApiWrapper, m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[], sudo: KeyringPair) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing validator count proposal ' + uuid().substring(0, 8)
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+    const announcingPeriod: BN = await this.apiWrapper.getAnnouncingPeriod()
+    const votingPeriod: BN = await this.apiWrapper.getVotingPeriod()
+    const revealingPeriod: BN = await this.apiWrapper.getRevealingPeriod()
+    const councilSize: BN = await this.apiWrapper.getCouncilSize()
+    const candidacyLimit: BN = await this.apiWrapper.getCandidacyLimit()
+    const newTermDuration: BN = await this.apiWrapper.getNewTermDuration()
+    const minCouncilStake: BN = await this.apiWrapper.getMinCouncilStake()
+    const minVotingStake: BN = await this.apiWrapper.getMinVotingStake()
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(200000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeElectionParametersFee(
+      description,
+      description,
+      proposalStake,
+      announcingPeriod,
+      votingPeriod,
+      revealingPeriod,
+      councilSize,
+      candidacyLimit,
+      newTermDuration,
+      minCouncilStake,
+      minVotingStake
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposedAnnouncingPeriod: BN = announcingPeriod.subn(1)
+    const proposedVotingPeriod: BN = votingPeriod.addn(1)
+    const proposedRevealingPeriod: BN = revealingPeriod.addn(1)
+    const proposedCouncilSize: BN = councilSize.addn(1)
+    const proposedCandidacyLimit: BN = candidacyLimit.addn(1)
+    const proposedNewTermDuration: BN = newTermDuration.addn(1)
+    const proposedMinCouncilStake: BN = minCouncilStake.addn(1)
+    const proposedMinVotingStake: BN = minVotingStake.addn(1)
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeElectionParameters(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      proposedAnnouncingPeriod,
+      proposedVotingPeriod,
+      proposedRevealingPeriod,
+      proposedCouncilSize,
+      proposedCandidacyLimit,
+      proposedNewTermDuration,
+      proposedMinCouncilStake,
+      proposedMinVotingStake
+    )
+    const proposalNumber = await proposalPromise
+
+    // Approving the proposal
+    const proposalExecutionPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await proposalExecutionPromise
+
+    // Assertions
+    const newAnnouncingPeriod: BN = await this.apiWrapper.getAnnouncingPeriod()
+    const newVotingPeriod: BN = await this.apiWrapper.getVotingPeriod()
+    const newRevealingPeriod: BN = await this.apiWrapper.getRevealingPeriod()
+    const newCouncilSize: BN = await this.apiWrapper.getCouncilSize()
+    const newCandidacyLimit: BN = await this.apiWrapper.getCandidacyLimit()
+    const newNewTermDuration: BN = await this.apiWrapper.getNewTermDuration()
+    const newMinCouncilStake: BN = await this.apiWrapper.getMinCouncilStake()
+    const newMinVotingStake: BN = await this.apiWrapper.getMinVotingStake()
+    assert(
+      proposedAnnouncingPeriod.eq(newAnnouncingPeriod),
+      `Announcing period has unexpected value ${newAnnouncingPeriod}, expected ${proposedAnnouncingPeriod}`
+    )
+    assert(
+      proposedVotingPeriod.eq(newVotingPeriod),
+      `Voting period has unexpected value ${newVotingPeriod}, expected ${proposedVotingPeriod}`
+    )
+    assert(
+      proposedRevealingPeriod.eq(newRevealingPeriod),
+      `Revealing has unexpected value ${newRevealingPeriod}, expected ${proposedRevealingPeriod}`
+    )
+    assert(
+      proposedCouncilSize.eq(newCouncilSize),
+      `Council size has unexpected value ${newCouncilSize}, expected ${proposedCouncilSize}`
+    )
+    assert(
+      proposedCandidacyLimit.eq(newCandidacyLimit),
+      `Candidacy limit has unexpected value ${newCandidacyLimit}, expected ${proposedCandidacyLimit}`
+    )
+    assert(
+      proposedNewTermDuration.eq(newNewTermDuration),
+      `New term duration has unexpected value ${newNewTermDuration}, expected ${proposedNewTermDuration}`
+    )
+    assert(
+      proposedMinCouncilStake.eq(newMinCouncilStake),
+      `Min council stake has unexpected value ${newMinCouncilStake}, expected ${proposedMinCouncilStake}`
+    )
+    assert(
+      proposedMinVotingStake.eq(newMinVotingStake),
+      `Min voting stake has unexpected value ${newMinVotingStake}, expected ${proposedMinVotingStake}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class SetLeadProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+
+  constructor(apiWrapper: ApiWrapper, m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[], sudo: KeyringPair) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing validator count proposal ' + uuid().substring(0, 8)
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(50000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeLeadFee(
+      description,
+      description,
+      proposalStake,
+      this.sudo.address
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeLead(this.m1KeyPairs[0], proposalTitle, description, proposalStake, this.m1KeyPairs[1])
+    const proposalNumber = await proposalPromise
+
+    // Approving the proposal
+    const proposalExecutionPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await proposalExecutionPromise
+    const newLead: string = await this.apiWrapper.getCurrentLeadAddress()
+    assert(
+      newLead === this.m1KeyPairs[1].address,
+      `New lead has unexpected value ${newLead}, expected ${this.m1KeyPairs[1].address}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class SpendingProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private spendingBalance: BN
+  private mintCapacity: BN
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    m2KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    spendingBalance: BN,
+    mintCapacity: BN
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+    this.spendingBalance = spendingBalance
+    this.mintCapacity = mintCapacity
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const description = 'spending proposal which is used for API network testing with some mock data'
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+
+    // Topping the balances
+    const proposalStake: BN = new BN(25000)
+    const runtimeProposalFee: BN = this.apiWrapper.estimateProposeSpendingFee(
+      description,
+      description,
+      proposalStake,
+      this.spendingBalance,
+      this.sudo.address
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake))
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+    await this.apiWrapper.sudoSetCouncilMintCapacity(this.sudo, this.mintCapacity)
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeSpending(
+      this.m1KeyPairs[0],
+      'testing spending' + uuid().substring(0, 8),
+      'spending to test proposal functionality' + uuid().substring(0, 8),
+      proposalStake,
+      this.spendingBalance,
+      this.sudo.address
+    )
+    const proposalNumber = await proposalPromise
+
+    // Approving spending proposal
+    const balanceBeforeMinting: BN = await this.apiWrapper.getBalance(this.sudo.address)
+    const spendingPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await spendingPromise
+    const balanceAfterMinting: BN = await this.apiWrapper.getBalance(this.sudo.address)
+    assert(
+      balanceAfterMinting.sub(balanceBeforeMinting).eq(this.spendingBalance),
+      `member ${
+        this.m1KeyPairs[0].address
+      } has unexpected balance ${balanceAfterMinting}, expected ${balanceBeforeMinting.add(this.spendingBalance)}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class TextProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+
+  constructor(apiWrapper: ApiWrapper, m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[], sudo: KeyringPair) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing text proposal ' + uuid().substring(0, 8)
+    const proposalText: string = 'Text of the testing proposal ' + uuid().substring(0, 8)
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(25000)
+    const runtimeProposalFee: BN = this.apiWrapper.estimateProposeTextFee(
+      proposalStake,
+      description,
+      description,
+      proposalText
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake))
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeText(this.m1KeyPairs[0], proposalStake, proposalTitle, description, proposalText)
+    const proposalNumber = await proposalPromise
+
+    // Approving text proposal
+    const textProposalPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await textProposalPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ValidatorCountProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private validatorCountIncrement: BN
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    m2KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    validatorCountIncrement: BN
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+    this.validatorCountIncrement = validatorCountIncrement
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const proposalTitle: string = 'Testing proposal ' + uuid().substring(0, 8)
+    const description: string = 'Testing validator count proposal ' + uuid().substring(0, 8)
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+
+    // Proposal stake calculation
+    const proposalStake: BN = new BN(100000)
+    const proposalFee: BN = this.apiWrapper.estimateProposeValidatorCountFee(description, description, proposalStake)
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, proposalFee.add(proposalStake))
+    const validatorCount: BN = await this.apiWrapper.getValidatorCount()
+
+    // Proposal creation
+    const proposedValidatorCount: BN = validatorCount.add(this.validatorCountIncrement)
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeValidatorCount(
+      this.m1KeyPairs[0],
+      proposalTitle,
+      description,
+      proposalStake,
+      proposedValidatorCount
+    )
+    const proposalNumber = await proposalPromise
+
+    // Approving the proposal
+    const proposalExecutionPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await proposalExecutionPromise
+    const newValidatorCount: BN = await this.apiWrapper.getValidatorCount()
+    assert(
+      proposedValidatorCount.eq(newValidatorCount),
+      `Validator count has unexpeccted value ${newValidatorCount}, expected ${proposedValidatorCount}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ContentWorkingGroupMintCapacityProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private mintingCapacityIncrement: BN
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    m1KeyPairs: KeyringPair[],
+    m2KeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    mintingCapacityIncrement: BN
+  ) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+    this.mintingCapacityIncrement = mintingCapacityIncrement
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const description = 'Mint capacity proposal which is used for API network testing'
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    const initialMintingCapacity: BN = await this.apiWrapper.getContentWorkingGroupMintCapacity()
+
+    // Topping the balances
+    const proposalStake: BN = new BN(50000)
+    const runtimeProposalFee: BN = this.apiWrapper.estimateProposeContentWorkingGroupMintCapacityFee(
+      description,
+      description,
+      proposalStake,
+      initialMintingCapacity.add(this.mintingCapacityIncrement)
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake))
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+
+    // Proposal creation
+    const proposedMintingCapacity: BN = initialMintingCapacity.add(this.mintingCapacityIncrement)
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeContentWorkingGroupMintCapacity(
+      this.m1KeyPairs[0],
+      'testing mint capacity' + uuid().substring(0, 8),
+      'mint capacity to test proposal functionality' + uuid().substring(0, 8),
+      proposalStake,
+      proposedMintingCapacity
+    )
+    const proposalNumber = await proposalPromise
+
+    // Approving mint capacity proposal
+    const mintCapacityPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await mintCapacityPromise
+    const newMintingCapacity: BN = await this.apiWrapper.getContentWorkingGroupMintCapacity()
+    assert(
+      proposedMintingCapacity.eq(newMintingCapacity),
+      `Content working group has unexpected minting capacity ${newMintingCapacity}, expected ${proposedMintingCapacity}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class UpdateRuntimeFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m1KeyPairs: KeyringPair[]
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+
+  constructor(apiWrapper: ApiWrapper, m1KeyPairs: KeyringPair[], m2KeyPairs: KeyringPair[], sudo: KeyringPair) {
+    this.apiWrapper = apiWrapper
+    this.m1KeyPairs = m1KeyPairs
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Setup
+    const runtime: Bytes = await this.apiWrapper.getRuntime()
+    const description = 'runtime upgrade proposal which is used for API network testing'
+    const runtimeVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+
+    // Topping the balances
+    const proposalStake: BN = new BN(1000000)
+    const runtimeProposalFee: BN = this.apiWrapper.estimateProposeRuntimeUpgradeFee(
+      proposalStake,
+      description,
+      description,
+      runtime
+    )
+    await this.apiWrapper.transferBalance(this.sudo, this.m1KeyPairs[0].address, runtimeProposalFee.add(proposalStake))
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, runtimeVoteFee)
+
+    // Proposal creation
+    const proposalPromise = this.apiWrapper.expectProposalCreated()
+    await this.apiWrapper.proposeRuntime(
+      this.m1KeyPairs[0],
+      proposalStake,
+      'testing runtime' + uuid().substring(0, 8),
+      'runtime to test proposal functionality' + uuid().substring(0, 8),
+      runtime
+    )
+    const proposalNumber = await proposalPromise
+
+    // Approving runtime update proposal
+    const runtimePromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, proposalNumber)
+    await runtimePromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class VoteForProposalFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private m2KeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private proposalNumber: BN
+
+  constructor(apiWrapper: ApiWrapper, m2KeyPairs: KeyringPair[], sudo: KeyringPair, proposalNumber: BN) {
+    this.apiWrapper = apiWrapper
+    this.m2KeyPairs = m2KeyPairs
+    this.sudo = sudo
+    this.proposalNumber = proposalNumber
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const proposalVoteFee: BN = this.apiWrapper.estimateVoteForProposalFee()
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.m2KeyPairs, proposalVoteFee)
+
+    // Approving the proposal
+    const proposalExecutionPromise = this.apiWrapper.expectProposalFinalized()
+    await this.apiWrapper.batchApproveProposal(this.m2KeyPairs, this.proposalNumber)
+    await proposalExecutionPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}

+ 1195 - 0
tests/network-tests/src/iznik/tests/fixtures/workingGroupModule.ts

@@ -0,0 +1,1195 @@
+import BN from 'bn.js'
+import { assert } from 'chai'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { Balance, BlockNumber, Event } from '@polkadot/types/interfaces'
+import { Keyring } from '@polkadot/api'
+import { Option, u32 } from '@polkadot/types'
+import { v4 as uuid } from 'uuid'
+import { RewardRelationship } from '@alexandria/types/recurring-rewards'
+import {
+  Application,
+  ApplicationIdToWorkerIdMap,
+  SlashingTerms,
+  Worker,
+  WorkingGroupOpeningPolicyCommitment,
+} from '@alexandria/types/working-group'
+import { Utils } from '../../utils/utils'
+import {
+  ActivateOpeningAt,
+  ApplicationRationingPolicy,
+  Opening as HiringOpening,
+  StakingPolicy,
+} from '@alexandria/types/hiring'
+import { Fixture } from './interfaces/fixture'
+
+export class AddWorkerOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private applicationStake: BN
+  private roleStake: BN
+  private activationDelay: BN
+  private unstakingPeriod: BN
+  private module: WorkingGroups
+
+  private result: BN | undefined
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    applicationStake: BN,
+    roleStake: BN,
+    activationDelay: BN,
+    unstakingPeriod: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.applicationStake = applicationStake
+    this.roleStake = roleStake
+    this.activationDelay = activationDelay
+    this.unstakingPeriod = unstakingPeriod
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Worker opening construction
+    const activateAtBlock: ActivateOpeningAt = new ActivateOpeningAt(
+      this.activationDelay.eqn(0)
+        ? 'CurrentBlock'
+        : { ExactBlock: (await this.apiWrapper.getBestBlock()).add(this.activationDelay) }
+    )
+    const commitment: WorkingGroupOpeningPolicyCommitment = new WorkingGroupOpeningPolicyCommitment({
+      application_rationing_policy: new Option(ApplicationRationingPolicy, {
+        max_active_applicants: new BN(this.membersKeyPairs.length) as u32,
+      }),
+      max_review_period_length: new BN(32) as u32,
+      application_staking_policy: new Option(StakingPolicy, {
+        amount: this.applicationStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_staking_policy: new Option(StakingPolicy, {
+        amount: this.roleStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_slashing_terms: new SlashingTerms({
+        Slashable: {
+          max_count: new BN(1),
+          max_percent_pts_per_time: new BN(100),
+        },
+      }),
+      fill_opening_successful_applicant_application_stake_unstaking_period: new Option(
+        u32,
+        this.unstakingPeriod as BlockNumber
+      ),
+      fill_opening_failed_applicant_application_stake_unstaking_period: new Option(
+        u32,
+        this.unstakingPeriod as BlockNumber
+      ),
+      fill_opening_failed_applicant_role_stake_unstaking_period: new Option(u32, this.unstakingPeriod as BlockNumber),
+      terminate_application_stake_unstaking_period: new Option(u32, this.unstakingPeriod as BlockNumber),
+      terminate_role_stake_unstaking_period: new Option(u32, this.unstakingPeriod as BlockNumber),
+      exit_role_application_stake_unstaking_period: new Option(u32, this.unstakingPeriod as BlockNumber),
+      exit_role_stake_unstaking_period: new Option(u32, this.unstakingPeriod as BlockNumber),
+    })
+
+    // Fee estimation and transfer
+    const addOpeningFee: BN = this.apiWrapper.estimateAddOpeningFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, addOpeningFee)
+
+    // Worker opening creation
+    const addOpeningPromise: Promise<Event> = this.apiWrapper.expectEvent('OpeningAdded')
+    await this.apiWrapper.addOpening(
+      this.lead,
+      activateAtBlock,
+      commitment,
+      uuid().substring(0, 8),
+      'Worker',
+      this.module,
+      expectFailure
+    )
+    if (!expectFailure) {
+      const openingId: BN = ((await addOpeningPromise).data[0] as unknown) as BN
+      console.log('received opening id ' + openingId)
+      this.result = openingId
+    }
+  }
+}
+
+export class AddLeaderOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private applicationStake: BN
+  private roleStake: BN
+  private activationDelay: BN
+  private module: WorkingGroups
+
+  private result: BN | undefined
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    applicationStake: BN,
+    roleStake: BN,
+    activationDelay: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.applicationStake = applicationStake
+    this.roleStake = roleStake
+    this.activationDelay = activationDelay
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Leader opening creation
+    const activateAtBlock: ActivateOpeningAt = new ActivateOpeningAt(
+      this.activationDelay.eqn(0)
+        ? 'CurrentBlock'
+        : { ExactBlock: (await this.apiWrapper.getBestBlock()).add(this.activationDelay) }
+    )
+    const commitment: WorkingGroupOpeningPolicyCommitment = new WorkingGroupOpeningPolicyCommitment({
+      application_rationing_policy: new Option(ApplicationRationingPolicy, {
+        max_active_applicants: new BN(this.membersKeyPairs.length) as u32,
+      }),
+      max_review_period_length: new BN(32) as u32,
+      application_staking_policy: new Option(StakingPolicy, {
+        amount: this.applicationStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_staking_policy: new Option(StakingPolicy, {
+        amount: this.roleStake,
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_slashing_terms: new SlashingTerms({
+        Slashable: {
+          max_count: new BN(1),
+          max_percent_pts_per_time: new BN(100),
+        },
+      }),
+      fill_opening_successful_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+    })
+
+    const addOpeningPromise: Promise<Event> = this.apiWrapper.expectEvent('OpeningAdded')
+    await this.apiWrapper.sudoAddOpening(
+      this.sudo,
+      activateAtBlock,
+      commitment,
+      uuid().substring(0, 8),
+      'Leader',
+      this.module
+    )
+    const openingId: BN = ((await addOpeningPromise).data[0] as unknown) as BN
+    this.result = openingId
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class AcceptApplicationsFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private openingId: BN
+  private module: WorkingGroups
+
+  public constructor(
+    apiWrapper: ApiWrapper,
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    openingId: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.lead = lead
+    this.sudo = sudo
+    this.openingId = openingId
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const acceptApplicationsFee = this.apiWrapper.estimateAcceptApplicationsFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, acceptApplicationsFee)
+
+    // Begin accepting applications
+    await this.apiWrapper.acceptApplications(this.lead, this.openingId, this.module)
+
+    const opening: HiringOpening = await this.apiWrapper.getHiringOpening(this.openingId)
+    assert(opening.is_active, `Opening ${this.openingId} is not active`)
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ApplyForOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private applicationStake: BN
+  private roleStake: BN
+  private openingId: BN
+  private module: WorkingGroups
+
+  public constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    applicationStake: BN,
+    roleStake: BN,
+    openingId: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.applicationStake = applicationStake
+    this.roleStake = roleStake
+    this.openingId = openingId
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const applyOnOpeningFee: BN = this.apiWrapper
+      .estimateApplyOnOpeningFee(this.sudo, this.module)
+      .add(this.applicationStake)
+      .add(this.roleStake)
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.membersKeyPairs, applyOnOpeningFee)
+
+    // Applying for created worker opening
+    await this.apiWrapper.batchApplyOnOpening(
+      this.membersKeyPairs,
+      this.openingId,
+      this.roleStake,
+      this.applicationStake,
+      uuid().substring(0, 8),
+      this.module,
+      expectFailure
+    )
+  }
+}
+
+export class WithdrawApplicationFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const withdrawApplicaitonFee: BN = this.apiWrapper.estimateWithdrawApplicationFee(this.module)
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.membersKeyPairs, withdrawApplicaitonFee)
+
+    // Application withdrawal
+    await this.apiWrapper.batchWithdrawApplication(this.membersKeyPairs, this.module)
+
+    // Assertions
+    this.membersKeyPairs.forEach(async (keyPair) => {
+      const activeApplications: BN[] = await this.apiWrapper.getActiveApplicationsIdsByRoleAccount(
+        keyPair.address,
+        this.module
+      )
+      assert(activeApplications.length === 0, `Unexpected active application found for ${keyPair.address}`)
+    })
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class BeginApplicationReviewFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private openingId: BN
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, lead: KeyringPair, sudo: KeyringPair, openingId: BN, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.lead = lead
+    this.sudo = sudo
+    this.openingId = openingId
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const beginReviewFee: BN = this.apiWrapper.estimateBeginApplicantReviewFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, beginReviewFee)
+
+    // Begin application review
+    const beginApplicantReviewPromise: Promise<BN> = this.apiWrapper.expectApplicationReviewBegan()
+    await this.apiWrapper.beginApplicantReview(this.lead, this.openingId, this.module)
+    await beginApplicantReviewPromise
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class BeginLeaderApplicationReviewFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private sudo: KeyringPair
+  private openingId: BN
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, sudo: KeyringPair, openingId: BN, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.sudo = sudo
+    this.openingId = openingId
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Begin application review
+    await this.apiWrapper.sudoBeginApplicantReview(this.sudo, this.openingId, this.module)
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class FillOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private openingId: BN
+  private firstPayoutInterval: BN
+  private payoutInterval: BN
+  private amountPerPayout: BN
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    openingId: BN,
+    firstPayoutInterval: BN,
+    payoutInterval: BN,
+    amountPerPayout: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.openingId = openingId
+    this.firstPayoutInterval = firstPayoutInterval
+    this.payoutInterval = payoutInterval
+    this.amountPerPayout = amountPerPayout
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const beginReviewFee: BN = this.apiWrapper.estimateBeginApplicantReviewFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, beginReviewFee)
+    const applicationIds: BN[] = (
+      await Promise.all(
+        this.membersKeyPairs.map(async (keypair) =>
+          this.apiWrapper.getActiveApplicationsIdsByRoleAccount(keypair.address, this.module)
+        )
+      )
+    ).flat()
+
+    // Fill worker opening
+    const now: BN = await this.apiWrapper.getBestBlock()
+    const fillOpeningPromise: Promise<ApplicationIdToWorkerIdMap> = this.apiWrapper.expectOpeningFilled()
+    await this.apiWrapper.fillOpening(
+      this.lead,
+      this.openingId,
+      applicationIds,
+      this.amountPerPayout,
+      now.add(this.firstPayoutInterval),
+      this.payoutInterval,
+      this.module
+    )
+    const applicationIdToWorkerIdMap: ApplicationIdToWorkerIdMap = await fillOpeningPromise
+
+    // Assertions
+    applicationIdToWorkerIdMap.forEach(async (workerId, applicationId) => {
+      const worker: Worker = await this.apiWrapper.getWorkerById(workerId, this.module)
+      const application: Application = await this.apiWrapper.getApplicationById(applicationId, this.module)
+      assert(
+        worker.role_account_id.toString() === application.role_account_id.toString(),
+        `Role account ids does not match, worker account: ${worker.role_account_id}, application account ${application.role_account_id}`
+      )
+    })
+    const openingWorkersAccounts: string[] = (await this.apiWrapper.getWorkers(this.module)).map((worker) =>
+      worker.role_account_id.toString()
+    )
+    this.membersKeyPairs.forEach((keyPair) =>
+      assert(openingWorkersAccounts.includes(keyPair.address), `Account ${keyPair.address} is not worker`)
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class FillLeaderOpeningFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private openingId: BN
+  private firstPayoutInterval: BN
+  private payoutInterval: BN
+  private amountPerPayout: BN
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    sudo: KeyringPair,
+    openingId: BN,
+    firstPayoutInterval: BN,
+    payoutInterval: BN,
+    amountPerPayout: BN,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.openingId = openingId
+    this.firstPayoutInterval = firstPayoutInterval
+    this.payoutInterval = payoutInterval
+    this.amountPerPayout = amountPerPayout
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const applicationIds: BN[] = (
+      await Promise.all(
+        this.membersKeyPairs.map(async (keypair) =>
+          this.apiWrapper.getActiveApplicationsIdsByRoleAccount(keypair.address, this.module)
+        )
+      )
+    ).flat()
+
+    // Fill leader opening
+    const now: BN = await this.apiWrapper.getBestBlock()
+    const fillOpeningPromise: Promise<ApplicationIdToWorkerIdMap> = this.apiWrapper.expectOpeningFilled()
+    await this.apiWrapper.sudoFillOpening(
+      this.sudo,
+      this.openingId,
+      applicationIds,
+      this.amountPerPayout,
+      now.add(this.firstPayoutInterval),
+      this.payoutInterval,
+      this.module
+    )
+
+    // Assertions
+    const applicationIdToWorkerIdMap: ApplicationIdToWorkerIdMap = await fillOpeningPromise
+    applicationIdToWorkerIdMap.forEach(async (workerId, applicationId) => {
+      const worker: Worker = await this.apiWrapper.getWorkerById(workerId, this.module)
+      const application: Application = await this.apiWrapper.getApplicationById(applicationId, this.module)
+      assert(
+        worker.role_account_id.toString() === application.role_account_id.toString(),
+        `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
+      )
+    })
+    const leadWorkerId: BN = (await this.apiWrapper.getLeadWorkerId(this.module))!
+    const openingLeaderAccount: string = (
+      await this.apiWrapper.getWorkerById(leadWorkerId, this.module)
+    ).role_account_id.toString()
+    assert(
+      openingLeaderAccount === this.membersKeyPairs[0].address,
+      `Unexpected leader account ${openingLeaderAccount}, expected ${this.membersKeyPairs[0].address}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class IncreaseStakeFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const increaseStakeFee: BN = this.apiWrapper.estimateIncreaseStakeFee(this.module)
+    const stakeIncrement: BN = new BN(1)
+    await this.apiWrapper.transferBalance(
+      this.sudo,
+      this.membersKeyPairs[0].address,
+      increaseStakeFee.add(stakeIncrement)
+    )
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Increase worker stake
+    const increasedWorkerStake: BN = (await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)).add(
+      stakeIncrement
+    )
+    await this.apiWrapper.increaseStake(this.membersKeyPairs[0], workerId, stakeIncrement, this.module)
+    const newWorkerStake: BN = await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)
+    assert(
+      increasedWorkerStake.eq(newWorkerStake),
+      `Unexpected worker stake ${newWorkerStake}, expected ${increasedWorkerStake}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class UpdateRewardAccountFixture implements Fixture {
+  public apiWrapper: ApiWrapper
+  public membersKeyPairs: KeyringPair[]
+  public keyring: Keyring
+  public sudo: KeyringPair
+  public module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    keyring: Keyring,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.keyring = keyring
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const updateRewardAccountFee: BN = this.apiWrapper.estimateUpdateRewardAccountFee(this.sudo.address, this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.membersKeyPairs[0].address, updateRewardAccountFee)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Update reward account
+    const createdAccount: KeyringPair = this.keyring.addFromUri(uuid().substring(0, 8))
+    await this.apiWrapper.updateRewardAccount(this.membersKeyPairs[0], workerId, createdAccount.address, this.module)
+    const newRewardAccount: string = await this.apiWrapper.getWorkerRewardAccount(workerId, this.module)
+    assert(
+      newRewardAccount === createdAccount.address,
+      `Unexpected role account ${newRewardAccount}, expected ${createdAccount.address}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class UpdateRoleAccountFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private keyring: Keyring
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    keyring: Keyring,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.keyring = keyring
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const updateRoleAccountFee: BN = this.apiWrapper.estimateUpdateRoleAccountFee(this.sudo.address, this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.membersKeyPairs[0].address, updateRoleAccountFee)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Update role account
+    const createdAccount: KeyringPair = this.keyring.addFromUri(uuid().substring(0, 8))
+    await this.apiWrapper.updateRoleAccount(this.membersKeyPairs[0], workerId, createdAccount.address, this.module)
+    const newRoleAccount: string = (
+      await this.apiWrapper.getWorkerById(workerId, this.module)
+    ).role_account_id.toString()
+    assert(
+      newRoleAccount === createdAccount.address,
+      `Unexpected role account ${newRoleAccount}, expected ${createdAccount.address}`
+    )
+
+    this.membersKeyPairs[0] = createdAccount
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class TerminateApplicationsFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const terminateApplicationFee = this.apiWrapper.estimateTerminateApplicationFee(this.module)
+    await this.apiWrapper.transferBalance(
+      this.sudo,
+      this.lead.address,
+      terminateApplicationFee.muln(this.membersKeyPairs.length)
+    )
+
+    // Terminate worker applications
+    await this.apiWrapper.batchTerminateApplication(this.lead, this.membersKeyPairs, this.module)
+    this.membersKeyPairs.forEach(async (keyPair) => {
+      const activeApplications = await this.apiWrapper.getActiveApplicationsIdsByRoleAccount(
+        keyPair.address,
+        this.module
+      )
+      assert(activeApplications.length === 0, `Account ${keyPair.address} has unexpected active applications`)
+    })
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class DecreaseStakeFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const decreaseStakeFee = this.apiWrapper.estimateDecreaseStakeFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, decreaseStakeFee)
+    const workerStakeDecrement = new BN(1)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Worker stake decrement
+    const decreasedWorkerStake: BN = (await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)).sub(
+      workerStakeDecrement
+    )
+    await this.apiWrapper.decreaseStake(this.lead, workerId, workerStakeDecrement, this.module, expectFailure)
+    const newWorkerStake: BN = await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)
+
+    // Assertions
+    if (!expectFailure) {
+      assert(
+        decreasedWorkerStake.eq(newWorkerStake),
+        `Unexpected worker stake ${newWorkerStake}, expected ${decreasedWorkerStake}`
+      )
+    }
+  }
+}
+
+export class SlashFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const slashStakeFee = this.apiWrapper.estimateSlashStakeFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, slashStakeFee)
+    const slashAmount = new BN(1)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Slash worker
+    const slashedStake: BN = (await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)).sub(slashAmount)
+    await this.apiWrapper.slashStake(this.lead, workerId, slashAmount, this.module, expectFailure)
+    const newStake: BN = await this.apiWrapper.getWorkerStakeAmount(workerId, this.module)
+
+    // Assertions
+    assert(slashedStake.eq(newStake), `Unexpected worker stake ${newStake}, expected ${slashedStake}`)
+  }
+}
+
+export class TerminateRoleFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private lead: KeyringPair
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(
+    apiWrapper: ApiWrapper,
+    membersKeyPairs: KeyringPair[],
+    lead: KeyringPair,
+    sudo: KeyringPair,
+    module: WorkingGroups
+  ) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.lead = lead
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const terminateRoleFee = this.apiWrapper.estimateTerminateRoleFee(this.module)
+    await this.apiWrapper.transferBalance(this.sudo, this.lead.address, terminateRoleFee)
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+
+    // Slash worker
+    await this.apiWrapper.terminateRole(this.lead, workerId, uuid().substring(0, 8), this.module, expectFailure)
+
+    // Assertions
+    this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+    const newWorkerId = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+    assert(newWorkerId === undefined, `Worker with account ${this.membersKeyPairs[0].address} is not terminated`)
+  }
+}
+
+export class LeaveRoleFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private sudo: KeyringPair
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], sudo: KeyringPair, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.sudo = sudo
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    // Fee estimation and transfer
+    const leaveRoleFee = this.apiWrapper.estimateLeaveRoleFee(this.module)
+    await this.apiWrapper.transferBalanceToAccounts(this.sudo, this.membersKeyPairs, leaveRoleFee)
+
+    await this.apiWrapper.batchLeaveRole(this.membersKeyPairs, uuid().substring(0, 8), expectFailure, this.module)
+
+    // Assertions
+    this.membersKeyPairs.forEach(async (keyPair) => {
+      this.apiWrapper.getWorkerIdByRoleAccount(keyPair.address, this.module)
+      const newWorkerId = await this.apiWrapper.getWorkerIdByRoleAccount(keyPair.address, this.module)
+      assert(newWorkerId === undefined, `Worker with account ${keyPair.address} is not terminated`)
+    })
+  }
+}
+
+export class AwaitPayoutFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private membersKeyPairs: KeyringPair[]
+  private module: WorkingGroups
+
+  constructor(apiWrapper: ApiWrapper, membersKeyPairs: KeyringPair[], module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.membersKeyPairs = membersKeyPairs
+    this.module = module
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const workerId: BN = await this.apiWrapper.getWorkerIdByRoleAccount(this.membersKeyPairs[0].address, this.module)
+    const worker: Worker = await this.apiWrapper.getWorkerById(workerId, this.module)
+    const reward: RewardRelationship = await this.apiWrapper.getRewardRelationship(worker.reward_relationship.unwrap())
+    const now: BN = await this.apiWrapper.getBestBlock()
+    const nextPaymentBlock: BN = new BN(reward.getField('next_payment_at_block').toString())
+    const payoutInterval: BN = new BN(reward.getField('payout_interval').toString())
+    const amountPerPayout: BN = new BN(reward.getField('amount_per_payout').toString())
+
+    assert(now.lt(nextPaymentBlock), `Payout already happened in block ${nextPaymentBlock} now ${now}`)
+    const balance = await this.apiWrapper.getBalance(this.membersKeyPairs[0].address)
+
+    const firstPayoutWaitingPeriod = nextPaymentBlock.sub(now).addn(1)
+    await Utils.wait(this.apiWrapper.getBlockDuration().mul(firstPayoutWaitingPeriod).toNumber())
+
+    const balanceAfterFirstPayout = await this.apiWrapper.getBalance(this.membersKeyPairs[0].address)
+    const expectedBalanceFirst = balance.add(amountPerPayout)
+    assert(
+      balanceAfterFirstPayout.eq(expectedBalanceFirst),
+      `Unexpected balance, expected ${expectedBalanceFirst} got ${balanceAfterFirstPayout}`
+    )
+
+    const secondPayoutWaitingPeriod = payoutInterval.addn(1)
+    await Utils.wait(this.apiWrapper.getBlockDuration().mul(secondPayoutWaitingPeriod).toNumber())
+
+    const balanceAfterSecondPayout = await this.apiWrapper.getBalance(this.membersKeyPairs[0].address)
+    const expectedBalanceSecond = expectedBalanceFirst.add(amountPerPayout)
+    assert(
+      balanceAfterSecondPayout.eq(expectedBalanceSecond),
+      `Unexpected balance, expected ${expectedBalanceSecond} got ${balanceAfterSecondPayout}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeadOpeningAddedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+
+  // Opening id
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper) {
+    this.apiWrapper = apiWrapper
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('OpeningAdded')
+    this.events.push(event)
+    this.result = (event.data as unknown) as BN
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeaderSetFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private leaderAddress: string
+  private module: WorkingGroups
+
+  // Lead worker id
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, leaderAddress: string, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.leaderAddress = leaderAddress
+    this.module = module
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('LeaderSet')
+    this.events.push(event)
+    const leadWorkerId: BN = (event.data as unknown) as BN
+    const worker: Worker = await this.apiWrapper.getWorkerById(leadWorkerId, this.module)
+    const leaderApplicationId = (
+      await this.apiWrapper.getApplicationsIdsByRoleAccount(this.leaderAddress, this.module)
+    )[0]
+    const application: Application = await this.apiWrapper.getApplicationById(leaderApplicationId, this.module)
+    assert(
+      worker.role_account_id.eq(application.role_account_id),
+      `Role account ids does not match, leader account: ${worker.role_account_id}, application account ${application.role_account_id}`
+    )
+    this.result = leadWorkerId
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectBeganApplicationReviewFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper) {
+    this.apiWrapper = apiWrapper
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('BeganApplicationReview')
+    this.events.push(event)
+    this.result = (event.data as unknown) as BN
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeaderRoleTerminatedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private module: WorkingGroups
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.module = module
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('TerminatedLeader')
+    this.events.push(event)
+    const leadWorkerId: BN | undefined = await this.apiWrapper.getLeadWorkerId(this.module)
+    assert(leadWorkerId === undefined, `Unexpected lead worker id: ${leadWorkerId}, expected none`)
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeaderRewardAmountUpdatedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private expectedReward: BN
+  private module: WorkingGroups
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, expectedReward: BN, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.expectedReward = expectedReward
+    this.module = module
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('WorkerRewardAmountUpdated')
+    this.events.push(event)
+    const leadWorkerId: BN = (await this.apiWrapper.getLeadWorkerId(this.module))!
+    const receivedReward: BN = (await this.apiWrapper.getRewardRelationship(leadWorkerId)).getField<Balance>(
+      'amount_per_payout'
+    )
+    assert(
+      receivedReward.eq(this.expectedReward),
+      `Unexpected reward amount for worker with id ${leadWorkerId}: ${receivedReward}, expected ${this.expectedReward}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeaderStakeDecreasedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private expectedStake: BN
+  private module: WorkingGroups
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, expectedStake: BN, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.expectedStake = expectedStake
+    this.module = module
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('StakeDecreased')
+    this.events.push(event)
+    const leadWorkerId: BN = (await this.apiWrapper.getLeadWorkerId(this.module))!
+    const receivedStake: BN = await this.apiWrapper.getWorkerStakeAmount(leadWorkerId, this.module)
+    assert(
+      receivedStake.eq(this.expectedStake),
+      `Unexpected stake amount for worker with id ${leadWorkerId}: ${receivedStake}, expected ${this.expectedStake}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectLeaderSlashedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private expectedStake: BN
+  private module: WorkingGroups
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, expectedStake: BN, module: WorkingGroups) {
+    this.apiWrapper = apiWrapper
+    this.expectedStake = expectedStake
+    this.module = module
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('StakeSlashed')
+    this.events.push(event)
+    const leadWorkerId: BN = (await this.apiWrapper.getLeadWorkerId(this.module))!
+    const receivedStake: BN = await this.apiWrapper.getWorkerStakeAmount(leadWorkerId, this.module)
+    assert(
+      receivedStake.eq(this.expectedStake),
+      `Unexpected stake amount for worker with id after slash ${leadWorkerId}: ${receivedStake}, expected ${this.expectedStake}`
+    )
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}
+
+export class ExpectMintCapacityChangedFixture implements Fixture {
+  private apiWrapper: ApiWrapper
+  private expectedMintCapacity: BN
+
+  private result: BN | undefined
+  private events: Event[] = []
+
+  constructor(apiWrapper: ApiWrapper, expectedMintCapacity: BN) {
+    this.apiWrapper = apiWrapper
+    this.expectedMintCapacity = expectedMintCapacity
+  }
+
+  public getResult(): BN | undefined {
+    return this.result
+  }
+
+  public getEvents(): Event[] {
+    return this.events
+  }
+
+  public async runner(expectFailure: boolean): Promise<void> {
+    const event: Event = await this.apiWrapper.expectEvent('MintCapacityChanged')
+    this.events.push(event)
+    const receivedMintCapacity = (event.data[1] as unknown) as BN
+    assert(
+      receivedMintCapacity.eq(this.expectedMintCapacity),
+      `Unexpected mint capacity: ${receivedMintCapacity}, expected ${this.expectedMintCapacity}`
+    )
+    this.result = receivedMintCapacity
+    if (expectFailure) {
+      throw new Error('Successful fixture run while expecting failure')
+    }
+  }
+}

+ 56 - 0
tests/network-tests/src/iznik/tests/membership/membershipCreationTest.ts

@@ -0,0 +1,56 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { Keyring, WsProvider } from '@polkadot/api'
+import { initConfig } from '../../utils/config'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { closeApi } from '../../utils/closeApi'
+import { BuyMembershipHappyCaseFixture, BuyMembershipWithInsufficienFundsFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+
+tap.mocha.describe('Membership creation scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const aKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+
+  const durationInBlocks = 7
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Buy membeship is accepted with sufficient funds', async () => happyCaseFixture.runner(false))
+
+  const insufficientFundsFixture: BuyMembershipWithInsufficienFundsFixture = new BuyMembershipWithInsufficienFundsFixture(
+    apiWrapper,
+    sudo,
+    aKeyPair[0],
+    paidTerms
+  )
+  tap.test('Account A can not buy the membership with insufficient funds', async () =>
+    insufficientFundsFixture.runner(false)
+  )
+
+  const buyMembershipAfterAccountTopUp = new BuyMembershipHappyCaseFixture(apiWrapper, sudo, aKeyPair, paidTerms)
+  tap.test('Account A was able to buy the membership with sufficient funds', async () =>
+    buyMembershipAfterAccountTopUp.runner(false)
+  )
+
+  closeApi(apiWrapper)
+})

+ 79 - 0
tests/network-tests/src/iznik/tests/proposals/contentWorkingGroupMintCapacityProposalTest.ts

@@ -0,0 +1,79 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { ContentWorkingGroupMintCapacityProposalFixture } from '../fixtures/proposalsModule'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { Utils } from '../../utils/utils'
+
+tap.mocha.describe('Validator count proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const mintingCapacityIncrement: BN = new BN(+process.env.MINTING_CAPACITY_INCREMENT!)
+
+  const durationInBlocks = 29
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const contentWorkingGroupMintCapacityProposalFixture: ContentWorkingGroupMintCapacityProposalFixture = new ContentWorkingGroupMintCapacityProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    sudo,
+    mintingCapacityIncrement
+  )
+  tap.test('Content working group mint capacity test', async () =>
+    contentWorkingGroupMintCapacityProposalFixture.runner(false)
+  )
+
+  closeApi(apiWrapper)
+})

+ 75 - 0
tests/network-tests/src/iznik/tests/proposals/electionParametersProposalTest.ts

@@ -0,0 +1,75 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { Utils } from '../../utils/utils'
+import { ElectionParametersProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Election parameters proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+
+  const durationInBlocks = 29
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const electionParametersProposalFixture: ElectionParametersProposalFixture = new ElectionParametersProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    sudo
+  )
+  tap.test('Election parameters proposal test', async () => electionParametersProposalFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 304 - 0
tests/network-tests/src/iznik/tests/proposals/manageLeaderRoleTest.ts

@@ -0,0 +1,304 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import {
+  BeginWorkingGroupLeaderApplicationReviewFixture,
+  CreateWorkingGroupLeaderOpeningFixture,
+  DecreaseLeaderStakeProposalFixture,
+  FillLeaderOpeningProposalFixture,
+  SetLeaderRewardProposalFixture,
+  SlashLeaderProposalFixture,
+  TerminateLeaderRoleProposalFixture,
+  VoteForProposalFixture,
+} from '../fixtures/proposalsModule'
+import {
+  ApplyForOpeningFixture,
+  ExpectBeganApplicationReviewFixture,
+  ExpectLeaderRewardAmountUpdatedFixture,
+  ExpectLeaderRoleTerminatedFixture,
+  ExpectLeaderSetFixture,
+  ExpectLeaderSlashedFixture,
+  ExpectLeaderStakeDecreasedFixture,
+  ExpectLeadOpeningAddedFixture,
+} from '../fixtures/workingGroupModule'
+import { Utils } from '../../utils/utils'
+
+tap.mocha.describe('Set lead proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const alteredPayoutAmount: BN = new BN(process.env.ALTERED_PAYOUT_AMOUNT!)
+  const stakeDecrement: BN = new BN(process.env.STAKE_DECREMENT!)
+  const slashAmount: BN = new BN(process.env.SLASH_AMOUNT!)
+  const durationInBlocks = 70
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const leaderMembershipFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buy membership for lead', async () => leaderMembershipFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const createWorkingGroupLeaderOpeningFixture: CreateWorkingGroupLeaderOpeningFixture = new CreateWorkingGroupLeaderOpeningFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    'Storage'
+  )
+  tap.test('Propose create leader opening', async () => createWorkingGroupLeaderOpeningFixture.runner(false))
+
+  let voteForCreateOpeningProposalFixture: VoteForProposalFixture
+  const expectLeadOpeningAddedFixture: ExpectLeadOpeningAddedFixture = new ExpectLeadOpeningAddedFixture(apiWrapper)
+  tap.test('Approve add opening proposal', async () => {
+    voteForCreateOpeningProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      createWorkingGroupLeaderOpeningFixture.getResult()!
+    )
+    voteForCreateOpeningProposalFixture.runner(false)
+    await expectLeadOpeningAddedFixture.runner(false)
+  })
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      new BN(expectLeadOpeningAddedFixture.getResult()!),
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginWorkingGroupLeaderApplicationReviewFixture: BeginWorkingGroupLeaderApplicationReviewFixture
+  tap.test('Propose begin leader application review', async () => {
+    beginWorkingGroupLeaderApplicationReviewFixture = new BeginWorkingGroupLeaderApplicationReviewFixture(
+      apiWrapper,
+      m1KeyPairs,
+      sudo,
+      new BN(expectLeadOpeningAddedFixture.getResult()!),
+      'Storage'
+    )
+    await beginWorkingGroupLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let voteForBeginReviewProposal: VoteForProposalFixture
+  const expectBeganApplicationReviewFixture: ExpectBeganApplicationReviewFixture = new ExpectBeganApplicationReviewFixture(
+    apiWrapper
+  )
+  tap.test('Approve begin application review', async () => {
+    voteForBeginReviewProposal = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      beginWorkingGroupLeaderApplicationReviewFixture.getResult()!
+    )
+    voteForBeginReviewProposal.runner(false)
+    await expectBeganApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningProposalFixture: FillLeaderOpeningProposalFixture
+  tap.test('Propose fill leader opening', async () => {
+    fillLeaderOpeningProposalFixture = new FillLeaderOpeningProposalFixture(
+      apiWrapper,
+      m1KeyPairs,
+      leadKeyPair[0].address,
+      sudo,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      new BN(expectLeadOpeningAddedFixture.getResult()!),
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningProposalFixture.runner(false)
+  })
+
+  let voteForFillLeaderProposalFixture: VoteForProposalFixture
+  const expectLeaderSetFixture: ExpectLeaderSetFixture = new ExpectLeaderSetFixture(
+    apiWrapper,
+    leadKeyPair[0].address,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Approve fill leader opening', async () => {
+    voteForFillLeaderProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      fillLeaderOpeningProposalFixture.getResult()!
+    )
+    voteForFillLeaderProposalFixture.runner(false)
+    await expectLeaderSetFixture.runner(false)
+  })
+
+  const setLeaderRewardProposalFixture: SetLeaderRewardProposalFixture = new SetLeaderRewardProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    alteredPayoutAmount,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Propose leader reward', async () => setLeaderRewardProposalFixture.runner(false))
+
+  let voteForeLeaderRewardFixture: VoteForProposalFixture
+  const expectLeaderRewardAmountUpdatedFixture: ExpectLeaderRewardAmountUpdatedFixture = new ExpectLeaderRewardAmountUpdatedFixture(
+    apiWrapper,
+    alteredPayoutAmount,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Approve new leader reward', async () => {
+    voteForeLeaderRewardFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      setLeaderRewardProposalFixture.getResult()!
+    )
+    voteForeLeaderRewardFixture.runner(false)
+    await expectLeaderRewardAmountUpdatedFixture.runner(false)
+  })
+
+  const decreaseLeaderStakeProposalFixture: DecreaseLeaderStakeProposalFixture = new DecreaseLeaderStakeProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    stakeDecrement,
+    WorkingGroups.StorageWorkingGroup
+  )
+  let newStake: BN
+  tap.test('Propose decrease stake', async () => decreaseLeaderStakeProposalFixture.runner(false))
+
+  let voteForDecreaseStakeProposal: VoteForProposalFixture
+  let expectLeaderStakeDecreasedFixture: ExpectLeaderStakeDecreasedFixture
+  tap.test('Approve decreased leader stake', async () => {
+    newStake = applicationStake.sub(stakeDecrement)
+    voteForFillLeaderProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      decreaseLeaderStakeProposalFixture.getResult()!
+    )
+    voteForFillLeaderProposalFixture.runner(false)
+    expectLeaderStakeDecreasedFixture = new ExpectLeaderStakeDecreasedFixture(
+      apiWrapper,
+      newStake,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await expectLeaderStakeDecreasedFixture.runner(false)
+  })
+
+  const slashLeaderProposalFixture: SlashLeaderProposalFixture = new SlashLeaderProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    slashAmount,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Propose leader slash', async () => slashLeaderProposalFixture.runner(false))
+
+  let voteForSlashProposalFixture: VoteForProposalFixture
+  let expectLeaderSlashedFixture: ExpectLeaderSlashedFixture
+  tap.test('Approve leader slash', async () => {
+    newStake = newStake.sub(slashAmount)
+    voteForSlashProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      slashLeaderProposalFixture.getResult()!
+    )
+    voteForSlashProposalFixture.runner(false)
+    expectLeaderSlashedFixture = new ExpectLeaderSlashedFixture(apiWrapper, newStake, WorkingGroups.StorageWorkingGroup)
+    await expectLeaderSlashedFixture.runner(false)
+  })
+
+  const terminateLeaderRoleProposalFixture: TerminateLeaderRoleProposalFixture = new TerminateLeaderRoleProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    leadKeyPair[0].address,
+    sudo,
+    false,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Propose terminate leader role', async () => terminateLeaderRoleProposalFixture.runner(false))
+
+  let voteForLeaderRoleTerminationFixture: VoteForProposalFixture
+  const expectLeaderRoleTerminatedFixture: ExpectLeaderRoleTerminatedFixture = new ExpectLeaderRoleTerminatedFixture(
+    apiWrapper,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Approve leader role termination', async () => {
+    voteForLeaderRoleTerminationFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      terminateLeaderRoleProposalFixture.getResult()!
+    )
+    voteForLeaderRoleTerminationFixture.runner(false)
+    await expectLeaderRoleTerminatedFixture.runner(false)
+  })
+
+  closeApi(apiWrapper)
+})

+ 75 - 0
tests/network-tests/src/iznik/tests/proposals/setLeadProposalTest.ts

@@ -0,0 +1,75 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { SetLeadProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Set lead proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+
+  const durationInBlocks = 29
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const setLeadProposalFixture: SetLeadProposalFixture = new SetLeadProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    sudo
+  )
+  tap.test('Set lead proposal test', async () => setLeadProposalFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 78 - 0
tests/network-tests/src/iznik/tests/proposals/spendingProposalTest.ts

@@ -0,0 +1,78 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { SpendingProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Spending proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const spendingBalance: BN = new BN(+process.env.SPENDING_BALANCE!)
+  const mintCapacity: BN = new BN(+process.env.COUNCIL_MINTING_CAPACITY!)
+  const durationInBlocks = 29
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const spendingProposalFixture: SpendingProposalFixture = new SpendingProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    sudo,
+    spendingBalance,
+    mintCapacity
+  )
+  tap.test('Spending proposal test', async () => spendingProposalFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 69 - 0
tests/network-tests/src/iznik/tests/proposals/textProposalTest.ts

@@ -0,0 +1,69 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { TextProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Text proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const durationInBlocks = 28
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const textProposalFixture: TextProposalFixture = new TextProposalFixture(apiWrapper, m1KeyPairs, m2KeyPairs, sudo)
+  tap.test('Text proposal test', async () => textProposalFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 77 - 0
tests/network-tests/src/iznik/tests/proposals/updateRuntime.ts

@@ -0,0 +1,77 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { UpdateRuntimeFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Update runtime scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const durationInBlocks = 54
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const updateRuntimeFixture: UpdateRuntimeFixture = new UpdateRuntimeFixture(apiWrapper, m1KeyPairs, m2KeyPairs, sudo)
+  tap.test('Upgrade runtime', async () => updateRuntimeFixture.runner(false))
+
+  const thirdMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    Utils.createKeyPairs(keyring, N),
+    paidTerms
+  )
+  tap.test('Creating third set of members', async () => thirdMemberSetFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 76 - 0
tests/network-tests/src/iznik/tests/proposals/validatorCountProposalTest.ts

@@ -0,0 +1,76 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { ValidatorCountProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Validator count proposal scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const validatorCountIncrement: BN = new BN(+process.env.VALIDATOR_COUNT_INCREMENT!)
+  const durationInBlocks = 29
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const validatorCountProposalFixture: ValidatorCountProposalFixture = new ValidatorCountProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    sudo,
+    validatorCountIncrement
+  )
+  tap.test('Validator count proposal', async () => validatorCountProposalFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 96 - 0
tests/network-tests/src/iznik/tests/proposals/workingGroupMintCapacityProposalTest.ts

@@ -0,0 +1,96 @@
+import { KeyringPair } from '@polkadot/keyring/types'
+import { initConfig } from '../../utils/config'
+import { Keyring, WsProvider } from '@polkadot/api'
+import BN from 'bn.js'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import tap from 'tap'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { Utils } from '../../utils/utils'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { VoteForProposalFixture, WorkingGroupMintCapacityProposalFixture } from '../fixtures/proposalsModule'
+import { ExpectMintCapacityChangedFixture } from '../fixtures/workingGroupModule'
+
+tap.mocha.describe('Set storage working group mint capacity scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.MEMBERSHIP_CREATION_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const mintCapacityIncrement: BN = new BN(process.env.MINT_CAPACITY_INCREMENT!)
+  const durationInBlocks = 30
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const newMintCapacity: BN = (await apiWrapper.getWorkingGroupMintCapacity(WorkingGroups.StorageWorkingGroup)).add(
+    mintCapacityIncrement
+  )
+  const workingGroupMintCapacityProposalFixture: WorkingGroupMintCapacityProposalFixture = new WorkingGroupMintCapacityProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    newMintCapacity,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Propose mint capacity', async () => workingGroupMintCapacityProposalFixture.runner(false))
+
+  let voteForProposalFixture: VoteForProposalFixture
+  const expectMintCapacityChanged: ExpectMintCapacityChangedFixture = new ExpectMintCapacityChangedFixture(
+    apiWrapper,
+    newMintCapacity
+  )
+  tap.test('Approve mint capacity', async () => {
+    voteForProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      workingGroupMintCapacityProposalFixture.getResult()!
+    )
+    voteForProposalFixture.runner(false)
+    await expectMintCapacityChanged.runner(false)
+  })
+
+  closeApi(apiWrapper)
+})

+ 154 - 0
tests/network-tests/src/iznik/tests/workingGroup/atLeastValueBugTest.ts

@@ -0,0 +1,154 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import {
+  AddLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  ApplyForOpeningFixture,
+  BeginLeaderApplicationReviewFixture,
+  FillLeaderOpeningFixture,
+  LeaveRoleFixture,
+} from '../fixtures/workingGroupModule'
+import BN from 'bn.js'
+import tap from 'tap'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+
+tap.mocha.describe('Worker application happy case scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const durationInBlocks = 48
+  const openingActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Creating a set of members', async () => happyCaseFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const addWorkerOpeningWithoutStakeFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    new BN(0),
+    new BN(0),
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening with 0 stake, expect failure', async () =>
+    addWorkerOpeningWithoutStakeFixture.runner(true)
+  )
+
+  const addWorkerOpeningWithoutUnstakingPeriodFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    new BN(0),
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening with 0 unstaking period, expect failure', async () =>
+    addWorkerOpeningWithoutUnstakingPeriodFixture.runner(true)
+  )
+
+  const leaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => leaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 280 - 0
tests/network-tests/src/iznik/tests/workingGroup/manageWorkerAsLeadTest.ts

@@ -0,0 +1,280 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import {
+  AddLeaderOpeningFixture,
+  ApplyForOpeningFixture,
+  BeginLeaderApplicationReviewFixture,
+  FillLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  WithdrawApplicationFixture,
+  BeginApplicationReviewFixture,
+  FillOpeningFixture,
+  LeaveRoleFixture,
+  DecreaseStakeFixture,
+  SlashFixture,
+  TerminateRoleFixture,
+} from '../fixtures/workingGroupModule'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+import BN from 'bn.js'
+import tap from 'tap'
+
+tap.mocha.describe('Manage worker as worker scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const durationInBlocks = 60
+  const openingActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Creating a set of members', async () => happyCaseFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const addWorkerOpeningFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening', async () => addWorkerOpeningFixture.runner(false))
+
+  let applyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('First apply for worker opening', async () => {
+    applyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningFixture.runner(false)
+  })
+
+  let beginApplicationReviewFixture: BeginApplicationReviewFixture
+  tap.test('Begin application review', async () => {
+    beginApplicationReviewFixture = new BeginApplicationReviewFixture(
+      apiWrapper,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginApplicationReviewFixture.runner(false)
+  })
+
+  let fillOpeningFixture: FillOpeningFixture
+  tap.test('Fill worker opening', async () => {
+    fillOpeningFixture = new FillOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillOpeningFixture.runner(false)
+  })
+
+  const leaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => leaveRoleFixture.runner(false))
+
+  const decreaseStakeFailureFixture: DecreaseStakeFixture = new DecreaseStakeFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Decrease worker stake, expect failure', async () => decreaseStakeFailureFixture.runner(true))
+
+  const addNewLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addNewLeaderOpeningFixture.runner(false))
+
+  let applyForNewLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForNewLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addNewLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForNewLeaderOpeningFixture.runner(false)
+  })
+
+  let beginNewLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginNewLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addNewLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginNewLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillNewLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillNewLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addNewLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillNewLeaderOpeningFixture.runner(false)
+  })
+
+  const decreaseStakeFixture: DecreaseStakeFixture = new DecreaseStakeFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Decrease worker stake', async () => decreaseStakeFixture.runner(false))
+
+  const slashFixture: SlashFixture = new SlashFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Slash worker', async () => slashFixture.runner(false))
+
+  const terminateRoleFixture: TerminateRoleFixture = new TerminateRoleFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Terminate worker role', async () => terminateRoleFixture.runner(false))
+
+  const newLeaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => newLeaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 209 - 0
tests/network-tests/src/iznik/tests/workingGroup/manageWorkerAsWorkerTest.ts

@@ -0,0 +1,209 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import {
+  AddLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  ApplyForOpeningFixture,
+  BeginApplicationReviewFixture,
+  BeginLeaderApplicationReviewFixture,
+  FillLeaderOpeningFixture,
+  FillOpeningFixture,
+  IncreaseStakeFixture,
+  LeaveRoleFixture,
+  UpdateRewardAccountFixture,
+} from '../fixtures/workingGroupModule'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+import BN from 'bn.js'
+import tap from 'tap'
+
+tap.mocha.describe('Manage worker as worker scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const durationInBlocks = 38
+  const openingActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Creating a set of members', async () => happyCaseFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const addWorkerOpeningFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening', async () => addWorkerOpeningFixture.runner(false))
+
+  let applyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('First apply for worker opening', async () => {
+    applyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningFixture.runner(false)
+  })
+
+  let beginApplicationReviewFixture: BeginApplicationReviewFixture
+  tap.test('Begin application review', async () => {
+    beginApplicationReviewFixture = new BeginApplicationReviewFixture(
+      apiWrapper,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginApplicationReviewFixture.runner(false)
+  })
+
+  let fillOpeningFixture: FillOpeningFixture
+  tap.test('Fill worker opening', async () => {
+    fillOpeningFixture = new FillOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillOpeningFixture.runner(false)
+  })
+
+  const increaseStakeFixture: IncreaseStakeFixture = new IncreaseStakeFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Increase worker stake', async () => increaseStakeFixture.runner(false))
+
+  const updateRewardAccountFixture: UpdateRewardAccountFixture = new UpdateRewardAccountFixture(
+    apiWrapper,
+    nKeyPairs,
+    keyring,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Update reward account', async () => updateRewardAccountFixture.runner(false))
+
+  const updateRoleAccountFixture: UpdateRewardAccountFixture = new UpdateRewardAccountFixture(
+    apiWrapper,
+    nKeyPairs,
+    keyring,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Update role account', async () => updateRoleAccountFixture.runner(false))
+
+  const newLeaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => newLeaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 204 - 0
tests/network-tests/src/iznik/tests/workingGroup/workerApplicationHappyCaseTest.ts

@@ -0,0 +1,204 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import BN from 'bn.js'
+import tap from 'tap'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+import {
+  AddLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  ApplyForOpeningFixture,
+  BeginApplicationReviewFixture,
+  BeginLeaderApplicationReviewFixture,
+  FillLeaderOpeningFixture,
+  FillOpeningFixture,
+  LeaveRoleFixture,
+  WithdrawApplicationFixture,
+} from '../fixtures/workingGroupModule'
+
+tap.mocha.describe('Worker application happy case scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const durationInBlocks = 48
+  const openingActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Creating a set of members', async () => happyCaseFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const addWorkerOpeningFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening', async () => addWorkerOpeningFixture.runner(false))
+
+  let firstApplyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('First apply for worker opening', async () => {
+    firstApplyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await firstApplyForWorkerOpeningFixture.runner(false)
+  })
+
+  const withdrawApplicationFixture: WithdrawApplicationFixture = new WithdrawApplicationFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Withdraw worker application', async () => withdrawApplicationFixture.runner(false))
+
+  let secondApplyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('Second apply for worker opening', async () => {
+    secondApplyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await secondApplyForWorkerOpeningFixture.runner(false)
+  })
+
+  let beginApplicationReviewFixture: BeginApplicationReviewFixture
+  tap.test('Begin application review', async () => {
+    beginApplicationReviewFixture = new BeginApplicationReviewFixture(
+      apiWrapper,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginApplicationReviewFixture.runner(false)
+  })
+
+  let fillOpeningFixture: FillOpeningFixture
+  tap.test('Fill worker opening', async () => {
+    fillOpeningFixture = new FillOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillOpeningFixture.runner(false)
+  })
+
+  const leaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => leaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 204 - 0
tests/network-tests/src/iznik/tests/workingGroup/workerApplicationRejectionCaseTest.ts

@@ -0,0 +1,204 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import BN from 'bn.js'
+import tap from 'tap'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+import {
+  AcceptApplicationsFixture,
+  AddLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  ApplyForOpeningFixture,
+  BeginLeaderApplicationReviewFixture,
+  FillLeaderOpeningFixture,
+  LeaveRoleFixture,
+  TerminateApplicationsFixture,
+} from '../fixtures/workingGroupModule'
+
+tap.mocha.describe('Worker application happy case scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const nKeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+  const nonMemberKeyPairs = Utils.createKeyPairs(keyring, N)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const firstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const durationInBlocks = 38
+  const openingActivationDelay: BN = new BN(100)
+  const leadOpeningActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const happyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    nKeyPairs,
+    paidTerms
+  )
+  tap.test('Creating a set of members', async () => happyCaseFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    leadOpeningActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const addWorkerOpeningFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening', async () => addWorkerOpeningFixture.runner(false))
+
+  let applyForWorkerOpeningBeforeAcceptanceFixture: ApplyForOpeningFixture
+  tap.test('Apply for worker opening, expect failure', async () => {
+    applyForWorkerOpeningBeforeAcceptanceFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningBeforeAcceptanceFixture.runner(true)
+  })
+
+  let acceptApplicationsFixture: AcceptApplicationsFixture
+  tap.test('Begin accepting worker applications', async () => {
+    acceptApplicationsFixture = new AcceptApplicationsFixture(
+      apiWrapper,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    acceptApplicationsFixture.runner(false)
+  })
+
+  let applyForWorkerOpeningAsNonMemberFixture: ApplyForOpeningFixture
+  tap.test('Apply for worker opening as non-member, expect failure', async () => {
+    applyForWorkerOpeningAsNonMemberFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nonMemberKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningAsNonMemberFixture.runner(true)
+  })
+
+  let applyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for worker opening', async () => {
+    applyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      nKeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningFixture.runner(false)
+  })
+
+  const terminateApplicationsFixture: TerminateApplicationsFixture = new TerminateApplicationsFixture(
+    apiWrapper,
+    nKeyPairs,
+    leadKeyPair[0],
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Terminate worker applicaitons', async () => terminateApplicationsFixture.runner(false))
+
+  const leaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => leaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 243 - 0
tests/network-tests/src/iznik/tests/workingGroup/workerPayoutTest.ts

@@ -0,0 +1,243 @@
+import { initConfig } from '../../utils/config'
+import { registerJoystreamTypes } from '@alexandria/types'
+import { closeApi } from '../../utils/closeApi'
+import { ApiWrapper, WorkingGroups } from '../../utils/apiWrapper'
+import { WsProvider, Keyring } from '@polkadot/api'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { setTestTimeout } from '../../utils/setTestTimeout'
+import {
+  AddLeaderOpeningFixture,
+  AddWorkerOpeningFixture,
+  ApplyForOpeningFixture,
+  AwaitPayoutFixture,
+  BeginApplicationReviewFixture,
+  BeginLeaderApplicationReviewFixture,
+  ExpectMintCapacityChangedFixture,
+  FillLeaderOpeningFixture,
+  FillOpeningFixture,
+  LeaveRoleFixture,
+} from '../fixtures/workingGroupModule'
+import BN from 'bn.js'
+import tap from 'tap'
+import { BuyMembershipHappyCaseFixture } from '../fixtures/membershipModule'
+import { Utils } from '../../utils/utils'
+import { ElectCouncilFixture } from '../fixtures/councilElectionModule'
+import { VoteForProposalFixture, WorkingGroupMintCapacityProposalFixture } from '../fixtures/proposalsModule'
+
+tap.mocha.describe('Worker application happy case scenario', async () => {
+  initConfig()
+  registerJoystreamTypes()
+
+  const nodeUrl: string = process.env.NODE_URL!
+  const sudoUri: string = process.env.SUDO_ACCOUNT_URI!
+  const keyring = new Keyring({ type: 'sr25519' })
+  const provider = new WsProvider(nodeUrl)
+  const apiWrapper: ApiWrapper = await ApiWrapper.create(provider)
+  const sudo: KeyringPair = keyring.addFromUri(sudoUri)
+
+  const N: number = +process.env.WORKING_GROUP_N!
+  const m1KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const m2KeyPairs: KeyringPair[] = Utils.createKeyPairs(keyring, N)
+  const leadKeyPair: KeyringPair[] = Utils.createKeyPairs(keyring, 1)
+
+  const paidTerms: number = +process.env.MEMBERSHIP_PAID_TERMS!
+  const K: number = +process.env.COUNCIL_ELECTION_K!
+  const greaterStake: BN = new BN(+process.env.COUNCIL_STAKE_GREATER_AMOUNT!)
+  const lesserStake: BN = new BN(+process.env.COUNCIL_STAKE_LESSER_AMOUNT!)
+  const applicationStake: BN = new BN(process.env.WORKING_GROUP_APPLICATION_STAKE!)
+  const roleStake: BN = new BN(process.env.WORKING_GROUP_ROLE_STAKE!)
+  const leaderFirstRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const leaderRewardInterval: BN = new BN(process.env.LONG_REWARD_INTERVAL!)
+  const firstRewardInterval: BN = new BN(process.env.SHORT_FIRST_REWARD_INTERVAL!)
+  const rewardInterval: BN = new BN(process.env.SHORT_REWARD_INTERVAL!)
+  const payoutAmount: BN = new BN(process.env.PAYOUT_AMOUNT!)
+  const unstakingPeriod: BN = new BN(process.env.STORAGE_WORKING_GROUP_UNSTAKING_PERIOD!)
+  const mintCapacity: BN = new BN(process.env.STORAGE_WORKING_GROUP_MINTING_CAPACITY!)
+  const durationInBlocks = 48
+  const openingActivationDelay: BN = new BN(0)
+
+  setTestTimeout(apiWrapper, durationInBlocks)
+
+  const firstMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m1KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating first set of members', async () => firstMemberSetFixture.runner(false))
+
+  const secondMemberSetFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    m2KeyPairs,
+    paidTerms
+  )
+  tap.test('Creating second set of members', async () => secondMemberSetFixture.runner(false))
+
+  const electCouncilFixture: ElectCouncilFixture = new ElectCouncilFixture(
+    apiWrapper,
+    m1KeyPairs,
+    m2KeyPairs,
+    K,
+    sudo,
+    greaterStake,
+    lesserStake
+  )
+  tap.test('Elect council', async () => electCouncilFixture.runner(false))
+
+  const leaderHappyCaseFixture: BuyMembershipHappyCaseFixture = new BuyMembershipHappyCaseFixture(
+    apiWrapper,
+    sudo,
+    leadKeyPair,
+    paidTerms
+  )
+  tap.test('Buying membership for leader account', async () => leaderHappyCaseFixture.runner(false))
+
+  const addLeaderOpeningFixture: AddLeaderOpeningFixture = new AddLeaderOpeningFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add lead opening', async () => await addLeaderOpeningFixture.runner(false))
+
+  let applyForLeaderOpeningFixture: ApplyForOpeningFixture
+  tap.test('Apply for lead opening', async () => {
+    applyForLeaderOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      applicationStake,
+      roleStake,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForLeaderOpeningFixture.runner(false)
+  })
+
+  let beginLeaderApplicationReviewFixture: BeginLeaderApplicationReviewFixture
+  tap.test('Begin lead application review', async () => {
+    beginLeaderApplicationReviewFixture = new BeginLeaderApplicationReviewFixture(
+      apiWrapper,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginLeaderApplicationReviewFixture.runner(false)
+  })
+
+  let fillLeaderOpeningFixture: FillLeaderOpeningFixture
+  tap.test('Fill lead opening', async () => {
+    fillLeaderOpeningFixture = new FillLeaderOpeningFixture(
+      apiWrapper,
+      leadKeyPair,
+      sudo,
+      addLeaderOpeningFixture.getResult()!,
+      leaderFirstRewardInterval,
+      leaderRewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillLeaderOpeningFixture.runner(false)
+  })
+
+  const workingGroupMintCapacityProposalFixture: WorkingGroupMintCapacityProposalFixture = new WorkingGroupMintCapacityProposalFixture(
+    apiWrapper,
+    m1KeyPairs,
+    sudo,
+    mintCapacity,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Propose mint capacity', async () => workingGroupMintCapacityProposalFixture.runner(false))
+
+  let voteForProposalFixture: VoteForProposalFixture
+  const expectMintCapacityChanged: ExpectMintCapacityChangedFixture = new ExpectMintCapacityChangedFixture(
+    apiWrapper,
+    mintCapacity
+  )
+  tap.test('Approve mint capacity', async () => {
+    voteForProposalFixture = new VoteForProposalFixture(
+      apiWrapper,
+      m2KeyPairs,
+      sudo,
+      workingGroupMintCapacityProposalFixture.getResult()!
+    )
+    voteForProposalFixture.runner(false)
+    await expectMintCapacityChanged.runner(false)
+  })
+
+  const addWorkerOpeningFixture: AddWorkerOpeningFixture = new AddWorkerOpeningFixture(
+    apiWrapper,
+    m1KeyPairs,
+    leadKeyPair[0],
+    sudo,
+    applicationStake,
+    roleStake,
+    openingActivationDelay,
+    unstakingPeriod,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Add worker opening', async () => addWorkerOpeningFixture.runner(false))
+
+  let applyForWorkerOpeningFixture: ApplyForOpeningFixture
+  tap.test('First apply for worker opening', async () => {
+    applyForWorkerOpeningFixture = new ApplyForOpeningFixture(
+      apiWrapper,
+      m1KeyPairs,
+      sudo,
+      applicationStake,
+      roleStake,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await applyForWorkerOpeningFixture.runner(false)
+  })
+
+  let beginApplicationReviewFixture: BeginApplicationReviewFixture
+  tap.test('Begin application review', async () => {
+    beginApplicationReviewFixture = new BeginApplicationReviewFixture(
+      apiWrapper,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await beginApplicationReviewFixture.runner(false)
+  })
+
+  let fillOpeningFixture: FillOpeningFixture
+  tap.test('Fill worker opening', async () => {
+    fillOpeningFixture = new FillOpeningFixture(
+      apiWrapper,
+      m1KeyPairs,
+      leadKeyPair[0],
+      sudo,
+      addWorkerOpeningFixture.getResult()!,
+      firstRewardInterval,
+      rewardInterval,
+      payoutAmount,
+      WorkingGroups.StorageWorkingGroup
+    )
+    await fillOpeningFixture.runner(false)
+  })
+
+  const awaitPayoutFixture: AwaitPayoutFixture = new AwaitPayoutFixture(
+    apiWrapper,
+    m1KeyPairs,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Await worker payout', async () => awaitPayoutFixture.runner(false))
+
+  const leaveRoleFixture: LeaveRoleFixture = new LeaveRoleFixture(
+    apiWrapper,
+    leadKeyPair,
+    sudo,
+    WorkingGroups.StorageWorkingGroup
+  )
+  tap.test('Leaving lead role', async () => leaveRoleFixture.runner(false))
+
+  closeApi(apiWrapper)
+})

+ 1551 - 0
tests/network-tests/src/iznik/utils/apiWrapper.ts

@@ -0,0 +1,1551 @@
+import { ApiPromise, WsProvider } from '@polkadot/api'
+import { Bytes, Option, u32, Vec } from '@polkadot/types'
+import { Codec } from '@polkadot/types/types'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { MemberId, PaidMembershipTerms } from '@alexandria/types/members'
+import { Mint, MintId } from '@alexandria/types/mint'
+import { Lead, LeadId } from '@alexandria/types/content-working-group'
+import {
+  Application,
+  ApplicationIdToWorkerIdMap,
+  Opening,
+  RewardPolicy,
+  SlashingTerms,
+  Worker,
+  WorkerId,
+  WorkingGroupOpeningPolicyCommitment,
+} from '@alexandria/types/working-group'
+import { RoleParameters } from '@alexandria/types/roles'
+import { Seat } from '@alexandria/types/council'
+import { AccountId, Balance, BalanceOf, BlockNumber, Event, EventRecord } from '@polkadot/types/interfaces'
+import BN from 'bn.js'
+import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { Sender } from './sender'
+import { Utils } from './utils'
+import { Stake, StakedState } from '@alexandria/types/stake'
+import { RewardRelationship } from '@alexandria/types/recurring-rewards'
+import {
+  ActivateOpeningAt,
+  Application as HiringApplication,
+  ApplicationId,
+  ApplicationRationingPolicy,
+  Opening as HiringOpening,
+  OpeningId,
+  StakingPolicy,
+} from '@alexandria/types/hiring'
+import { FillOpeningParameters } from '@alexandria/types/proposals'
+import { WorkingGroup } from '@alexandria/types/common'
+
+export enum WorkingGroups {
+  StorageWorkingGroup = 'storageWorkingGroup',
+}
+
+export class ApiWrapper {
+  private readonly api: ApiPromise
+  private readonly sender: Sender
+
+  public static async create(provider: WsProvider): Promise<ApiWrapper> {
+    const api = await ApiPromise.create({ provider })
+    return new ApiWrapper(api)
+  }
+
+  constructor(api: ApiPromise) {
+    this.api = api
+    this.sender = new Sender(api)
+  }
+
+  public close() {
+    this.api.disconnect()
+  }
+
+  public getWorkingGroupString(workingGroup: WorkingGroups): string {
+    switch (workingGroup) {
+      case WorkingGroups.StorageWorkingGroup:
+        return 'Storage'
+      default:
+        throw new Error(`Invalid working group string representation: ${workingGroup}`)
+    }
+  }
+
+  public async buyMembership(
+    account: KeyringPair,
+    paidTermsId: number,
+    name: string,
+    expectFailure = false
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.members.buyMembership(paidTermsId, /* Handle: */ name, /* Avatar uri: */ '', /* About: */ ''),
+      account,
+      expectFailure
+    )
+  }
+
+  public getMemberIds(address: string): Promise<MemberId[]> {
+    return this.api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
+  }
+
+  public getBalance(address: string): Promise<Balance> {
+    return this.api.query.balances.freeBalance<Balance>(address)
+  }
+
+  public async transferBalance(from: KeyringPair, to: string, amount: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.balances.transfer(to, amount), from)
+  }
+
+  public getPaidMembershipTerms(paidTermsId: number): Promise<Option<PaidMembershipTerms>> {
+    return this.api.query.members.paidMembershipTermsById<Option<PaidMembershipTerms>>(paidTermsId)
+  }
+
+  public getMembershipFee(paidTermsId: number): Promise<BN> {
+    return this.getPaidMembershipTerms(paidTermsId).then((terms) => terms.unwrap().fee.toBn())
+  }
+
+  public async transferBalanceToAccounts(from: KeyringPair, to: KeyringPair[], amount: BN): Promise<void[]> {
+    return Promise.all(
+      to.map(async (keyPair) => {
+        await this.transferBalance(from, keyPair.address, amount)
+      })
+    )
+  }
+
+  private getBaseTxFee(): BN {
+    return this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionBaseFee).toBn()
+  }
+
+  private estimateTxFee(tx: SubmittableExtrinsic<'promise'>): BN {
+    const baseFee: BN = this.getBaseTxFee()
+    const byteFee: BN = this.api.createType('BalanceOf', this.api.consts.transactionPayment.transactionByteFee).toBn()
+    return Utils.calcTxLength(tx).mul(byteFee).add(baseFee)
+  }
+
+  public estimateBuyMembershipFee(account: KeyringPair, paidTermsId: number, name: string): BN {
+    return this.estimateTxFee(
+      this.api.tx.members.buyMembership(paidTermsId, /* Handle: */ name, /* Avatar uri: */ '', /* About: */ '')
+    )
+  }
+
+  public estimateApplyForCouncilFee(amount: BN): BN {
+    return this.estimateTxFee(this.api.tx.councilElection.apply(amount))
+  }
+
+  public estimateVoteForCouncilFee(nominee: string, salt: string, stake: BN): BN {
+    const hashedVote: string = Utils.hashVote(nominee, salt)
+    return this.estimateTxFee(this.api.tx.councilElection.vote(hashedVote, stake))
+  }
+
+  public estimateRevealVoteFee(nominee: string, salt: string): BN {
+    const hashedVote: string = Utils.hashVote(nominee, salt)
+    return this.estimateTxFee(this.api.tx.councilElection.reveal(hashedVote, nominee, salt))
+  }
+
+  public estimateProposeRuntimeUpgradeFee(stake: BN, name: string, description: string, runtime: Bytes | string): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(stake, name, description, stake, runtime)
+    )
+  }
+
+  public estimateProposeTextFee(stake: BN, name: string, description: string, text: string): BN {
+    return this.estimateTxFee(this.api.tx.proposalsCodex.createTextProposal(stake, name, description, stake, text))
+  }
+
+  public estimateProposeSpendingFee(
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSpendingProposal(stake, title, description, stake, balance, destination)
+    )
+  }
+
+  public estimateProposeContentWorkingGroupMintCapacityFee(
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        stake,
+        title,
+        description,
+        stake,
+        balance
+      )
+    )
+  }
+
+  public estimateProposeValidatorCountFee(title: string, description: string, stake: BN): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetValidatorCountProposal(stake, title, description, stake, stake)
+    )
+  }
+
+  public estimateProposeLeadFee(title: string, description: string, stake: BN, address: string): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetLeadProposal(stake, title, description, stake, { stake, address })
+    )
+  }
+
+  public estimateProposeEvictStorageProviderFee(title: string, description: string, stake: BN, address: string): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createEvictStorageProviderProposal(stake, title, description, stake, address)
+    )
+  }
+
+  public estimateProposeStorageRoleParametersFee(
+    title: string,
+    description: string,
+    stake: BN,
+    minStake: BN,
+    minActors: BN,
+    maxActors: BN,
+    reward: BN,
+    rewardPeriod: BN,
+    bondingPeriod: BN,
+    unbondingPeriod: BN,
+    minServicePeriod: BN,
+    startupGracePeriod: BN,
+    entryRequestFee: BN
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(stake, title, description, stake, [
+        minStake,
+        minActors,
+        maxActors,
+        reward,
+        rewardPeriod,
+        bondingPeriod,
+        unbondingPeriod,
+        minServicePeriod,
+        startupGracePeriod,
+        entryRequestFee,
+      ])
+    )
+  }
+
+  public estimateProposeElectionParametersFee(
+    title: string,
+    description: string,
+    stake: BN,
+    announcingPeriod: BN,
+    votingPeriod: BN,
+    revealingPeriod: BN,
+    councilSize: BN,
+    candidacyLimit: BN,
+    newTermDuration: BN,
+    minCouncilStake: BN,
+    minVotingStake: BN
+  ): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetElectionParametersProposal(stake, title, description, stake, [
+        announcingPeriod,
+        votingPeriod,
+        revealingPeriod,
+        councilSize,
+        candidacyLimit,
+        newTermDuration,
+        minCouncilStake,
+        minVotingStake,
+      ])
+    )
+  }
+
+  public estimateVoteForProposalFee(): BN {
+    return this.estimateTxFee(this.api.tx.proposalsEngine.vote(0, 0, 'Approve'))
+  }
+
+  public estimateAddOpeningFee(module: WorkingGroups): BN {
+    const commitment: WorkingGroupOpeningPolicyCommitment = new WorkingGroupOpeningPolicyCommitment({
+      application_rationing_policy: new Option(ApplicationRationingPolicy, {
+        max_active_applicants: new BN(32) as u32,
+      }),
+      max_review_period_length: new BN(32) as u32,
+      application_staking_policy: new Option(StakingPolicy, {
+        amount: new BN(1),
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_staking_policy: new Option(StakingPolicy, {
+        amount: new BN(1),
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_slashing_terms: new SlashingTerms({
+        Slashable: {
+          max_count: new BN(0),
+          max_percent_pts_per_time: new BN(0),
+        },
+      }),
+      fill_opening_successful_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+    })
+
+    return this.estimateTxFee(
+      this.api.tx[module].addOpening('CurrentBlock', commitment, 'Human readable text', 'Worker')
+    )
+  }
+
+  public estimateAcceptApplicationsFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].acceptApplications(0))
+  }
+
+  public estimateApplyOnOpeningFee(account: KeyringPair, module: WorkingGroups): BN {
+    return this.estimateTxFee(
+      this.api.tx[module].applyOnOpening(
+        0,
+        0,
+        account.address,
+        0,
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test'
+      )
+    )
+  }
+
+  public estimateBeginApplicantReviewFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].beginApplicantReview(0))
+  }
+
+  public estimateFillOpeningFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(
+      this.api.tx[module].fillOpening(0, [0], {
+        'amount_per_payout': 0,
+        'next_payment_at_block': 0,
+        'payout_interval': 0,
+      })
+    )
+  }
+
+  public estimateIncreaseStakeFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].increaseStake(0, 0))
+  }
+
+  public estimateDecreaseStakeFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].decreaseStake(0, 0))
+  }
+
+  public estimateUpdateRoleAccountFee(address: string, module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].updateRoleAccount(0, address))
+  }
+
+  public estimateUpdateRewardAccountFee(address: string, module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].updateRewardAccount(0, address))
+  }
+
+  public estimateLeaveRoleFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].leaveRole(0, 'Long justification text'))
+  }
+
+  public estimateWithdrawApplicationFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].withdrawApplication(0))
+  }
+
+  public estimateTerminateApplicationFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].terminateApplication(0))
+  }
+
+  public estimateSlashStakeFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(this.api.tx[module].slashStake(0, 0))
+  }
+
+  public estimateTerminateRoleFee(module: WorkingGroups): BN {
+    return this.estimateTxFee(
+      this.api.tx[module].terminateRole(
+        0,
+        'Long justification text explaining why the worker role will be terminated',
+        false
+      )
+    )
+  }
+
+  public estimateProposeCreateWorkingGroupLeaderOpeningFee(): BN {
+    const commitment: WorkingGroupOpeningPolicyCommitment = new WorkingGroupOpeningPolicyCommitment({
+      application_rationing_policy: new Option(ApplicationRationingPolicy, {
+        max_active_applicants: new BN(32) as u32,
+      }),
+      max_review_period_length: new BN(32) as u32,
+      application_staking_policy: new Option(StakingPolicy, {
+        amount: new BN(1),
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_staking_policy: new Option(StakingPolicy, {
+        amount: new BN(1),
+        amount_mode: 'AtLeast',
+        crowded_out_unstaking_period_length: new BN(1),
+        review_period_expired_unstaking_period_length: new BN(1),
+      }),
+      role_slashing_terms: new SlashingTerms({
+        Slashable: {
+          max_count: new BN(0),
+          max_percent_pts_per_time: new BN(0),
+        },
+      }),
+      fill_opening_successful_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      fill_opening_failed_applicant_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      terminate_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_application_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+      exit_role_stake_unstaking_period: new Option(u32, new BN(1) as BlockNumber),
+    })
+
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
+        0,
+        'some long title for the purpose of testing',
+        'some long description for the purpose of testing',
+        0,
+        {
+          'activate_at': 'CurrentBlock',
+          'commitment': commitment,
+          'human_readable_text': 'Opening readable text',
+          'working_group': 'Storage',
+        }
+      )
+    )
+  }
+
+  public estimateProposeBeginWorkingGroupLeaderApplicationReviewFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        'Storage'
+      )
+    )
+  }
+
+  public estimateProposeFillLeaderOpeningFee(): BN {
+    const fillOpeningParameters: FillOpeningParameters = new FillOpeningParameters({
+      opening_id: new BN(0) as OpeningId,
+      successful_application_id: new BN(0) as ApplicationId,
+      reward_policy: new Option(
+        RewardPolicy,
+        new RewardPolicy({
+          amount_per_payout: new BN(1) as Balance,
+          next_payment_at_block: new BN(99999) as BlockNumber,
+          payout_interval: new Option(u32, new BN(99999) as u32),
+        })
+      ),
+      working_group: new WorkingGroup('Storage'),
+    })
+
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        fillOpeningParameters
+      )
+    )
+  }
+
+  public estimateProposeTerminateLeaderRoleFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        {
+          'worker_id': 0,
+          'rationale': 'Exceptionaly long and extraordinary descriptive rationale',
+          'slash': true,
+          'working_group': 'Storage',
+        }
+      )
+    )
+  }
+
+  public estimateProposeLeaderRewardFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    )
+  }
+
+  public estimateProposeDecreaseLeaderStakeFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    )
+  }
+
+  public estimateProposeSlashLeaderStakeFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        0,
+        'Storage'
+      )
+    )
+  }
+
+  public estimateProposeWorkingGroupMintCapacityFee(): BN {
+    return this.estimateTxFee(
+      this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
+        0,
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        'Some testing text used for estimation purposes which is longer than text expected during the test',
+        0,
+        0,
+        'Storage'
+      )
+    )
+  }
+
+  private applyForCouncilElection(account: KeyringPair, amount: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.councilElection.apply(amount), account, false)
+  }
+
+  public batchApplyForCouncilElection(accounts: KeyringPair[], amount: BN): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair) => {
+        await this.applyForCouncilElection(keyPair, amount)
+      })
+    )
+  }
+
+  public async getCouncilElectionStake(address: string): Promise<BN> {
+    // TODO alter then `applicantStake` type will be introduced
+    return this.api.query.councilElection.applicantStakes(address).then((stake) => {
+      const parsed = JSON.parse(stake.toString())
+      return new BN(parsed.new)
+    })
+  }
+
+  private voteForCouncilMember(account: KeyringPair, nominee: string, salt: string, stake: BN): Promise<void> {
+    const hashedVote: string = Utils.hashVote(nominee, salt)
+    return this.sender.signAndSend(this.api.tx.councilElection.vote(hashedVote, stake), account, false)
+  }
+
+  public batchVoteForCouncilMember(
+    accounts: KeyringPair[],
+    nominees: KeyringPair[],
+    salt: string[],
+    stake: BN
+  ): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair, index) => {
+        await this.voteForCouncilMember(keyPair, nominees[index].address, salt[index], stake)
+      })
+    )
+  }
+
+  private revealVote(account: KeyringPair, commitment: string, nominee: string, salt: string): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.councilElection.reveal(commitment, nominee, salt), account, false)
+  }
+
+  public batchRevealVote(accounts: KeyringPair[], nominees: KeyringPair[], salt: string[]): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair, index) => {
+        const commitment = Utils.hashVote(nominees[index].address, salt[index])
+        await this.revealVote(keyPair, commitment, nominees[index].address, salt[index])
+      })
+    )
+  }
+
+  // TODO consider using configurable genesis instead
+  public sudoStartAnnouncingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageAnnouncing(endsAtBlock)),
+      sudo,
+      false
+    )
+  }
+
+  public sudoStartVotingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageVoting(endsAtBlock)),
+      sudo,
+      false
+    )
+  }
+
+  public sudoStartRevealingPerion(sudo: KeyringPair, endsAtBlock: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.councilElection.setStageRevealing(endsAtBlock)),
+      sudo,
+      false
+    )
+  }
+
+  public sudoSetCouncilMintCapacity(sudo: KeyringPair, capacity: BN): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx.council.setCouncilMintCapacity(capacity)),
+      sudo,
+      false
+    )
+  }
+
+  public sudoSetWorkingGroupMintCapacity(sudo: KeyringPair, capacity: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.sudo.sudo(this.api.tx[module].setMintCapacity(capacity)), sudo, false)
+  }
+
+  public getBestBlock(): Promise<BN> {
+    return this.api.derive.chain.bestNumber()
+  }
+
+  public getCouncil(): Promise<Seat[]> {
+    return this.api.query.council.activeCouncil<Vec<Codec>>().then((seats) => {
+      return (seats as unknown) as Seat[]
+    })
+  }
+
+  public getRuntime(): Promise<Bytes> {
+    return this.api.query.substrate.code<Bytes>()
+  }
+
+  public async proposeRuntime(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    runtime: Bytes | string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createRuntimeUpgradeProposal(memberId, name, description, stake, runtime),
+      account,
+      false
+    )
+  }
+
+  public async proposeText(
+    account: KeyringPair,
+    stake: BN,
+    name: string,
+    description: string,
+    text: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createTextProposal(memberId, name, description, stake, text),
+      account,
+      false
+    )
+  }
+
+  public async proposeSpending(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN,
+    destination: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSpendingProposal(memberId, title, description, stake, balance, destination),
+      account,
+      false
+    )
+  }
+
+  public async proposeContentWorkingGroupMintCapacity(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    balance: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetContentWorkingGroupMintCapacityProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        balance
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeValidatorCount(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    validatorCount: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetValidatorCountProposal(memberId, title, description, stake, validatorCount),
+      account,
+      false
+    )
+  }
+
+  public async proposeLead(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    leadAccount: KeyringPair
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    const leadMemberId: BN = (await this.getMemberIds(leadAccount.address))[0].toBn()
+    const addressString: string = leadAccount.address
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetLeadProposal(memberId, title, description, stake, [
+        leadMemberId,
+        addressString,
+      ]),
+      account,
+      false
+    )
+  }
+
+  public async proposeEvictStorageProvider(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    storageProvider: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createEvictStorageProviderProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        storageProvider
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeStorageRoleParameters(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    minStake: BN,
+    minActors: BN,
+    maxActors: BN,
+    reward: BN,
+    rewardPeriod: BN,
+    bondingPeriod: BN,
+    unbondingPeriod: BN,
+    minServicePeriod: BN,
+    startupGracePeriod: BN,
+    entryRequestFee: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetStorageRoleParametersProposal(memberId, title, description, stake, [
+        minStake,
+        minActors,
+        maxActors,
+        reward,
+        rewardPeriod,
+        bondingPeriod,
+        unbondingPeriod,
+        minServicePeriod,
+        startupGracePeriod,
+        entryRequestFee,
+      ]),
+      account,
+      false
+    )
+  }
+
+  public async proposeElectionParameters(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    announcingPeriod: BN,
+    votingPeriod: BN,
+    revealingPeriod: BN,
+    councilSize: BN,
+    candidacyLimit: BN,
+    newTermDuration: BN,
+    minCouncilStake: BN,
+    minVotingStake: BN
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetElectionParametersProposal(memberId, title, description, stake, [
+        announcingPeriod,
+        votingPeriod,
+        revealingPeriod,
+        councilSize,
+        candidacyLimit,
+        newTermDuration,
+        minCouncilStake,
+        minVotingStake,
+      ]),
+      account,
+      false
+    )
+  }
+
+  public async proposeBeginWorkingGroupLeaderApplicationReview(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    stake: BN,
+    openingId: BN,
+    workingGroup: string
+  ) {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createBeginReviewWorkingGroupLeaderApplicationsProposal(
+        memberId,
+        title,
+        description,
+        stake,
+        openingId,
+        workingGroup
+      ),
+      account,
+      false
+    )
+  }
+
+  public approveProposal(account: KeyringPair, memberId: BN, proposal: BN): Promise<void> {
+    return this.sender.signAndSend(this.api.tx.proposalsEngine.vote(memberId, proposal, 'Approve'), account, false)
+  }
+
+  public batchApproveProposal(council: KeyringPair[], proposal: BN): Promise<void[]> {
+    return Promise.all(
+      council.map(async (keyPair) => {
+        const memberId: BN = (await this.getMemberIds(keyPair.address))[0].toBn()
+        await this.approveProposal(keyPair, memberId, proposal)
+      })
+    )
+  }
+
+  public getBlockDuration(): BN {
+    return this.api.createType('Moment', this.api.consts.babe.expectedBlockTime).toBn()
+  }
+
+  public expectProposalCreated(): Promise<BN> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (record.event.method && record.event.method.toString() === 'ProposalCreated') {
+            unsubscribe()
+            resolve(new BN(record.event.data[1].toString()))
+          }
+        })
+      })
+    })
+  }
+
+  public expectRuntimeUpgraded(): Promise<void> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (record.event.method.toString() === 'RuntimeUpdated') {
+            unsubscribe()
+            resolve()
+          }
+        })
+      })
+    })
+  }
+
+  public expectProposalFinalized(): Promise<void> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (
+            record.event.method &&
+            record.event.method.toString() === 'ProposalStatusUpdated' &&
+            record.event.data[1].toString().includes('Executed')
+          ) {
+            unsubscribe()
+            resolve()
+          }
+        })
+      })
+    })
+  }
+
+  public expectOpeningFilled(): Promise<ApplicationIdToWorkerIdMap> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (record.event.method && record.event.method.toString() === 'OpeningFilled') {
+            unsubscribe()
+            resolve((record.event.data[1] as unknown) as ApplicationIdToWorkerIdMap)
+          }
+        })
+      })
+    })
+  }
+
+  public expectEvent(eventName: string): Promise<Event> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (record.event.method && record.event.method.toString() === eventName) {
+            unsubscribe()
+            resolve(record.event)
+          }
+        })
+      })
+    })
+  }
+
+  public expectApplicationReviewBegan(): Promise<BN> {
+    return new Promise(async (resolve) => {
+      const unsubscribe = await this.api.query.system.events<Vec<EventRecord>>((events) => {
+        events.forEach((record) => {
+          if (record.event.method && record.event.method.toString() === 'BeganApplicationReview') {
+            unsubscribe()
+            resolve((record.event.data as unknown) as BN)
+          }
+        })
+      })
+    })
+  }
+
+  public getTotalIssuance(): Promise<BN> {
+    return this.api.query.balances.totalIssuance<Balance>()
+  }
+
+  public async getRequiredProposalStake(numerator: number, denominator: number): Promise<BN> {
+    const issuance: number = await (await this.getTotalIssuance()).toNumber()
+    const stake = (issuance * numerator) / denominator
+    return new BN(stake.toFixed(0))
+  }
+
+  public getProposalCount(): Promise<BN> {
+    return this.api.query.proposalsEngine.proposalCount<u32>()
+  }
+
+  public async getContentWorkingGroupMintCapacity(): Promise<BN> {
+    const mintId: MintId = await this.api.query.contentWorkingGroup.mint<MintId>()
+    const mintCodec = await this.api.query.minting.mints<Codec[]>(mintId)
+    const mint: Mint = (mintCodec[0] as unknown) as Mint
+    return mint.getField<Balance>('capacity')
+  }
+
+  public async getWorkingGroupMintCapacity(module: WorkingGroups): Promise<BN> {
+    const mintId: MintId = await this.api.query[module].mint<MintId>()
+    const mintCodec = await this.api.query.minting.mints<Codec[]>(mintId)
+    const mint: Mint = (mintCodec[0] as unknown) as Mint
+    return mint.getField<Balance>('capacity')
+  }
+
+  public getValidatorCount(): Promise<BN> {
+    return this.api.query.staking.validatorCount<u32>()
+  }
+
+  public async getCurrentLeadAddress(): Promise<string> {
+    const leadId: Option<LeadId> = await this.api.query.contentWorkingGroup.currentLeadId<Option<LeadId>>()
+    const leadCodec = await this.api.query.contentWorkingGroup.leadById<Codec[]>(leadId.unwrap())
+    const lead = (leadCodec[0] as unknown) as Lead
+    return lead.role_account.toString()
+  }
+
+  public async createStorageProvider(account: KeyringPair): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0].toBn()
+    await this.sender.signAndSend(this.api.tx.actors.roleEntryRequest('StorageProvider', memberId), account, false)
+    await this.sender.signAndSend(this.api.tx.actors.stake('StorageProvider', account.address), account, false)
+    return
+  }
+
+  public async isStorageProvider(address: string): Promise<boolean> {
+    const storageProviders: Vec<AccountId> = await this.api.query.actors.accountIdsByRole<Vec<AccountId>>(
+      'StorageProvider'
+    )
+    const accountWorkers: BN = await this.getWorkerIdByRoleAccount(address, WorkingGroups.StorageWorkingGroup)
+    return accountWorkers !== undefined
+  }
+
+  public async addOpening(
+    leader: KeyringPair,
+    actiavteAt: ActivateOpeningAt,
+    commitment: WorkingGroupOpeningPolicyCommitment,
+    text: string,
+    type: string,
+    module: WorkingGroups,
+    expectFailure: boolean
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.createAddOpeningTransaction(actiavteAt, commitment, text, type, module),
+      leader,
+      expectFailure
+    )
+  }
+
+  public async sudoAddOpening(
+    sudo: KeyringPair,
+    actiavteAt: ActivateOpeningAt,
+    commitment: WorkingGroupOpeningPolicyCommitment,
+    text: string,
+    type: string,
+    module: WorkingGroups
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.createAddOpeningTransaction(actiavteAt, commitment, text, type, module)),
+      sudo,
+      false
+    )
+  }
+
+  public async proposeCreateWorkingGroupLeaderOpening(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    actiavteAt: ActivateOpeningAt,
+    commitment: WorkingGroupOpeningPolicyCommitment,
+    text: string,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createAddWorkingGroupLeaderOpeningProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        {
+          activate_at: actiavteAt,
+          commitment: commitment,
+          human_readable_text: text,
+          working_group: workingGroup,
+        }
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeFillLeaderOpening(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    fillOpeningParameters: FillOpeningParameters
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createFillWorkingGroupLeaderOpeningProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        fillOpeningParameters
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeTerminateLeaderRole(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    leadWorkerId: BN,
+    rationale: string,
+    slash: boolean,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createTerminateWorkingGroupLeaderRoleProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        {
+          'worker_id': leadWorkerId,
+          rationale,
+          slash,
+          'working_group': workingGroup,
+        }
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeLeaderReward(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetWorkingGroupLeaderRewardProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeDecreaseLeaderStake(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createDecreaseWorkingGroupLeaderStakeProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeSlashLeaderStake(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    workerId: BN,
+    rewardAmount: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSlashWorkingGroupLeaderStakeProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        workerId,
+        rewardAmount,
+        workingGroup
+      ),
+      account,
+      false
+    )
+  }
+
+  public async proposeWorkingGroupMintCapacity(
+    account: KeyringPair,
+    title: string,
+    description: string,
+    proposalStake: BN,
+    mintCapacity: BN,
+    workingGroup: string
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx.proposalsCodex.createSetWorkingGroupMintCapacityProposal(
+        memberId,
+        title,
+        description,
+        proposalStake,
+        mintCapacity,
+        workingGroup
+      ),
+      account,
+      false
+    )
+  }
+
+  private createAddOpeningTransaction(
+    actiavteAt: ActivateOpeningAt,
+    commitment: WorkingGroupOpeningPolicyCommitment,
+    text: string,
+    type: string,
+    module: WorkingGroups
+  ): SubmittableExtrinsic<'promise'> {
+    return this.api.tx[module].addOpening(actiavteAt, commitment, text, type)
+  }
+
+  public async acceptApplications(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].acceptApplications(openingId), leader, false)
+  }
+
+  public async beginApplicantReview(leader: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].beginApplicantReview(openingId), leader, false)
+  }
+
+  public async sudoBeginApplicantReview(sudo: KeyringPair, openingId: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(this.api.tx[module].beginApplicantReview(openingId)),
+      sudo,
+      false
+    )
+  }
+
+  public async applyOnOpening(
+    account: KeyringPair,
+    roleAccountAddress: string,
+    openingId: BN,
+    roleStake: BN,
+    applicantStake: BN,
+    text: string,
+    expectFailure: boolean,
+    module: WorkingGroups
+  ): Promise<void> {
+    const memberId: BN = (await this.getMemberIds(account.address))[0]
+    return this.sender.signAndSend(
+      this.api.tx[module].applyOnOpening(memberId, openingId, roleAccountAddress, roleStake, applicantStake, text),
+      account,
+      expectFailure
+    )
+  }
+
+  public async batchApplyOnOpening(
+    accounts: KeyringPair[],
+    openingId: BN,
+    roleStake: BN,
+    applicantStake: BN,
+    text: string,
+    module: WorkingGroups,
+    expectFailure: boolean
+  ): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair) => {
+        await this.applyOnOpening(
+          keyPair,
+          keyPair.address,
+          openingId,
+          roleStake,
+          applicantStake,
+          text,
+          expectFailure,
+          module
+        )
+      })
+    )
+  }
+
+  public async fillOpening(
+    leader: KeyringPair,
+    openingId: BN,
+    applicationId: BN[],
+    amountPerPayout: BN,
+    nextPaymentBlock: BN,
+    payoutInterval: BN,
+    module: WorkingGroups
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx[module].fillOpening(openingId, applicationId, {
+        'amount_per_payout': amountPerPayout,
+        'next_payment_at_block': nextPaymentBlock,
+        'payout_interval': payoutInterval,
+      }),
+      leader,
+      false
+    )
+  }
+
+  public async sudoFillOpening(
+    sudo: KeyringPair,
+    openingId: BN,
+    applicationId: BN[],
+    amountPerPayout: BN,
+    nextPaymentBlock: BN,
+    payoutInterval: BN,
+    module: WorkingGroups
+  ): Promise<void> {
+    return this.sender.signAndSend(
+      this.api.tx.sudo.sudo(
+        this.api.tx[module].fillOpening(openingId, applicationId, {
+          'amount_per_payout': amountPerPayout,
+          'next_payment_at_block': nextPaymentBlock,
+          'payout_interval': payoutInterval,
+        })
+      ),
+      sudo,
+      false
+    )
+  }
+
+  public async increaseStake(worker: KeyringPair, workerId: BN, stake: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].increaseStake(workerId, stake), worker, false)
+  }
+
+  public async decreaseStake(
+    leader: KeyringPair,
+    workerId: BN,
+    stake: BN,
+    module: WorkingGroups,
+    expectFailure: boolean
+  ): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].decreaseStake(workerId, stake), leader, expectFailure)
+  }
+
+  public async slashStake(
+    leader: KeyringPair,
+    workerId: BN,
+    stake: BN,
+    module: WorkingGroups,
+    expectFailure: boolean
+  ): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].slashStake(workerId, stake), leader, expectFailure)
+  }
+
+  public async updateRoleAccount(
+    worker: KeyringPair,
+    workerId: BN,
+    newRoleAccount: string,
+    module: WorkingGroups
+  ): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].updateRoleAccount(workerId, newRoleAccount), worker, false)
+  }
+
+  public async updateRewardAccount(
+    worker: KeyringPair,
+    workerId: BN,
+    newRewardAccount: string,
+    module: WorkingGroups
+  ): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].updateRewardAccount(workerId, newRewardAccount), worker, false)
+  }
+
+  public async withdrawApplication(account: KeyringPair, workerId: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].withdrawApplication(workerId), account, false)
+  }
+
+  public async batchWithdrawApplication(accounts: KeyringPair[], module: WorkingGroups): Promise<void[]> {
+    return Promise.all(
+      accounts.map(async (keyPair) => {
+        const applicationIds: BN[] = await this.getApplicationsIdsByRoleAccount(keyPair.address, module)
+        await this.withdrawApplication(keyPair, applicationIds[0], module)
+      })
+    )
+  }
+
+  public async terminateApplication(leader: KeyringPair, applicationId: BN, module: WorkingGroups): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].terminateApplication(applicationId), leader, false)
+  }
+
+  public async batchTerminateApplication(
+    leader: KeyringPair,
+    roleAccounts: KeyringPair[],
+    module: WorkingGroups
+  ): Promise<void[]> {
+    return Promise.all(
+      roleAccounts.map(async (keyPair) => {
+        const applicationIds: BN[] = await this.getActiveApplicationsIdsByRoleAccount(keyPair.address, module)
+        await this.terminateApplication(leader, applicationIds[0], module)
+      })
+    )
+  }
+
+  public async terminateRole(
+    leader: KeyringPair,
+    applicationId: BN,
+    text: string,
+    module: WorkingGroups,
+    expectFailure: boolean
+  ): Promise<void> {
+    return this.sender.signAndSend(this.api.tx[module].terminateRole(applicationId, text, false), leader, expectFailure)
+  }
+
+  public async leaveRole(
+    account: KeyringPair,
+    text: string,
+    expectFailure: boolean,
+    module: WorkingGroups
+  ): Promise<void> {
+    const workerId: BN = await this.getWorkerIdByRoleAccount(account.address, module)
+    return this.sender.signAndSend(this.api.tx[module].leaveRole(workerId, text), account, expectFailure)
+  }
+
+  public async batchLeaveRole(
+    roleAccounts: KeyringPair[],
+    text: string,
+    expectFailure: boolean,
+    module: WorkingGroups
+  ): Promise<void[]> {
+    return Promise.all(
+      roleAccounts.map(async (keyPair) => {
+        await this.leaveRole(keyPair, text, expectFailure, module)
+      })
+    )
+  }
+
+  public async getStorageRoleParameters(): Promise<RoleParameters> {
+    return (await this.api.query.actors.parameters<Option<RoleParameters>>('StorageProvider')).unwrap()
+  }
+
+  public async getAnnouncingPeriod(): Promise<BN> {
+    return this.api.query.councilElection.announcingPeriod<BlockNumber>()
+  }
+
+  public async getVotingPeriod(): Promise<BN> {
+    return this.api.query.councilElection.votingPeriod<BlockNumber>()
+  }
+
+  public async getRevealingPeriod(): Promise<BN> {
+    return this.api.query.councilElection.revealingPeriod<BlockNumber>()
+  }
+
+  public async getCouncilSize(): Promise<BN> {
+    return this.api.query.councilElection.councilSize<u32>()
+  }
+
+  public async getCandidacyLimit(): Promise<BN> {
+    return this.api.query.councilElection.candidacyLimit<u32>()
+  }
+
+  public async getNewTermDuration(): Promise<BN> {
+    return this.api.query.councilElection.newTermDuration<BlockNumber>()
+  }
+
+  public async getMinCouncilStake(): Promise<BN> {
+    return this.api.query.councilElection.minCouncilStake<BalanceOf>()
+  }
+
+  public async getMinVotingStake(): Promise<BN> {
+    return this.api.query.councilElection.minVotingStake<BalanceOf>()
+  }
+
+  public async getNextOpeningId(module: WorkingGroups): Promise<BN> {
+    return this.api.query[module].nextOpeningId<u32>()
+  }
+
+  public async getNextApplicationId(module: WorkingGroups): Promise<BN> {
+    return this.api.query[module].nextApplicationId<u32>()
+  }
+
+  public async getOpening(id: BN, module: WorkingGroups): Promise<Opening> {
+    return ((await this.api.query[module].openingById<Codec[]>(id))[0] as unknown) as Opening
+  }
+
+  public async getHiringOpening(id: BN): Promise<HiringOpening> {
+    return ((await this.api.query.hiring.openingById<Codec[]>(id))[0] as unknown) as HiringOpening
+  }
+
+  public async getWorkers(module: WorkingGroups): Promise<Worker[]> {
+    return ((await this.api.query[module].workerById<Codec[]>())[1] as unknown) as Worker[]
+  }
+
+  public async getWorkerById(id: BN, module: WorkingGroups): Promise<Worker> {
+    return ((await this.api.query[module].workerById<Codec[]>(id))[0] as unknown) as Worker
+  }
+
+  public async getWorkerIdByRoleAccount(address: string, module: WorkingGroups): Promise<BN> {
+    const workersAndIds = await this.api.query[module].workerById<Codec[]>()
+    const workers: Worker[] = (workersAndIds[1] as unknown) as Worker[]
+    const ids: WorkerId[] = (workersAndIds[0] as unknown) as WorkerId[]
+    const index: number = workers.findIndex((worker) => worker.role_account_id.toString() === address)
+    return ids[index]
+  }
+
+  public async getApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise<BN[]> {
+    const applicationsAndIds = await this.api.query[module].applicationById<Codec[]>()
+    const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[]
+    const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[]
+    return applications
+      .map((application, index) => (application.role_account_id.toString() === address ? ids[index] : undefined))
+      .filter((id) => id !== undefined) as BN[]
+  }
+
+  public async getHiringApplicationById(id: BN): Promise<HiringApplication> {
+    return ((await this.api.query.hiring.applicationById<Codec[]>(id))[0] as unknown) as HiringApplication
+  }
+
+  public async getApplicationById(id: BN, module: WorkingGroups): Promise<Application> {
+    return ((await this.api.query[module].applicationById<Codec[]>(id))[0] as unknown) as Application
+  }
+
+  public async getActiveApplicationsIdsByRoleAccount(address: string, module: WorkingGroups): Promise<BN[]> {
+    const applicationsAndIds = await this.api.query[module].applicationById<Codec[]>()
+    const applications: Application[] = (applicationsAndIds[1] as unknown) as Application[]
+    const ids: ApplicationId[] = (applicationsAndIds[0] as unknown) as ApplicationId[]
+    return (
+      await Promise.all(
+        applications.map(async (application, index) => {
+          if (
+            application.role_account_id.toString() === address &&
+            (await this.getHiringApplicationById(application.application_id)).stage.type === 'Active'
+          ) {
+            return ids[index]
+          } else {
+            return undefined
+          }
+        })
+      )
+    ).filter((index) => index !== undefined) as BN[]
+  }
+
+  public async getStake(id: BN): Promise<Stake> {
+    return ((await this.api.query.stake.stakes<Codec[]>(id))[0] as unknown) as Stake
+  }
+
+  public async getWorkerStakeAmount(workerId: BN, module: WorkingGroups): Promise<BN> {
+    const stakeId: BN = (await this.getWorkerById(workerId, module)).role_stake_profile.unwrap().stake_id
+    return (((await this.getStake(stakeId)).staking_status.value as unknown) as StakedState).staked_amount
+  }
+
+  public async getRewardRelationship(id: BN): Promise<RewardRelationship> {
+    return ((
+      await this.api.query.recurringRewards.rewardRelationships<Codec[]>(id)
+    )[0] as unknown) as RewardRelationship
+  }
+
+  public async getWorkerRewardAccount(workerId: BN, module: WorkingGroups): Promise<string> {
+    const rewardRelationshipId: BN = (await this.getWorkerById(workerId, module)).reward_relationship.unwrap()
+    return (await this.getRewardRelationship(rewardRelationshipId)).getField('account').toString()
+  }
+
+  public async getLeadWorkerId(module: WorkingGroups): Promise<BN | undefined> {
+    return (await this.api.query[module].currentLead<Option<WorkerId>>()).unwrapOr(undefined)
+  }
+}

+ 8 - 0
tests/network-tests/src/iznik/utils/closeApi.ts

@@ -0,0 +1,8 @@
+import { ApiWrapper } from './apiWrapper'
+import tap from 'tap'
+
+export function closeApi(apiWrapper: ApiWrapper) {
+  tap.teardown(() => {
+    apiWrapper.close()
+  })
+}

+ 5 - 0
tests/network-tests/src/iznik/utils/config.ts

@@ -0,0 +1,5 @@
+import { config } from 'dotenv'
+
+export function initConfig() {
+  config()
+}

+ 66 - 0
tests/network-tests/src/iznik/utils/sender.ts

@@ -0,0 +1,66 @@
+import BN from 'bn.js'
+import { ApiPromise } from '@polkadot/api'
+import { Index } from '@polkadot/types/interfaces'
+import { SubmittableExtrinsic } from '@polkadot/api/types'
+import { KeyringPair } from '@polkadot/keyring/types'
+
+export class Sender {
+  private readonly api: ApiPromise
+  private static nonceMap: Map<string, BN> = new Map()
+
+  constructor(api: ApiPromise) {
+    this.api = api
+  }
+
+  private async getNonce(address: string): Promise<BN> {
+    let oncahinNonce: BN = new BN(0)
+    if (!Sender.nonceMap.get(address)) {
+      oncahinNonce = await this.api.query.system.accountNonce<Index>(address)
+    }
+    let nonce: BN | undefined = Sender.nonceMap.get(address)
+    if (!nonce) {
+      nonce = oncahinNonce
+    }
+    const nextNonce: BN = nonce.addn(1)
+    Sender.nonceMap.set(address, nextNonce)
+    return nonce
+  }
+
+  private clearNonce(address: string): void {
+    Sender.nonceMap.delete(address)
+  }
+
+  public async signAndSend(
+    tx: SubmittableExtrinsic<'promise'>,
+    account: KeyringPair,
+    expectFailure = false
+  ): Promise<void> {
+    return new Promise(async (resolve, reject) => {
+      const nonce: BN = await this.getNonce(account.address)
+      const signedTx = tx.sign(account, { nonce })
+      await signedTx
+        .send(async (result) => {
+          if (result.status.isFinalized === true && result.events !== undefined) {
+            result.events.forEach((event) => {
+              if (event.event.method === 'ExtrinsicFailed') {
+                if (expectFailure) {
+                  resolve()
+                } else {
+                  reject(new Error('Extrinsic failed unexpectedly'))
+                }
+              }
+            })
+            resolve()
+          }
+          if (result.status.isFuture) {
+            console.log('nonce ' + nonce + ' for account ' + account.address + ' is in future')
+            this.clearNonce(account.address)
+            reject(new Error('Extrinsic nonce is in future'))
+          }
+        })
+        .catch((error) => {
+          reject(error)
+        })
+    })
+  }
+}

+ 7 - 0
tests/network-tests/src/iznik/utils/setTestTimeout.ts

@@ -0,0 +1,7 @@
+import tap from 'tap'
+import { ApiWrapper } from './apiWrapper'
+
+export function setTestTimeout(apiWrapper: ApiWrapper, durationInBlocks: number) {
+  const durationInMillis = apiWrapper.getBlockDuration().muln(durationInBlocks).toNumber()
+  tap.setTimeout(durationInMillis)
+}

+ 63 - 0
tests/network-tests/src/iznik/utils/utils.ts

@@ -0,0 +1,63 @@
+import { IExtrinsic } from '@polkadot/types/types'
+import { compactToU8a, stringToU8a } from '@polkadot/util'
+import { blake2AsHex } from '@polkadot/util-crypto'
+import BN from 'bn.js'
+import fs from 'fs'
+import Keyring, { decodeAddress } from '@polkadot/keyring'
+import { Seat } from '@alexandria/types/council'
+import { KeyringPair } from '@polkadot/keyring/types'
+import { v4 as uuid } from 'uuid'
+
+export class Utils {
+  private static LENGTH_ADDRESS = 32 + 1 // publicKey + prefix
+  private static LENGTH_ERA = 2 // assuming mortals
+  private static LENGTH_SIGNATURE = 64 // assuming ed25519 or sr25519
+  private static LENGTH_VERSION = 1 // 0x80 & version
+
+  public static calcTxLength = (extrinsic?: IExtrinsic | null, nonce?: BN): BN => {
+    return new BN(
+      Utils.LENGTH_VERSION +
+        Utils.LENGTH_ADDRESS +
+        Utils.LENGTH_SIGNATURE +
+        Utils.LENGTH_ERA +
+        compactToU8a(nonce || 0).length +
+        (extrinsic ? extrinsic.encodedLength : 0)
+    )
+  }
+
+  /** hash(accountId + salt) */
+  public static hashVote(accountId: string, salt: string): string {
+    const accountU8a = decodeAddress(accountId)
+    const saltU8a = stringToU8a(salt)
+    const voteU8a = new Uint8Array(accountU8a.length + saltU8a.length)
+    voteU8a.set(accountU8a)
+    voteU8a.set(saltU8a, accountU8a.length)
+
+    const hash = blake2AsHex(voteU8a, 256)
+    return hash
+  }
+
+  public static wait(ms: number): Promise<void> {
+    return new Promise((resolve) => setTimeout(resolve, ms))
+  }
+
+  public static getTotalStake(seat: Seat): BN {
+    return new BN(+seat.stake.toString() + seat.backers.reduce((a, baker) => a + +baker.stake.toString(), 0))
+  }
+
+  public static readRuntimeFromFile(path: string): string {
+    return '0x' + fs.readFileSync(path).toString('hex')
+  }
+
+  public static camelToSnakeCase(key: string): string {
+    return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
+  }
+
+  public static createKeyPairs(keyring: Keyring, n: number): KeyringPair[] {
+    const nKeyPairs: KeyringPair[] = []
+    for (let i = 0; i < n; i++) {
+      nKeyPairs.push(keyring.addFromUri(i + uuid().substring(0, 8)))
+    }
+    return nKeyPairs
+  }
+}

+ 1 - 1
types/package.json

@@ -48,7 +48,7 @@
     "joystream",
     "runtime"
   ],
-  "license": "GPLv3",
+  "license": "GPL-3.0-only",
   "bugs": {
     "url": "https://github.com/Joystream/joystream/issues"
   },

+ 220 - 4
yarn.lock

@@ -2,6 +2,10 @@
 # yarn lockfile v1
 
 
+"@alexandria/types@link:types":
+  version "0.0.0"
+  uid ""
+
 "@babel/cli@^7.7.4":
   version "7.7.4"
   resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.7.4.tgz#38804334c8db40209f88c69a5c90998e60cca18b"
@@ -1993,7 +1997,6 @@
   dependencies:
     "@polkadot/api" "^1.26.1"
     "@polkadot/keyring" "^3.0.1"
-    "@polkadot/typegen" "^1.26.1"
     "@polkadot/types" "^1.26.1"
     "@types/lodash" "^4.14.157"
     "@types/vfile" "^4.0.0"
@@ -2772,6 +2775,29 @@
     "@nodelib/fs.scandir" "2.1.3"
     fastq "^1.6.0"
 
+"@oclif/color@^0.x":
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/@oclif/color/-/color-0.1.2.tgz#28b07e2850d9ce814d0b587ce3403b7ad8f7d987"
+  integrity sha512-M9o+DOrb8l603qvgz1FogJBUGLqcMFL1aFg2ZEL0FbXJofiNTLOWIeB4faeZTLwE6dt0xH9GpCVpzksMMzGbmA==
+  dependencies:
+    ansi-styles "^3.2.1"
+    chalk "^3.0.0"
+    strip-ansi "^5.2.0"
+    supports-color "^5.4.0"
+    tslib "^1"
+
+"@oclif/command@^1.5.10":
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.7.0.tgz#194dcc0be58f24470d884ff0cc09ed78c623b2a4"
+  integrity sha512-TkknFtWcZI8te0E8sW+ohiblExrLx73rIcV4KdIzDX01u+oTZWZaap51F6TSGFnR/Gey0WctaDvJhZlt4xgKdA==
+  dependencies:
+    "@oclif/config" "^1.15.1"
+    "@oclif/errors" "^1.3.3"
+    "@oclif/parser" "^3.8.3"
+    "@oclif/plugin-help" "^3"
+    debug "^4.1.1"
+    semver "^5.6.0"
+
 "@oclif/command@^1.5.13", "@oclif/command@^1.5.19", "@oclif/command@^1.5.20", "@oclif/command@^1.6.0":
   version "1.6.1"
   resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.6.1.tgz#774e860f283f32a728377da1c2a90beb8aadf9f5"
@@ -2794,6 +2820,18 @@
     debug "^4.1.1"
     tslib "^1.9.3"
 
+"@oclif/config@^1.12.8", "@oclif/config@^1.13.0":
+  version "1.16.0"
+  resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.16.0.tgz#fffe9939f1a7fac1e45e532b5c785381257f8aa6"
+  integrity sha512-vOnMPQcHokC03WBCuLipTxksTwgZcmDOnH2H0UHqndfKKN9GVDzpZTH6zaFVQBdjTME5VtRzg9A2UaNmq6OXWw==
+  dependencies:
+    "@oclif/errors" "^1.3.3"
+    "@oclif/parser" "^3.8.0"
+    debug "^4.1.1"
+    globby "^11.0.1"
+    is-wsl "^2.1.1"
+    tslib "^1.9.3"
+
 "@oclif/dev-cli@^1.22.2":
   version "1.22.2"
   resolved "https://registry.yarnpkg.com/@oclif/dev-cli/-/dev-cli-1.22.2.tgz#e890f93d0335c0e3faaa25741999776259b2171f"
@@ -2823,6 +2861,17 @@
     strip-ansi "^5.0.0"
     wrap-ansi "^4.0.0"
 
+"@oclif/errors@^1.3.3":
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.3.tgz#fb597dfbc58c6b8609dc0b2fdf91a2d487818a82"
+  integrity sha512-EJR6AIOEkt/NnARNIVAskPDVtdhtO5TTNXmhDrGqMoWVsr0R6DkkLrMyq95BmHvlVWM1nduoq4fQPuCyuF2jaA==
+  dependencies:
+    clean-stack "^3.0.0"
+    fs-extra "^9.0.1"
+    indent-string "^4.0.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^7.0.0"
+
 "@oclif/linewrap@^1.0.0":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91"
@@ -2838,6 +2887,19 @@
     chalk "^2.4.2"
     tslib "^1.9.3"
 
+"@oclif/plugin-autocomplete@^0.2.0":
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/@oclif/plugin-autocomplete/-/plugin-autocomplete-0.2.0.tgz#e965ae83abdb81ff97eb7d63a975bcedb78dfdd9"
+  integrity sha512-pHbaE2PH7d9lHjCgFrrQ+ZIwvY+7OAQaGoaANqDbicBNDK/Rszt4N4oGj22dJT7sCQ8a/3Eh942rjxYIq9Mi9Q==
+  dependencies:
+    "@oclif/command" "^1.5.13"
+    "@oclif/config" "^1.13.0"
+    chalk "^2.4.1"
+    cli-ux "^5.2.1"
+    debug "^4.0.0"
+    fs-extra "^7.0.0"
+    moment "^2.22.1"
+
 "@oclif/plugin-help@^2.1.6", "@oclif/plugin-help@^2.2.3":
   version "2.2.3"
   resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-2.2.3.tgz#b993041e92047f0e1762668aab04d6738ac06767"
@@ -2867,6 +2929,32 @@
     widest-line "^2.0.1"
     wrap-ansi "^4.0.0"
 
+"@oclif/plugin-not-found@^1.2.4":
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/@oclif/plugin-not-found/-/plugin-not-found-1.2.4.tgz#160108c82f0aa10f4fb52cee4e0135af34b7220b"
+  integrity sha512-G440PCuMi/OT8b71aWkR+kCWikngGtyRjOR24sPMDbpUFV4+B3r51fz1fcqeUiiEOYqUpr0Uy/sneUe1O/NfBg==
+  dependencies:
+    "@oclif/color" "^0.x"
+    "@oclif/command" "^1.6.0"
+    cli-ux "^4.9.0"
+    fast-levenshtein "^2.0.6"
+    lodash "^4.17.13"
+
+"@oclif/plugin-warn-if-update-available@^1.7.0":
+  version "1.7.0"
+  resolved "https://registry.yarnpkg.com/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-1.7.0.tgz#5a72abe39ce0b831eb4ae81cb64eb4b9f3ea424a"
+  integrity sha512-Nwyz3BJ8RhsfQ+OmFSsJSPIfn5YJqMrCzPh72Zgo2jqIjKIBWD8N9vTTe4kZlpeUUn77SyXFfwlBQbNCL5OEuQ==
+  dependencies:
+    "@oclif/command" "^1.5.10"
+    "@oclif/config" "^1.12.8"
+    "@oclif/errors" "^1.2.2"
+    chalk "^2.4.1"
+    debug "^4.1.0"
+    fs-extra "^7.0.0"
+    http-call "^5.2.2"
+    lodash.template "^4.4.0"
+    semver "^5.6.0"
+
 "@oclif/screen@^1.0.3":
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/@oclif/screen/-/screen-1.0.4.tgz#b740f68609dfae8aa71c3a6cab15d816407ba493"
@@ -5719,6 +5807,11 @@ asynckit@^0.4.0:
   resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
   integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
 
+at-least-node@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
+  integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
+
 atob-lite@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/atob-lite/-/atob-lite-2.0.0.tgz#0fef5ad46f1bd7a8502c65727f0367d5ee43d696"
@@ -7272,6 +7365,13 @@ clean-stack@^2.0.0:
   resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
   integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
 
+clean-stack@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.0.tgz#a7c249369fcf0f33c7888c20ea3f3dc79620211f"
+  integrity sha512-RHxtgFvXsRQ+1AM7dlozLDY7ssmvUUh0XEnfnyhYgJTO6beNZHBogiaCwGM9Q3rFrUkYxOtsZRC0zAturg5bjg==
+  dependencies:
+    escape-string-regexp "4.0.0"
+
 cli-boxes@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
@@ -7339,6 +7439,33 @@ cli-truncate@^2.1.0:
     slice-ansi "^3.0.0"
     string-width "^4.2.0"
 
+cli-ux@^4.9.0:
+  version "4.9.3"
+  resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-4.9.3.tgz#4c3e070c1ea23eef010bbdb041192e0661be84ce"
+  integrity sha512-/1owvF0SZ5Gn54cgrikJ0QskgTzeg30HGjkmjFoaHDJzAqFpuX1DBpFR8aLvsE1J5s9MgeYRENQK4BFwOag5VA==
+  dependencies:
+    "@oclif/errors" "^1.2.2"
+    "@oclif/linewrap" "^1.0.0"
+    "@oclif/screen" "^1.0.3"
+    ansi-escapes "^3.1.0"
+    ansi-styles "^3.2.1"
+    cardinal "^2.1.1"
+    chalk "^2.4.1"
+    clean-stack "^2.0.0"
+    extract-stack "^1.0.0"
+    fs-extra "^7.0.0"
+    hyperlinker "^1.0.0"
+    indent-string "^3.2.0"
+    is-wsl "^1.1.0"
+    lodash "^4.17.11"
+    password-prompt "^1.0.7"
+    semver "^5.6.0"
+    strip-ansi "^5.0.0"
+    supports-color "^5.5.0"
+    supports-hyperlinks "^1.0.1"
+    treeify "^1.1.0"
+    tslib "^1.9.3"
+
 cli-ux@^5.2.1, cli-ux@^5.4.5:
   version "5.4.6"
   resolved "https://registry.yarnpkg.com/cli-ux/-/cli-ux-5.4.6.tgz#26bbe7d1fb20402ce362240ea5dc434168ddbd46"
@@ -9695,6 +9822,11 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
 
+escape-string-regexp@4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
 escape-string-regexp@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
@@ -10601,6 +10733,18 @@ fast-glob@^3.0.3:
     merge2 "^1.3.0"
     micromatch "^4.0.2"
 
+fast-glob@^3.1.1:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3"
+  integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.0"
+    merge2 "^1.3.0"
+    micromatch "^4.0.2"
+    picomatch "^2.2.1"
+
 fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
@@ -11179,6 +11323,16 @@ fs-extra@^8.0.1, fs-extra@^8.1.0:
     jsonfile "^4.0.0"
     universalify "^0.1.0"
 
+fs-extra@^9.0.1:
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc"
+  integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==
+  dependencies:
+    at-least-node "^1.0.0"
+    graceful-fs "^4.2.0"
+    jsonfile "^6.0.1"
+    universalify "^1.0.0"
+
 fs-minipass@^1.2.5:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7"
@@ -11696,6 +11850,18 @@ globby@^10.0.2:
     merge2 "^1.2.3"
     slash "^3.0.0"
 
+globby@^11.0.1:
+  version "11.0.1"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357"
+  integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.1.1"
+    ignore "^5.1.4"
+    merge2 "^1.3.0"
+    slash "^3.0.0"
+
 globby@^6.1.0:
   version "6.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
@@ -12242,7 +12408,7 @@ http-cache-semantics@^3.8.1:
   resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
   integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
 
-http-call@^5.1.2:
+http-call@^5.1.2, http-call@^5.2.2:
   version "5.3.0"
   resolved "https://registry.yarnpkg.com/http-call/-/http-call-5.3.0.tgz#4ded815b13f423de176eb0942d69c43b25b148db"
   integrity sha512-ahwimsC23ICE4kPl9xTBjKB4inbRaeLyZeRunC/1Jy/Z6X8tv22MEAjK+KBOMSVLaqXPTTmd8638waVIKLGx2w==
@@ -12491,6 +12657,11 @@ ignore@^5.1.1:
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf"
   integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==
 
+ignore@^5.1.4:
+  version "5.1.8"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
+  integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
+
 image-size@~0.5.0:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c"
@@ -13252,6 +13423,11 @@ is-directory@^0.3.1:
   resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
   integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
 
+is-docker@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b"
+  integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==
+
 is-dom@^1.0.9:
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.1.0.tgz#af1fced292742443bb59ca3f76ab5e80907b4e8a"
@@ -13705,6 +13881,13 @@ is-wsl@^2.1.0:
   resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
   integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
 
+is-wsl@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
+  integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
+  dependencies:
+    is-docker "^2.0.0"
+
 isarray@0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
@@ -14427,6 +14610,15 @@ jsonfile@^4.0.0:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
+jsonfile@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.0.1.tgz#98966cba214378c8c84b82e085907b40bf614179"
+  integrity sha512-jR2b5v7d2vIOust+w3wtFKZIfpC2pnRmFAhAC/BuweZFQR8qZzxH1OyrQ10HmdVYiXWkYUqPVsz91cG7EL2FBg==
+  dependencies:
+    universalify "^1.0.0"
+  optionalDependencies:
+    graceful-fs "^4.1.6"
+
 jsonparse@^1.2.0:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
@@ -16041,6 +16233,11 @@ moment@^2.10.2, moment@^2.24.0:
   resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
   integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
 
+moment@^2.22.1:
+  version "2.27.0"
+  resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d"
+  integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==
+
 moo@^0.4.3:
   version "0.4.3"
   resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e"
@@ -17564,7 +17761,7 @@ pascalcase@^0.1.1:
   resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
   integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
 
-password-prompt@^1.1.2:
+password-prompt@^1.0.7, password-prompt@^1.1.2:
   version "1.1.2"
   resolved "https://registry.yarnpkg.com/password-prompt/-/password-prompt-1.1.2.tgz#85b2f93896c5bd9e9f2d6ff0627fa5af3dc00923"
   integrity sha512-bpuBhROdrhuN3E7G/koAju0WjVw9/uQOG5Co5mokNj0MiOSBVZS1JTwM4zl55hu0WFmIEFvO9cU9sJQiBIYeIA==
@@ -22689,6 +22886,11 @@ traverse@~0.6.6:
   resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
   integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc=
 
+treeify@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/treeify/-/treeify-1.1.0.tgz#4e31c6a463accd0943879f30667c4fdaff411bb8"
+  integrity sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==
+
 treport@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/treport/-/treport-1.0.2.tgz#5f99e68198982984415434a2a84df2af2dd7171d"
@@ -22824,7 +23026,7 @@ tsconfig-paths@^3.4.0, tsconfig-paths@^3.9.0:
     minimist "^1.2.0"
     strip-bom "^3.0.0"
 
-tslib@^1.11.1:
+tslib@^1, tslib@^1.11.1:
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
   integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
@@ -23239,6 +23441,11 @@ universalify@^0.1.0:
   resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
   integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
 
+universalify@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d"
+  integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==
+
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -24178,6 +24385,15 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrappy@1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"