Browse Source

Merge latest iznik changes, resolve chain_spec merge conflict

iorveth 4 years ago
parent
commit
4d15ae3ed6
100 changed files with 2167 additions and 1372 deletions
  1. 25 0
      .github/workflows/run-network-tests.yml
  2. 6 5
      .travis.yml
  3. 194 2
      Cargo.lock
  4. 2 2
      README.md
  5. 31 31
      cli/README.md
  6. 2 2
      cli/package.json
  7. 81 108
      cli/src/Api.ts
  8. 101 113
      cli/src/Types.ts
  9. 5 3
      cli/src/base/AccountsCommandBase.ts
  10. 42 27
      cli/src/base/ApiCommandBase.ts
  11. 4 4
      cli/src/base/WorkingGroupsCommandBase.ts
  12. 2 3
      cli/src/commands/account/current.ts
  13. 4 4
      cli/src/commands/account/transferTokens.ts
  14. 11 14
      cli/src/commands/api/inspect.ts
  15. 1 0
      cli/src/commands/working-groups/application.ts
  16. 2 2
      cli/src/commands/working-groups/createOpening.ts
  17. 3 5
      cli/src/commands/working-groups/decreaseWorkerStake.ts
  18. 3 4
      cli/src/commands/working-groups/evictWorker.ts
  19. 4 8
      cli/src/commands/working-groups/fillOpening.ts
  20. 1 0
      cli/src/commands/working-groups/opening.ts
  21. 2 5
      cli/src/commands/working-groups/slashWorker.ts
  22. 2 4
      cli/src/commands/working-groups/startAcceptingApplications.ts
  23. 2 4
      cli/src/commands/working-groups/startReviewPeriod.ts
  24. 3 4
      cli/src/commands/working-groups/terminateApplication.ts
  25. 2 2
      cli/src/commands/working-groups/updateRewardAccount.ts
  26. 2 2
      cli/src/commands/working-groups/updateRoleAccount.ts
  27. 2 2
      cli/src/commands/working-groups/updateWorkerReward.ts
  28. 2 2
      cli/src/helpers/validation.ts
  29. 11 9
      cli/src/promptOptions/addWorkerOpening.ts
  30. 5 1
      cli/tsconfig.json
  31. 22 0
      devops/ansible/build-and-run-tests-exported-chainspec-playbook.yml
  32. 6 1
      devops/ansible/build-and-run-tests-single-node-playbook.yml
  33. 6 1
      devops/ansible/build-and-run-tests-two-nodes-playbook.yml
  34. 6 1
      devops/ansible/build-image-playbook.yml
  35. 2 2
      devops/ansible/docker-compose.yml
  36. 4 0
      devops/ansible/roles/alter_block_creation_time/tasks/main.yml
  37. 1 1
      devops/ansible/roles/build_docker_image/tasks/main.yml
  38. 16 5
      devops/ansible/roles/install_dependencies/tasks/main.yml
  39. 38 0
      devops/ansible/roles/run_tests_exported_chainspec/tasks/main.yml
  40. 16 5
      devops/ansible/roles/run_tests_single_node/tasks/main.yml
  41. 0 37
      devops/dockerfiles/ansible-node/Dockerfile
  42. 1 1
      devops/dockerfiles/node-and-runtime/Dockerfile
  43. 2 2
      devops/dockerfiles/rust-builder/Dockerfile
  44. 5 5
      devops/git-hooks/pre-push
  45. 4 3
      node/Cargo.toml
  46. 3 3
      node/README.md
  47. 0 0
      node/res/acropolis_members.json
  48. 0 0
      node/res/forum_data_acropolis_encoded.json
  49. 0 0
      node/res/forum_data_acropolis_serialized.json
  50. 0 1
      node/res/forum_data_empty.json
  51. 391 0
      node/src/chain_spec/content_config.rs
  52. 149 0
      node/src/chain_spec/forum_config.rs
  53. 18 0
      node/src/chain_spec/initial_balances.rs
  54. 13 0
      node/src/chain_spec/initial_members.rs
  55. 140 132
      node/src/chain_spec/mod.rs
  56. 2 2
      node/src/chain_spec/proposals_config.rs
  57. 0 90
      node/src/forum_config/from_encoded.rs
  58. 0 51
      node/src/forum_config/from_serialized.rs
  59. 0 10
      node/src/forum_config/mod.rs
  60. 0 3
      node/src/lib.rs
  61. 0 38
      node/src/members_config.rs
  62. 244 247
      node/src/service.rs
  63. 7 11
      package.json
  64. 0 9
      pioneer/.eslintignore
  65. 2 0
      pioneer/.eslintrc.js
  66. 39 73
      pioneer/.storybook/webpack.config.js
  67. 5 4
      pioneer/package.json
  68. 90 90
      pioneer/packages/apps-config/src/settings/endpoints.ts
  69. 2 2
      pioneer/packages/apps-routing/src/accounts.ts
  70. 16 0
      pioneer/packages/apps-routing/src/index.ts
  71. 17 0
      pioneer/packages/apps-routing/src/joy-election.ts
  72. 15 0
      pioneer/packages/apps-routing/src/joy-forum.ts
  73. 15 0
      pioneer/packages/apps-routing/src/joy-media.ts
  74. 16 0
      pioneer/packages/apps-routing/src/joy-proposals.ts
  75. 6 8
      pioneer/packages/apps-routing/src/joy-roles.ts
  76. 20 0
      pioneer/packages/apps-routing/src/memo.ts
  77. 1 1
      pioneer/packages/apps-routing/src/staking.ts
  78. 2 0
      pioneer/packages/apps-routing/src/types.ts
  79. 0 0
      pioneer/packages/apps/public/images/default-thumbnail.png
  80. 1 0
      pioneer/packages/apps/public/locales/en/app-accounts.json
  81. 1 0
      pioneer/packages/apps/public/locales/en/index.json
  82. 0 1
      pioneer/packages/apps/public/locales/en/joy-settings.json
  83. 0 1
      pioneer/packages/apps/public/locales/en/joy-utils-old.json
  84. 4 1
      pioneer/packages/apps/public/locales/en/joy-utils.json
  85. 1 1
      pioneer/packages/apps/src/Content/NotFound.tsx
  86. 24 6
      pioneer/packages/apps/src/Content/index.tsx
  87. 5 2
      pioneer/packages/apps/src/SideBar/Item.tsx
  88. 0 2
      pioneer/packages/apps/src/SideBar/index.tsx
  89. 14 7
      pioneer/packages/apps/src/initSettings.ts
  90. 14 0
      pioneer/packages/apps/webpack.base.config.js
  91. 0 0
      pioneer/packages/joy-election/.skip-build
  92. 3 3
      pioneer/packages/joy-election/package.json
  93. 9 6
      pioneer/packages/joy-election/src/Applicant.tsx
  94. 44 24
      pioneer/packages/joy-election/src/Applicants.tsx
  95. 19 21
      pioneer/packages/joy-election/src/ApplyForm.tsx
  96. 2 2
      pioneer/packages/joy-election/src/CandidatePreview.tsx
  97. 8 7
      pioneer/packages/joy-election/src/Council.tsx
  98. 83 53
      pioneer/packages/joy-election/src/Dashboard.tsx
  99. 21 18
      pioneer/packages/joy-election/src/Reveals.tsx
  100. 15 6
      pioneer/packages/joy-election/src/SealedVote.tsx

+ 25 - 0
.github/workflows/run-network-tests.yml

@@ -0,0 +1,25 @@
+name: run-network-tests
+on:
+  pull_request:
+    types: [labeled]
+  workflow_dispatch:
+
+jobs:
+  run_ansible_tests:
+    if: github.event.label.name == 'run-network-tests' || github.event.action == null
+    name: run network tests using ansible
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+      - uses: actions/checkout@v1
+      - uses: actions/setup-node@v1
+        with:
+          node-version: ${{ matrix.node-version }}
+      - name: install toolchain
+        run: curl https://getsubstrate.io -sSf | bash -s -- --fast
+      - name: ansible build and tests
+        run: |
+          cd ./devops/ansible
+          ansible-playbook -i hosts build-and-run-tests-single-node-playbook.yml --become -v

+ 6 - 5
.travis.yml

@@ -24,7 +24,7 @@ before_install:
     fi
     fi
 
 
 install:
 install:
-  - rustup install nightly-2020-05-23
+  - rustup install nightly-2020-05-23 --force
   - rustup target add wasm32-unknown-unknown --toolchain nightly-2020-05-23
   - rustup target add wasm32-unknown-unknown --toolchain nightly-2020-05-23
   # travis installs rust using rustup with the "minimal" profile so these tools are not installed by default
   # travis installs rust using rustup with the "minimal" profile so these tools are not installed by default
   - rustup component add rustfmt
   - rustup component add rustfmt
@@ -34,9 +34,10 @@ before_script:
   - cargo fmt --all -- --check
   - cargo fmt --all -- --check
 
 
 script:
 script:
-  # we set release as build type for all steps to benefit from already compiled packages in prior steps
-  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release -- -D warnings
-  - BUILD_DUMMY_WASM_BINARY=1 cargo test --release --verbose --all
-  - TRIGGER_WASM_BUILD=1 WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release -p joystream-node
+  - export WASM_BUILD_TOOLCHAIN=nightly-2020-05-23
+  - BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release --all -- -D warnings
+  - travis_wait 75 cargo test --release --verbose --all -- --ignored
+  - cargo build --release
   - ls -l ./target/release/wbuild/joystream-node-runtime/
   - ls -l ./target/release/wbuild/joystream-node-runtime/
   - ./target/release/joystream-node --version
   - ./target/release/joystream-node --version
+  - ./target/release/chain-spec-builder --version

+ 194 - 2
Cargo.lock

@@ -565,6 +565,7 @@ name = "chain-spec-builder"
 version = "3.0.0"
 version = "3.0.0"
 dependencies = [
 dependencies = [
  "ansi_term 0.12.1",
  "ansi_term 0.12.1",
+ "enum-utils",
  "joystream-node",
  "joystream-node",
  "rand 0.7.3",
  "rand 0.7.3",
  "sc-chain-spec",
  "sc-chain-spec",
@@ -927,6 +928,30 @@ dependencies = [
  "syn 0.11.11",
  "syn 0.11.11",
 ]
 ]
 
 
+[[package]]
+name = "enum-utils"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed327f716d0d351d86c9fd3398d20ee39ad8f681873cc081da2ca1c10fed398a"
+dependencies = [
+ "enum-utils-from-str",
+ "failure",
+ "proc-macro2",
+ "quote 1.0.7",
+ "serde_derive_internals",
+ "syn 1.0.17",
+]
+
+[[package]]
+name = "enum-utils-from-str"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49be08bad6e4ca87b2b8e74146987d4e5cb3b7512efa50ef505b51a22227ee1"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.7",
+]
+
 [[package]]
 [[package]]
 name = "env_logger"
 name = "env_logger"
 version = "0.7.1"
 version = "0.7.1"
@@ -1968,12 +1993,13 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "joystream-node"
 name = "joystream-node"
-version = "3.0.0"
+version = "3.1.0"
 dependencies = [
 dependencies = [
  "frame-benchmarking",
  "frame-benchmarking",
  "frame-benchmarking-cli",
  "frame-benchmarking-cli",
  "frame-system",
  "frame-system",
  "futures 0.3.4",
  "futures 0.3.4",
+ "hex",
  "joystream-node-runtime",
  "joystream-node-runtime",
  "jsonrpc-core",
  "jsonrpc-core",
  "node-inspect",
  "node-inspect",
@@ -1998,6 +2024,7 @@ dependencies = [
  "sc-network",
  "sc-network",
  "sc-rpc-api",
  "sc-rpc-api",
  "sc-service",
  "sc-service",
+ "sc-service-test",
  "sc-transaction-pool",
  "sc-transaction-pool",
  "serde",
  "serde",
  "serde_json",
  "serde_json",
@@ -2026,7 +2053,7 @@ dependencies = [
 
 
 [[package]]
 [[package]]
 name = "joystream-node-runtime"
 name = "joystream-node-runtime"
-version = "7.0.0"
+version = "7.3.0"
 dependencies = [
 dependencies = [
  "frame-benchmarking",
  "frame-benchmarking",
  "frame-executive",
  "frame-executive",
@@ -2069,6 +2096,7 @@ dependencies = [
  "pallet-token-mint",
  "pallet-token-mint",
  "pallet-transaction-payment",
  "pallet-transaction-payment",
  "pallet-transaction-payment-rpc-runtime-api",
  "pallet-transaction-payment-rpc-runtime-api",
+ "pallet-utility",
  "pallet-versioned-store",
  "pallet-versioned-store",
  "pallet-versioned-store-permissions",
  "pallet-versioned-store-permissions",
  "pallet-working-group",
  "pallet-working-group",
@@ -3815,12 +3843,29 @@ dependencies = [
  "sp-std",
  "sp-std",
 ]
 ]
 
 
+[[package]]
+name = "pallet-utility"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "frame-benchmarking",
+ "frame-support",
+ "frame-system",
+ "parity-scale-codec",
+ "serde",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+]
+
 [[package]]
 [[package]]
 name = "pallet-versioned-store"
 name = "pallet-versioned-store"
 version = "3.0.0"
 version = "3.0.0"
 dependencies = [
 dependencies = [
  "frame-support",
  "frame-support",
  "frame-system",
  "frame-system",
+ "pallet-common",
  "pallet-timestamp",
  "pallet-timestamp",
  "parity-scale-codec",
  "parity-scale-codec",
  "serde",
  "serde",
@@ -3836,9 +3881,11 @@ version = "3.0.0"
 dependencies = [
 dependencies = [
  "frame-support",
  "frame-support",
  "frame-system",
  "frame-system",
+ "pallet-common",
  "pallet-timestamp",
  "pallet-timestamp",
  "pallet-versioned-store",
  "pallet-versioned-store",
  "parity-scale-codec",
  "parity-scale-codec",
+ "serde",
  "sp-arithmetic",
  "sp-arithmetic",
  "sp-core",
  "sp-core",
  "sp-io",
  "sp-io",
@@ -5616,6 +5663,43 @@ dependencies = [
  "wasm-timer",
  "wasm-timer",
 ]
 ]
 
 
+[[package]]
+name = "sc-service-test"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "env_logger",
+ "fdlimit",
+ "futures 0.1.29",
+ "futures 0.3.4",
+ "hex-literal",
+ "log",
+ "parity-scale-codec",
+ "parking_lot 0.10.2",
+ "sc-block-builder",
+ "sc-client-api",
+ "sc-client-db",
+ "sc-executor",
+ "sc-light",
+ "sc-network",
+ "sc-service",
+ "sp-api",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-externalities",
+ "sp-panic-handler",
+ "sp-runtime",
+ "sp-state-machine",
+ "sp-storage",
+ "sp-transaction-pool",
+ "sp-trie",
+ "substrate-test-runtime",
+ "substrate-test-runtime-client",
+ "tempfile",
+ "tokio 0.1.22",
+]
+
 [[package]]
 [[package]]
 name = "sc-state-db"
 name = "sc-state-db"
 version = "0.8.0-rc4"
 version = "0.8.0-rc4"
@@ -5835,6 +5919,17 @@ dependencies = [
  "syn 1.0.17",
  "syn 1.0.17",
 ]
 ]
 
 
+[[package]]
+name = "serde_derive_internals"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.7",
+ "syn 1.0.17",
+]
+
 [[package]]
 [[package]]
 name = "serde_json"
 name = "serde_json"
 version = "1.0.57"
 version = "1.0.57"
@@ -6181,6 +6276,20 @@ dependencies = [
  "wasm-timer",
  "wasm-timer",
 ]
 ]
 
 
+[[package]]
+name = "sp-consensus-aura"
+version = "0.8.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "parity-scale-codec",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-inherents",
+ "sp-runtime",
+ "sp-std",
+ "sp-timestamp",
+]
+
 [[package]]
 [[package]]
 name = "sp-consensus-babe"
 name = "sp-consensus-babe"
 version = "0.8.0-rc4"
 version = "0.8.0-rc4"
@@ -6792,6 +6901,89 @@ dependencies = [
  "tokio 0.2.22",
  "tokio 0.2.22",
 ]
 ]
 
 
+[[package]]
+name = "substrate-test-client"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "futures 0.3.4",
+ "hash-db",
+ "parity-scale-codec",
+ "sc-client-api",
+ "sc-client-db",
+ "sc-consensus",
+ "sc-executor",
+ "sc-light",
+ "sc-service",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-keyring",
+ "sp-runtime",
+ "sp-state-machine",
+]
+
+[[package]]
+name = "substrate-test-runtime"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "cfg-if",
+ "frame-executive",
+ "frame-support",
+ "frame-system",
+ "frame-system-rpc-runtime-api",
+ "log",
+ "memory-db",
+ "pallet-babe",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "parity-util-mem",
+ "sc-service",
+ "serde",
+ "sp-api",
+ "sp-application-crypto",
+ "sp-block-builder",
+ "sp-consensus-aura",
+ "sp-consensus-babe",
+ "sp-core",
+ "sp-finality-grandpa",
+ "sp-inherents",
+ "sp-io",
+ "sp-keyring",
+ "sp-offchain",
+ "sp-runtime",
+ "sp-runtime-interface",
+ "sp-session",
+ "sp-std",
+ "sp-transaction-pool",
+ "sp-trie",
+ "sp-version",
+ "substrate-wasm-builder-runner",
+ "trie-db",
+]
+
+[[package]]
+name = "substrate-test-runtime-client"
+version = "2.0.0-rc4"
+source = "git+https://github.com/paritytech/substrate.git?rev=00768a1f21a579c478fe5d4f51e1fa71f7db9fd4#00768a1f21a579c478fe5d4f51e1fa71f7db9fd4"
+dependencies = [
+ "futures 0.3.4",
+ "parity-scale-codec",
+ "sc-block-builder",
+ "sc-client-api",
+ "sc-consensus",
+ "sc-light",
+ "sc-service",
+ "sp-api",
+ "sp-blockchain",
+ "sp-consensus",
+ "sp-core",
+ "sp-runtime",
+ "substrate-test-client",
+ "substrate-test-runtime",
+]
+
 [[package]]
 [[package]]
 name = "substrate-wasm-builder-runner"
 name = "substrate-wasm-builder-runner"
 version = "1.0.6"
 version = "1.0.6"

+ 2 - 2
README.md

@@ -93,8 +93,8 @@ You can also run your our own joystream-node:
 
 
 ```sh
 ```sh
 git checkout master
 git checkout master
-cargo build --release
-cargo run --release -- --pruning archive --chain testnets/rome.json
+WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release
+./target/release/joystream-node -- --pruning archive --chain testnets/rome.json
 ```
 ```
 
 
 Wait for the node to sync to the latest block, then change pioneer settings "remote node" option to "Local Node", or follow the link below:
 Wait for the node to sync to the latest block, then change pioneer settings "remote node" option to "Local Node", or follow the link below:

+ 31 - 31
cli/README.md

@@ -108,7 +108,7 @@ OPTIONS
   --showSpecial  Whether to show special (DEV chain) accounts
   --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)_
+_See code: [src/commands/account/choose.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/choose.ts)_
 
 
 ## `joystream-cli account:create NAME`
 ## `joystream-cli account:create NAME`
 
 
@@ -122,7 +122,7 @@ ARGUMENTS
   NAME  Account name
   NAME  Account name
 ```
 ```
 
 
-_See code: [src/commands/account/create.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/create.ts)_
+_See code: [src/commands/account/create.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/create.ts)_
 
 
 ## `joystream-cli account:current`
 ## `joystream-cli account:current`
 
 
@@ -137,7 +137,7 @@ ALIASES
   $ joystream-cli account:default
   $ joystream-cli account:default
 ```
 ```
 
 
-_See code: [src/commands/account/current.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/current.ts)_
+_See code: [src/commands/account/current.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/current.ts)_
 
 
 ## `joystream-cli account:export PATH`
 ## `joystream-cli account:export PATH`
 
 
@@ -154,7 +154,7 @@ OPTIONS
   -a, --all  If provided, exports all existing accounts into "exported_accounts" folder inside given path
   -a, --all  If provided, exports all existing accounts into "exported_accounts" folder inside given path
 ```
 ```
 
 
-_See code: [src/commands/account/export.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/export.ts)_
+_See code: [src/commands/account/export.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/export.ts)_
 
 
 ## `joystream-cli account:forget`
 ## `joystream-cli account:forget`
 
 
@@ -165,7 +165,7 @@ USAGE
   $ joystream-cli account:forget
   $ joystream-cli account:forget
 ```
 ```
 
 
-_See code: [src/commands/account/forget.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/forget.ts)_
+_See code: [src/commands/account/forget.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/forget.ts)_
 
 
 ## `joystream-cli account:import BACKUPFILEPATH`
 ## `joystream-cli account:import BACKUPFILEPATH`
 
 
@@ -179,7 +179,7 @@ ARGUMENTS
   BACKUPFILEPATH  Path to account backup JSON file
   BACKUPFILEPATH  Path to account backup JSON file
 ```
 ```
 
 
-_See code: [src/commands/account/import.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/import.ts)_
+_See code: [src/commands/account/import.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/import.ts)_
 
 
 ## `joystream-cli account:transferTokens RECIPIENT AMOUNT`
 ## `joystream-cli account:transferTokens RECIPIENT AMOUNT`
 
 
@@ -194,7 +194,7 @@ ARGUMENTS
   AMOUNT     Amount of tokens to transfer
   AMOUNT     Amount of tokens to transfer
 ```
 ```
 
 
-_See code: [src/commands/account/transferTokens.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/account/transferTokens.ts)_
+_See code: [src/commands/account/transferTokens.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/account/transferTokens.ts)_
 
 
 ## `joystream-cli api:getUri`
 ## `joystream-cli api:getUri`
 
 
@@ -205,7 +205,7 @@ USAGE
   $ joystream-cli api:getUri
   $ joystream-cli api:getUri
 ```
 ```
 
 
-_See code: [src/commands/api/getUri.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/api/getUri.ts)_
+_See code: [src/commands/api/getUri.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/api/getUri.ts)_
 
 
 ## `joystream-cli api:inspect`
 ## `joystream-cli api:inspect`
 
 
@@ -221,15 +221,15 @@ OPTIONS
       If no "--method" flag is provided then all methods in that module will be listed along with the descriptions.
       If no "--method" flag is provided then all methods in that module will be listed along with the descriptions.
 
 
   -a, --callArgs=callArgs
   -a, --callArgs=callArgs
-      Specifies the arguments to use when calling a method. Multiple arguments can be separated with a comma, ie.
+      Specifies the arguments to use when calling a method. Multiple arguments can be separated with a comma, ie. 
       "-a=arg1,arg2".
       "-a=arg1,arg2".
       You can omit this flag even if the method requires some aguments.
       You can omit this flag even if the method requires some aguments.
       In that case you will be promted to provide value for each required argument.
       In that case you will be promted to provide value for each required argument.
-      Ommiting this flag is recommended when input parameters are of more complex types (and it's hard to specify them as
+      Ommiting this flag is recommended when input parameters are of more complex types (and it's hard to specify them as 
       just simple comma-separated strings)
       just simple comma-separated strings)
 
 
   -e, --exec
   -e, --exec
-      Provide this flag if you want to execute the actual call, instead of displaying the method description (which is
+      Provide this flag if you want to execute the actual call, instead of displaying the method description (which is 
       default)
       default)
 
 
   -m, --method=method
   -m, --method=method
@@ -249,7 +249,7 @@ EXAMPLES
   $ api:inspect -t=query -M=members -m=membershipById -e -a=1
   $ api:inspect -t=query -M=members -m=membershipById -e -a=1
 ```
 ```
 
 
-_See code: [src/commands/api/inspect.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/api/inspect.ts)_
+_See code: [src/commands/api/inspect.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/api/inspect.ts)_
 
 
 ## `joystream-cli api:setUri [URI]`
 ## `joystream-cli api:setUri [URI]`
 
 
@@ -263,7 +263,7 @@ ARGUMENTS
   URI  Uri of the node api WS provider (if skipped, a prompt will be displayed)
   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)_
+_See code: [src/commands/api/setUri.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/api/setUri.ts)_
 
 
 ## `joystream-cli autocomplete [SHELL]`
 ## `joystream-cli autocomplete [SHELL]`
 
 
@@ -297,7 +297,7 @@ USAGE
   $ joystream-cli council:info
   $ joystream-cli council:info
 ```
 ```
 
 
-_See code: [src/commands/council/info.ts](https://github.com/Joystream/substrate-runtime-joystream/blob/master/cli/src/commands/council/info.ts)_
+_See code: [src/commands/council/info.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/council/info.ts)_
 
 
 ## `joystream-cli help [COMMAND]`
 ## `joystream-cli help [COMMAND]`
 
 
@@ -333,7 +333,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/application.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/application.ts)_
 
 
 ## `joystream-cli working-groups:createOpening`
 ## `joystream-cli working-groups:createOpening`
 
 
@@ -359,7 +359,7 @@ OPTIONS
   -s, --skipPrompts          Whether to skip all prompts when adding from draft (will use all default values)
   -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)_
+_See code: [src/commands/working-groups/createOpening.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/createOpening.ts)_
 
 
 ## `joystream-cli working-groups:decreaseWorkerStake WORKERID`
 ## `joystream-cli working-groups:decreaseWorkerStake WORKERID`
 
 
@@ -378,7 +378,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/decreaseWorkerStake.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/decreaseWorkerStake.ts)_
 
 
 ## `joystream-cli working-groups:evictWorker WORKERID`
 ## `joystream-cli working-groups:evictWorker WORKERID`
 
 
@@ -397,7 +397,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/evictWorker.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/evictWorker.ts)_
 
 
 ## `joystream-cli working-groups:fillOpening WGOPENINGID`
 ## `joystream-cli working-groups:fillOpening WGOPENINGID`
 
 
@@ -416,7 +416,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/fillOpening.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/fillOpening.ts)_
 
 
 ## `joystream-cli working-groups:increaseStake`
 ## `joystream-cli working-groups:increaseStake`
 
 
@@ -432,7 +432,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/increaseStake.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/increaseStake.ts)_
 
 
 ## `joystream-cli working-groups:leaveRole`
 ## `joystream-cli working-groups:leaveRole`
 
 
@@ -448,7 +448,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/leaveRole.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/leaveRole.ts)_
 
 
 ## `joystream-cli working-groups:opening WGOPENINGID`
 ## `joystream-cli working-groups:opening WGOPENINGID`
 
 
@@ -467,7 +467,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/opening.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/opening.ts)_
 
 
 ## `joystream-cli working-groups:openings`
 ## `joystream-cli working-groups:openings`
 
 
@@ -483,7 +483,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/openings.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/openings.ts)_
 
 
 ## `joystream-cli working-groups:overview`
 ## `joystream-cli working-groups:overview`
 
 
@@ -499,7 +499,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/overview.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/overview.ts)_
 
 
 ## `joystream-cli working-groups:slashWorker WORKERID`
 ## `joystream-cli working-groups:slashWorker WORKERID`
 
 
@@ -518,7 +518,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/slashWorker.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/slashWorker.ts)_
 
 
 ## `joystream-cli working-groups:startAcceptingApplications WGOPENINGID`
 ## `joystream-cli working-groups:startAcceptingApplications WGOPENINGID`
 
 
@@ -537,7 +537,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/startAcceptingApplications.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/startAcceptingApplications.ts)_
 
 
 ## `joystream-cli working-groups:startReviewPeriod WGOPENINGID`
 ## `joystream-cli working-groups:startReviewPeriod WGOPENINGID`
 
 
@@ -556,7 +556,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/startReviewPeriod.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/startReviewPeriod.ts)_
 
 
 ## `joystream-cli working-groups:terminateApplication WGAPPLICATIONID`
 ## `joystream-cli working-groups:terminateApplication WGAPPLICATIONID`
 
 
@@ -575,7 +575,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/terminateApplication.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/terminateApplication.ts)_
 
 
 ## `joystream-cli working-groups:updateRewardAccount [ACCOUNTADDRESS]`
 ## `joystream-cli working-groups:updateRewardAccount [ACCOUNTADDRESS]`
 
 
@@ -594,7 +594,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/updateRewardAccount.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/updateRewardAccount.ts)_
 
 
 ## `joystream-cli working-groups:updateRoleAccount [ACCOUNTADDRESS]`
 ## `joystream-cli working-groups:updateRoleAccount [ACCOUNTADDRESS]`
 
 
@@ -613,7 +613,7 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/updateRoleAccount.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/updateRoleAccount.ts)_
 
 
 ## `joystream-cli working-groups:updateWorkerReward WORKERID`
 ## `joystream-cli working-groups:updateWorkerReward WORKERID`
 
 
@@ -632,5 +632,5 @@ OPTIONS
                      Available values are: storageProviders.
                      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)_
+_See code: [src/commands/working-groups/updateWorkerReward.ts](https://github.com/Joystream/joystream/blob/master/cli/src/commands/working-groups/updateWorkerReward.ts)_
 <!-- commandsstop -->
 <!-- commandsstop -->

+ 2 - 2
cli/package.json

@@ -1,7 +1,7 @@
 {
 {
   "name": "@joystream/cli",
   "name": "@joystream/cli",
   "description": "Command Line Interface for Joystream community and governance activities",
   "description": "Command Line Interface for Joystream community and governance activities",
-  "version": "0.1.0",
+  "version": "0.2.0",
   "author": "Leszek Wiesner",
   "author": "Leszek Wiesner",
   "bin": {
   "bin": {
     "joystream-cli": "./bin/run"
     "joystream-cli": "./bin/run"
@@ -15,7 +15,7 @@
     "@oclif/plugin-help": "^2.2.3",
     "@oclif/plugin-help": "^2.2.3",
     "@oclif/plugin-not-found": "^1.2.4",
     "@oclif/plugin-not-found": "^1.2.4",
     "@oclif/plugin-warn-if-update-available": "^1.7.0",
     "@oclif/plugin-warn-if-update-available": "^1.7.0",
-    "@polkadot/api": "^0.96.1",
+    "@polkadot/api": "1.26.1",
     "@types/inquirer": "^6.5.0",
     "@types/inquirer": "^6.5.0",
     "@types/proper-lockfile": "^4.1.1",
     "@types/proper-lockfile": "^4.1.1",
     "@types/slug": "^0.9.1",
     "@types/slug": "^0.9.1",

+ 81 - 108
cli/src/Api.ts

@@ -1,13 +1,12 @@
 import BN from 'bn.js'
 import BN from 'bn.js'
-import { registerJoystreamTypes } from '@joystream/types/'
+import { types } from '@joystream/types/'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { ApiPromise, WsProvider } from '@polkadot/api'
-import { QueryableStorageMultiArg } from '@polkadot/api/types'
+import { QueryableStorageMultiArg, SubmittableExtrinsic, QueryableStorageEntry } from '@polkadot/api/types'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
-import { Hash, Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
+import { Balance, Moment, BlockNumber } from '@polkadot/types/interfaces'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { KeyringPair } from '@polkadot/keyring/types'
-import { Codec } from '@polkadot/types/types'
-import { Option, Vec } from '@polkadot/types'
-import { u32 } from '@polkadot/types/primitive'
+import { Codec, CodecArg } from '@polkadot/types/types'
+import { Option, Vec, UInt } from '@polkadot/types'
 import {
 import {
   AccountSummary,
   AccountSummary,
   CouncilInfoObj,
   CouncilInfoObj,
@@ -24,7 +23,7 @@ import {
   UnstakingPeriods,
   UnstakingPeriods,
   StakingPolicyUnstakingPeriodKey,
   StakingPolicyUnstakingPeriodKey,
 } from './Types'
 } from './Types'
-import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types'
+import { DeriveBalancesAll } from '@polkadot/api-derive/types'
 import { CLIError } from '@oclif/errors'
 import { CLIError } from '@oclif/errors'
 import ExitCodes from './ExitCodes'
 import ExitCodes from './ExitCodes'
 import {
 import {
@@ -46,12 +45,11 @@ import {
 import { MemberId, Membership } from '@joystream/types/members'
 import { MemberId, Membership } from '@joystream/types/members'
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
 import { RewardRelationship, RewardRelationshipId } from '@joystream/types/recurring-rewards'
 import { Stake, StakeId } from '@joystream/types/stake'
 import { Stake, StakeId } from '@joystream/types/stake'
-import { LinkageResult } from '@polkadot/types/codec/Linkage'
 
 
 import { InputValidationLengthConstraint } from '@joystream/types/common'
 import { InputValidationLengthConstraint } from '@joystream/types/common'
 
 
 export const DEFAULT_API_URI = 'ws://localhost:9944/'
 export const DEFAULT_API_URI = 'ws://localhost:9944/'
-const DEFAULT_DECIMALS = new u32(12)
+const DEFAULT_DECIMALS = new BN(12)
 
 
 // Mapping of working group to api module
 // Mapping of working group to api module
 export const apiModuleByGroup: { [key in WorkingGroups]: string } = {
 export const apiModuleByGroup: { [key in WorkingGroups]: string } = {
@@ -72,8 +70,7 @@ export default class Api {
 
 
   private static async initApi(apiUri: string = DEFAULT_API_URI): Promise<ApiPromise> {
   private static async initApi(apiUri: string = DEFAULT_API_URI): Promise<ApiPromise> {
     const wsProvider: WsProvider = new WsProvider(apiUri)
     const wsProvider: WsProvider = new WsProvider(apiUri)
-    registerJoystreamTypes()
-    const api = await ApiPromise.create({ provider: wsProvider })
+    const api = await ApiPromise.create({ provider: wsProvider, types })
 
 
     // Initializing some api params based on pioneer/packages/react-api/Api.tsx
     // Initializing some api params based on pioneer/packages/react-api/Api.tsx
     const [properties] = await Promise.all([api.rpc.system.properties()])
     const [properties] = await Promise.all([api.rpc.system.properties()])
@@ -95,23 +92,27 @@ export default class Api {
     return new Api(originalApi)
     return new Api(originalApi)
   }
   }
 
 
-  private async queryMultiOnce(queries: Parameters<typeof ApiPromise.prototype.queryMulti>[0]): Promise<Codec[]> {
-    let results: Codec[] = []
-
-    const unsub = await this._api.queryMulti(queries, (res) => {
-      results = res
+  private queryMultiOnce(queries: Parameters<typeof ApiPromise.prototype.queryMulti>[0]): Promise<Codec[]> {
+    return new Promise((resolve, reject) => {
+      let unsub: () => void
+      this._api
+        .queryMulti(queries, (res) => {
+          // unsub should already be set at this point
+          if (!unsub) {
+            reject(new CLIError('API queryMulti issue - unsub method not set!', { exit: ExitCodes.ApiError }))
+          }
+          unsub()
+          resolve(res)
+        })
+        .then((unsubscribe) => (unsub = unsubscribe))
+        .catch((e) => reject(e))
     })
     })
-    unsub()
-
-    if (!results.length || results.length !== queries.length) {
-      throw new CLIError('API querying issue', { exit: ExitCodes.ApiError })
-    }
-
-    return results
   }
   }
 
 
-  async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DerivedBalances[]> {
-    const accountsBalances: DerivedBalances[] = await this._api.derive.balances.votingBalances(accountAddresses)
+  async getAccountsBalancesInfo(accountAddresses: string[]): Promise<DeriveBalancesAll[]> {
+    const accountsBalances: DeriveBalancesAll[] = await Promise.all(
+      accountAddresses.map((addr) => this._api.derive.balances.all(addr))
+    )
 
 
     return accountsBalances
     return accountsBalances
   }
   }
@@ -119,7 +120,7 @@ export default class Api {
   // Get on-chain data related to given account.
   // Get on-chain data related to given account.
   // For now it's just account balances
   // For now it's just account balances
   async getAccountSummary(accountAddresses: string): Promise<AccountSummary> {
   async getAccountSummary(accountAddresses: string): Promise<AccountSummary> {
-    const balances: DerivedBalances = (await this.getAccountsBalancesInfo([accountAddresses]))[0]
+    const balances: DeriveBalancesAll = (await this.getAccountsBalancesInfo([accountAddresses]))[0]
     // TODO: Some more information can be fetched here in the future
     // TODO: Some more information can be fetched here in the future
 
 
     return { balances }
     return { balances }
@@ -146,34 +147,29 @@ export default class Api {
     return createCouncilInfoObj(...results)
     return createCouncilInfoObj(...results)
   }
   }
 
 
-  // TODO: This formula is probably not too good, so some better implementation will be required in the future
-  async estimateFee(account: KeyringPair, recipientAddr: string, amount: BN): Promise<BN> {
-    const transfer = this._api.tx.balances.transfer(recipientAddr, amount)
-    const signature = account.sign(transfer.toU8a())
-    const transactionByteSize: BN = new BN(transfer.encodedLength + signature.length)
-
-    const fees: DerivedFees = await this._api.derive.balances.fees()
-
-    const estimatedFee = fees.transactionBaseFee.add(fees.transactionByteFee.mul(transactionByteSize))
-
-    return estimatedFee
+  async estimateFee(account: KeyringPair, tx: SubmittableExtrinsic<'promise'>): Promise<Balance> {
+    const paymentInfo = await tx.paymentInfo(account)
+    return paymentInfo.partialFee
   }
   }
 
 
-  async transfer(account: KeyringPair, recipientAddr: string, amount: BN): Promise<Hash> {
-    const txHash = await this._api.tx.balances.transfer(recipientAddr, amount).signAndSend(account)
-    return txHash
+  createTransferTx(recipient: string, amount: BN) {
+    return this._api.tx.balances.transfer(recipient, amount)
   }
   }
 
 
   // Working groups
   // Working groups
-  // TODO: This is a lot of repeated logic from "/pioneer/joy-roles/src/transport.substrate.ts"
-  // (although simplified a little bit)
-  // Hopefully this will be refactored to "joystream-js" soon
-  protected singleLinkageResult<T extends Codec>(result: LinkageResult) {
-    return result[0] as T
-  }
+  // TODO: This is a lot of repeated logic from "/pioneer/joy-utils/transport"
+  // It will be refactored to "joystream-js" soon
+  async entriesByIds<IDType extends UInt, ValueType extends Codec>(
+    apiMethod: QueryableStorageEntry<'promise'>,
+    firstKey?: CodecArg // First key in case of double maps
+  ): Promise<[IDType, ValueType][]> {
+    const entries: [IDType, ValueType][] = (await apiMethod.entries<ValueType>(firstKey)).map(([storageKey, value]) => [
+      // If double-map (first key is provided), we map entries by second key
+      storageKey.args[firstKey !== undefined ? 1 : 0] as IDType,
+      value,
+    ])
 
 
-  protected multiLinkageResult<K extends Codec, V extends Codec>(result: LinkageResult): [Vec<K>, Vec<V>] {
-    return [result[0] as Vec<K>, result[1] as Vec<V>]
+    return entries.sort((a, b) => a[0].toNumber() - b[0].toNumber())
   }
   }
 
 
   protected async blockHash(height: number): Promise<string> {
   protected async blockHash(height: number): Promise<string> {
@@ -214,7 +210,7 @@ export default class Api {
   }
   }
 
 
   protected async stakeValue(stakeId: StakeId): Promise<Balance> {
   protected async stakeValue(stakeId: StakeId): Promise<Balance> {
-    const stake = this.singleLinkageResult<Stake>((await this._api.query.stake.stakes(stakeId)) as LinkageResult)
+    const stake = await this._api.query.stake.stakes<Stake>(stakeId)
     return stake.value
     return stake.value
   }
   }
 
 
@@ -223,8 +219,8 @@ export default class Api {
   }
   }
 
 
   protected async workerReward(relationshipId: RewardRelationshipId): Promise<Reward> {
   protected async workerReward(relationshipId: RewardRelationshipId): Promise<Reward> {
-    const rewardRelationship = this.singleLinkageResult<RewardRelationship>(
-      (await this._api.query.recurringRewards.rewardRelationships(relationshipId)) as LinkageResult
+    const rewardRelationship = await this._api.query.recurringRewards.rewardRelationships<RewardRelationship>(
+      relationshipId
     )
     )
 
 
     return {
     return {
@@ -266,18 +262,16 @@ export default class Api {
   }
   }
 
 
   async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
   async workerByWorkerId(group: WorkingGroups, workerId: number): Promise<Worker> {
-    const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
+    const nextId = await this.workingGroupApiQuery(group).nextWorkerId<WorkerId>()
 
 
     // This is chain specfic, but if next id is still 0, it means no workers have been added yet
     // This is chain specfic, but if next id is still 0, it means no workers have been added yet
     if (workerId < 0 || workerId >= nextId.toNumber()) {
     if (workerId < 0 || workerId >= nextId.toNumber()) {
       throw new CLIError('Invalid worker id!')
       throw new CLIError('Invalid worker id!')
     }
     }
 
 
-    const worker = this.singleLinkageResult<Worker>(
-      (await this.workingGroupApiQuery(group).workerById(workerId)) as LinkageResult
-    )
+    const worker = await this.workingGroupApiQuery(group).workerById<Worker>(workerId)
 
 
-    if (!worker.is_active) {
+    if (worker.isEmpty) {
       throw new CLIError('This worker is not active anymore')
       throw new CLIError('This worker is not active anymore')
     }
     }
 
 
@@ -286,67 +280,51 @@ export default class Api {
 
 
   async groupMember(group: WorkingGroups, workerId: number) {
   async groupMember(group: WorkingGroups, workerId: number) {
     const worker = await this.workerByWorkerId(group, workerId)
     const worker = await this.workerByWorkerId(group, workerId)
-    return await this.parseGroupMember(new WorkerId(workerId), worker)
+    return await this.parseGroupMember(this._api.createType('WorkerId', workerId), worker)
   }
   }
 
 
   async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
   async groupMembers(group: WorkingGroups): Promise<GroupMember[]> {
-    const nextId = (await this.workingGroupApiQuery(group).nextWorkerId()) as WorkerId
-
-    // This is chain specfic, but if next id is still 0, it means no workers have been added yet
-    if (nextId.eq(0)) {
-      return []
-    }
+    const workerEntries = await this.entriesByIds<WorkerId, Worker>(this.workingGroupApiQuery(group).workerById)
 
 
-    const [workerIds, workers] = this.multiLinkageResult<WorkerId, Worker>(
-      (await this.workingGroupApiQuery(group).workerById()) as LinkageResult
+    const groupMembers: GroupMember[] = await Promise.all(
+      workerEntries.map(([id, worker]) => this.parseGroupMember(id, worker))
     )
     )
 
 
-    const groupMembers: GroupMember[] = []
-    for (const [index, worker] of Object.entries(workers.toArray())) {
-      const workerId = workerIds[parseInt(index)]
-      if (worker.is_active) {
-        groupMembers.push(await this.parseGroupMember(workerId, worker))
-      }
-    }
-
-    return groupMembers.reverse()
+    return groupMembers.reverse() // Sort by newest
   }
   }
 
 
   async openingsByGroup(group: WorkingGroups): Promise<GroupOpening[]> {
   async openingsByGroup(group: WorkingGroups): Promise<GroupOpening[]> {
-    const openings: GroupOpening[] = []
-    const nextId = (await this.workingGroupApiQuery(group).nextOpeningId()) as OpeningId
+    let openings: GroupOpening[] = []
+    const nextId = await this.workingGroupApiQuery(group).nextOpeningId<OpeningId>()
 
 
     // This is chain specfic, but if next id is still 0, it means no openings have been added yet
     // This is chain specfic, but if next id is still 0, it means no openings have been added yet
     if (!nextId.eq(0)) {
     if (!nextId.eq(0)) {
-      const highestId = nextId.toNumber() - 1
-      for (let i = highestId; i >= 0; i--) {
-        openings.push(await this.groupOpening(group, i))
-      }
+      const ids = Array.from(Array(nextId.toNumber()).keys()).reverse() // Sort by newest
+      openings = await Promise.all(ids.map((id) => this.groupOpening(group, id)))
     }
     }
 
 
     return openings
     return openings
   }
   }
 
 
   protected async hiringOpeningById(id: number | OpeningId): Promise<Opening> {
   protected async hiringOpeningById(id: number | OpeningId): Promise<Opening> {
-    const result = (await this._api.query.hiring.openingById(id)) as LinkageResult
-    return this.singleLinkageResult<Opening>(result)
+    const result = await this._api.query.hiring.openingById<Opening>(id)
+    return result
   }
   }
 
 
   protected async hiringApplicationById(id: number | ApplicationId): Promise<Application> {
   protected async hiringApplicationById(id: number | ApplicationId): Promise<Application> {
-    const result = (await this._api.query.hiring.applicationById(id)) as LinkageResult
-    return this.singleLinkageResult<Application>(result)
+    const result = await this._api.query.hiring.applicationById<Application>(id)
+    return result
   }
   }
 
 
   async wgApplicationById(group: WorkingGroups, wgApplicationId: number): Promise<WGApplication> {
   async wgApplicationById(group: WorkingGroups, wgApplicationId: number): Promise<WGApplication> {
-    const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
+    const nextAppId = await this.workingGroupApiQuery(group).nextApplicationId<ApplicationId>()
 
 
     if (wgApplicationId < 0 || wgApplicationId >= nextAppId.toNumber()) {
     if (wgApplicationId < 0 || wgApplicationId >= nextAppId.toNumber()) {
       throw new CLIError('Invalid working group application ID!')
       throw new CLIError('Invalid working group application ID!')
     }
     }
 
 
-    return this.singleLinkageResult<WGApplication>(
-      (await this.workingGroupApiQuery(group).applicationById(wgApplicationId)) as LinkageResult
-    )
+    const result = await this.workingGroupApiQuery(group).applicationById<WGApplication>(wgApplicationId)
+    return result
   }
   }
 
 
   protected async parseApplication(wgApplicationId: number, wgApplication: WGApplication): Promise<GroupApplication> {
   protected async parseApplication(wgApplicationId: number, wgApplication: WGApplication): Promise<GroupApplication> {
@@ -376,18 +354,15 @@ export default class Api {
   }
   }
 
 
   protected async groupOpeningApplications(group: WorkingGroups, wgOpeningId: number): Promise<GroupApplication[]> {
   protected async groupOpeningApplications(group: WorkingGroups, wgOpeningId: number): Promise<GroupApplication[]> {
-    const applications: GroupApplication[] = []
-
-    const nextAppId = (await this.workingGroupApiQuery(group).nextApplicationId()) as ApplicationId
-    for (let i = 0; i < nextAppId.toNumber(); i++) {
-      const wgApplication = await this.wgApplicationById(group, i)
-      if (wgApplication.opening_id.toNumber() !== wgOpeningId) {
-        continue
-      }
-      applications.push(await this.parseApplication(i, wgApplication))
-    }
+    const wgApplicationEntries = await this.entriesByIds<ApplicationId, WGApplication>(
+      this.workingGroupApiQuery(group).applicationById
+    )
 
 
-    return applications
+    return Promise.all(
+      wgApplicationEntries
+        .filter(([, /* id */ wgApplication]) => wgApplication.opening_id.eqn(wgOpeningId))
+        .map(([id, wgApplication]) => this.parseApplication(id.toNumber(), wgApplication))
+    )
   }
   }
 
 
   async groupOpening(group: WorkingGroups, wgOpeningId: number): Promise<GroupOpening> {
   async groupOpening(group: WorkingGroups, wgOpeningId: number): Promise<GroupOpening> {
@@ -397,9 +372,7 @@ export default class Api {
       throw new CLIError('Invalid working group opening ID!')
       throw new CLIError('Invalid working group opening ID!')
     }
     }
 
 
-    const groupOpening = this.singleLinkageResult<WGOpening>(
-      (await this.workingGroupApiQuery(group).openingById(wgOpeningId)) as LinkageResult
-    )
+    const groupOpening = await this.workingGroupApiQuery(group).openingById<WGOpening>(wgOpeningId)
 
 
     const openingId = groupOpening.hiring_opening_id.toNumber()
     const openingId = groupOpening.hiring_opening_id.toNumber()
     const opening = await this.hiringOpeningById(openingId)
     const opening = await this.hiringOpeningById(openingId)
@@ -417,19 +390,19 @@ export default class Api {
       sp.isSome ? unstakingPeriod(sp.unwrap()[key]) : 0
       sp.isSome ? unstakingPeriod(sp.unwrap()[key]) : 0
 
 
     const unstakingPeriods: Partial<UnstakingPeriods> = {
     const unstakingPeriods: Partial<UnstakingPeriods> = {
-      ['review_period_expired_application_stake_unstaking_period_length']: spUnstakingPeriod(
+      'review_period_expired_application_stake_unstaking_period_length': spUnstakingPeriod(
         applSP,
         applSP,
         'review_period_expired_unstaking_period_length'
         'review_period_expired_unstaking_period_length'
       ),
       ),
-      ['crowded_out_application_stake_unstaking_period_length']: spUnstakingPeriod(
+      'crowded_out_application_stake_unstaking_period_length': spUnstakingPeriod(
         applSP,
         applSP,
         'crowded_out_unstaking_period_length'
         'crowded_out_unstaking_period_length'
       ),
       ),
-      ['review_period_expired_role_stake_unstaking_period_length']: spUnstakingPeriod(
+      'review_period_expired_role_stake_unstaking_period_length': spUnstakingPeriod(
         roleSP,
         roleSP,
         'review_period_expired_unstaking_period_length'
         'review_period_expired_unstaking_period_length'
       ),
       ),
-      ['crowded_out_role_stake_unstaking_period_length']: spUnstakingPeriod(
+      'crowded_out_role_stake_unstaking_period_length': spUnstakingPeriod(
         roleSP,
         roleSP,
         'crowded_out_unstaking_period_length'
         'crowded_out_unstaking_period_length'
       ),
       ),
@@ -493,11 +466,11 @@ export default class Api {
   }
   }
 
 
   async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
   async getMemberIdsByControllerAccount(address: string): Promise<MemberId[]> {
-    const ids = (await this._api.query.members.memberIdsByControllerAccountId(address)) as Vec<MemberId>
+    const ids = await this._api.query.members.memberIdsByControllerAccountId<Vec<MemberId>>(address)
     return ids.toArray()
     return ids.toArray()
   }
   }
 
 
   async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
   async workerExitRationaleConstraint(group: WorkingGroups): Promise<InputValidationLengthConstraint> {
-    return (await this.workingGroupApiQuery(group).workerExitRationaleText()) as InputValidationLengthConstraint
+    return await this.workingGroupApiQuery(group).workerExitRationaleText<InputValidationLengthConstraint>()
   }
   }
 }
 }

+ 101 - 113
cli/src/Types.ts

@@ -5,7 +5,7 @@ import { Constructor, Codec } from '@polkadot/types/types'
 import { Struct, Vec } from '@polkadot/types/codec'
 import { Struct, Vec } from '@polkadot/types/codec'
 import { u32 } from '@polkadot/types/primitive'
 import { u32 } from '@polkadot/types/primitive'
 import { BlockNumber, Balance, AccountId } from '@polkadot/types/interfaces'
 import { BlockNumber, Balance, AccountId } from '@polkadot/types/interfaces'
-import { DerivedBalances } from '@polkadot/api-derive/types'
+import { DeriveBalancesAll } from '@polkadot/api-derive/types'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { WorkerId, OpeningType } from '@joystream/types/working-group'
 import { WorkerId, OpeningType } from '@joystream/types/working-group'
 import { Membership, MemberId } from '@joystream/types/members'
 import { Membership, MemberId } from '@joystream/types/members'
@@ -25,6 +25,7 @@ import {
 import ajv from 'ajv'
 import ajv from 'ajv'
 import { Opening, StakingPolicy, ApplicationStageKeys } from '@joystream/types/hiring'
 import { Opening, StakingPolicy, ApplicationStageKeys } from '@joystream/types/hiring'
 import { Validator } from 'inquirer'
 import { Validator } from 'inquirer'
+import { JoyStructCustom } from '@joystream/types/common'
 
 
 // KeyringPair type extended with mandatory "meta.name"
 // KeyringPair type extended with mandatory "meta.name"
 // It's used for accounts/keys management within CLI.
 // It's used for accounts/keys management within CLI.
@@ -37,7 +38,7 @@ export type NamedKeyringPair = KeyringPair & {
 
 
 // Summary of the account information fetched from the api for "account:current" purposes (currently just balances)
 // Summary of the account information fetched from the api for "account:current" purposes (currently just balances)
 export type AccountSummary = {
 export type AccountSummary = {
-  balances: DerivedBalances
+  balances: DeriveBalancesAll
 }
 }
 
 
 // This function allows us to easily transform the tuple into the object
 // This function allows us to easily transform the tuple into the object
@@ -186,187 +187,174 @@ export type GroupOpening = {
 // Note those types are not part of the runtime etc., we just use them to simplify prompting for values
 // Note those types are not part of the runtime etc., we just use them to simplify prompting for values
 // (since there exists functionality that handles that for substrate types like: Struct, Vec etc.)
 // (since there exists functionality that handles that for substrate types like: Struct, Vec etc.)
 interface WithJSONable<T> {
 interface WithJSONable<T> {
-  toJSON: () => T
+  toJSONObj: () => T
 }
 }
-export class HRTJobSpecificsStruct extends Struct implements WithJSONable<JobSpecifics> {
-  constructor(value?: JobSpecifics) {
-    super(
-      {
-        title: 'Text',
-        description: 'Text',
-      },
-      value
-    )
-  }
+export class HRTJobSpecificsStruct
+  extends JoyStructCustom({
+    title: Text,
+    description: Text,
+  })
+  implements WithJSONable<JobSpecifics> {
   get title(): string {
   get title(): string {
-    return (this.get('title') as Text).toString()
+    return this.getField('title').toString()
   }
   }
+
   get description(): string {
   get description(): string {
-    return (this.get('description') as Text).toString()
+    return this.getField('description').toString()
   }
   }
-  toJSON(): JobSpecifics {
+
+  toJSONObj(): JobSpecifics {
     const { title, description } = this
     const { title, description } = this
     return { title, description }
     return { title, description }
   }
   }
 }
 }
-export class HRTEntryInMembershipModukeStruct extends Struct implements WithJSONable<EntryInMembershipModuke> {
-  constructor(value?: EntryInMembershipModuke) {
-    super(
-      {
-        handle: 'Text',
-      },
-      value
-    )
-  }
+export class HRTEntryInMembershipModukeStruct
+  extends JoyStructCustom({
+    handle: Text,
+  })
+  implements WithJSONable<EntryInMembershipModuke> {
   get handle(): string {
   get handle(): string {
-    return (this.get('handle') as Text).toString()
+    return this.getField('handle').toString()
   }
   }
-  toJSON(): EntryInMembershipModuke {
+
+  toJSONObj(): EntryInMembershipModuke {
     const { handle } = this
     const { handle } = this
     return { handle }
     return { handle }
   }
   }
 }
 }
-export class HRTCreatorDetailsStruct extends Struct implements WithJSONable<CreatorDetails> {
-  constructor(value?: CreatorDetails) {
-    super(
-      {
-        membership: HRTEntryInMembershipModukeStruct,
-      },
-      value
-    )
-  }
+export class HRTCreatorDetailsStruct
+  extends JoyStructCustom({
+    membership: HRTEntryInMembershipModukeStruct,
+  })
+  implements WithJSONable<CreatorDetails> {
   get membership(): EntryInMembershipModuke {
   get membership(): EntryInMembershipModuke {
-    return (this.get('membership') as HRTEntryInMembershipModukeStruct).toJSON()
+    return this.getField('membership').toJSONObj()
   }
   }
-  toJSON(): CreatorDetails {
+
+  toJSONObj(): CreatorDetails {
     const { membership } = this
     const { membership } = this
     return { membership }
     return { membership }
   }
   }
 }
 }
-export class HRTHiringProcessStruct extends Struct implements WithJSONable<HiringProcess> {
-  constructor(value?: HiringProcess) {
-    super(
-      {
-        details: 'Vec<Text>',
-      },
-      value
-    )
-  }
+export class HRTHiringProcessStruct
+  extends JoyStructCustom({
+    details: Vec.with(Text),
+  })
+  implements WithJSONable<HiringProcess> {
   get details(): AdditionalRolehiringProcessDetails {
   get details(): AdditionalRolehiringProcessDetails {
-    return (this.get('details') as Vec<Text>).toArray().map((v) => v.toString())
+    return this.getField('details')
+      .toArray()
+      .map((v) => v.toString())
   }
   }
-  toJSON(): HiringProcess {
+
+  toJSONObj(): HiringProcess {
     const { details } = this
     const { details } = this
     return { details }
     return { details }
   }
   }
 }
 }
-export class HRTQuestionFieldStruct extends Struct implements WithJSONable<QuestionField> {
-  constructor(value?: QuestionField) {
-    super(
-      {
-        title: 'Text',
-        type: 'Text',
-      },
-      value
-    )
-  }
+export class HRTQuestionFieldStruct
+  extends JoyStructCustom({
+    title: Text,
+    type: Text,
+  })
+  implements WithJSONable<QuestionField> {
   get title(): string {
   get title(): string {
-    return (this.get('title') as Text).toString()
+    return this.getField('title').toString()
   }
   }
+
   get type(): string {
   get type(): string {
-    return (this.get('type') as Text).toString()
+    return this.getField('type').toString()
   }
   }
-  toJSON(): QuestionField {
+
+  toJSONObj(): QuestionField {
     const { title, type } = this
     const { title, type } = this
     return { title, type }
     return { title, type }
   }
   }
 }
 }
 class HRTQuestionsFieldsVec extends Vec.with(HRTQuestionFieldStruct) implements WithJSONable<QuestionsFields> {
 class HRTQuestionsFieldsVec extends Vec.with(HRTQuestionFieldStruct) implements WithJSONable<QuestionsFields> {
-  toJSON(): QuestionsFields {
-    return this.toArray().map((v) => v.toJSON())
+  toJSONObj(): QuestionsFields {
+    return this.toArray().map((v) => v.toJSONObj())
   }
   }
 }
 }
-export class HRTQuestionSectionStruct extends Struct implements WithJSONable<QuestionSection> {
-  constructor(value?: QuestionSection) {
-    super(
-      {
-        title: 'Text',
-        questions: HRTQuestionsFieldsVec,
-      },
-      value
-    )
-  }
+export class HRTQuestionSectionStruct
+  extends JoyStructCustom({
+    title: Text,
+    questions: HRTQuestionsFieldsVec,
+  })
+  implements WithJSONable<QuestionSection> {
   get title(): string {
   get title(): string {
-    return (this.get('title') as Text).toString()
+    return this.getField('title').toString()
   }
   }
+
   get questions(): QuestionsFields {
   get questions(): QuestionsFields {
-    return (this.get('questions') as HRTQuestionsFieldsVec).toJSON()
+    return this.getField('questions').toJSONObj()
   }
   }
-  toJSON(): QuestionSection {
+
+  toJSONObj(): QuestionSection {
     const { title, questions } = this
     const { title, questions } = this
     return { title, questions }
     return { title, questions }
   }
   }
 }
 }
 export class HRTQuestionSectionsVec extends Vec.with(HRTQuestionSectionStruct)
 export class HRTQuestionSectionsVec extends Vec.with(HRTQuestionSectionStruct)
   implements WithJSONable<QuestionSections> {
   implements WithJSONable<QuestionSections> {
-  toJSON(): QuestionSections {
-    return this.toArray().map((v) => v.toJSON())
+  toJSONObj(): QuestionSections {
+    return this.toArray().map((v) => v.toJSONObj())
   }
   }
 }
 }
-export class HRTApplicationDetailsStruct extends Struct implements WithJSONable<ApplicationDetails> {
-  constructor(value?: ApplicationDetails) {
-    super(
-      {
-        sections: HRTQuestionSectionsVec,
-      },
-      value
-    )
-  }
+export class HRTApplicationDetailsStruct
+  extends JoyStructCustom({
+    sections: HRTQuestionSectionsVec,
+  })
+  implements WithJSONable<ApplicationDetails> {
   get sections(): QuestionSections {
   get sections(): QuestionSections {
-    return (this.get('sections') as HRTQuestionSectionsVec).toJSON()
+    return this.getField('sections').toJSONObj()
   }
   }
-  toJSON(): ApplicationDetails {
+
+  toJSONObj(): ApplicationDetails {
     const { sections } = this
     const { sections } = this
     return { sections }
     return { sections }
   }
   }
 }
 }
-export class HRTStruct extends Struct implements WithJSONable<GenericJoyStreamRoleSchema> {
-  constructor(value?: GenericJoyStreamRoleSchema) {
-    super(
-      {
-        version: 'u32',
-        headline: 'Text',
-        job: HRTJobSpecificsStruct,
-        application: HRTApplicationDetailsStruct,
-        reward: 'Text',
-        creator: HRTCreatorDetailsStruct,
-        process: HRTHiringProcessStruct,
-      },
-      value
-    )
-  }
+export class HRTStruct
+  extends JoyStructCustom({
+    version: u32,
+    headline: Text,
+    job: HRTJobSpecificsStruct,
+    application: HRTApplicationDetailsStruct,
+    reward: Text,
+    creator: HRTCreatorDetailsStruct,
+    process: HRTHiringProcessStruct,
+  })
+  implements WithJSONable<GenericJoyStreamRoleSchema> {
   get version(): number {
   get version(): number {
-    return (this.get('version') as u32).toNumber()
+    return this.getField('version').toNumber()
   }
   }
+
   get headline(): string {
   get headline(): string {
-    return (this.get('headline') as Text).toString()
+    return this.getField('headline').toString()
   }
   }
+
   get job(): JobSpecifics {
   get job(): JobSpecifics {
-    return (this.get('job') as HRTJobSpecificsStruct).toJSON()
+    return this.getField('job').toJSONObj()
   }
   }
+
   get application(): ApplicationDetails {
   get application(): ApplicationDetails {
-    return (this.get('application') as HRTApplicationDetailsStruct).toJSON()
+    return this.getField('application').toJSONObj()
   }
   }
+
   get reward(): string {
   get reward(): string {
-    return (this.get('reward') as Text).toString()
+    return this.getField('reward').toString()
   }
   }
+
   get creator(): CreatorDetails {
   get creator(): CreatorDetails {
-    return (this.get('creator') as HRTCreatorDetailsStruct).toJSON()
+    return this.getField('creator').toJSONObj()
   }
   }
+
   get process(): HiringProcess {
   get process(): HiringProcess {
-    return (this.get('process') as HRTHiringProcessStruct).toJSON()
+    return this.getField('process').toJSONObj()
   }
   }
-  toJSON(): GenericJoyStreamRoleSchema {
+
+  toJSONObj(): GenericJoyStreamRoleSchema {
     const { version, headline, job, application, reward, creator, process } = this
     const { version, headline, job, application, reward, creator, process } = this
     return { version, headline, job, application, reward, creator, process }
     return { version, headline, job, application, reward, creator, process }
   }
   }

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

@@ -8,7 +8,7 @@ import ApiCommandBase from './ApiCommandBase'
 import { Keyring } from '@polkadot/api'
 import { Keyring } from '@polkadot/api'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import { NamedKeyringPair } from '../Types'
 import { NamedKeyringPair } from '../Types'
-import { DerivedBalances } from '@polkadot/api-derive/types'
+import { DeriveBalancesAll } from '@polkadot/api-derive/types'
 import { toFixedLength } from '../helpers/display'
 import { toFixedLength } from '../helpers/display'
 
 
 const ACCOUNTS_DIRNAME = 'accounts'
 const ACCOUNTS_DIRNAME = 'accounts'
@@ -54,7 +54,9 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     const keyring = new Keyring({ type: 'sr25519' })
     const keyring = new Keyring({ type: 'sr25519' })
     keyring.addFromUri('//Alice', { name: 'Alice' })
     keyring.addFromUri('//Alice', { name: 'Alice' })
     keyring.addFromUri('//Bob', { name: 'Bob' })
     keyring.addFromUri('//Bob', { name: 'Bob' })
-    keyring.getPairs().forEach((pair) => this.saveAccount({ ...pair, meta: { name: pair.meta.name } }, '', true))
+    keyring
+      .getPairs()
+      .forEach((pair) => this.saveAccount({ ...pair, meta: { name: pair.meta.name as string } }, '', true))
   }
   }
 
 
   fetchAccountFromJsonFile(jsonBackupFilePath: string): NamedKeyringPair {
   fetchAccountFromJsonFile(jsonBackupFilePath: string): NamedKeyringPair {
@@ -186,7 +188,7 @@ export default abstract class AccountsCommandBase extends ApiCommandBase {
     message = 'Select an account',
     message = 'Select an account',
     showBalances = true
     showBalances = true
   ): Promise<NamedKeyringPair> {
   ): Promise<NamedKeyringPair> {
-    let balances: DerivedBalances[]
+    let balances: DeriveBalancesAll[]
     if (showBalances) {
     if (showBalances) {
       balances = await this.getApi().getAccountsBalancesInfo(accounts.map((acc) => acc.address))
       balances = await this.getApi().getAccountsBalancesInfo(accounts.map((acc) => acc.address))
     }
     }

+ 42 - 27
cli/src/base/ApiCommandBase.ts

@@ -2,13 +2,14 @@ import ExitCodes from '../ExitCodes'
 import { CLIError } from '@oclif/errors'
 import { CLIError } from '@oclif/errors'
 import StateAwareCommandBase from './StateAwareCommandBase'
 import StateAwareCommandBase from './StateAwareCommandBase'
 import Api from '../Api'
 import Api from '../Api'
-import { getTypeDef, createType, Option, Tuple, Bytes } from '@polkadot/types'
-import { Codec, TypeDef, TypeDefInfo, Constructor } from '@polkadot/types/types'
+import { getTypeDef, Option, Tuple, Bytes } from '@polkadot/types'
+import { Registry, Codec, CodecArg, TypeDef, TypeDefInfo, Constructor } from '@polkadot/types/types'
+
 import { Vec, Struct, Enum } from '@polkadot/types/codec'
 import { Vec, Struct, Enum } from '@polkadot/types/codec'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { ApiPromise, WsProvider } from '@polkadot/api'
 import { KeyringPair } from '@polkadot/keyring/types'
 import { KeyringPair } from '@polkadot/keyring/types'
 import chalk from 'chalk'
 import chalk from 'chalk'
-import { SubmittableResultImpl } from '@polkadot/api/types'
+import { InterfaceTypes } from '@polkadot/types/types/registry'
 import ajv from 'ajv'
 import ajv from 'ajv'
 import { ApiMethodArg, ApiMethodNamedArgs, ApiParamsOptions, ApiParamOptions } from '../Types'
 import { ApiMethodArg, ApiMethodNamedArgs, ApiParamsOptions, ApiParamOptions } from '../Types'
 import { createParamOptions } from '../helpers/promptOptions'
 import { createParamOptions } from '../helpers/promptOptions'
@@ -32,6 +33,14 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     return this.getApi().getOriginalApi()
     return this.getApi().getOriginalApi()
   }
   }
 
 
+  getTypesRegistry(): Registry {
+    return this.getOriginalApi().registry
+  }
+
+  createType<K extends keyof InterfaceTypes>(typeName: K, value?: unknown): InterfaceTypes[K] {
+    return this.getOriginalApi().createType(typeName, value)
+  }
+
   async init() {
   async init() {
     await super.init()
     await super.init()
     let apiUri: string = this.getPreservedState().apiUri
     let apiUri: string = this.getPreservedState().apiUri
@@ -81,6 +90,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
 
   isApiUriValid(uri: string) {
   isApiUriValid(uri: string) {
     try {
     try {
+      // eslint-disable-next-line no-new
       new WsProvider(uri)
       new WsProvider(uri)
     } catch (e) {
     } catch (e) {
       return false
       return false
@@ -90,8 +100,8 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
 
   // This is needed to correctly handle some structs, enums etc.
   // This is needed to correctly handle some structs, enums etc.
   // Where the main typeDef doesn't provide enough information
   // Where the main typeDef doesn't provide enough information
-  protected getRawTypeDef(type: string) {
-    const instance = createType(type as any)
+  protected getRawTypeDef(type: keyof InterfaceTypes) {
+    const instance = this.createType(type)
     return getTypeDef(instance.toRawType())
     return getTypeDef(instance.toRawType())
   }
   }
 
 
@@ -120,7 +130,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
   async promptForSimple(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Codec> {
   async promptForSimple(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Codec> {
     // If no default provided - get default value resulting from providing empty string
     // If no default provided - get default value resulting from providing empty string
     const defaultValueString =
     const defaultValueString =
-      paramOptions?.value?.default?.toString() || createType(typeDef.type as any, '').toString()
+      paramOptions?.value?.default?.toString() || this.createType(typeDef.type as any, '').toString()
     const providedValue = await this.simplePrompt({
     const providedValue = await this.simplePrompt({
       message: `Provide value for ${this.paramName(typeDef)}`,
       message: `Provide value for ${this.paramName(typeDef)}`,
       type: 'input',
       type: 'input',
@@ -129,7 +139,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       default: (defaultValueString === '0x' ? '' : defaultValueString) || undefined,
       default: (defaultValueString === '0x' ? '' : defaultValueString) || undefined,
       validate: paramOptions?.validator,
       validate: paramOptions?.validator,
     })
     })
-    return createType(typeDef.type as any, providedValue)
+    return this.createType(typeDef.type as any, providedValue)
   }
   }
 
 
   // Prompt for Option<Codec> value
   // Prompt for Option<Codec> value
@@ -149,10 +159,10 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
         createParamOptions(subtype.name, defaultValue?.unwrapOr(undefined))
         createParamOptions(subtype.name, defaultValue?.unwrapOr(undefined))
       )
       )
       this.closeIndentGroup()
       this.closeIndentGroup()
-      return new Option(subtype.type as any, value)
+      return this.createType(`Option<${subtype.type}>` as any, value)
     }
     }
 
 
-    return new Option(subtype.type as any, null)
+    return this.createType(`Option<${subtype.type}>` as any, null)
   }
   }
 
 
   // Prompt for Tuple
   // Prompt for Tuple
@@ -173,7 +183,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     }
     }
     this.closeIndentGroup()
     this.closeIndentGroup()
 
 
-    return new Tuple(subtypes.map((subtype) => subtype.type) as any, result)
+    return new Tuple(this.getTypesRegistry(), subtypes.map((subtype) => subtype.type) as any, result)
   }
   }
 
 
   // Prompt for Struct
   // Prompt for Struct
@@ -182,7 +192,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
 
     this.openIndentGroup()
     this.openIndentGroup()
     const structType = typeDef.type
     const structType = typeDef.type
-    const rawTypeDef = this.getRawTypeDef(structType)
+    const rawTypeDef = this.getRawTypeDef(structType as keyof InterfaceTypes)
     // We assume struct typeDef always has array of typeDefs inside ".sub"
     // We assume struct typeDef always has array of typeDefs inside ".sub"
     const structSubtypes = rawTypeDef.sub as TypeDef[]
     const structSubtypes = rawTypeDef.sub as TypeDef[]
     const structDefault = paramOptions?.value?.default as Struct | undefined
     const structDefault = paramOptions?.value?.default as Struct | undefined
@@ -200,7 +210,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     }
     }
     this.closeIndentGroup()
     this.closeIndentGroup()
 
 
-    return createType(structType as any, structValues)
+    return this.createType(structType as any, structValues)
   }
   }
 
 
   // Prompt for Vec
   // Prompt for Vec
@@ -228,12 +238,12 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     } while (addAnother)
     } while (addAnother)
     this.closeIndentGroup()
     this.closeIndentGroup()
 
 
-    return new Vec(subtype.type as any, entries)
+    return this.createType(`Vec<${subtype.type}>` as any, entries)
   }
   }
 
 
   // Prompt for Enum
   // Prompt for Enum
   async promptForEnum(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Enum> {
   async promptForEnum(typeDef: TypeDef, paramOptions?: ApiParamOptions): Promise<Enum> {
-    const enumType = typeDef.type
+    const enumType = typeDef.type as keyof InterfaceTypes
     const rawTypeDef = this.getRawTypeDef(enumType)
     const rawTypeDef = this.getRawTypeDef(enumType)
     // We assume enum always has array on TypeDefs inside ".sub"
     // We assume enum always has array on TypeDefs inside ".sub"
     const enumSubtypes = rawTypeDef.sub as TypeDef[]
     const enumSubtypes = rawTypeDef.sub as TypeDef[]
@@ -253,12 +263,12 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
 
 
     if (enumSubtype.type !== 'Null') {
     if (enumSubtype.type !== 'Null') {
       const subtypeOptions = createParamOptions(enumSubtype.name, defaultValue?.value)
       const subtypeOptions = createParamOptions(enumSubtype.name, defaultValue?.value)
-      return createType(enumType as any, {
+      return this.createType(enumType as any, {
         [enumSubtype.name!]: await this.promptForParam(enumSubtype.type, subtypeOptions),
         [enumSubtype.name!]: await this.promptForParam(enumSubtype.type, subtypeOptions),
       })
       })
     }
     }
 
 
-    return createType(enumType as any, enumSubtype.name)
+    return this.createType(enumType as any, enumSubtype.name)
   }
   }
 
 
   // Prompt for param based on "paramType" string (ie. Option<MemeberId>)
   // Prompt for param based on "paramType" string (ie. Option<MemeberId>)
@@ -268,7 +278,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     paramOptions?: ApiParamOptions // TODO: This is not fully implemented for all types yet
     paramOptions?: ApiParamOptions // TODO: This is not fully implemented for all types yet
   ): Promise<ApiMethodArg> {
   ): Promise<ApiMethodArg> {
     const typeDef = getTypeDef(paramType)
     const typeDef = getTypeDef(paramType)
-    const rawTypeDef = this.getRawTypeDef(paramType)
+    const rawTypeDef = this.getRawTypeDef(paramType as keyof InterfaceTypes)
 
 
     if (paramOptions?.forcedName) {
     if (paramOptions?.forcedName) {
       typeDef.name = paramOptions.forcedName
       typeDef.name = paramOptions.forcedName
@@ -309,18 +319,23 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     defaultValue?: Bytes,
     defaultValue?: Bytes,
     schemaValidator?: ajv.ValidateFunction
     schemaValidator?: ajv.ValidateFunction
   ) {
   ) {
-    const rawType = new jsonStruct().toRawType()
+    const JsonStructObject = jsonStruct
+    const rawType = new JsonStructObject(this.getTypesRegistry()).toRawType()
     const typeDef = getTypeDef(rawType)
     const typeDef = getTypeDef(rawType)
 
 
     const defaultStruct =
     const defaultStruct =
-      defaultValue && new jsonStruct(JSON.parse(Buffer.from(defaultValue.toHex().replace('0x', ''), 'hex').toString()))
+      defaultValue &&
+      new JsonStructObject(
+        this.getTypesRegistry(),
+        JSON.parse(Buffer.from(defaultValue.toHex().replace('0x', ''), 'hex').toString())
+      )
 
 
     if (argName) {
     if (argName) {
       typeDef.name = argName
       typeDef.name = argName
     }
     }
 
 
-    let isValid = true,
-      jsonText: string
+    let isValid = true
+    let jsonText: string
     do {
     do {
       const structVal = await this.promptForStruct(typeDef, createParamOptions(typeDef.name, defaultStruct))
       const structVal = await this.promptForStruct(typeDef, createParamOptions(typeDef.name, defaultStruct))
       jsonText = JSON.stringify(structVal.toJSON())
       jsonText = JSON.stringify(structVal.toJSON())
@@ -338,7 +353,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       }
       }
     } while (!isValid)
     } while (!isValid)
 
 
-    return new Bytes('0x' + Buffer.from(jsonText, 'ascii').toString('hex'))
+    return this.createType('Bytes', '0x' + Buffer.from(jsonText, 'ascii').toString('hex'))
   }
   }
 
 
   async promptForExtrinsicParams(
   async promptForExtrinsicParams(
@@ -364,18 +379,18 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     return values
     return values
   }
   }
 
 
-  sendExtrinsic(account: KeyringPair, module: string, method: string, params: Codec[]) {
+  sendExtrinsic(account: KeyringPair, module: string, method: string, params: CodecArg[]) {
     return new Promise((resolve, reject) => {
     return new Promise((resolve, reject) => {
       const extrinsicMethod = this.getOriginalApi().tx[module][method]
       const extrinsicMethod = this.getOriginalApi().tx[module][method]
       let unsubscribe: () => void
       let unsubscribe: () => void
       extrinsicMethod(...params)
       extrinsicMethod(...params)
-        .signAndSend(account, {}, (result: SubmittableResultImpl) => {
+        .signAndSend(account, {}, (result) => {
           // Implementation loosely based on /pioneer/packages/react-signer/src/Modal.tsx
           // Implementation loosely based on /pioneer/packages/react-signer/src/Modal.tsx
           if (!result || !result.status) {
           if (!result || !result.status) {
             return
             return
           }
           }
 
 
-          if (result.status.isFinalized) {
+          if (result.status.isInBlock) {
             unsubscribe()
             unsubscribe()
             result.events
             result.events
               .filter(({ event: { section } }): boolean => section === 'system')
               .filter(({ event: { section } }): boolean => section === 'system')
@@ -401,7 +416,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
     account: KeyringPair,
     account: KeyringPair,
     module: string,
     module: string,
     method: string,
     method: string,
-    params: Codec[],
+    params: CodecArg[],
     warnOnly = false // If specified - only warning will be displayed (instead of error beeing thrown)
     warnOnly = false // If specified - only warning will be displayed (instead of error beeing thrown)
   ) {
   ) {
     try {
     try {
@@ -449,7 +464,7 @@ export default abstract class ApiCommandBase extends StateAwareCommandBase {
       const argName = arg.name.toString()
       const argName = arg.name.toString()
       const argType = arg.type.toString()
       const argType = arg.type.toString()
       try {
       try {
-        parsedArgs.push({ name: argName, value: createType(argType as any, draftJSONObj[parseInt(index)]) })
+        parsedArgs.push({ name: argName, value: this.createType(argType as any, draftJSONObj[parseInt(index)]) })
       } catch (e) {
       } catch (e) {
         throw new CLIError(`Couldn't parse ${argName} value from draft at ${draftFilePath}!`, {
         throw new CLIError(`Couldn't parse ${argName} value from draft at ${draftFilePath}!`, {
           exit: ExitCodes.InvalidFile,
           exit: ExitCodes.InvalidFile,

+ 4 - 4
cli/src/base/WorkingGroupsCommandBase.ts

@@ -110,13 +110,13 @@ export default abstract class WorkingGroupsCommandBase extends AccountsCommandBa
       })),
       })),
     })
     })
 
 
-    return acceptedApplications
+    return acceptedApplications.sort() // Sort just in case, since runtime expects them to be sorted
   }
   }
 
 
   async promptForNewOpeningDraftName() {
   async promptForNewOpeningDraftName() {
-    let draftName = '',
-      fileExists = false,
-      overrideConfirmed = false
+    let draftName = ''
+    let fileExists = false
+    let overrideConfirmed = false
 
 
     do {
     do {
       draftName = await this.simplePrompt({
       draftName = await this.simplePrompt({

+ 2 - 3
cli/src/commands/account/current.ts

@@ -1,6 +1,5 @@
 import AccountsCommandBase from '../../base/AccountsCommandBase'
 import AccountsCommandBase from '../../base/AccountsCommandBase'
 import { AccountSummary, NameValueObj, NamedKeyringPair } from '../../Types'
 import { AccountSummary, NameValueObj, NamedKeyringPair } from '../../Types'
-import { DerivedBalances } from '@polkadot/api-derive/types'
 import { displayHeader, displayNameValueTable } from '../../helpers/display'
 import { displayHeader, displayNameValueTable } from '../../helpers/display'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import moment from 'moment'
 import moment from 'moment'
@@ -15,7 +14,7 @@ export default class AccountCurrent extends AccountsCommandBase {
 
 
     displayHeader('Account information')
     displayHeader('Account information')
     const creationDate: string = currentAccount.meta.whenCreated
     const creationDate: string = currentAccount.meta.whenCreated
-      ? moment(currentAccount.meta.whenCreated).format('YYYY-MM-DD HH:mm:ss')
+      ? moment(currentAccount.meta.whenCreated as string | number).format('YYYY-MM-DD HH:mm:ss')
       : '?'
       : '?'
     const accountRows: NameValueObj[] = [
     const accountRows: NameValueObj[] = [
       { name: 'Account name:', value: currentAccount.meta.name },
       { name: 'Account name:', value: currentAccount.meta.name },
@@ -25,7 +24,7 @@ export default class AccountCurrent extends AccountsCommandBase {
     displayNameValueTable(accountRows)
     displayNameValueTable(accountRows)
 
 
     displayHeader('Balances')
     displayHeader('Balances')
-    const balances: DerivedBalances = summary.balances
+    const balances = summary.balances
     const balancesRows: NameValueObj[] = [
     const balancesRows: NameValueObj[] = [
       { name: 'Total balance:', value: formatBalance(balances.votingBalance) },
       { name: 'Total balance:', value: formatBalance(balances.votingBalance) },
       { name: 'Transferable balance:', value: formatBalance(balances.availableBalance) },
       { name: 'Transferable balance:', value: formatBalance(balances.availableBalance) },

+ 4 - 4
cli/src/commands/account/transferTokens.ts

@@ -6,7 +6,6 @@ import { formatBalance } from '@polkadot/util'
 import { Hash } from '@polkadot/types/interfaces'
 import { Hash } from '@polkadot/types/interfaces'
 import { NamedKeyringPair } from '../../Types'
 import { NamedKeyringPair } from '../../Types'
 import { checkBalance, validateAddress } from '../../helpers/validation'
 import { checkBalance, validateAddress } from '../../helpers/validation'
-import { DerivedBalances } from '@polkadot/api-derive/types'
 
 
 type AccountTransferArgs = {
 type AccountTransferArgs = {
   recipient: string
   recipient: string
@@ -36,15 +35,16 @@ export default class AccountTransferTokens extends AccountsCommandBase {
 
 
     // Initial validation
     // Initial validation
     validateAddress(args.recipient, 'Invalid recipient address')
     validateAddress(args.recipient, 'Invalid recipient address')
-    const accBalances: DerivedBalances = (await this.getApi().getAccountsBalancesInfo([selectedAccount.address]))[0]
+    const accBalances = (await this.getApi().getAccountsBalancesInfo([selectedAccount.address]))[0]
     checkBalance(accBalances, amountBN)
     checkBalance(accBalances, amountBN)
 
 
     await this.requestAccountDecoding(selectedAccount)
     await this.requestAccountDecoding(selectedAccount)
 
 
     this.log(chalk.white('Estimating fee...'))
     this.log(chalk.white('Estimating fee...'))
+    const tx = await this.getApi().createTransferTx(args.recipient, amountBN)
     let estimatedFee: BN
     let estimatedFee: BN
     try {
     try {
-      estimatedFee = await this.getApi().estimateFee(selectedAccount, args.recipient, amountBN)
+      estimatedFee = await this.getApi().estimateFee(selectedAccount, tx)
     } catch (e) {
     } catch (e) {
       this.error('Could not estimate the fee.', { exit: ExitCodes.UnexpectedException })
       this.error('Could not estimate the fee.', { exit: ExitCodes.UnexpectedException })
     }
     }
@@ -57,7 +57,7 @@ export default class AccountTransferTokens extends AccountsCommandBase {
     await this.requireConfirmation('Do you confirm the transfer?')
     await this.requireConfirmation('Do you confirm the transfer?')
 
 
     try {
     try {
-      const txHash: Hash = await this.getApi().transfer(selectedAccount, args.recipient, amountBN)
+      const txHash: Hash = await tx.signAndSend(selectedAccount)
       this.log(chalk.greenBright('Transaction succesfully sent!'))
       this.log(chalk.greenBright('Transaction succesfully sent!'))
       this.log(chalk.white('Hash:', txHash.toString()))
       this.log(chalk.white('Hash:', txHash.toString()))
     } catch (e) {
     } catch (e) {

+ 11 - 14
cli/src/commands/api/inspect.ts

@@ -2,9 +2,8 @@ import { flags } from '@oclif/command'
 import { CLIError } from '@oclif/errors'
 import { CLIError } from '@oclif/errors'
 import { displayNameValueTable } from '../../helpers/display'
 import { displayNameValueTable } from '../../helpers/display'
 import { ApiPromise } from '@polkadot/api'
 import { ApiPromise } from '@polkadot/api'
-import { Option } from '@polkadot/types'
 import { Codec } from '@polkadot/types/types'
 import { Codec } from '@polkadot/types/types'
-import { ConstantCodec } from '@polkadot/api-metadata/consts/types'
+import { ConstantCodec } from '@polkadot/metadata/Decorated/consts/types'
 import ExitCodes from '../../ExitCodes'
 import ExitCodes from '../../ExitCodes'
 import chalk from 'chalk'
 import chalk from 'chalk'
 import { NameValueObj, ApiMethodArg } from '../../Types'
 import { NameValueObj, ApiMethodArg } from '../../Types'
@@ -99,7 +98,7 @@ export default class ApiInspect extends ApiCommandBase {
       return [type.asDoubleMap.key1.toString(), type.asDoubleMap.key2.toString()]
       return [type.asDoubleMap.key1.toString(), type.asDoubleMap.key2.toString()]
     }
     }
     if (type.isMap) {
     if (type.isMap) {
-      return type.asMap.linked.isTrue ? [`Option<${type.asMap.key.toString()}>`] : [type.asMap.key.toString()]
+      return [type.asMap.key.toString()]
     }
     }
     return []
     return []
   }
   }
@@ -110,14 +109,17 @@ export default class ApiInspect extends ApiCommandBase {
       const {
       const {
         meta: { type, modifier },
         meta: { type, modifier },
       } = method.creator
       } = method.creator
+      let typeName = type.toString()
       if (type.isDoubleMap) {
       if (type.isDoubleMap) {
-        return type.asDoubleMap.value.toString()
+        typeName = type.asDoubleMap.value.toString()
       }
       }
-      if (modifier.isOptional) {
-        return `Option<${type.toString()}>`
+      if (type.isMap) {
+        typeName = type.asMap.value.toString()
       }
       }
+
+      return modifier.isOptional ? `Option<${typeName}>` : typeName
     }
     }
-    // Fallback for "query" and default for "consts"
+    // Fallback for "consts"
     return this.getMethodMeta(apiType, apiModule, apiMethod).type.toString()
     return this.getMethodMeta(apiType, apiModule, apiMethod).type.toString()
   }
   }
 
 
@@ -127,7 +129,7 @@ export default class ApiInspect extends ApiCommandBase {
     api: ApiPromise,
     api: ApiPromise,
     flags: ApiInspectFlags
     flags: ApiInspectFlags
   ): { apiType: ApiType | undefined; apiModule: string | undefined; apiMethod: string | undefined } {
   ): { apiType: ApiType | undefined; apiModule: string | undefined; apiMethod: string | undefined } {
-    let apiType: ApiType | undefined = undefined
+    let apiType: ApiType | undefined
     const { module: apiModule, method: apiMethod } = flags
     const { module: apiModule, method: apiMethod } = flags
 
 
     if (flags.type !== undefined) {
     if (flags.type !== undefined) {
@@ -155,12 +157,7 @@ export default class ApiInspect extends ApiCommandBase {
     for (const [key, paramType] of Object.entries(paramTypes)) {
     for (const [key, paramType] of Object.entries(paramTypes)) {
       this.log(chalk.bold.white(`Parameter no. ${parseInt(key) + 1} (${paramType}):`))
       this.log(chalk.bold.white(`Parameter no. ${parseInt(key) + 1} (${paramType}):`))
       const paramValue = await this.promptForParam(paramType)
       const paramValue = await this.promptForParam(paramType)
-      if (paramValue instanceof Option && paramValue.isSome) {
-        result.push(paramValue.unwrap())
-      } else if (!(paramValue instanceof Option)) {
-        result.push(paramValue)
-      }
-      // In case of empty option we MUST NOT add anything to the array (otherwise it causes some error)
+      result.push(paramValue)
     }
     }
 
 
     return result
     return result

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

@@ -11,6 +11,7 @@ export default class WorkingGroupsApplication extends WorkingGroupsCommandBase {
       description: 'Working Group Application ID',
       description: 'Working Group Application ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }

+ 2 - 2
cli/src/commands/working-groups/createOpening.ts

@@ -53,8 +53,8 @@ export default class WorkingGroupsCreateOpening extends WorkingGroupsCommandBase
       const module = apiModuleByGroup[this.group]
       const module = apiModuleByGroup[this.group]
       const method = 'addOpening'
       const method = 'addOpening'
 
 
-      let saveDraft = false,
-        params: ApiMethodArg[]
+      let saveDraft = false
+      let params: ApiMethodArg[]
       if (flags.createDraftOnly) {
       if (flags.createDraftOnly) {
         params = await this.promptForExtrinsicParams(module, method, promptOptions)
         params = await this.promptForExtrinsicParams(module, method, promptOptions)
         saveDraft = true
         saveDraft = true

+ 3 - 5
cli/src/commands/working-groups/decreaseWorkerStake.ts

@@ -1,6 +1,5 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { WorkerId } from '@joystream/types/working-group'
 import { Balance } from '@polkadot/types/interfaces'
 import { Balance } from '@polkadot/types/interfaces'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import { minMaxInt } from '../../validators/common'
 import { minMaxInt } from '../../validators/common'
@@ -11,6 +10,7 @@ export default class WorkingGroupsDecreaseWorkerStake extends WorkingGroupsComma
   static description =
   static description =
     'Decreases given worker stake by an amount that will be returned to the worker role account. ' +
     'Decreases given worker stake by an amount that will be returned to the worker role account. ' +
     'Requires lead access.'
     'Requires lead access.'
+
   static args = [
   static args = [
     {
     {
       name: 'workerId',
       name: 'workerId',
@@ -18,6 +18,7 @@ export default class WorkingGroupsDecreaseWorkerStake extends WorkingGroupsComma
       description: 'Worker ID',
       description: 'Worker ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -41,10 +42,7 @@ export default class WorkingGroupsDecreaseWorkerStake extends WorkingGroupsComma
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
-    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'decreaseStake', [
-      new WorkerId(workerId),
-      balance,
-    ])
+    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'decreaseStake', [workerId, balance])
 
 
     this.log(
     this.log(
       chalk.green(
       chalk.green(

+ 3 - 4
cli/src/commands/working-groups/evictWorker.ts

@@ -1,7 +1,5 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { WorkerId } from '@joystream/types/working-group'
-import { bool } from '@polkadot/types/primitive'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import chalk from 'chalk'
 import chalk from 'chalk'
 import { createParamOptions } from '../../helpers/promptOptions'
 import { createParamOptions } from '../../helpers/promptOptions'
@@ -15,6 +13,7 @@ export default class WorkingGroupsEvictWorker extends WorkingGroupsCommandBase {
       description: 'Worker ID',
       description: 'Worker ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -43,9 +42,9 @@ export default class WorkingGroupsEvictWorker extends WorkingGroupsCommandBase {
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'terminateRole', [
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'terminateRole', [
-      new WorkerId(workerId),
+      workerId,
       rationale,
       rationale,
-      new bool(shouldSlash),
+      shouldSlash,
     ])
     ])
 
 
     this.log(chalk.green(`Worker ${chalk.white(workerId)} has been evicted!`))
     this.log(chalk.green(`Worker ${chalk.white(workerId)} has been evicted!`))

+ 4 - 8
cli/src/commands/working-groups/fillOpening.ts

@@ -1,8 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { OpeningStatus } from '../../Types'
 import { OpeningStatus } from '../../Types'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { OpeningId } from '@joystream/types/hiring'
-import { ApplicationIdSet, RewardPolicy } from '@joystream/types/working-group'
 import chalk from 'chalk'
 import chalk from 'chalk'
 import { createParamOptions } from '../../helpers/promptOptions'
 import { createParamOptions } from '../../helpers/promptOptions'
 
 
@@ -15,6 +13,7 @@ export default class WorkingGroupsFillOpening extends WorkingGroupsCommandBase {
       description: 'Working Group Opening ID',
       description: 'Working Group Opening ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -30,16 +29,13 @@ export default class WorkingGroupsFillOpening extends WorkingGroupsCommandBase {
     const opening = await this.getOpeningForLeadAction(openingId, OpeningStatus.InReview)
     const opening = await this.getOpeningForLeadAction(openingId, OpeningStatus.InReview)
 
 
     const applicationIds = await this.promptForApplicationsToAccept(opening)
     const applicationIds = await this.promptForApplicationsToAccept(opening)
-    const rewardPolicyOpt = await this.promptForParam(
-      `Option<${RewardPolicy.name}>`,
-      createParamOptions('RewardPolicy')
-    )
+    const rewardPolicyOpt = await this.promptForParam(`Option<RewardPolicy>`, createParamOptions('RewardPolicy'))
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'fillOpening', [
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'fillOpening', [
-      new OpeningId(openingId),
-      new ApplicationIdSet(applicationIds),
+      openingId,
+      applicationIds,
       rewardPolicyOpt,
       rewardPolicyOpt,
     ])
     ])
 
 

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

@@ -15,6 +15,7 @@ export default class WorkingGroupsOpening extends WorkingGroupsCommandBase {
       description: 'Working Group Opening ID',
       description: 'Working Group Opening ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }

+ 2 - 5
cli/src/commands/working-groups/slashWorker.ts

@@ -1,6 +1,5 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { WorkerId } from '@joystream/types/working-group'
 import { Balance } from '@polkadot/types/interfaces'
 import { Balance } from '@polkadot/types/interfaces'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import { minMaxInt } from '../../validators/common'
 import { minMaxInt } from '../../validators/common'
@@ -16,6 +15,7 @@ export default class WorkingGroupsSlashWorker extends WorkingGroupsCommandBase {
       description: 'Worker ID',
       description: 'Worker ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -39,10 +39,7 @@ export default class WorkingGroupsSlashWorker extends WorkingGroupsCommandBase {
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
-    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'slashStake', [
-      new WorkerId(workerId),
-      balance,
-    ])
+    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'slashStake', [workerId, balance])
 
 
     this.log(
     this.log(
       chalk.green(
       chalk.green(

+ 2 - 4
cli/src/commands/working-groups/startAcceptingApplications.ts

@@ -1,7 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { OpeningStatus } from '../../Types'
 import { OpeningStatus } from '../../Types'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { OpeningId } from '@joystream/types/hiring'
 import chalk from 'chalk'
 import chalk from 'chalk'
 
 
 export default class WorkingGroupsStartAcceptingApplications extends WorkingGroupsCommandBase {
 export default class WorkingGroupsStartAcceptingApplications extends WorkingGroupsCommandBase {
@@ -13,6 +12,7 @@ export default class WorkingGroupsStartAcceptingApplications extends WorkingGrou
       description: 'Working Group Opening ID',
       description: 'Working Group Opening ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -29,9 +29,7 @@ export default class WorkingGroupsStartAcceptingApplications extends WorkingGrou
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
-    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'acceptApplications', [
-      new OpeningId(openingId),
-    ])
+    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'acceptApplications', [openingId])
 
 
     this.log(
     this.log(
       chalk.green(`Opening ${chalk.white(openingId)} status changed to: ${chalk.white('Accepting Applications')}`)
       chalk.green(`Opening ${chalk.white(openingId)} status changed to: ${chalk.white('Accepting Applications')}`)

+ 2 - 4
cli/src/commands/working-groups/startReviewPeriod.ts

@@ -1,7 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { OpeningStatus } from '../../Types'
 import { OpeningStatus } from '../../Types'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { OpeningId } from '@joystream/types/hiring'
 import chalk from 'chalk'
 import chalk from 'chalk'
 
 
 export default class WorkingGroupsStartReviewPeriod extends WorkingGroupsCommandBase {
 export default class WorkingGroupsStartReviewPeriod extends WorkingGroupsCommandBase {
@@ -13,6 +12,7 @@ export default class WorkingGroupsStartReviewPeriod extends WorkingGroupsCommand
       description: 'Working Group Opening ID',
       description: 'Working Group Opening ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -29,9 +29,7 @@ export default class WorkingGroupsStartReviewPeriod extends WorkingGroupsCommand
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
-    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'beginApplicantReview', [
-      new OpeningId(openingId),
-    ])
+    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'beginApplicantReview', [openingId])
 
 
     this.log(chalk.green(`Opening ${chalk.white(openingId)} status changed to: ${chalk.white('In Review')}`))
     this.log(chalk.green(`Opening ${chalk.white(openingId)} status changed to: ${chalk.white('In Review')}`))
   }
   }

+ 3 - 4
cli/src/commands/working-groups/terminateApplication.ts

@@ -1,6 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { ApplicationStageKeys, ApplicationId } from '@joystream/types/hiring'
+import { ApplicationStageKeys } from '@joystream/types/hiring'
 import chalk from 'chalk'
 import chalk from 'chalk'
 
 
 export default class WorkingGroupsTerminateApplication extends WorkingGroupsCommandBase {
 export default class WorkingGroupsTerminateApplication extends WorkingGroupsCommandBase {
@@ -12,6 +12,7 @@ export default class WorkingGroupsTerminateApplication extends WorkingGroupsComm
       description: 'Working Group Application ID',
       description: 'Working Group Application ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -29,9 +30,7 @@ export default class WorkingGroupsTerminateApplication extends WorkingGroupsComm
 
 
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
-    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'terminateApplication', [
-      new ApplicationId(applicationId),
-    ])
+    await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'terminateApplication', [applicationId])
 
 
     this.log(chalk.green(`Application ${chalk.white(applicationId)} has been succesfully terminated!`))
     this.log(chalk.green(`Application ${chalk.white(applicationId)} has been succesfully terminated!`))
   }
   }

+ 2 - 2
cli/src/commands/working-groups/updateRewardAccount.ts

@@ -1,7 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
 import { validateAddress } from '../../helpers/validation'
 import { validateAddress } from '../../helpers/validation'
-import { GenericAccountId } from '@polkadot/types'
 import chalk from 'chalk'
 import chalk from 'chalk'
 import ExitCodes from '../../ExitCodes'
 import ExitCodes from '../../ExitCodes'
 
 
@@ -14,6 +13,7 @@ export default class WorkingGroupsUpdateRewardAccount extends WorkingGroupsComma
       description: 'New reward account address (if omitted, one of the existing CLI accounts can be selected)',
       description: 'New reward account address (if omitted, one of the existing CLI accounts can be selected)',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -40,7 +40,7 @@ export default class WorkingGroupsUpdateRewardAccount extends WorkingGroupsComma
 
 
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRewardAccount', [
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRewardAccount', [
       worker.workerId,
       worker.workerId,
-      new GenericAccountId(newRewardAccount),
+      newRewardAccount,
     ])
     ])
 
 
     this.log(chalk.green(`Succesfully updated the reward account to: ${chalk.white(newRewardAccount)})`))
     this.log(chalk.green(`Succesfully updated the reward account to: ${chalk.white(newRewardAccount)})`))

+ 2 - 2
cli/src/commands/working-groups/updateRoleAccount.ts

@@ -1,7 +1,6 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
 import { validateAddress } from '../../helpers/validation'
 import { validateAddress } from '../../helpers/validation'
-import { GenericAccountId } from '@polkadot/types'
 import chalk from 'chalk'
 import chalk from 'chalk'
 
 
 export default class WorkingGroupsUpdateRoleAccount extends WorkingGroupsCommandBase {
 export default class WorkingGroupsUpdateRoleAccount extends WorkingGroupsCommandBase {
@@ -13,6 +12,7 @@ export default class WorkingGroupsUpdateRoleAccount extends WorkingGroupsCommand
       description: 'New role account address (if omitted, one of the existing CLI accounts can be selected)',
       description: 'New role account address (if omitted, one of the existing CLI accounts can be selected)',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -34,7 +34,7 @@ export default class WorkingGroupsUpdateRoleAccount extends WorkingGroupsCommand
 
 
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRoleAccount', [
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRoleAccount', [
       worker.workerId,
       worker.workerId,
-      new GenericAccountId(newRoleAccount),
+      newRoleAccount,
     ])
     ])
 
 
     this.log(chalk.green(`Succesfully updated the role account to: ${chalk.white(newRoleAccount)})`))
     this.log(chalk.green(`Succesfully updated the role account to: ${chalk.white(newRoleAccount)})`))

+ 2 - 2
cli/src/commands/working-groups/updateWorkerReward.ts

@@ -1,6 +1,5 @@
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import WorkingGroupsCommandBase from '../../base/WorkingGroupsCommandBase'
 import { apiModuleByGroup } from '../../Api'
 import { apiModuleByGroup } from '../../Api'
-import { WorkerId } from '@joystream/types/working-group'
 import { formatBalance } from '@polkadot/util'
 import { formatBalance } from '@polkadot/util'
 import chalk from 'chalk'
 import chalk from 'chalk'
 import { Reward } from '../../Types'
 import { Reward } from '../../Types'
@@ -17,6 +16,7 @@ export default class WorkingGroupsUpdateWorkerReward extends WorkingGroupsComman
       description: 'Worker ID',
       description: 'Worker ID',
     },
     },
   ]
   ]
+
   static flags = {
   static flags = {
     ...WorkingGroupsCommandBase.flags,
     ...WorkingGroupsCommandBase.flags,
   }
   }
@@ -56,7 +56,7 @@ export default class WorkingGroupsUpdateWorkerReward extends WorkingGroupsComman
     await this.requestAccountDecoding(account)
     await this.requestAccountDecoding(account)
 
 
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRewardAmount', [
     await this.sendAndFollowExtrinsic(account, apiModuleByGroup[this.group], 'updateRewardAmount', [
-      new WorkerId(workerId),
+      workerId,
       newRewardValue,
       newRewardValue,
     ])
     ])
 
 

+ 2 - 2
cli/src/helpers/validation.ts

@@ -1,7 +1,7 @@
 import BN from 'bn.js'
 import BN from 'bn.js'
 import ExitCodes from '../ExitCodes'
 import ExitCodes from '../ExitCodes'
 import { decodeAddress } from '@polkadot/util-crypto'
 import { decodeAddress } from '@polkadot/util-crypto'
-import { DerivedBalances } from '@polkadot/api-derive/types'
+import { DeriveBalancesAll } from '@polkadot/api-derive/types'
 import { CLIError } from '@oclif/errors'
 import { CLIError } from '@oclif/errors'
 
 
 export function validateAddress(address: string, errorMessage = 'Invalid address'): void {
 export function validateAddress(address: string, errorMessage = 'Invalid address'): void {
@@ -12,7 +12,7 @@ export function validateAddress(address: string, errorMessage = 'Invalid address
   }
   }
 }
 }
 
 
-export function checkBalance(accBalances: DerivedBalances, requiredBalance: BN): void {
+export function checkBalance(accBalances: DeriveBalancesAll, requiredBalance: BN): void {
   if (requiredBalance.gt(accBalances.availableBalance)) {
   if (requiredBalance.gt(accBalances.availableBalance)) {
     throw new CLIError('Not enough balance available', { exit: ExitCodes.InvalidInput })
     throw new CLIError('Not enough balance available', { exit: ExitCodes.InvalidInput })
   }
   }

+ 11 - 9
cli/src/promptOptions/addWorkerOpening.ts

@@ -1,32 +1,32 @@
 import { ApiParamsOptions, ApiParamOptions, HRTStruct } from '../Types'
 import { ApiParamsOptions, ApiParamOptions, HRTStruct } from '../Types'
-import {
-  OpeningType,
-  SlashingTerms,
-  UnslashableTerms,
-  OpeningType_Worker as OpeningTypeWorker,
-  WorkingGroupOpeningPolicyCommitment,
-} from '@joystream/types/working-group'
+import { OpeningType, WorkingGroupOpeningPolicyCommitment } from '@joystream/types/working-group'
+import { SlashingTerms } from '@joystream/types/common'
 import { Bytes } from '@polkadot/types'
 import { Bytes } from '@polkadot/types'
 import { schemaValidator } from '@joystream/types/hiring'
 import { schemaValidator } from '@joystream/types/hiring'
+import { createType } from '@joystream/types'
 
 
 class OpeningPolicyCommitmentOptions implements ApiParamsOptions {
 class OpeningPolicyCommitmentOptions implements ApiParamsOptions {
   [paramName: string]: ApiParamOptions
   [paramName: string]: ApiParamOptions
   public role_slashing_terms: ApiParamOptions<SlashingTerms> = {
   public role_slashing_terms: ApiParamOptions<SlashingTerms> = {
     value: {
     value: {
-      default: SlashingTerms.create('Unslashable', new UnslashableTerms()),
+      default: createType('SlashingTerms', { Unslashable: null }),
       locked: true,
       locked: true,
     },
     },
   }
   }
+
   // Rename fields containing "curator" (solivg minor UI issue related to flat namespace)
   // Rename fields containing "curator" (solivg minor UI issue related to flat namespace)
   public terminate_curator_application_stake_unstaking_period: ApiParamOptions = {
   public terminate_curator_application_stake_unstaking_period: ApiParamOptions = {
     forcedName: 'terminate_application_stake_unstaking_period',
     forcedName: 'terminate_application_stake_unstaking_period',
   }
   }
+
   public terminate_curator_role_stake_unstaking_period: ApiParamOptions = {
   public terminate_curator_role_stake_unstaking_period: ApiParamOptions = {
     forcedName: 'terminate_role_stake_unstaking_period',
     forcedName: 'terminate_role_stake_unstaking_period',
   }
   }
+
   public exit_curator_role_application_stake_unstaking_period: ApiParamOptions = {
   public exit_curator_role_application_stake_unstaking_period: ApiParamOptions = {
     forcedName: 'exit_role_application_stake_unstaking_period',
     forcedName: 'exit_role_application_stake_unstaking_period',
   }
   }
+
   public exit_curator_role_stake_unstaking_period: ApiParamOptions = {
   public exit_curator_role_stake_unstaking_period: ApiParamOptions = {
     forcedName: 'exit_role_stake_unstaking_period',
     forcedName: 'exit_role_stake_unstaking_period',
   }
   }
@@ -37,10 +37,11 @@ class AddWrokerOpeningOptions implements ApiParamsOptions {
   // Lock value for opening_type
   // Lock value for opening_type
   public opening_type: ApiParamOptions<OpeningType> = {
   public opening_type: ApiParamOptions<OpeningType> = {
     value: {
     value: {
-      default: OpeningType.create('Worker', new OpeningTypeWorker()),
+      default: createType('OpeningType', { Worker: null }),
       locked: true,
       locked: true,
     },
     },
   }
   }
+
   // Json schema for human_readable_text
   // Json schema for human_readable_text
   public human_readable_text: ApiParamOptions<Bytes> = {
   public human_readable_text: ApiParamOptions<Bytes> = {
     jsonSchema: {
     jsonSchema: {
@@ -48,6 +49,7 @@ class AddWrokerOpeningOptions implements ApiParamsOptions {
       struct: HRTStruct,
       struct: HRTStruct,
     },
     },
   }
   }
+
   // Lock value for role_slashing_terms
   // Lock value for role_slashing_terms
   public commitment: ApiParamOptions<WorkingGroupOpeningPolicyCommitment> = {
   public commitment: ApiParamOptions<WorkingGroupOpeningPolicyCommitment> = {
     nestedOptions: new OpeningPolicyCommitmentOptions(),
     nestedOptions: new OpeningPolicyCommitmentOptions(),

+ 5 - 1
cli/tsconfig.json

@@ -9,7 +9,11 @@
     "target": "es2017",
     "target": "es2017",
     "esModuleInterop": true,
     "esModuleInterop": true,
     "types" : [ "node" ],
     "types" : [ "node" ],
-    "noUnusedLocals": true
+    "noUnusedLocals": true,
+    "baseUrl": ".",
+    "paths": {
+      "@polkadot/types/augment": ["../types/augment-codec/augment-types.ts"],
+    }
   },
   },
   "include": [
   "include": [
     "src/**/*"
     "src/**/*"

+ 22 - 0
devops/ansible/build-and-run-tests-exported-chainspec-playbook.yml

@@ -0,0 +1,22 @@
+- hosts: 127.0.0.1
+  user: root
+  become: yes
+  become_method: sudo
+
+  tasks:
+
+    - name: install dependencies
+      include_role:
+        name: install_dependencies
+
+    - name: alter block creation time
+      include_role:
+        name: alter_block_creation_time
+
+    - name: build node
+      include_role:
+        name: build_docker_image
+
+    - name: run tests
+      include_role:
+        name: run_tests_exported_chainspec

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

@@ -4,14 +4,19 @@
   become_method: sudo
   become_method: sudo
 
 
   tasks:
   tasks:
+
     - name: install dependencies
     - name: install dependencies
       include_role:
       include_role:
         name: install_dependencies
         name: install_dependencies
 
 
+    - name: alter block creation time
+      include_role:
+        name: alter_block_creation_time
+
     - name: build node
     - name: build node
       include_role:
       include_role:
         name: build_docker_image
         name: build_docker_image
-        
+
     - name: run tests
     - name: run tests
       include_role:
       include_role:
         name: run_tests_single_node
         name: run_tests_single_node

+ 6 - 1
devops/ansible/build-and-run-tests-two-nodes-playbook.yml

@@ -4,14 +4,19 @@
   become_method: sudo
   become_method: sudo
 
 
   tasks:
   tasks:
+
     - name: install dependencies
     - name: install dependencies
       include_role:
       include_role:
         name: install_dependencies
         name: install_dependencies
 
 
+    - name: alter block creation time
+      include_role:
+        name: alter_block_creation_time
+
     - name: build node
     - name: build node
       include_role:
       include_role:
         name: build_docker_image
         name: build_docker_image
-        
+
     - name: run tests
     - name: run tests
       include_role:
       include_role:
         name: run_tests_two_nodes
         name: run_tests_two_nodes

+ 6 - 1
devops/ansible/build-image-playbook.yml

@@ -4,10 +4,15 @@
   become_method: sudo
   become_method: sudo
 
 
   tasks:
   tasks:
+
     - name: install dependencies
     - name: install dependencies
       include_role:
       include_role:
         name: install_dependencies
         name: install_dependencies
 
 
+    - name: alter block creation time
+      include_role:
+        name: alter_block_creation_time
+
     - name: build node
     - name: build node
       include_role:
       include_role:
-        name: build_docker_image
+        name: build_docker_image

+ 2 - 2
devops/ansible/docker-compose.yml

@@ -3,7 +3,7 @@ services:
   node_alice:
   node_alice:
     image: joystream/node-testing
     image: joystream/node-testing
     container_name: alice
     container_name: alice
-    entrypoint: ./node --chain=chainspec.json --alice --validator --ws-external --rpc-cors=all
+    entrypoint: ./node --dev --alice --validator --unsafe-ws-external --rpc-cors=all
     ports:
     ports:
       - "30333:30333"
       - "30333:30333"
       - "9933:9933"
       - "9933:9933"
@@ -15,7 +15,7 @@ services:
   node_bob:
   node_bob:
     image: joystream/node-testing
     image: joystream/node-testing
     container_name: bob
     container_name: bob
-    entrypoint: ./node --chain=chainspec.json --bob --ws-external --rpc-cors=all
+    entrypoint: ./node --dev --bob --validator --unsafe-ws-external --rpc-cors=all
     ports:
     ports:
       - "30335:30333"
       - "30335:30333"
       - "9935:9933"
       - "9935:9933"

+ 4 - 0
devops/ansible/roles/alter_block_creation_time/tasks/main.yml

@@ -0,0 +1,4 @@
+- name: alter block creation time
+  shell: ./scripts/alter-block-creation-time.sh
+  args:
+    chdir: ../../

+ 1 - 1
devops/ansible/roles/build_docker_image/tasks/main.yml

@@ -1,4 +1,4 @@
 - name: create testing node docker image
 - name: create testing node docker image
-  shell: ./scripts/build-joystream-testing-node-docker-image.sh
+  shell: ./scripts/build-joystream-node-docker-image.sh
   args:
   args:
     chdir: ../../
     chdir: ../../

+ 16 - 5
devops/ansible/roles/install_dependencies/tasks/main.yml

@@ -1,7 +1,13 @@
-- name: install pip on Debian
+- name: install pip and npm on Debian
   block:
   block:
+    - name: create temporary folder
+      file:
+        path: ../../.tmp
+        state: directory
     - name: install pip using apt
     - name: install pip using apt
       apt: name=python-pip state=present
       apt: name=python-pip state=present
+    - name: install npm using apt
+      apt: name=npm state=present
   when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
   when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
 
 
 - name: install pip on Mac
 - name: install pip on Mac
@@ -11,15 +17,15 @@
         path: ../../.tmp
         path: ../../.tmp
         state: directory
         state: directory
     - name: get pip installer using curl
     - name: get pip installer using curl
-      get_url: 
+      get_url:
         url: https://bootstrap.pypa.io/get-pip.py
         url: https://bootstrap.pypa.io/get-pip.py
         dest: ../../.tmp/get-pip.py
         dest: ../../.tmp/get-pip.py
     - name: install pip
     - name: install pip
       shell: python ../../.tmp/get-pip.py
       shell: python ../../.tmp/get-pip.py
   when: ansible_distribution == 'MacOSX'
   when: ansible_distribution == 'MacOSX'
-  always: 
+  always:
     - name: remove pip installer script
     - name: remove pip installer script
-      file: 
+      file:
         path: ../../.tmp/get-pip.py
         path: ../../.tmp/get-pip.py
         state: absent
         state: absent
 
 
@@ -31,5 +37,10 @@
     name: yarn
     name: yarn
     global: yes
     global: yes
 
 
+- name: Install pyrsistent
+  pip:
+    name: pyrsistent==0.16.0
+
 - name: Install docker compose
 - name: Install docker compose
-  pip: name=docker-compose
+  pip:
+    name: docker-compose==1.26.2

+ 38 - 0
devops/ansible/roles/run_tests_exported_chainspec/tasks/main.yml

@@ -0,0 +1,38 @@
+- name: run network
+  block:
+    - name: yarn install for joystream types
+      shell: yarn workspace @joystream/types install
+
+    - name: yarn build for joystream types
+      shell: yarn workspace @joystream/types build
+
+    - name: yarn install for network tests
+      shell: yarn workspace joystream-testing install
+
+    - name: run docker container
+      docker_container:
+        name: "joystream-node"
+        image: "joystream/node"
+        ports:
+          - "9944:9944"
+        mounts:
+          - target: /testnet-state
+            source: "{{ playbook_dir }}/../../testnets/nicaea-exported-state"
+            type: bind
+            read_only: yes
+        entrypoint: ./node --chain ../testnet-state/raw_chain_spec.json --alice --validator --unsafe-ws-external --rpc-cors=all
+        state: started
+
+    - name: execute network tests
+      shell: yarn test >> ../../.tmp/tests.log
+      args:
+        chdir: ../../tests/network-tests/
+
+  always:
+    - name: display tests log
+      shell: cat ../../.tmp/tests.log
+
+    - name: stop docker container
+      docker_container:
+        name: "joystream-node-testing"
+        state: absent

+ 16 - 5
devops/ansible/roles/run_tests_single_node/tasks/main.yml

@@ -1,21 +1,32 @@
 - name: run network
 - name: run network
   block:
   block:
+    - name: yarn install for joystream types
+      shell: yarn workspace @joystream/types install
+
+    - name: yarn build for joystream types
+      shell: yarn workspace @joystream/types build
+
+    - name: yarn install for network tests
+      shell: yarn workspace joystream-testing install
 
 
     - name: run docker container
     - name: run docker container
       docker_container:
       docker_container:
-        name: "joystream-node-testing"
-        image: "joystream/node-testing"
+        name: "joystream-node"
+        image: "joystream/node"
         ports:
         ports:
           - "9944:9944"
           - "9944:9944"
-        entrypoint: ./node --chain=chainspec.json --alice --validator --ws-external --rpc-cors=all
+        entrypoint: ./node --dev --alice --validator --unsafe-ws-external --rpc-cors=all
         state: started
         state: started
 
 
     - name: execute network tests
     - name: execute network tests
-      shell: yarn debug >> ../../.tmp/tests.log
+      shell: yarn test >> ../../.tmp/tests.log
       args:
       args:
         chdir: ../../tests/network-tests/
         chdir: ../../tests/network-tests/
-        
+
   always:
   always:
+    - name: display tests log
+      shell: cat ../../.tmp/tests.log
+
     - name: stop docker container
     - name: stop docker container
       docker_container:
       docker_container:
         name: "joystream-node-testing"
         name: "joystream-node-testing"

+ 0 - 37
devops/dockerfiles/ansible-node/Dockerfile

@@ -1,37 +0,0 @@
-FROM joystream/rust-builder AS builder
-LABEL description="Compiles all workspace artifacts"
-WORKDIR /joystream
-COPY . /joystream
-
-# Build joystream-node and its dependencies - runtime
-RUN cargo build --release -p joystream-node
-RUN /joystream/scripts/create-test-chainspec.sh
-
-FROM debian:stretch
-LABEL description="Joystream node"
-WORKDIR /joystream
-COPY --from=builder /joystream/target/release/joystream-node /joystream/node
-COPY --from=builder /joystream/target/release/wbuild/joystream-node-runtime/joystream_node_runtime.compact.wasm /joystream/runtime.compact.wasm
-COPY --from=builder /joystream/.tmp/chainspec.json /joystream/chainspec.json
-
-# confirm it works
-RUN /joystream/node --version
-
-# https://manpages.debian.org/stretch/coreutils/b2sum.1.en.html
-# RUN apt-get install coreutils
-# print the blake2 256 hash of the wasm blob
-RUN b2sum -l 256 /joystream/runtime.compact.wasm
-# print the blake2 512 hash of the wasm blob
-RUN b2sum -l 512 /joystream/runtime.compact.wasm
-
-EXPOSE 30333 9933 9944
-
-# Use these volumes to persits chain state and keystore, eg.:
-# --base-path /data
-# optionally separate keystore (otherwise it will be stored in the base path)
-# --keystore-path /keystore
-# if base-path isn't specified, chain state is stored inside container in ~/.local/share/joystream-node/
-# which is not ideal
-VOLUME ["/data", "/keystore"]
-
-ENTRYPOINT ["/joystream/node"]

+ 1 - 1
devops/dockerfiles/node-and-runtime/Dockerfile

@@ -4,7 +4,7 @@ WORKDIR /joystream
 COPY . /joystream
 COPY . /joystream
 
 
 # Build joystream-node and its dependencies - runtime
 # Build joystream-node and its dependencies - runtime
-RUN cargo build --release -p joystream-node
+RUN WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release -p joystream-node
 
 
 FROM debian:stretch
 FROM debian:stretch
 LABEL description="Joystream node"
 LABEL description="Joystream node"

+ 2 - 2
devops/dockerfiles/rust-builder/Dockerfile

@@ -1,8 +1,8 @@
-FROM liuchong/rustup:1.43.0 AS builder
+FROM liuchong/rustup:1.46.0 AS builder
 LABEL description="Rust and WASM build environment for joystream and substrate"
 LABEL description="Rust and WASM build environment for joystream and substrate"
 
 
 WORKDIR /setup
 WORKDIR /setup
 COPY setup.sh /setup
 COPY setup.sh /setup
 ENV TERM=xterm
 ENV TERM=xterm
 
 
-RUN ./setup.sh
+RUN ./setup.sh

+ 5 - 5
devops/git-hooks/pre-push

@@ -1,10 +1,10 @@
 #!/bin/sh
 #!/bin/sh
 set -e
 set -e
 
 
-echo '+cargo test --release --all'
-BUILD_DUMMY_WASM_BINARY=1 cargo test --all
-
-echo '+cargo clippy --release --all -- -D warnings'
-BUILD_DUMMY_WASM_BINARY=1 cargo clippy --all -- -D warnings
+export WASM_BUILD_TOOLCHAIN=nightly-2020-05-23
 
 
+echo '+cargo clippy --all -- -D warnings'
+BUILD_DUMMY_WASM_BINARY=1 cargo clippy --release --all -- -D warnings
 
 
+echo '+cargo test --all'
+cargo test --release --all

+ 4 - 3
node/Cargo.toml

@@ -3,7 +3,7 @@ authors = ['Joystream contributors']
 build = 'build.rs'
 build = 'build.rs'
 edition = '2018'
 edition = '2018'
 name = 'joystream-node'
 name = 'joystream-node'
-version = '3.0.0'
+version = '3.1.0'
 default-run = "joystream-node"
 default-run = "joystream-node"
 
 
 [[bin]]
 [[bin]]
@@ -20,6 +20,8 @@ futures = { version = "0.3.1", features = ["compat"] }
 jsonrpc-core = "14.2.0"
 jsonrpc-core = "14.2.0"
 structopt = { version = "0.3.8", optional = true}
 structopt = { version = "0.3.8", optional = true}
 serde_json = '1.0'
 serde_json = '1.0'
+codec = { package = "parity-scale-codec", version = "1.3.1" }
+hex = { package = "hex", version = "0.4.2" }
 
 
 # primitives
 # primitives
 sp-authority-discovery = { package = 'sp-authority-discovery', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sp-authority-discovery = { package = 'sp-authority-discovery', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
@@ -74,11 +76,10 @@ browser-utils = { package = 'substrate-browser-utils', git = 'https://github.com
 
 
 [dev-dependencies]
 [dev-dependencies]
 tempfile = "3.1.0"
 tempfile = "3.1.0"
-codec = { package = "parity-scale-codec", version = "1.3.1" }
 sp-timestamp = { package = 'sp-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sp-timestamp = { package = 'sp-timestamp', default-features = false, git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sp-keyring = { package = 'sp-keyring', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sp-keyring = { package = 'sp-keyring', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 sc-consensus-babe = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', features = ["test-helpers"]}
 sc-consensus-babe = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4', features = ["test-helpers"]}
-# sc-service-test = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
+sc-service-test = { git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 frame-system = { package = 'frame-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 frame-system = { package = 'frame-system', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-transaction-payment = { package = 'pallet-transaction-payment', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-transaction-payment = { package = 'pallet-transaction-payment', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-grandpa = { package = 'pallet-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }
 pallet-grandpa = { package = 'pallet-grandpa', git = 'https://github.com/paritytech/substrate.git', rev = '00768a1f21a579c478fe5d4f51e1fa71f7db9fd4' }

+ 3 - 3
node/README.md

@@ -26,7 +26,7 @@ cd joystream/
 Compile the node and runtime:
 Compile the node and runtime:
 
 
 ```bash
 ```bash
-cargo build --release
+WASM_BUILD_TOOLCHAIN=nightly-2020-05-23 cargo build --release
 ```
 ```
 
 
 This produces the binary in `./target/release/joystream-node`
 This produces the binary in `./target/release/joystream-node`
@@ -34,7 +34,7 @@ This produces the binary in `./target/release/joystream-node`
 ### Running local development chain
 ### Running local development chain
 
 
 ```bash
 ```bash
-cargo run --release -- --dev
+./target/release/joystream-node --dev
 ```
 ```
 
 
 If you repeatedly need to restart a new chain,
 If you repeatedly need to restart a new chain,
@@ -49,7 +49,7 @@ this script will build and run a fresh new local development chain (purging exis
 Use the `--chain` argument, and specify the path to the genesis `chain.json` file for that public network. The JSON "chain spec" files for Joystream public networks can be found in [../testnets/](../testnets/).
 Use the `--chain` argument, and specify the path to the genesis `chain.json` file for that public network. The JSON "chain spec" files for Joystream public networks can be found in [../testnets/](../testnets/).
 
 
 ```bash
 ```bash
-cargo run --release -- --chain testnets/rome.json
+./target/release/joystream-node --chain testnets/rome.json
 ```
 ```
 
 
 ### Tests and code quality
 ### Tests and code quality

File diff suppressed because it is too large
+ 0 - 0
node/res/acropolis_members.json


File diff suppressed because it is too large
+ 0 - 0
node/res/forum_data_acropolis_encoded.json


File diff suppressed because it is too large
+ 0 - 0
node/res/forum_data_acropolis_serialized.json


+ 0 - 1
node/res/forum_data_empty.json

@@ -1 +0,0 @@
-{ "categories":[], "posts":[], "threads":[] }

+ 391 - 0
node/src/chain_spec/content_config.rs

@@ -0,0 +1,391 @@
+use codec::Decode;
+use node_runtime::common::constraints::InputValidationLengthConstraint;
+use node_runtime::{
+    content_wg::{Channel, ChannelId, Principal, PrincipalId},
+    data_directory::DataObject,
+    primitives::{AccountId, BlockNumber, Credential},
+    versioned_store::{Class, ClassId, Entity, EntityId},
+    versioned_store_permissions::ClassPermissions,
+    ContentId, ContentWorkingGroupConfig, DataDirectoryConfig, Runtime, VersionedStoreConfig,
+    VersionedStorePermissionsConfig,
+};
+use serde::Deserialize;
+use std::{fs, path::Path};
+
+// Because of the way that the @joystream/types were implemented the getters for
+// the string types return a `string` not the `Text` type so when we are serializing
+// them to json we get a string rather than an array of bytes, so deserializing them
+// is failing. So we are relying on parity codec encoding instead..
+#[derive(Decode)]
+struct ClassAndPermissions {
+    class: Class,
+    permissions: ClassPermissions<ClassId, Credential, u16, BlockNumber>,
+}
+
+#[derive(Decode)]
+struct EntityAndMaintainer {
+    entity: Entity,
+    maintainer: Option<Credential>,
+}
+
+#[derive(Decode)]
+struct DataObjectAndContentId {
+    content_id: ContentId,
+    data_object: DataObject<Runtime>,
+}
+
+#[derive(Decode)]
+struct ContentData {
+    /// classes and their associted permissions
+    classes: Vec<ClassAndPermissions>,
+    /// entities and their associated maintainer
+    entities: Vec<EntityAndMaintainer>,
+    /// DataObject(s) and ContentId
+    data_objects: Vec<DataObjectAndContentId>,
+    /// Media Channels
+    channels: Vec<ChannelAndId>,
+}
+
+#[derive(Deserialize)]
+struct EncodedClassAndPermissions {
+    /// hex encoded Class
+    class: String,
+    /// hex encoded ClassPermissions<ClassId, Credential, u16, BlockNumber>,
+    permissions: String,
+}
+
+impl EncodedClassAndPermissions {
+    fn decode(&self) -> ClassAndPermissions {
+        // hex string must not include '0x' prefix!
+        let encoded_class =
+            hex::decode(&self.class[2..].as_bytes()).expect("failed to parse class hex string");
+        let encoded_permissions = hex::decode(&self.permissions[2..].as_bytes())
+            .expect("failed to parse class permissions hex string");
+        ClassAndPermissions {
+            class: Decode::decode(&mut encoded_class.as_slice()).unwrap(),
+            permissions: Decode::decode(&mut encoded_permissions.as_slice()).unwrap(),
+        }
+    }
+}
+
+#[derive(Deserialize)]
+struct EncodedEntityAndMaintainer {
+    /// hex encoded Entity
+    entity: String,
+    /// hex encoded Option<Credential>
+    maintainer: Option<String>,
+}
+
+impl EncodedEntityAndMaintainer {
+    fn decode(&self) -> EntityAndMaintainer {
+        // hex string must not include '0x' prefix!
+        let encoded_entity =
+            hex::decode(&self.entity[2..].as_bytes()).expect("failed to parse entity hex string");
+        let encoded_maintainer = self.maintainer.as_ref().map(|maintainer| {
+            hex::decode(&maintainer[2..].as_bytes()).expect("failed to parse maintainer hex string")
+        });
+        EntityAndMaintainer {
+            entity: Decode::decode(&mut encoded_entity.as_slice()).unwrap(),
+            maintainer: encoded_maintainer
+                .map(|maintainer| Decode::decode(&mut maintainer.as_slice()).unwrap()),
+        }
+    }
+}
+
+#[derive(Deserialize)]
+struct EncodedDataObjectAndContentId {
+    /// hex encoded ContentId
+    content_id: String,
+    /// hex encoded DataObject<Runtime>
+    data_object: String,
+}
+
+impl EncodedDataObjectAndContentId {
+    fn decode(&self) -> DataObjectAndContentId {
+        // hex string must not include '0x' prefix!
+        let encoded_content_id = hex::decode(&self.content_id[2..].as_bytes())
+            .expect("failed to parse content_id hex string");
+        let encoded_data_object = hex::decode(&self.data_object[2..].as_bytes())
+            .expect("failed to parse data_object hex string");
+        DataObjectAndContentId {
+            content_id: Decode::decode(&mut encoded_content_id.as_slice()).unwrap(),
+            data_object: Decode::decode(&mut encoded_data_object.as_slice()).unwrap(),
+        }
+    }
+}
+
+#[derive(Decode)]
+struct ChannelAndId {
+    id: ChannelId<Runtime>,
+    channel: Channel<u64, AccountId, BlockNumber, PrincipalId<Runtime>>,
+}
+
+#[derive(Deserialize)]
+struct EncodedChannelAndId {
+    /// ChannelId number
+    id: u64,
+    /// hex encoded Channel
+    channel: String,
+}
+
+impl EncodedChannelAndId {
+    fn decode(&self) -> ChannelAndId {
+        let id = self.id;
+        let encoded_channel =
+            hex::decode(&self.channel[2..].as_bytes()).expect("failed to parse channel hex string");
+        ChannelAndId {
+            id: id as ChannelId<Runtime>,
+            channel: Decode::decode(&mut encoded_channel.as_slice()).unwrap(),
+        }
+    }
+}
+
+#[derive(Deserialize)]
+struct EncodedContentData {
+    /// classes and their associted permissions
+    classes: Vec<EncodedClassAndPermissions>,
+    /// entities and their associated maintainer
+    entities: Vec<EncodedEntityAndMaintainer>,
+    /// DataObject(s) and ContentId
+    data_objects: Vec<EncodedDataObjectAndContentId>,
+    /// Media Channels
+    channels: Vec<EncodedChannelAndId>,
+}
+
+fn parse_content_data(data_file: &Path) -> EncodedContentData {
+    let data = fs::read_to_string(data_file).expect("Failed reading file");
+    serde_json::from_str(&data).expect("failed parsing content data")
+}
+
+impl EncodedContentData {
+    pub fn decode(&self) -> ContentData {
+        ContentData {
+            classes: self
+                .classes
+                .iter()
+                .map(|class_and_perm| class_and_perm.decode())
+                .collect(),
+            entities: self
+                .entities
+                .iter()
+                .map(|entities_and_maintainer| entities_and_maintainer.decode())
+                .collect(),
+            data_objects: self
+                .data_objects
+                .iter()
+                .map(|data_objects| data_objects.decode())
+                .collect(),
+            channels: self
+                .channels
+                .iter()
+                .map(|channel_and_id| channel_and_id.decode())
+                .collect(),
+        }
+    }
+}
+
+/// Generates a VersionedStoreConfig genesis config
+/// with pre-populated classes and entities parsed from a json file serialized
+/// as a ContentData struct.
+pub fn versioned_store_config_from_json(data_file: &Path) -> VersionedStoreConfig {
+    let content = parse_content_data(data_file).decode();
+    let base_config = empty_versioned_store_config();
+    let first_id = 1;
+
+    let next_class_id: ClassId = content
+        .classes
+        .last()
+        .map_or(first_id, |class_and_perm| class_and_perm.class.id + 1);
+    assert_eq!(next_class_id, (content.classes.len() + 1) as ClassId);
+
+    let next_entity_id: EntityId = content
+        .entities
+        .last()
+        .map_or(first_id, |entity_and_maintainer| {
+            entity_and_maintainer.entity.id + 1
+        });
+
+    VersionedStoreConfig {
+        class_by_id: content
+            .classes
+            .into_iter()
+            .map(|class_and_permissions| {
+                (class_and_permissions.class.id, class_and_permissions.class)
+            })
+            .collect(),
+        entity_by_id: content
+            .entities
+            .into_iter()
+            .map(|entity_and_maintainer| {
+                (
+                    entity_and_maintainer.entity.id,
+                    entity_and_maintainer.entity,
+                )
+            })
+            .collect(),
+        next_class_id,
+        next_entity_id,
+        ..base_config
+    }
+}
+
+/// Generates basic empty VersionedStoreConfig genesis config
+pub fn empty_versioned_store_config() -> VersionedStoreConfig {
+    VersionedStoreConfig {
+        class_by_id: vec![],
+        entity_by_id: vec![],
+        next_class_id: 1,
+        next_entity_id: 1,
+        property_name_constraint: InputValidationLengthConstraint::new(1, 99),
+        property_description_constraint: InputValidationLengthConstraint::new(1, 999),
+        class_name_constraint: InputValidationLengthConstraint::new(1, 99),
+        class_description_constraint: InputValidationLengthConstraint::new(1, 999),
+    }
+}
+
+/// Generates a basic empty VersionedStorePermissionsConfig genesis config
+pub fn empty_versioned_store_permissions_config() -> VersionedStorePermissionsConfig {
+    VersionedStorePermissionsConfig {
+        class_permissions_by_class_id: vec![],
+        entity_maintainer_by_entity_id: vec![],
+    }
+}
+
+/// Generates a `VersionedStorePermissionsConfig` genesis config
+/// pre-populated with permissions and entity maintainers parsed from
+/// a json file serialized as a `ContentData` struct.
+pub fn versioned_store_permissions_config_from_json(
+    data_file: &Path,
+) -> VersionedStorePermissionsConfig {
+    let content = parse_content_data(data_file).decode();
+
+    VersionedStorePermissionsConfig {
+        class_permissions_by_class_id: content
+            .classes
+            .into_iter()
+            .map(|class_and_perm| (class_and_perm.class.id, class_and_perm.permissions))
+            .collect(),
+        entity_maintainer_by_entity_id: content
+            .entities
+            .into_iter()
+            .filter_map(|entity_and_maintainer| {
+                entity_and_maintainer
+                    .maintainer
+                    .map(|maintainer| (entity_and_maintainer.entity.id, maintainer))
+            })
+            .collect(),
+    }
+}
+
+/// Generates a basic empty `DataDirectoryConfig` genesis config
+pub fn empty_data_directory_config() -> DataDirectoryConfig {
+    DataDirectoryConfig {
+        data_object_by_content_id: vec![],
+        known_content_ids: vec![],
+    }
+}
+
+/// Generates a `DataDirectoryConfig` genesis config
+/// pre-populated with data objects and known content ids parsed from
+/// a json file serialized as a `ContentData` struct
+pub fn data_directory_config_from_json(data_file: &Path) -> DataDirectoryConfig {
+    let content = parse_content_data(data_file).decode();
+
+    DataDirectoryConfig {
+        data_object_by_content_id: content
+            .data_objects
+            .iter()
+            .map(|object| (object.content_id, object.data_object.clone()))
+            .collect(),
+        known_content_ids: content
+            .data_objects
+            .into_iter()
+            .map(|object| object.content_id)
+            .collect(),
+    }
+}
+
+/// Generates a basic `ContentWorkingGroupConfig` genesis config without any active curators
+/// curator lead or channels.
+pub fn empty_content_working_group_config() -> ContentWorkingGroupConfig {
+    ContentWorkingGroupConfig {
+        mint_capacity: 0,
+        curator_opening_by_id: vec![],
+        next_curator_opening_id: 0,
+        curator_application_by_id: vec![],
+        next_curator_application_id: 0,
+        channel_by_id: vec![],
+        next_channel_id: 1,
+        channel_id_by_handle: vec![],
+        curator_by_id: vec![],
+        next_curator_id: 0,
+        principal_by_id: vec![],
+        next_principal_id: 0,
+        channel_creation_enabled: true, // there is no extrinsic to change it so enabling at genesis
+        unstaker_by_stake_id: vec![],
+        channel_handle_constraint: InputValidationLengthConstraint::new(5, 20),
+        channel_description_constraint: InputValidationLengthConstraint::new(1, 1024),
+        opening_human_readable_text: InputValidationLengthConstraint::new(1, 2048),
+        curator_application_human_readable_text: InputValidationLengthConstraint::new(1, 2048),
+        curator_exit_rationale_text: InputValidationLengthConstraint::new(1, 2048),
+        channel_avatar_constraint: InputValidationLengthConstraint::new(5, 1024),
+        channel_banner_constraint: InputValidationLengthConstraint::new(5, 1024),
+        channel_title_constraint: InputValidationLengthConstraint::new(5, 1024),
+    }
+}
+
+/// Generates a `ContentWorkingGroupConfig` genesis config
+/// pre-populated with channels and corresponding princial channel owners
+/// parsed from a json file serialized as a `ContentData` struct
+pub fn content_working_group_config_from_json(data_file: &Path) -> ContentWorkingGroupConfig {
+    let content = parse_content_data(data_file).decode();
+    let first_channel_id = 1;
+    let first_principal_id = 0;
+
+    let next_channel_id: ChannelId<Runtime> = content
+        .channels
+        .last()
+        .map_or(first_channel_id, |channel_and_id| channel_and_id.id + 1);
+    assert_eq!(
+        next_channel_id,
+        (content.channels.len() + 1) as ChannelId<Runtime>
+    );
+
+    let base_config = empty_content_working_group_config();
+
+    ContentWorkingGroupConfig {
+        channel_by_id: content
+            .channels
+            .iter()
+            .enumerate()
+            .map(|(ix, channel_and_id)| {
+                (
+                    channel_and_id.id,
+                    Channel {
+                        principal_id: first_principal_id + ix as PrincipalId<Runtime>,
+                        ..channel_and_id.channel.clone()
+                    },
+                )
+            })
+            .collect(),
+        next_channel_id,
+        channel_id_by_handle: content
+            .channels
+            .iter()
+            .map(|channel_and_id| (channel_and_id.channel.handle.clone(), channel_and_id.id))
+            .collect(),
+        principal_by_id: content
+            .channels
+            .iter()
+            .enumerate()
+            .map(|(ix, channel_and_id)| {
+                (
+                    first_principal_id + ix as PrincipalId<Runtime>,
+                    Principal::ChannelOwner(channel_and_id.id),
+                )
+            })
+            .collect(),
+        next_principal_id: first_principal_id + content.channels.len() as PrincipalId<Runtime>,
+        ..base_config
+    }
+}

+ 149 - 0
node/src/chain_spec/forum_config.rs

@@ -0,0 +1,149 @@
+use codec::Decode;
+use node_runtime::{
+    common::constraints::InputValidationLengthConstraint,
+    forum::{Category, CategoryId, Post, Thread},
+    AccountId, BlockNumber, ForumConfig, Moment, PostId, ThreadId,
+};
+use serde::Deserialize;
+use std::{fs, path::Path};
+
+fn new_validation(min: u16, max_min_diff: u16) -> InputValidationLengthConstraint {
+    InputValidationLengthConstraint { min, max_min_diff }
+}
+
+#[derive(Decode)]
+struct ForumData {
+    categories: Vec<Category<BlockNumber, Moment, AccountId>>,
+    posts: Vec<Post<BlockNumber, Moment, AccountId, ThreadId, PostId>>,
+    threads: Vec<Thread<BlockNumber, Moment, AccountId, ThreadId>>,
+}
+
+#[derive(Deserialize)]
+struct EncodedForumData {
+    /// hex encoded categories
+    categories: Vec<String>,
+    /// hex encoded posts
+    posts: Vec<String>,
+    /// hex encoded threads
+    threads: Vec<String>,
+}
+
+impl EncodedForumData {
+    fn decode(&self) -> ForumData {
+        ForumData {
+            categories: self
+                .categories
+                .iter()
+                .map(|category| {
+                    let encoded_category = hex::decode(&category[2..].as_bytes())
+                        .expect("failed to parse category hex string");
+                    Decode::decode(&mut encoded_category.as_slice()).unwrap()
+                })
+                .collect(),
+            posts: self
+                .posts
+                .iter()
+                .map(|post| {
+                    let encoded_post = hex::decode(&post[2..].as_bytes())
+                        .expect("failed to parse post hex string");
+                    Decode::decode(&mut encoded_post.as_slice()).unwrap()
+                })
+                .collect(),
+            threads: self
+                .threads
+                .iter()
+                .map(|thread| {
+                    let encoded_thread = hex::decode(&thread[2..].as_bytes())
+                        .expect("failed to parse thread hex string");
+                    Decode::decode(&mut encoded_thread.as_slice()).unwrap()
+                })
+                .collect(),
+        }
+    }
+}
+
+fn parse_forum_json(data_file: &Path) -> EncodedForumData {
+    let data = fs::read_to_string(data_file).expect("Failed reading file");
+    serde_json::from_str(&data).expect("failed parsing members data")
+}
+
+/// Generates a `ForumConfig` geneis config pre-populated with
+/// categories, threads and posts parsed
+/// from a json file serialized as `EncodedForumData`
+pub fn from_json(forum_sudo: AccountId, data_file: &Path) -> ForumConfig {
+    let forum_data = parse_forum_json(data_file);
+    create(forum_sudo, forum_data)
+}
+
+/// Generates a basic empty `ForumConfig` geneis config
+pub fn empty(forum_sudo: AccountId) -> ForumConfig {
+    let forum_data = EncodedForumData {
+        categories: vec![],
+        threads: vec![],
+        posts: vec![],
+    };
+    create(forum_sudo, forum_data)
+}
+
+fn create(forum_sudo: AccountId, forum_data: EncodedForumData) -> ForumConfig {
+    let first_id = 1;
+    let forum_data = forum_data.decode();
+
+    let next_category_id: CategoryId = forum_data
+        .categories
+        .last()
+        .map_or(first_id, |category| category.id + 1);
+
+    assert_eq!(
+        next_category_id,
+        (forum_data.categories.len() + 1) as CategoryId
+    );
+
+    let next_thread_id: ThreadId = forum_data
+        .threads
+        .last()
+        .map_or(first_id, |thread| thread.id + 1);
+
+    assert_eq!(next_thread_id, (forum_data.threads.len() + 1) as ThreadId);
+
+    let next_post_id: PostId = forum_data.posts.last().map_or(first_id, |post| post.id + 1);
+
+    assert_eq!(next_post_id, (forum_data.posts.len() + 1) as PostId);
+
+    ForumConfig {
+        category_by_id: forum_data
+            .categories
+            .into_iter()
+            .map(|encoded_category| {
+                let category = encoded_category;
+                (category.id, category)
+            })
+            .collect(),
+        thread_by_id: forum_data
+            .threads
+            .into_iter()
+            .map(|encoded_thread| {
+                let thread = encoded_thread;
+                (thread.id, thread)
+            })
+            .collect(),
+        post_by_id: forum_data
+            .posts
+            .into_iter()
+            .map(|encoded_post| {
+                let post = encoded_post;
+                (post.id, post)
+            })
+            .collect(),
+        next_category_id,
+        next_thread_id,
+        next_post_id,
+        forum_sudo,
+        category_title_constraint: new_validation(10, 90),
+        category_description_constraint: new_validation(10, 490),
+        thread_title_constraint: new_validation(10, 90),
+        post_text_constraint: new_validation(10, 2990),
+        thread_moderation_rationale_constraint: new_validation(10, 290),
+        post_moderation_rationale_constraint: new_validation(10, 290),
+    }
+}

+ 18 - 0
node/src/chain_spec/initial_balances.rs

@@ -0,0 +1,18 @@
+use node_runtime::{AccountId, Balance};
+use serde::Deserialize;
+use std::{fs, path::Path};
+
+#[derive(Deserialize)]
+struct SerializedInitialBalances {
+    balances: Vec<(AccountId, Balance)>,
+}
+
+fn parse_json(data_file: &Path) -> SerializedInitialBalances {
+    let data = fs::read_to_string(data_file).expect("Failed reading file");
+    serde_json::from_str(&data).expect("failed parsing balances data")
+}
+
+/// Deserializes initial balances from json file
+pub fn from_json(data_file: &Path) -> Vec<(AccountId, Balance)> {
+    parse_json(data_file).balances
+}

+ 13 - 0
node/src/chain_spec/initial_members.rs

@@ -0,0 +1,13 @@
+use node_runtime::{membership, AccountId, Moment};
+use std::{fs, path::Path};
+
+/// Generates a Vec of genesis members parsed from a json file
+pub fn from_json(data_file: &Path) -> Vec<membership::genesis::Member<u64, AccountId, Moment>> {
+    let data = fs::read_to_string(data_file).expect("Failed reading file");
+    serde_json::from_str(&data).expect("failed parsing members data")
+}
+
+/// Generates an empty Vec of genesis members
+pub fn none() -> Vec<membership::genesis::Member<u64, AccountId, Moment>> {
+    vec![]
+}

+ 140 - 132
node/src/chain_spec.rs → node/src/chain_spec/mod.rs

@@ -29,23 +29,29 @@ use sp_runtime::traits::{IdentifyAccount, Verify};
 use sp_runtime::Perbill;
 use sp_runtime::Perbill;
 
 
 use node_runtime::{
 use node_runtime::{
-    versioned_store::InputValidationLengthConstraint as VsInputValidation,
-    AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig, ContentDirectoryConfig,
-    ContentDirectoryWorkingGroupConfig, ContentWorkingGroupConfig, CouncilConfig,
-    CouncilElectionConfig, DataObjectStorageRegistryConfig, DataObjectTypeRegistryConfig,
-    ElectionParameters, GrandpaConfig, ImOnlineConfig, MembersConfig, ProposalsCodexConfig,
-    SessionConfig, SessionKeys, Signature, StakerStatus, StakingConfig, StorageWorkingGroupConfig,
-    SudoConfig, SystemConfig, VersionedStoreConfig, DAYS, WASM_BINARY,
+    membership, AuthorityDiscoveryConfig, BabeConfig, Balance, BalancesConfig,
+    ContentDirectoryConfig, ContentDirectoryWorkingGroupConfig, ContentWorkingGroupConfig,
+    CouncilConfig, CouncilElectionConfig, DataDirectoryConfig, DataObjectStorageRegistryConfig,
+    DataObjectTypeRegistryConfig, ElectionParameters, ForumConfig, GrandpaConfig, ImOnlineConfig,
+    MembersConfig, Moment, ProposalsCodexConfig, SessionConfig, SessionKeys, Signature,
+    StakerStatus, StakingConfig, StorageWorkingGroupConfig, SudoConfig, SystemConfig,
+    VersionedStoreConfig, VersionedStorePermissionsConfig, DAYS, WASM_BINARY,
 };
 };
 
 
+// Exported to be used by chain-spec-builder
 pub use node_runtime::{AccountId, GenesisConfig};
 pub use node_runtime::{AccountId, GenesisConfig};
 
 
+pub mod content_config;
+pub mod forum_config;
+pub mod initial_balances;
+pub mod initial_members;
+pub mod proposals_config;
+
 type AccountPublic = <Signature as Verify>::Signer;
 type AccountPublic = <Signature as Verify>::Signer;
 
 
 /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
 /// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
 pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
 pub type ChainSpec = sc_service::GenericChainSpec<GenesisConfig>;
 
 
-use node_runtime::common::constraints::InputValidationLengthConstraint;
 use sc_chain_spec::ChainType;
 use sc_chain_spec::ChainType;
 
 
 /// The chain specification option. This is expected to come in from the CLI and
 /// The chain specification option. This is expected to come in from the CLI and
@@ -127,7 +133,14 @@ impl Alternative {
                             get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Alice//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Bob//stash"),
                         ],
                         ],
-                        crate::proposals_config::development(),
+                        proposals_config::development(),
+                        initial_members::none(),
+                        forum_config::empty(get_account_id_from_seed::<sr25519::Public>("Alice")),
+                        content_config::empty_versioned_store_config(),
+                        content_config::empty_versioned_store_permissions_config(),
+                        content_config::empty_data_directory_config(),
+                        content_config::empty_content_working_group_config(),
+                        vec![],
                     )
                     )
                 },
                 },
                 Vec::new(),
                 Vec::new(),
@@ -161,7 +174,14 @@ impl Alternative {
                             get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Eve//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
                             get_account_id_from_seed::<sr25519::Public>("Ferdie//stash"),
                         ],
                         ],
-                        crate::proposals_config::development(),
+                        proposals_config::development(),
+                        initial_members::none(),
+                        forum_config::empty(get_account_id_from_seed::<sr25519::Public>("Alice")),
+                        content_config::empty_versioned_store_config(),
+                        content_config::empty_versioned_store_permissions_config(),
+                        content_config::empty_data_directory_config(),
+                        content_config::empty_content_working_group_config(),
+                        vec![],
                     )
                     )
                 },
                 },
                 Vec::new(),
                 Vec::new(),
@@ -174,10 +194,6 @@ impl Alternative {
     }
     }
 }
 }
 
 
-fn new_vs_validation(min: u16, max_min_diff: u16) -> VsInputValidation {
-    VsInputValidation { min, max_min_diff }
-}
-
 pub fn chain_spec_properties() -> json::map::Map<String, json::Value> {
 pub fn chain_spec_properties() -> json::map::Map<String, json::Value> {
     let mut properties: json::map::Map<String, json::Value> = json::map::Map::new();
     let mut properties: json::map::Map<String, json::Value> = json::map::Map::new();
     properties.insert(
     properties.insert(
@@ -190,7 +206,9 @@ pub fn chain_spec_properties() -> json::map::Map<String, json::Value> {
     );
     );
     properties
     properties
 }
 }
-
+// This method should be refactored after Alexandria to reduce number of arguments
+// as more args will likely be needed
+#[allow(clippy::too_many_arguments)]
 pub fn testnet_genesis(
 pub fn testnet_genesis(
     initial_authorities: Vec<(
     initial_authorities: Vec<(
         AccountId,
         AccountId,
@@ -203,11 +221,16 @@ pub fn testnet_genesis(
     root_key: AccountId,
     root_key: AccountId,
     endowed_accounts: Vec<AccountId>,
     endowed_accounts: Vec<AccountId>,
     cpcp: node_runtime::ProposalsConfigParameters,
     cpcp: node_runtime::ProposalsConfigParameters,
+    members: Vec<membership::genesis::Member<u64, AccountId, Moment>>,
+    forum_config: ForumConfig,
+    versioned_store_config: VersionedStoreConfig,
+    versioned_store_permissions_config: VersionedStorePermissionsConfig,
+    data_directory_config: DataDirectoryConfig,
+    content_working_group_config: ContentWorkingGroupConfig,
+    initial_balances: Vec<(AccountId, Balance)>,
 ) -> GenesisConfig {
 ) -> GenesisConfig {
-    const CENTS: Balance = 1;
-    const DOLLARS: Balance = 100 * CENTS;
-    const STASH: Balance = 20 * DOLLARS;
-    const ENDOWMENT: Balance = 100_000 * DOLLARS;
+    const STASH: Balance = 5_000;
+    const ENDOWMENT: Balance = 100_000_000;
 
 
     let default_text_constraint = node_runtime::working_group::default_text_constraint();
     let default_text_constraint = node_runtime::working_group::default_text_constraint();
 
 
@@ -222,22 +245,26 @@ pub fn testnet_genesis(
                 .cloned()
                 .cloned()
                 .map(|k| (k, ENDOWMENT))
                 .map(|k| (k, ENDOWMENT))
                 .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
                 .chain(initial_authorities.iter().map(|x| (x.0.clone(), STASH)))
+                .chain(
+                    initial_balances
+                        .iter()
+                        .map(|(account, balance)| (account.clone(), *balance)),
+                )
                 .collect(),
                 .collect(),
         }),
         }),
         pallet_staking: Some(StakingConfig {
         pallet_staking: Some(StakingConfig {
             validator_count: 20,
             validator_count: 20,
-            minimum_validator_count: 1,
+            minimum_validator_count: initial_authorities.len() as u32,
             stakers: initial_authorities
             stakers: initial_authorities
                 .iter()
                 .iter()
                 .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator))
                 .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator))
                 .collect(),
                 .collect(),
             invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),
             invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(),
             slash_reward_fraction: Perbill::from_percent(10),
             slash_reward_fraction: Perbill::from_percent(10),
+            history_depth: 336,
             ..Default::default()
             ..Default::default()
         }),
         }),
-        pallet_sudo: Some(SudoConfig {
-            key: root_key.clone(),
-        }),
+        pallet_sudo: Some(SudoConfig { key: root_key }),
         pallet_babe: Some(BabeConfig {
         pallet_babe: Some(BabeConfig {
             authorities: vec![],
             authorities: vec![],
         }),
         }),
@@ -265,21 +292,22 @@ pub fn testnet_genesis(
         election: Some(CouncilElectionConfig {
         election: Some(CouncilElectionConfig {
             auto_start: true,
             auto_start: true,
             election_parameters: ElectionParameters {
             election_parameters: ElectionParameters {
-                announcing_period: 3 * DAYS,
+                announcing_period: 2 * DAYS,
                 voting_period: 1 * DAYS,
                 voting_period: 1 * DAYS,
                 revealing_period: 1 * DAYS,
                 revealing_period: 1 * DAYS,
-                council_size: 12,
+                council_size: 6,
                 candidacy_limit: 25,
                 candidacy_limit: 25,
-                min_council_stake: 10 * DOLLARS,
-                new_term_duration: 14 * DAYS,
-                min_voting_stake: 1 * DOLLARS,
+                min_council_stake: 1_000,
+                new_term_duration: 10 * DAYS,
+                min_voting_stake: 100,
             },
             },
         }),
         }),
         membership: Some(MembersConfig {
         membership: Some(MembersConfig {
             default_paid_membership_fee: 100u128,
             default_paid_membership_fee: 100u128,
-            members: vec![],
+            members,
         }),
         }),
-        forum: Some(crate::forum_config::from_serialized::create(root_key)),
+        forum: Some(forum_config),
+        data_directory: Some(data_directory_config),
         data_object_type_registry: Some(DataObjectTypeRegistryConfig {
         data_object_type_registry: Some(DataObjectTypeRegistryConfig {
             first_data_object_type_id: 1,
             first_data_object_type_id: 1,
         }),
         }),
@@ -300,40 +328,6 @@ pub fn testnet_genesis(
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_application_human_readable_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
             worker_exit_rationale_text_constraint: default_text_constraint,
         }),
         }),
-        versioned_store: Some(VersionedStoreConfig {
-            class_by_id: vec![],
-            entity_by_id: vec![],
-            next_class_id: 1,
-            next_entity_id: 1,
-            property_name_constraint: new_vs_validation(1, 99),
-            property_description_constraint: new_vs_validation(1, 999),
-            class_name_constraint: new_vs_validation(1, 99),
-            class_description_constraint: new_vs_validation(1, 999),
-        }),
-        content_wg: Some(ContentWorkingGroupConfig {
-            mint_capacity: 100_000,
-            curator_opening_by_id: vec![],
-            next_curator_opening_id: 0,
-            curator_application_by_id: vec![],
-            next_curator_application_id: 0,
-            channel_by_id: vec![],
-            next_channel_id: 1,
-            channel_id_by_handle: vec![],
-            curator_by_id: vec![],
-            next_curator_id: 0,
-            principal_by_id: vec![],
-            next_principal_id: 0,
-            channel_creation_enabled: true, // there is no extrinsic to change it so enabling at genesis
-            unstaker_by_stake_id: vec![],
-            channel_handle_constraint: InputValidationLengthConstraint::new(5, 20),
-            channel_description_constraint: InputValidationLengthConstraint::new(1, 1024),
-            opening_human_readable_text: InputValidationLengthConstraint::new(1, 2048),
-            curator_application_human_readable_text: InputValidationLengthConstraint::new(1, 2048),
-            curator_exit_rationale_text: InputValidationLengthConstraint::new(1, 2048),
-            channel_avatar_constraint: InputValidationLengthConstraint::new(5, 1024),
-            channel_banner_constraint: InputValidationLengthConstraint::new(5, 1024),
-            channel_title_constraint: InputValidationLengthConstraint::new(5, 1024),
-        }),
         content_directory: Some({
         content_directory: Some({
             ContentDirectoryConfig {
             ContentDirectoryConfig {
                 curator_group_by_id: vec![],
                 curator_group_by_id: vec![],
@@ -342,6 +336,9 @@ pub fn testnet_genesis(
                 next_curator_group_id: 1,
                 next_curator_group_id: 1,
             }
             }
         }),
         }),
+        versioned_store: Some(versioned_store_config),
+        versioned_store_permissions: Some(versioned_store_permissions_config),
+        content_wg: Some(content_working_group_config),
         proposals_codex: Some(ProposalsCodexConfig {
         proposals_codex: Some(ProposalsCodexConfig {
             set_validator_count_proposal_voting_period: cpcp
             set_validator_count_proposal_voting_period: cpcp
                 .set_validator_count_proposal_voting_period,
                 .set_validator_count_proposal_voting_period,
@@ -399,76 +396,87 @@ pub fn testnet_genesis(
     }
     }
 }
 }
 
 
-// Tests are commented out until we find a solution to why
-// building dependencies for the tests are taking so long on Travis CI
+#[cfg(test)]
+pub(crate) mod tests {
+    use super::*;
+    use crate::service::{new_full, new_light};
+    use sc_service_test;
 
 
-// #[cfg(test)]
-// pub(crate) mod tests {
-//     use super::*;
-//     use crate::service::{new_full, new_light};
-//     use sc_service_test;
-
-//     fn local_testnet_genesis_instant_single() -> GenesisConfig {
-//         testnet_genesis(
-//             vec![get_authority_keys_from_seed("Alice")],
-//             get_account_id_from_seed::<sr25519::Public>("Alice"),
-//             vec![get_authority_keys_from_seed("Alice").0],
-//             crate::proposals_config::development(),
-//         )
-//     }
+    fn local_testnet_genesis_instant_single() -> GenesisConfig {
+        testnet_genesis(
+            vec![get_authority_keys_from_seed("Alice")],
+            get_account_id_from_seed::<sr25519::Public>("Alice"),
+            vec![get_authority_keys_from_seed("Alice").0],
+            proposals_config::development(),
+            initial_members::none(),
+            forum_config::empty(get_account_id_from_seed::<sr25519::Public>("Alice")),
+            content_config::empty_versioned_store_config(),
+            content_config::empty_versioned_store_permissions_config(),
+            content_config::empty_data_directory_config(),
+            content_config::empty_content_working_group_config(),
+            vec![],
+        )
+    }
 
 
-//     /// Local testnet config (single validator - Alice)
-//     pub fn integration_test_config_with_single_authority() -> ChainSpec {
-//         ChainSpec::from_genesis(
-//             "Integration Test",
-//             "test",
-//             ChainType::Development,
-//             local_testnet_genesis_instant_single,
-//             vec![],
-//             None,
-//             None,
-//             None,
-//             Default::default(),
-//         )
-//     }
+    /// Local testnet config (single validator - Alice)
+    pub fn integration_test_config_with_single_authority() -> ChainSpec {
+        ChainSpec::from_genesis(
+            "Integration Test",
+            "test",
+            ChainType::Development,
+            local_testnet_genesis_instant_single,
+            vec![],
+            None,
+            None,
+            None,
+            Default::default(),
+        )
+    }
 
 
-//     fn local_testnet_genesis() -> GenesisConfig {
-//         testnet_genesis(
-//             vec![
-//                 get_authority_keys_from_seed("Alice"),
-//                 get_authority_keys_from_seed("Bob"),
-//             ],
-//             get_account_id_from_seed::<sr25519::Public>("Alice"),
-//             vec![
-//                 get_authority_keys_from_seed("Alice").0,
-//                 get_authority_keys_from_seed("Bob").0,
-//             ],
-//             crate::proposals_config::development(),
-//         )
-//     }
+    fn local_testnet_genesis() -> GenesisConfig {
+        testnet_genesis(
+            vec![
+                get_authority_keys_from_seed("Alice"),
+                get_authority_keys_from_seed("Bob"),
+            ],
+            get_account_id_from_seed::<sr25519::Public>("Alice"),
+            vec![
+                get_authority_keys_from_seed("Alice").0,
+                get_authority_keys_from_seed("Bob").0,
+            ],
+            proposals_config::development(),
+            initial_members::none(),
+            forum_config::empty(get_account_id_from_seed::<sr25519::Public>("Alice")),
+            content_config::empty_versioned_store_config(),
+            content_config::empty_versioned_store_permissions_config(),
+            content_config::empty_data_directory_config(),
+            content_config::empty_content_working_group_config(),
+            vec![],
+        )
+    }
 
 
-//     /// Local testnet config (multivalidator Alice + Bob)
-//     pub fn integration_test_config_with_two_authorities() -> ChainSpec {
-//         ChainSpec::from_genesis(
-//             "Integration Test",
-//             "test",
-//             ChainType::Development,
-//             local_testnet_genesis,
-//             vec![],
-//             None,
-//             None,
-//             None,
-//             Default::default(),
-//         )
-//     }
+    /// Local testnet config (multivalidator Alice + Bob)
+    pub fn integration_test_config_with_two_authorities() -> ChainSpec {
+        ChainSpec::from_genesis(
+            "Integration Test",
+            "test",
+            ChainType::Development,
+            local_testnet_genesis,
+            vec![],
+            None,
+            None,
+            None,
+            Default::default(),
+        )
+    }
 
 
-//     #[test]
-//     #[ignore]
-//     fn test_connectivity() {
-//         sc_service_test::connectivity(
-//             integration_test_config_with_two_authorities(),
-//             |config| new_full(config),
-//             |config| new_light(config),
-//         );
-//     }
-// }
+    #[test]
+    #[ignore]
+    fn test_connectivity() {
+        sc_service_test::connectivity(
+            integration_test_config_with_two_authorities(),
+            |config| new_full(config),
+            |config| new_light(config),
+        );
+    }
+}

+ 2 - 2
node/src/proposals_config.rs → node/src/chain_spec/proposals_config.rs

@@ -8,10 +8,10 @@ pub fn development() -> ProposalsConfigParameters {
 
 
 /// Staging chain config. Shorter grace periods and voting periods than default.
 /// Staging chain config. Shorter grace periods and voting periods than default.
 pub fn staging() -> ProposalsConfigParameters {
 pub fn staging() -> ProposalsConfigParameters {
-    ProposalsConfigParameters::with_grace_and_voting_periods(200, 600)
+    ProposalsConfigParameters::with_grace_and_voting_periods(20, 30)
 }
 }
 
 
 /// The default configuration as defined in the runtime module
 /// The default configuration as defined in the runtime module
-pub fn default() -> ProposalsConfigParameters {
+pub fn production() -> ProposalsConfigParameters {
     ProposalsConfigParameters::default()
     ProposalsConfigParameters::default()
 }
 }

+ 0 - 90
node/src/forum_config/from_encoded.rs

@@ -1,90 +0,0 @@
-// This module is not used but included as sample code
-// and highlights some pitfalls.
-
-use node_runtime::{
-    forum::{
-        Category, CategoryId, Post, PostId, Thread, ThreadId,
-    },
-    AccountId, BlockNumber, ForumConfig, Moment,
-};
-use serde::Deserialize;
-use serde_json::Result;
-use super::new_validation;
-
-use codec::Decode;
-
-#[derive(Deserialize)]
-struct ForumData {
-    /// hex encoded categories
-    categories: Vec<(CategoryId, String)>,
-    /// hex encoded posts
-    posts: Vec<(PostId, String)>,
-    /// hex encoded threads
-    threads: Vec<(ThreadId, String)>,
-}
-
-fn decode_post(encoded: String) -> Post<BlockNumber, Moment, AccountId> {
-    // hex string must not include '0x' prefix!
-    let encoded = hex::decode(encoded.as_bytes()).expect("failed to parse hex string");
-    Decode::decode(&mut encoded.as_slice()).unwrap()
-}
-
-fn decode_category(encoded: String) -> Category<BlockNumber, Moment, AccountId> {
-    // hex string must not include '0x' prefix!
-    let encoded = hex::decode(encoded.as_bytes()).expect("failed to parse hex string");
-    Decode::decode(&mut encoded.as_slice()).unwrap()
-}
-
-fn decode_thread(encoded: String) -> Thread<BlockNumber, Moment, AccountId> {
-    // hex string must not include '0x' prefix!
-    let encoded = hex::decode(encoded.as_bytes()).expect("failed to parse hex string");
-    Decode::decode(&mut encoded.as_slice()).unwrap()
-}
-
-fn parse_forum_json() -> Result<ForumData> {
-    let data = include_str!("../../res/forum_data_acropolis_encoded.json");
-    serde_json::from_str(data)
-}
-
-pub fn create(forum_sudo: AccountId) -> ForumConfig {
-    let forum_data = parse_forum_json().expect("failed loading forum data");
-
-    let next_category_id: CategoryId = forum_data
-        .categories
-        .last()
-        .map_or(1, |category| category.0 + 1);
-    let next_thread_id: ThreadId = forum_data.threads.last().map_or(1, |thread| thread.0 + 1);
-    let next_post_id: PostId = forum_data.posts.last().map_or(1, |post| post.0 + 1);
-
-    ForumConfig {
-        // Decoding will fail because of differnt type used for
-        // BlockNumber between Acropolis (u64) and Rome (u32)
-        // As long as types between chains are identical this approach works nicely
-        // since we don't need to use an intermediate format or do any transformation on source data.
-        category_by_id: forum_data
-            .categories
-            .into_iter()
-            .map(|category| (category.0, decode_category(category.1)))
-            .collect(),
-        thread_by_id: forum_data
-            .threads
-            .into_iter()
-            .map(|thread| (thread.0, decode_thread(thread.1)))
-            .collect(),
-        post_by_id: forum_data
-            .posts
-            .into_iter()
-            .map(|post| (post.0, decode_post(post.1)))
-            .collect(),
-        next_category_id,
-        next_thread_id,
-        next_post_id,
-        forum_sudo,
-        category_title_constraint: new_validation(10, 90),
-        category_description_constraint: new_validation(10, 490),
-        thread_title_constraint: new_validation(10, 90),
-        post_text_constraint: new_validation(10, 990),
-        thread_moderation_rationale_constraint: new_validation(10, 290),
-        post_moderation_rationale_constraint: new_validation(10, 290),
-    }
-}

+ 0 - 51
node/src/forum_config/from_serialized.rs

@@ -1,51 +0,0 @@
-#![allow(clippy::type_complexity)]
-
-use super::new_validation;
-use node_runtime::{
-    forum::{Category, CategoryId, Post, Thread},
-    AccountId, BlockNumber, ForumConfig, Moment, PostId, ThreadId,
-};
-use serde::Deserialize;
-use serde_json::Result;
-
-#[derive(Deserialize)]
-struct ForumData {
-    categories: Vec<(CategoryId, Category<BlockNumber, Moment, AccountId>)>,
-    posts: Vec<(
-        PostId,
-        Post<BlockNumber, Moment, AccountId, ThreadId, PostId>,
-    )>,
-    threads: Vec<(ThreadId, Thread<BlockNumber, Moment, AccountId, ThreadId>)>,
-}
-
-fn parse_forum_json() -> Result<ForumData> {
-    let data = include_str!("../../res/forum_data_empty.json");
-    serde_json::from_str(data)
-}
-
-pub fn create(forum_sudo: AccountId) -> ForumConfig {
-    let forum_data = parse_forum_json().expect("failed loading forum data");
-
-    let next_category_id: CategoryId = forum_data
-        .categories
-        .last()
-        .map_or(1, |category| category.0 + 1);
-    let next_thread_id: ThreadId = forum_data.threads.last().map_or(1, |thread| thread.0 + 1);
-    let next_post_id: PostId = forum_data.posts.last().map_or(1, |post| post.0 + 1);
-
-    ForumConfig {
-        category_by_id: forum_data.categories,
-        thread_by_id: forum_data.threads,
-        post_by_id: forum_data.posts,
-        next_category_id,
-        next_thread_id,
-        next_post_id,
-        category_title_constraint: new_validation(10, 90),
-        category_description_constraint: new_validation(10, 490),
-        thread_title_constraint: new_validation(10, 90),
-        post_text_constraint: new_validation(10, 990),
-        thread_moderation_rationale_constraint: new_validation(10, 290),
-        post_moderation_rationale_constraint: new_validation(10, 290),
-        forum_sudo,
-    }
-}

+ 0 - 10
node/src/forum_config/mod.rs

@@ -1,10 +0,0 @@
-pub mod from_serialized;
-
-// Not exported - only here as sample code
-// mod from_encoded;
-
-use node_runtime::common::constraints::InputValidationLengthConstraint;
-
-pub fn new_validation(min: u16, max_min_diff: u16) -> InputValidationLengthConstraint {
-    InputValidationLengthConstraint { min, max_min_diff }
-}

+ 0 - 3
node/src/lib.rs

@@ -1,8 +1,5 @@
 pub mod chain_spec;
 pub mod chain_spec;
 pub mod cli;
 pub mod cli;
-pub mod forum_config;
-pub mod members_config;
-pub mod proposals_config;
 #[macro_use]
 #[macro_use]
 pub mod service;
 pub mod service;
 pub mod command;
 pub mod command;

+ 0 - 38
node/src/members_config.rs

@@ -1,38 +0,0 @@
-use serde::Deserialize;
-use serde_json::Result;
-
-use sp_core::crypto::{AccountId32, Ss58Codec};
-
-#[derive(Deserialize)]
-struct Member {
-    /// SS58 Encoded public key
-    address: String,
-    handle: String,
-    avatar_uri: String,
-    about: String,
-}
-
-fn parse_members_json() -> Result<Vec<Member>> {
-    let data = include_str!("../res/acropolis_members.json");
-    serde_json::from_str(data)
-}
-
-pub fn decode_address(address: String) -> AccountId32 {
-    AccountId32::from_ss58check(address.as_ref()).expect("failed to decode account id")
-}
-
-pub fn initial_members() -> Vec<(AccountId32, String, String, String)> {
-    let members = parse_members_json().expect("failed parsing members data");
-
-    members
-        .into_iter()
-        .map(|member| {
-            (
-                decode_address(member.address),
-                member.handle,
-                member.avatar_uri,
-                member.about,
-            )
-        })
-        .collect()
-}

+ 244 - 247
node/src/service.rs

@@ -415,250 +415,247 @@ pub fn new_light(config: Configuration) -> Result<impl AbstractService, ServiceE
     Ok(service)
     Ok(service)
 }
 }
 
 
-// Tests are commented out until we find a solution to why
-// building dependencies for the tests are taking so long on Travis CI
-
-// #[cfg(test)]
-// mod tests {
-//     use crate::node_executor;
-//     use crate::node_rpc;
-//     use crate::service::{new_full, new_light};
-//     use codec::{Decode, Encode};
-//     use node_runtime::RuntimeApi;
-//     use node_runtime::{currency::CENTS, SLOT_DURATION};
-//     use node_runtime::{opaque::Block, AccountId, DigestItem, Signature};
-//     use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
-//     use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY};
-//     use sc_consensus_epochs::descendent_query;
-//     use sc_finality_grandpa::{self as grandpa};
-//     use sc_service::AbstractService;
-//     use sp_consensus::{
-//         BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
-//         RecordProof,
-//     };
-//     use sp_core::{crypto::Pair as CryptoPair, H256};
-//     use sp_finality_tracker;
-//     use sp_keyring::AccountKeyring;
-//     use sp_runtime::traits::IdentifyAccount;
-//     use sp_runtime::{
-//         generic::{BlockId, Digest, Era, SignedPayload},
-//         traits::Verify,
-//         traits::{Block as BlockT, Header as HeaderT},
-//         OpaqueExtrinsic,
-//     };
-//     use sp_timestamp;
-//     use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool};
-//     use std::{any::Any, borrow::Cow, sync::Arc};
-
-//     type AccountPublic = <Signature as Verify>::Signer;
-
-//     // Long running test. Run it locally only after the node changes.
-//     #[test]
-//     // It is "ignored", but the node-cli ignored tests are running on the CI.
-//     // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`.
-//     #[ignore]
-//     fn test_sync() {
-//         let keystore_path = tempfile::tempdir().expect("Creates keystore path");
-//         let keystore =
-//             sc_keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
-//         let alice = keystore
-//             .write()
-//             .insert_ephemeral_from_seed::<sc_consensus_babe::AuthorityPair>("//Alice")
-//             .expect("Creates authority pair");
-
-//         let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
-
-//         // For the block factory
-//         let mut slot_num = 1u64;
-
-//         // For the extrinsics factory
-//         let bob = Arc::new(AccountKeyring::Bob.pair());
-//         let charlie = Arc::new(AccountKeyring::Charlie.pair());
-//         let mut index = 0;
-
-//         sc_service_test::sync(
-//             chain_spec,
-//             |config| {
-//                 let mut setup_handles = None;
-//                 new_full!(
-//                     config,
-//                     |block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
-//                      babe_link: &sc_consensus_babe::BabeLink<Block>| {
-//                         setup_handles = Some((block_import.clone(), babe_link.clone()));
-//                     }
-//                 )
-//                 .map(move |(node, x)| (node, (x, setup_handles.unwrap())))
-//             },
-//             |config| new_light(config),
-//             |service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
-//                 let mut inherent_data = inherent_data_providers
-//                     .create_inherent_data()
-//                     .expect("Creates inherent data.");
-//                 inherent_data.replace_data(sp_finality_tracker::INHERENT_IDENTIFIER, &1u64);
-
-//                 let parent_id = BlockId::number(service.client().chain_info().best_number);
-//                 let parent_header = service.client().header(&parent_id).unwrap().unwrap();
-//                 let parent_hash = parent_header.hash();
-//                 let parent_number = *parent_header.number();
-
-//                 futures::executor::block_on(service.transaction_pool().maintain(
-//                     ChainEvent::NewBlock {
-//                         is_new_best: true,
-//                         hash: parent_header.hash(),
-//                         tree_route: None,
-//                         header: parent_header.clone(),
-//                     },
-//                 ));
-
-//                 let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
-//                     service.client(),
-//                     service.transaction_pool(),
-//                     None,
-//                 );
-
-//                 let epoch_descriptor = babe_link
-//                     .epoch_changes()
-//                     .lock()
-//                     .epoch_descriptor_for_child_of(
-//                         descendent_query(&*service.client()),
-//                         &parent_hash,
-//                         parent_number,
-//                         slot_num,
-//                     )
-//                     .unwrap()
-//                     .unwrap();
-
-//                 let mut digest = Digest::<H256>::default();
-
-//                 // even though there's only one authority some slots might be empty,
-//                 // so we must keep trying the next slots until we can claim one.
-//                 let babe_pre_digest = loop {
-//                     inherent_data.replace_data(
-//                         sp_timestamp::INHERENT_IDENTIFIER,
-//                         &(slot_num * SLOT_DURATION),
-//                     );
-//                     if let Some(babe_pre_digest) = sc_consensus_babe::test_helpers::claim_slot(
-//                         slot_num,
-//                         &parent_header,
-//                         &*service.client(),
-//                         &keystore,
-//                         &babe_link,
-//                     ) {
-//                         break babe_pre_digest;
-//                     }
-
-//                     slot_num += 1;
-//                 };
-
-//                 digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(
-//                     babe_pre_digest,
-//                 ));
-
-//                 let new_block = futures::executor::block_on(async move {
-//                     let proposer = proposer_factory.init(&parent_header).await;
-//                     proposer
-//                         .unwrap()
-//                         .propose(
-//                             inherent_data,
-//                             digest,
-//                             std::time::Duration::from_secs(1),
-//                             RecordProof::Yes,
-//                         )
-//                         .await
-//                 })
-//                 .expect("Error making test block")
-//                 .block;
-
-//                 let (new_header, new_body) = new_block.deconstruct();
-//                 let pre_hash = new_header.hash();
-//                 // sign the pre-sealed hash of the block and then
-//                 // add it to a digest item.
-//                 let to_sign = pre_hash.encode();
-//                 let signature = alice.sign(&to_sign[..]);
-//                 let item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
-//                 slot_num += 1;
-
-//                 let mut params = BlockImportParams::new(BlockOrigin::File, new_header);
-//                 params.post_digests.push(item);
-//                 params.body = Some(new_body);
-//                 params.intermediates.insert(
-//                     Cow::from(INTERMEDIATE_KEY),
-//                     Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
-//                 );
-//                 params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
-
-//                 block_import
-//                     .import_block(params, Default::default())
-//                     .expect("error importing test block");
-//             },
-//             |service, _| {
-//                 let amount = 5 * CENTS;
-//                 let to: AccountId = AccountPublic::from(bob.public()).into_account().into();
-//                 let from: AccountId = AccountPublic::from(charlie.public()).into_account().into();
-//                 let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
-//                 let best_block_id = BlockId::number(service.client().chain_info().best_number);
-//                 let (spec_version, transaction_version) = {
-//                     let version = service.client().runtime_version_at(&best_block_id).unwrap();
-//                     (version.spec_version, version.transaction_version)
-//                 };
-//                 let signer = charlie.clone();
-
-//                 let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
-
-//                 let check_spec_version = frame_system::CheckSpecVersion::new();
-//                 let check_tx_version = frame_system::CheckTxVersion::new();
-//                 let check_genesis = frame_system::CheckGenesis::new();
-//                 let check_era = frame_system::CheckEra::from(Era::Immortal);
-//                 let check_nonce = frame_system::CheckNonce::from(index);
-//                 let check_weight = frame_system::CheckWeight::new();
-//                 let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0);
-//                 let validate_grandpa_equivocation =
-//                     pallet_grandpa::ValidateEquivocationReport::new();
-//                 let extra = (
-//                     check_spec_version,
-//                     check_tx_version,
-//                     check_genesis,
-//                     check_era,
-//                     check_nonce,
-//                     check_weight,
-//                     payment,
-//                     validate_grandpa_equivocation,
-//                 );
-//                 let raw_payload = SignedPayload::from_raw(
-//                     function,
-//                     extra,
-//                     (
-//                         spec_version,
-//                         transaction_version,
-//                         genesis_hash,
-//                         genesis_hash,
-//                         (),
-//                         (),
-//                         (),
-//                         (),
-//                     ),
-//                 );
-//                 let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
-//                 let (function, extra, _) = raw_payload.deconstruct();
-//                 let xt =
-//                     UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra)
-//                         .encode();
-//                 let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
-
-//                 index += 1;
-//                 OpaqueExtrinsic(v)
-//             },
-//         );
-//     }
-
-//     #[test]
-//     #[ignore]
-//     fn test_consensus() {
-//         sc_service_test::consensus(
-//             crate::chain_spec::tests::integration_test_config_with_two_authorities(),
-//             |config| new_full(config),
-//             |config| new_light(config),
-//             vec!["//Alice".into(), "//Bob".into()],
-//         )
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use crate::node_executor;
+    use crate::node_rpc;
+    use crate::service::{new_full, new_light};
+    use codec::{Decode, Encode};
+    use node_runtime::RuntimeApi;
+    use node_runtime::{currency::CENTS, SLOT_DURATION};
+    use node_runtime::{opaque::Block, AccountId, DigestItem, Signature};
+    use node_runtime::{BalancesCall, Call, UncheckedExtrinsic};
+    use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY};
+    use sc_consensus_epochs::descendent_query;
+    use sc_finality_grandpa::{self as grandpa};
+    use sc_service::AbstractService;
+    use sp_consensus::{
+        BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer,
+        RecordProof,
+    };
+    use sp_core::{crypto::Pair as CryptoPair, H256};
+    use sp_finality_tracker;
+    use sp_keyring::AccountKeyring;
+    use sp_runtime::traits::IdentifyAccount;
+    use sp_runtime::{
+        generic::{BlockId, Digest, Era, SignedPayload},
+        traits::Verify,
+        traits::{Block as BlockT, Header as HeaderT},
+        OpaqueExtrinsic,
+    };
+    use sp_timestamp;
+    use sp_transaction_pool::{ChainEvent, MaintainedTransactionPool};
+    use std::{any::Any, borrow::Cow, sync::Arc};
+
+    type AccountPublic = <Signature as Verify>::Signer;
+
+    // Long running test. Run it locally only after the node changes.
+    #[test]
+    // It is "ignored", but the node-cli ignored tests are running on the CI.
+    // This can be run locally with `cargo test --release -p node-cli test_sync -- --ignored`.
+    #[ignore]
+    fn test_sync() {
+        let keystore_path = tempfile::tempdir().expect("Creates keystore path");
+        let keystore =
+            sc_keystore::Store::open(keystore_path.path(), None).expect("Creates keystore");
+        let alice = keystore
+            .write()
+            .insert_ephemeral_from_seed::<sc_consensus_babe::AuthorityPair>("//Alice")
+            .expect("Creates authority pair");
+
+        let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority();
+
+        // For the block factory
+        let mut slot_num = 1u64;
+
+        // For the extrinsics factory
+        let bob = Arc::new(AccountKeyring::Bob.pair());
+        let charlie = Arc::new(AccountKeyring::Charlie.pair());
+        let mut index = 0;
+
+        sc_service_test::sync(
+            chain_spec,
+            |config| {
+                let mut setup_handles = None;
+                new_full!(
+                    config,
+                    |block_import: &sc_consensus_babe::BabeBlockImport<Block, _, _>,
+                     babe_link: &sc_consensus_babe::BabeLink<Block>| {
+                        setup_handles = Some((block_import.clone(), babe_link.clone()));
+                    }
+                )
+                .map(move |(node, x)| (node, (x, setup_handles.unwrap())))
+            },
+            |config| new_light(config),
+            |service, &mut (ref inherent_data_providers, (ref mut block_import, ref babe_link))| {
+                let mut inherent_data = inherent_data_providers
+                    .create_inherent_data()
+                    .expect("Creates inherent data.");
+                inherent_data.replace_data(sp_finality_tracker::INHERENT_IDENTIFIER, &1u64);
+
+                let parent_id = BlockId::number(service.client().chain_info().best_number);
+                let parent_header = service.client().header(&parent_id).unwrap().unwrap();
+                let parent_hash = parent_header.hash();
+                let parent_number = *parent_header.number();
+
+                futures::executor::block_on(service.transaction_pool().maintain(
+                    ChainEvent::NewBlock {
+                        is_new_best: true,
+                        hash: parent_header.hash(),
+                        tree_route: None,
+                        header: parent_header.clone(),
+                    },
+                ));
+
+                let mut proposer_factory = sc_basic_authorship::ProposerFactory::new(
+                    service.client(),
+                    service.transaction_pool(),
+                    None,
+                );
+
+                let epoch_descriptor = babe_link
+                    .epoch_changes()
+                    .lock()
+                    .epoch_descriptor_for_child_of(
+                        descendent_query(&*service.client()),
+                        &parent_hash,
+                        parent_number,
+                        slot_num,
+                    )
+                    .unwrap()
+                    .unwrap();
+
+                let mut digest = Digest::<H256>::default();
+
+                // even though there's only one authority some slots might be empty,
+                // so we must keep trying the next slots until we can claim one.
+                let babe_pre_digest = loop {
+                    inherent_data.replace_data(
+                        sp_timestamp::INHERENT_IDENTIFIER,
+                        &(slot_num * SLOT_DURATION),
+                    );
+                    if let Some(babe_pre_digest) = sc_consensus_babe::test_helpers::claim_slot(
+                        slot_num,
+                        &parent_header,
+                        &*service.client(),
+                        &keystore,
+                        &babe_link,
+                    ) {
+                        break babe_pre_digest;
+                    }
+
+                    slot_num += 1;
+                };
+
+                digest.push(<DigestItem as CompatibleDigestItem>::babe_pre_digest(
+                    babe_pre_digest,
+                ));
+
+                let new_block = futures::executor::block_on(async move {
+                    let proposer = proposer_factory.init(&parent_header).await;
+                    proposer
+                        .unwrap()
+                        .propose(
+                            inherent_data,
+                            digest,
+                            std::time::Duration::from_secs(1),
+                            RecordProof::Yes,
+                        )
+                        .await
+                })
+                .expect("Error making test block")
+                .block;
+
+                let (new_header, new_body) = new_block.deconstruct();
+                let pre_hash = new_header.hash();
+                // sign the pre-sealed hash of the block and then
+                // add it to a digest item.
+                let to_sign = pre_hash.encode();
+                let signature = alice.sign(&to_sign[..]);
+                let item = <DigestItem as CompatibleDigestItem>::babe_seal(signature.into());
+                slot_num += 1;
+
+                let mut params = BlockImportParams::new(BlockOrigin::File, new_header);
+                params.post_digests.push(item);
+                params.body = Some(new_body);
+                params.intermediates.insert(
+                    Cow::from(INTERMEDIATE_KEY),
+                    Box::new(BabeIntermediate::<Block> { epoch_descriptor }) as Box<dyn Any>,
+                );
+                params.fork_choice = Some(ForkChoiceStrategy::LongestChain);
+
+                block_import
+                    .import_block(params, Default::default())
+                    .expect("error importing test block");
+            },
+            |service, _| {
+                let amount = 5 * CENTS;
+                let to: AccountId = AccountPublic::from(bob.public()).into_account().into();
+                let from: AccountId = AccountPublic::from(charlie.public()).into_account().into();
+                let genesis_hash = service.client().block_hash(0).unwrap().unwrap();
+                let best_block_id = BlockId::number(service.client().chain_info().best_number);
+                let (spec_version, transaction_version) = {
+                    let version = service.client().runtime_version_at(&best_block_id).unwrap();
+                    (version.spec_version, version.transaction_version)
+                };
+                let signer = charlie.clone();
+
+                let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
+
+                let check_spec_version = frame_system::CheckSpecVersion::new();
+                let check_tx_version = frame_system::CheckTxVersion::new();
+                let check_genesis = frame_system::CheckGenesis::new();
+                let check_era = frame_system::CheckEra::from(Era::Immortal);
+                let check_nonce = frame_system::CheckNonce::from(index);
+                let check_weight = frame_system::CheckWeight::new();
+                let payment = pallet_transaction_payment::ChargeTransactionPayment::from(0);
+                let validate_grandpa_equivocation =
+                    pallet_grandpa::ValidateEquivocationReport::new();
+                let extra = (
+                    check_spec_version,
+                    check_tx_version,
+                    check_genesis,
+                    check_era,
+                    check_nonce,
+                    check_weight,
+                    payment,
+                    validate_grandpa_equivocation,
+                );
+                let raw_payload = SignedPayload::from_raw(
+                    function,
+                    extra,
+                    (
+                        spec_version,
+                        transaction_version,
+                        genesis_hash,
+                        genesis_hash,
+                        (),
+                        (),
+                        (),
+                        (),
+                    ),
+                );
+                let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
+                let (function, extra, _) = raw_payload.deconstruct();
+                let xt =
+                    UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra)
+                        .encode();
+                let v: Vec<u8> = Decode::decode(&mut xt.as_slice()).unwrap();
+
+                index += 1;
+                OpaqueExtrinsic(v)
+            },
+        );
+    }
+
+    #[test]
+    #[ignore]
+    fn test_consensus() {
+        sc_service_test::consensus(
+            crate::chain_spec::tests::integration_test_config_with_two_authorities(),
+            |config| new_full(config),
+            |config| new_light(config),
+            vec!["//Alice".into(), "//Bob".into()],
+        )
+    }
+}

+ 7 - 11
package.json

@@ -20,24 +20,20 @@
     "devops/eslint-config",
     "devops/eslint-config",
     "devops/prettier-config",
     "devops/prettier-config",
     "pioneer",
     "pioneer",
-    "pioneer/packages/apps*",
-    "pioneer/packages/page*",
-    "pioneer/packages/react*",
-    "pioneer/packages/joy-utils",
-    "pioneer/packages/joy-members",
-    "pioneer/packages/joy-pages",
+    "pioneer/packages/*",
     "utils/api-examples"
     "utils/api-examples"
   ],
   ],
   "resolutions": {
   "resolutions": {
     "@polkadot/api": "1.26.1",
     "@polkadot/api": "1.26.1",
     "@polkadot/api-contract": "1.26.1",
     "@polkadot/api-contract": "1.26.1",
-    "@polkadot/keyring": "3.0.1",
+    "@polkadot/keyring": "^3.0.1",
     "@polkadot/types": "1.26.1",
     "@polkadot/types": "1.26.1",
-    "@polkadot/util": "3.0.1",
-    "@polkadot/util-crypto": "3.0.1",
-    "@polkadot/wasm-crypto": "1.2.1",
+    "@polkadot/util": "^3.0.1",
+    "@polkadot/util-crypto": "^3.0.1",
+    "@polkadot/wasm-crypto": "^1.2.1",
     "babel-core": "^7.0.0-bridge.0",
     "babel-core": "^7.0.0-bridge.0",
-    "typescript": "^3.9.7"
+    "typescript": "^3.9.7",
+    "bn.js": "^5.1.2"
   },
   },
   "devDependencies": {
   "devDependencies": {
     "husky": "^4.2.5",
     "husky": "^4.2.5",

+ 0 - 9
pioneer/.eslintignore

@@ -1,14 +1,5 @@
 **/build/*
 **/build/*
 **/coverage/*
 **/coverage/*
 **/node_modules/*
 **/node_modules/*
-packages/old-apps/*
-packages/joy-election/*
-packages/joy-forum/*
-packages/joy-help/*
-packages/joy-media/*
-packages/joy-proposals/*
-packages/joy-roles/*
-packages/joy-settings/*
-packages/joy-utils-old/*
 .eslintrc.js
 .eslintrc.js
 i18next-scanner.config.js
 i18next-scanner.config.js

+ 2 - 0
pioneer/.eslintrc.js

@@ -27,6 +27,8 @@ module.exports = {
     'react/jsx-max-props-per-line': 'off',
     'react/jsx-max-props-per-line': 'off',
     'sort-destructure-keys/sort-destructure-keys': 'off',
     'sort-destructure-keys/sort-destructure-keys': 'off',
     '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
     '@typescript-eslint/unbound-method': 'warn', // Doesn't work well with our version of Formik, see: https://github.com/formium/formik/issues/2589
+    'react-hooks/exhaustive-deps': 'warn', // Causes more issues than it solves currently
+    'no-void': 'off' // Otherwise we cannot mark unhandles promises
   },
   },
   // isolate pioneer from monorepo eslint rules
   // isolate pioneer from monorepo eslint rules
   root: true
   root: true

+ 39 - 73
pioneer/.storybook/webpack.config.js

@@ -1,81 +1,47 @@
 const path = require('path')
 const path = require('path')
 const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
 const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
 module.exports = ({ config }) => {
 module.exports = ({ config }) => {
+  // Styles (replace the provided rule):
+  const originalCssRuleIndex = config.module.rules.findIndex(rule => rule.test.source.includes('.css'));
+  config.module.rules[originalCssRuleIndex] = {
+    test: /\.(sa|sc|c)ss$/i,
+    use: [
+      // Creates `style` nodes from JS strings
+      'style-loader',
+      // Translates CSS into CommonJS
+      'css-loader',
+      // Compiles Sass to CSS
+      'sass-loader'
+    ]
+  };
 
 
-// Post CSS loader for sources:
-config.module.rules.push({
-  test: /\.css$/,
-  include: path.resolve(__dirname, '../packages'),
-  exclude: /(node_modules)/,
-  use: [
-    {
-      loader: require.resolve('postcss-loader'),
-      options: {
-        // Set postcss.config.js config path && ctx
-        config: {
-          path: '../postcss.config.js',
-        },
-        ident: 'postcss',
-        plugins: () => [
-          require('precss'),
-          require('autoprefixer'),
-          require('postcss-simple-vars'),
-          require('postcss-nested'),
-          require('postcss-import'),
-          require('postcss-clean')(),
-          require('postcss-flexbugs-fixes')
-        ]
-      }
-    }
-  ]
-});
+  // TypeScript loader (via Babel to match polkadot/apps)
+  config.module.rules.push({
+    test: /\.(js|ts|tsx)$/,
+    exclude: /(node_modules)/,
+    use: [
+      {
+        loader: require.resolve('babel-loader'),
+        options: require('@polkadot/dev/config/babel')
+      },
+    ],
+  });
+  config.resolve.extensions.push('.js', '.ts', '.tsx');
 
 
-// TypeScript loader (via Babel to match polkadot/apps)
-config.module.rules.push({
-  test: /\.(js|ts|tsx)$/,
-  exclude: /(node_modules)/,
-  use: [
-    {
-      loader: require.resolve('babel-loader'),
-      options: require('@polkadot/dev-react/config/babel')
-    },
-  ],
-});
-config.resolve.extensions.push('.js', '.ts', '.tsx');
+  // TSConfig, uses the same file as packages
+  config.resolve.plugins = config.resolve.plugins || [];
+  config.resolve.plugins.push(
+    new TsconfigPathsPlugin({
+      configFile: path.resolve(__dirname, '../tsconfig.json'),
+    })
+  );
 
 
-// TSConfig, uses the same file as packages
-config.resolve.plugins = config.resolve.plugins || [];
-config.resolve.plugins.push(
-  new TsconfigPathsPlugin({
-    configFile: path.resolve(__dirname, '../tsconfig.json'),
-  })
-);
+  // Stories parser
+  config.module.rules.push({
+      test: /\.stories\.tsx?$/,
+      loaders: [require.resolve('@storybook/source-loader')],
+      enforce: 'pre',
+  });
 
 
-// Stories parser
-config.module.rules.push({
-    test: /\.stories\.tsx?$/,
-    loaders: [require.resolve('@storybook/source-loader')],
-    enforce: 'pre',
-});
-
-// CSS preprocessors
-config.module.rules.push(
-    {
-        test: /\.s[ac]ss$/i,
-        use: [
-            // Creates `style` nodes from JS strings
-            'style-loader',
-            // Translates CSS into CommonJS
-            'css-loader',
-            // Compiles Sass to CSS
-            'sass-loader',
-        ],
-    },
-    {
-        test: /\.less$/,
-        loaders: [ 'style-loader', 'css-loader', 'less-loader' ]
-    }
-);
-
-return config;
+  return config;
 };
 };

+ 5 - 4
pioneer/package.json

@@ -25,7 +25,6 @@
     "test": "echo \"skipping tests\"",
     "test": "echo \"skipping tests\"",
     "vanitygen": "node packages/app-accounts/scripts/vanitygen.js",
     "vanitygen": "node packages/app-accounts/scripts/vanitygen.js",
     "start": "yarn clean && cd packages/apps && webpack --config webpack.config.js",
     "start": "yarn clean && cd packages/apps && webpack --config webpack.config.js",
-    "generate-schemas": "json2ts -i packages/joy-types/src/schemas/role.schema.json -o packages/joy-types/src/schemas/role.schema.ts",
     "build-storybook": "build-storybook -c .storybook",
     "build-storybook": "build-storybook -c .storybook",
     "storybook": "start-storybook -s ./packages/apps/public -p 3001"
     "storybook": "start-storybook -s ./packages/apps/public -p 3001"
   },
   },
@@ -40,7 +39,7 @@
     "@types/chart.js": "^2.9.23",
     "@types/chart.js": "^2.9.23",
     "@types/file-saver": "^2.0.1",
     "@types/file-saver": "^2.0.1",
     "@types/i18next": "^13.0.0",
     "@types/i18next": "^13.0.0",
-    "@types/jest": "^26.0.7",
+    "@types/jest": "^26.0.10",
     "@types/react-beautiful-dnd": "^13.0.0",
     "@types/react-beautiful-dnd": "^13.0.0",
     "@types/react-copy-to-clipboard": "^4.3.0",
     "@types/react-copy-to-clipboard": "^4.3.0",
     "@types/react-dom": "^16.9.8",
     "@types/react-dom": "^16.9.8",
@@ -74,11 +73,13 @@
     "@storybook/addon-actions": "^5.2.5",
     "@storybook/addon-actions": "^5.2.5",
     "@storybook/addon-console": "^1.2.1",
     "@storybook/addon-console": "^1.2.1",
     "@storybook/react": "^5.2.5",
     "@storybook/react": "^5.2.5",
-    "json-schema-to-typescript": "^7.1.0",
     "storybook-react-router": "^1.0.8",
     "storybook-react-router": "^1.0.8",
     "typescript": "^3.9.7",
     "typescript": "^3.9.7",
     "eslint-plugin-header": "^3.0.0",
     "eslint-plugin-header": "^3.0.0",
-    "eslint-plugin-sort-destructure-keys": "^1.3.5"
+    "eslint-plugin-sort-destructure-keys": "^1.3.5",
+    "jest": "^26.4.1",
+    "ts-jest": "^26.2.0",
+    "tsconfig-paths-webpack-plugin": "^3.2.0"
   },
   },
   "dependencies": {
   "dependencies": {
     "@types/lodash": "^4.14.138",
     "@types/lodash": "^4.14.138",

+ 90 - 90
pioneer/packages/apps-config/src/settings/endpoints.ts

@@ -33,94 +33,94 @@ function createLive (t: TFunction): LinkOption[] {
       info: 'joystream',
       info: 'joystream',
       text: t<string>('rpc.joystream', 'Joystream (Current Testnet, hosted by Jsgenesis)', { ns: 'apps-config' }),
       text: t<string>('rpc.joystream', 'Joystream (Current Testnet, hosted by Jsgenesis)', { ns: 'apps-config' }),
       value: 'wss://rome-rpc-endpoint.joystream.org:9944'
       value: 'wss://rome-rpc-endpoint.joystream.org:9944'
-    },
-    {
-      dnslink: 'polkadot',
-      info: 'polkadot',
-      text: t<string>('rpc.polkadot.parity', 'Polkadot (Live, hosted by Parity)', { ns: 'apps-config' }),
-      value: 'wss://rpc.polkadot.io'
-    },
-    {
-      dnslink: 'polkadot',
-      info: 'polkadot',
-      text: t<string>('rpc.polkadot.w3f', 'Polkadot (Live, hosted by Web3 Foundation)', { ns: 'apps-config' }),
-      value: 'wss://cc1-1.polkadot.network'
-    },
-    {
-      dnslink: 'kusama',
-      info: 'kusama',
-      text: t<string>('rpc.kusama.parity', 'Kusama (Polkadot Canary, hosted by Parity)', { ns: 'apps-config' }),
-      value: 'wss://kusama-rpc.polkadot.io/'
-    },
-    {
-      dnslink: 'kusama',
-      info: 'kusama',
-      text: t<string>('rpc.kusama.w3f', 'Kusama (Polkadot Canary, hosted by Web3 Foundation)', { ns: 'apps-config' }),
-      value: 'wss://cc3-5.kusama.network/'
-    },
-    {
-      dnslink: 'kusama',
-      info: 'kusama',
-      text: t<string>('rpc.kusama.ava', 'Kusama (Polkadot Canary, user-run public nodes; see https://status.cloud.ava.do/)', { ns: 'apps-config' }),
-      value: 'wss://kusama.polkadot.cloud.ava.do/'
-    },
-    {
-      dnslink: 'centrifuge',
-      info: 'centrifuge',
-      text: t<string>('rpc.centrifuge', 'Centrifuge (Mainnet, hosted by Centrifuge)', { ns: 'apps-config' }),
-      value: 'wss://fullnode.centrifuge.io'
-    },
-    {
-      dnslink: 'edgeware',
-      info: 'edgeware',
-      text: t<string>('rpc.edgeware', 'Edgeware (Mainnet, hosted by Commonwealth Labs)', { ns: 'apps-config' }),
-      value: 'wss://mainnet1.edgewa.re'
-    },
-    {
-      dnslink: 'kulupu',
-      info: 'substrate',
-      text: t<string>('rpc.kulupu', 'Kulupu (Kulupu Mainnet, hosted by Kulupu)', { ns: 'apps-config' }),
-      value: 'wss://rpc.kulupu.network/ws'
     }
     }
+    // {
+    //   dnslink: 'polkadot',
+    //   info: 'polkadot',
+    //   text: t<string>('rpc.polkadot.parity', 'Polkadot (Live, hosted by Parity)', { ns: 'apps-config' }),
+    //   value: 'wss://rpc.polkadot.io'
+    // },
+    // {
+    //   dnslink: 'polkadot',
+    //   info: 'polkadot',
+    //   text: t<string>('rpc.polkadot.w3f', 'Polkadot (Live, hosted by Web3 Foundation)', { ns: 'apps-config' }),
+    //   value: 'wss://cc1-1.polkadot.network'
+    // },
+    // {
+    //   dnslink: 'kusama',
+    //   info: 'kusama',
+    //   text: t<string>('rpc.kusama.parity', 'Kusama (Polkadot Canary, hosted by Parity)', { ns: 'apps-config' }),
+    //   value: 'wss://kusama-rpc.polkadot.io/'
+    // },
+    // {
+    //   dnslink: 'kusama',
+    //   info: 'kusama',
+    //   text: t<string>('rpc.kusama.w3f', 'Kusama (Polkadot Canary, hosted by Web3 Foundation)', { ns: 'apps-config' }),
+    //   value: 'wss://cc3-5.kusama.network/'
+    // },
+    // {
+    //   dnslink: 'kusama',
+    //   info: 'kusama',
+    //   text: t<string>('rpc.kusama.ava', 'Kusama (Polkadot Canary, user-run public nodes; see https://status.cloud.ava.do/)', { ns: 'apps-config' }),
+    //   value: 'wss://kusama.polkadot.cloud.ava.do/'
+    // },
+    // {
+    //   dnslink: 'centrifuge',
+    //   info: 'centrifuge',
+    //   text: t<string>('rpc.centrifuge', 'Centrifuge (Mainnet, hosted by Centrifuge)', { ns: 'apps-config' }),
+    //   value: 'wss://fullnode.centrifuge.io'
+    // },
+    // {
+    //   dnslink: 'edgeware',
+    //   info: 'edgeware',
+    //   text: t<string>('rpc.edgeware', 'Edgeware (Mainnet, hosted by Commonwealth Labs)', { ns: 'apps-config' }),
+    //   value: 'wss://mainnet1.edgewa.re'
+    // },
+    // {
+    //   dnslink: 'kulupu',
+    //   info: 'substrate',
+    //   text: t<string>('rpc.kulupu', 'Kulupu (Kulupu Mainnet, hosted by Kulupu)', { ns: 'apps-config' }),
+    //   value: 'wss://rpc.kulupu.network/ws'
+    // }
   ];
   ];
 }
 }
 
 
-function createTest (t: TFunction): LinkOption[] {
-  return [
-    {
-      dnslink: 'westend',
-      info: 'westend',
-      text: t<string>('rpc.westend', 'Westend (Polkadot Testnet, hosted by Parity)', { ns: 'apps-config' }),
-      value: 'wss://westend-rpc.polkadot.io'
-    },
-    {
-      info: 'acala',
-      text: t<string>('rpc.mandala', 'Mandala (Acala Testnet, hosted by Acala)', { ns: 'apps-config' }),
-      value: 'wss://node-6684611762228215808.jm.onfinality.io/ws'
-    },
-    {
-      info: 'edgeware',
-      text: t<string>('rpc.berlin', 'Berlin (Edgeware Testnet, hosted by Commonwealth Labs)', { ns: 'apps-config' }),
-      value: 'wss://berlin1.edgewa.re'
-    },
-    {
-      info: 'substrate',
-      text: t<string>('rpc.flamingfir', 'Flaming Fir (Substrate Testnet, hosted by Parity)', { ns: 'apps-config' }),
-      value: 'wss://substrate-rpc.parity.io/'
-    },
-    {
-      info: 'nodle',
-      text: t<string>('rpc.arcadia', 'Arcadia (Nodle Testnet, hosted by Nodle)', { ns: 'apps-config' }),
-      value: 'wss://arcadia1.nodleprotocol.io/'
-    },
-    {
-      info: 'datahighway',
-      isDisabled: true,
-      text: t<string>('rpc.datahighway.harbour', 'Harbour (DataHighway Testnet, hosted by MXC)', { ns: 'apps-config' }),
-      value: 'wss://testnet-harbour.datahighway.com'
-    }
-  ];
-}
+// function createTest (t: TFunction): LinkOption[] {
+//   return [
+//     {
+//       dnslink: 'westend',
+//       info: 'westend',
+//       text: t<string>('rpc.westend', 'Westend (Polkadot Testnet, hosted by Parity)', { ns: 'apps-config' }),
+//       value: 'wss://westend-rpc.polkadot.io'
+//     },
+//     {
+//       info: 'acala',
+//       text: t<string>('rpc.mandala', 'Mandala (Acala Testnet, hosted by Acala)', { ns: 'apps-config' }),
+//       value: 'wss://node-6684611762228215808.jm.onfinality.io/ws'
+//     },
+//     {
+//       info: 'edgeware',
+//       text: t<string>('rpc.berlin', 'Berlin (Edgeware Testnet, hosted by Commonwealth Labs)', { ns: 'apps-config' }),
+//       value: 'wss://berlin1.edgewa.re'
+//     },
+//     {
+//       info: 'substrate',
+//       text: t<string>('rpc.flamingfir', 'Flaming Fir (Substrate Testnet, hosted by Parity)', { ns: 'apps-config' }),
+//       value: 'wss://substrate-rpc.parity.io/'
+//     },
+//     {
+//       info: 'nodle',
+//       text: t<string>('rpc.arcadia', 'Arcadia (Nodle Testnet, hosted by Nodle)', { ns: 'apps-config' }),
+//       value: 'wss://arcadia1.nodleprotocol.io/'
+//     },
+//     {
+//       info: 'datahighway',
+//       isDisabled: true,
+//       text: t<string>('rpc.datahighway.harbour', 'Harbour (DataHighway Testnet, hosted by MXC)', { ns: 'apps-config' }),
+//       value: 'wss://testnet-harbour.datahighway.com'
+//     }
+//   ];
+// }
 
 
 function createCustom (t: TFunction): LinkOption[] {
 function createCustom (t: TFunction): LinkOption[] {
   const WS_URL = (
   const WS_URL = (
@@ -158,12 +158,12 @@ export default function create (t: TFunction): LinkOption[] {
       value: ''
       value: ''
     },
     },
     ...createLive(t),
     ...createLive(t),
-    {
-      isHeader: true,
-      text: t<string>('rpc.header.test', 'Test networks', { ns: 'apps-config' }),
-      value: ''
-    },
-    ...createTest(t),
+    // {
+    //   isHeader: true,
+    //   text: t<string>('rpc.header.test', 'Test networks', { ns: 'apps-config' }),
+    //   value: ''
+    // },
+    // ...createTest(t),
     {
     {
       isHeader: true,
       isHeader: true,
       text: t<string>('rpc.header.dev', 'Development', { ns: 'apps-config' }),
       text: t<string>('rpc.header.dev', 'Development', { ns: 'apps-config' }),

+ 2 - 2
pioneer/packages/apps-routing/src/accounts.ts

@@ -12,9 +12,9 @@ export default function create (t: <T = string> (key: string, text: string, opti
     display: {
     display: {
       needsApi: []
       needsApi: []
     },
     },
-    icon: 'users',
+    icon: 'key',
     name: 'accounts',
     name: 'accounts',
-    text: t<string>('nav.accounts', 'Accounts', { ns: 'apps-routing' }),
+    text: t<string>('nav.accounts', 'My Keys', { ns: 'apps-routing' }),
     useCounter
     useCounter
   };
   };
 }
 }

+ 16 - 0
pioneer/packages/apps-routing/src/index.ts

@@ -18,14 +18,25 @@ import storage from './storage';
 import sudo from './sudo';
 import sudo from './sudo';
 import toolbox from './toolbox';
 import toolbox from './toolbox';
 import transfer from './transfer';
 import transfer from './transfer';
+// import memo from './memo';
 // Joy packages
 // Joy packages
 import members from './joy-members';
 import members from './joy-members';
 import { terms, privacyPolicy } from './joy-pages';
 import { terms, privacyPolicy } from './joy-pages';
+import election from './joy-election';
+import proposals from './joy-proposals';
+import roles from './joy-roles';
+import media from './joy-media';
+import forum from './joy-forum';
 
 
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
 export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Routes {
   return appSettings.uiMode === 'light'
   return appSettings.uiMode === 'light'
     ? [
     ? [
+      media(t),
       members(t),
       members(t),
+      roles(t),
+      election(t),
+      proposals(t),
+      forum(t),
       staking(t),
       staking(t),
       null,
       null,
       transfer(t),
       transfer(t),
@@ -36,7 +47,12 @@ export default function create (t: <T = string> (key: string, text: string, opti
       privacyPolicy(t)
       privacyPolicy(t)
     ]
     ]
     : [
     : [
+      media(t),
       members(t),
       members(t),
+      roles(t),
+      election(t),
+      proposals(t),
+      forum(t),
       staking(t),
       staking(t),
       null,
       null,
       transfer(t),
       transfer(t),

+ 17 - 0
pioneer/packages/apps-routing/src/joy-election.ts

@@ -0,0 +1,17 @@
+import { Route } from './types';
+
+import Election from '@polkadot/joy-election/index';
+import SidebarSubtitle from '@polkadot/joy-election/SidebarSubtitle';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Election,
+    display: {
+      needsApi: ['query.council.activeCouncil', 'query.councilElection.stage']
+    },
+    text: t<string>('nav.election', 'Council', { ns: 'apps-routing' }),
+    icon: 'university',
+    name: 'council',
+    SubtitleComponent: SidebarSubtitle
+  };
+}

+ 15 - 0
pioneer/packages/apps-routing/src/joy-forum.ts

@@ -0,0 +1,15 @@
+import { Route } from './types';
+
+import Forum from '@polkadot/joy-forum/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Forum,
+    display: {
+      needsApi: ['query.forum.threadById']
+    },
+    text: t<string>('nav.forum', 'Forum', { ns: 'apps-routing' }),
+    icon: 'comment-dots',
+    name: 'forum'
+  };
+}

+ 15 - 0
pioneer/packages/apps-routing/src/joy-media.ts

@@ -0,0 +1,15 @@
+import Media from '@polkadot/joy-media/index';
+
+import { Route } from './types';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Media,
+    display: {
+      needsApi: ['query.storageWorkingGroup.workerById', 'query.dataObjectStorageRegistry.relationshipsByContentId']
+    },
+    text: t<string>('nav.media', 'Media', { ns: 'apps-routing' }),
+    icon: 'play-circle',
+    name: 'media'
+  };
+}

+ 16 - 0
pioneer/packages/apps-routing/src/joy-proposals.ts

@@ -0,0 +1,16 @@
+import { Route } from './types';
+
+import Proposals from '@polkadot/joy-proposals/index';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    Component: Proposals,
+    display: {
+      needsApi: ['query.proposalsEngine.proposalCount']
+    },
+    text: t<string>('nav.proposals', 'Proposals', { ns: 'apps-routing' }),
+    icon: 'tasks',
+    name: 'proposals'
+    // TODO: useCounter with active proposals count? (could be a nice addition)
+  };
+}

+ 6 - 8
pioneer/packages/old-apps/apps-routing/src/joy-roles.ts → pioneer/packages/apps-routing/src/joy-roles.ts

@@ -1,9 +1,9 @@
-import { Routes } from './types';
+import { Route } from './types';
 
 
 import Roles from '@polkadot/joy-roles/index';
 import Roles from '@polkadot/joy-roles/index';
 
 
-export default ([
-  {
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
     Component: Roles,
     Component: Roles,
     display: {
     display: {
       needsApi: [
       needsApi: [
@@ -11,10 +11,8 @@ export default ([
         'query.storageWorkingGroup.mint'
         'query.storageWorkingGroup.mint'
       ]
       ]
     },
     },
-    i18n: {
-      defaultValue: 'Working groups'
-    },
+    text: t<string>('nav.roles', 'Working groups', { ns: 'apps-routing' }),
     icon: 'users',
     icon: 'users',
     name: 'working-groups'
     name: 'working-groups'
-  }
-] as Routes);
+  };
+}

+ 20 - 0
pioneer/packages/apps-routing/src/memo.ts

@@ -0,0 +1,20 @@
+import { Route } from './types';
+
+import { MemoModal } from '@polkadot/joy-utils/react/components/Memo';
+
+export default function create (t: <T = string> (key: string, text: string, options: { ns: string }) => T): Route {
+  return {
+    // Assert to get around the uncecessary requirement for RouteProps
+    Component: MemoModal as React.ComponentType<any>,
+    Modal: MemoModal,
+    display: {
+      isHidden: false,
+      needsApi: [
+        'tx.memo.updateMemo'
+      ]
+    },
+    icon: 'sticky-note',
+    name: 'memo',
+    text: t<string>('nav.memo', 'My memo', { ns: 'apps-routing' })
+  };
+}

+ 1 - 1
pioneer/packages/apps-routing/src/staking.ts

@@ -16,6 +16,6 @@ export default function create (t: <T = string> (key: string, text: string, opti
     },
     },
     icon: 'certificate',
     icon: 'certificate',
     name: 'staking',
     name: 'staking',
-    text: t<string>('nav.staking', 'Staking', { ns: 'apps-routing' })
+    text: t<string>('nav.staking', 'Validators', { ns: 'apps-routing' })
   };
   };
 }
 }

+ 2 - 0
pioneer/packages/apps-routing/src/types.ts

@@ -24,6 +24,8 @@ export interface Route {
   name: string;
   name: string;
   text: string;
   text: string;
   useCounter?: () => number | string | null;
   useCounter?: () => number | string | null;
+  // Joystream-specific
+  SubtitleComponent?: React.ComponentType<any>;
 }
 }
 
 
 export type Routes = (Route | null)[];
 export type Routes = (Route | null)[];

+ 0 - 0
pioneer/packages/old-apps/apps/public/images/default-thumbnail.png → pioneer/packages/apps/public/images/default-thumbnail.png


+ 1 - 0
pioneer/packages/apps/public/locales/en/app-accounts.json

@@ -75,6 +75,7 @@
   "Multisig approvals": "Multisig approvals",
   "Multisig approvals": "Multisig approvals",
   "Multisig approvals pending": "Multisig approvals pending",
   "Multisig approvals pending": "Multisig approvals pending",
   "Multisig message with call (for final approval)": "Multisig message with call (for final approval)",
   "Multisig message with call (for final approval)": "Multisig message with call (for final approval)",
+  "My Memo": "My Memo",
   "My On-Chain Name": "My On-Chain Name",
   "My On-Chain Name": "My On-Chain Name",
   "My accounts": "My accounts",
   "My accounts": "My accounts",
   "My contacts": "My contacts",
   "My contacts": "My contacts",

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

@@ -25,6 +25,7 @@
   "joy-media.json",
   "joy-media.json",
   "joy-members.json",
   "joy-members.json",
   "joy-roles.json",
   "joy-roles.json",
+  "joy-utils.json",
   "react-components.json",
   "react-components.json",
   "react-params.json",
   "react-params.json",
   "react-query.json",
   "react-query.json",

+ 0 - 1
pioneer/packages/apps/public/locales/en/joy-settings.json

@@ -1 +0,0 @@
-{}

+ 0 - 1
pioneer/packages/apps/public/locales/en/joy-utils-old.json

@@ -1 +0,0 @@
-{}

+ 4 - 1
pioneer/packages/apps/public/locales/en/joy-utils.json

@@ -1 +1,4 @@
-{}
+{
+  "click to select or drag and drop the file here": "click to select or drag and drop the file here",
+  "{{name}} ({{size}} bytes)": "{{name}} ({{size}} bytes)"
+}

+ 1 - 1
pioneer/packages/apps/src/Content/NotFound.tsx

@@ -7,7 +7,7 @@ import { Redirect } from 'react-router';
 
 
 function NotFound (): React.ReactElement {
 function NotFound (): React.ReactElement {
   return (
   return (
-    <Redirect to='/staking' />
+    <Redirect to='/media' />
   );
   );
 }
 }
 
 

+ 24 - 6
pioneer/packages/apps/src/Content/index.tsx

@@ -16,6 +16,11 @@ import { useTranslation } from '../translate';
 import NotFound from './NotFound';
 import NotFound from './NotFound';
 import Status from './Status';
 import Status from './Status';
 
 
+// Joystream-specific
+// We must use transport provider here instead of /apps/src/index
+// to avoid "Cannot create Transport: The Substrate API is not ready yet." error
+import { TransportProvider } from '@polkadot/joy-utils/react/context';
+
 interface Props {
 interface Props {
   className?: string;
   className?: string;
 }
 }
@@ -60,11 +65,25 @@ function Content ({ className }: Props): React.ReactElement<Props> {
                 ? <NotFound />
                 ? <NotFound />
                 : (
                 : (
                   <ErrorBoundary trigger={name}>
                   <ErrorBoundary trigger={name}>
-                    <Component
-                      basePath={`/${name}`}
-                      location={location}
-                      onStatusChange={queueAction}
-                    />
+                    { needsApi
+                      // Add transport provider for routes that need the api
+                      // (the above condition makes sure it's aleady initialized at this point)
+                      ? (
+                        <TransportProvider>
+                          <Component
+                            basePath={`/${name}`}
+                            location={location}
+                            onStatusChange={queueAction}
+                          />
+                        </TransportProvider>
+                      )
+                      : (
+                        <Component
+                          basePath={`/${name}`}
+                          location={location}
+                          onStatusChange={queueAction}
+                        />
+                      ) }
                   </ErrorBoundary>
                   </ErrorBoundary>
                 )
                 )
               }
               }
@@ -78,7 +97,6 @@ function Content ({ className }: Props): React.ReactElement<Props> {
 }
 }
 
 
 export default React.memo(styled(Content)`
 export default React.memo(styled(Content)`
-  background: rgba(250, 250, 250);
   padding: 0 1.5rem;
   padding: 0 1.5rem;
   position: relative;
   position: relative;
   width: 100%;
   width: 100%;

+ 5 - 2
pioneer/packages/apps/src/SideBar/Item.tsx

@@ -79,12 +79,15 @@ function Item ({ isCollapsed, onClick, route }: Props): React.ReactElement<Props
     return null;
     return null;
   }
   }
 
 
-  const { Modal, icon, name, text } = route;
+  const { Modal, SubtitleComponent, icon, name, text } = route;
 
 
   const body = (
   const body = (
     <>
     <>
       <Icon icon={icon} />
       <Icon icon={icon} />
-      <span className='text'>{text}</span>
+      <span className='text'>
+        {text}
+        { SubtitleComponent && <SubtitleComponent/> }
+      </span>
       {!!count && (
       {!!count && (
         <Badge
         <Badge
           color='counter'
           color='counter'

+ 0 - 2
pioneer/packages/apps/src/SideBar/index.tsx

@@ -14,7 +14,6 @@ import NetworkModal from '../modals/Network';
 import { useTranslation } from '../translate';
 import { useTranslation } from '../translate';
 import ChainInfo from './ChainInfo';
 import ChainInfo from './ChainInfo';
 import Item from './Item';
 import Item from './Item';
-import NodeInfo from './NodeInfo';
 
 
 interface Props {
 interface Props {
   className?: string;
   className?: string;
@@ -101,7 +100,6 @@ function SideBar ({ className = '', collapse, handleResize, isCollapsed, isMenuO
                 )
                 )
             ))}
             ))}
             <Menu.Divider hidden />
             <Menu.Divider hidden />
-            {!isCollapsed && <NodeInfo />}
           </div>
           </div>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
           <div className={`apps--SideBar-collapse ${isCollapsed ? 'collapsed' : 'expanded'}`}>
             <Button
             <Button

+ 14 - 7
pioneer/packages/apps/src/initSettings.ts

@@ -8,11 +8,17 @@ import { createEndpoints } from '@polkadot/apps-config/settings';
 import { extractIpfsDetails } from '@polkadot/react-hooks/useIpfs';
 import { extractIpfsDetails } from '@polkadot/react-hooks/useIpfs';
 import settings from '@polkadot/ui-settings';
 import settings from '@polkadot/ui-settings';
 
 
-function getApiUrl (): string {
+// Joystream-specific override in order to include default UIMODE
+function getInitSettings () {
   // we split here so that both these forms are allowed
   // we split here so that both these forms are allowed
   //  - http://localhost:3000/?rpc=wss://substrate-rpc.parity.io/#/explorer
   //  - http://localhost:3000/?rpc=wss://substrate-rpc.parity.io/#/explorer
   //  - http://localhost:3000/#/explorer?rpc=wss://substrate-rpc.parity.io
   //  - http://localhost:3000/#/explorer?rpc=wss://substrate-rpc.parity.io
   const urlOptions = queryString.parse(location.href.split('?')[1]);
   const urlOptions = queryString.parse(location.href.split('?')[1]);
+  const stored = store.get('settings') as Record<string, unknown> || {};
+
+  // uiMode - set to "light" if not in storage
+  const uiMode = stored.uiMode ? stored.uiMode as string : 'light';
+  let apiUrl: string;
 
 
   // if specified, this takes priority
   // if specified, this takes priority
   if (urlOptions.rpc) {
   if (urlOptions.rpc) {
@@ -20,7 +26,7 @@ function getApiUrl (): string {
       throw new Error('Invalid WS endpoint specified');
       throw new Error('Invalid WS endpoint specified');
     }
     }
 
 
-    return urlOptions.rpc.split('#')[0]; // https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer;
+    apiUrl = urlOptions.rpc.split('#')[0]; // https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer;
   }
   }
 
 
   const endpoints = createEndpoints(<T = string>(): T => ('' as unknown as T));
   const endpoints = createEndpoints(<T = string>(): T => ('' as unknown as T));
@@ -31,24 +37,25 @@ function getApiUrl (): string {
     const option = endpoints.find(({ dnslink }) => dnslink === ipnsChain);
     const option = endpoints.find(({ dnslink }) => dnslink === ipnsChain);
 
 
     if (option) {
     if (option) {
-      return option.value as string;
+      apiUrl = option.value as string;
     }
     }
   }
   }
 
 
-  const stored = store.get('settings') as Record<string, unknown> || {};
   const fallbackUrl = endpoints.find(({ value }) => !!value);
   const fallbackUrl = endpoints.find(({ value }) => !!value);
 
 
   // via settings, or the default chain
   // via settings, or the default chain
-  return [stored.apiUrl, process.env.WS_URL].includes(settings.apiUrl)
+  apiUrl = [stored.apiUrl, process.env.WS_URL].includes(settings.apiUrl)
     ? settings.apiUrl // keep as-is
     ? settings.apiUrl // keep as-is
     : fallbackUrl
     : fallbackUrl
       ? fallbackUrl.value as string // grab the fallback
       ? fallbackUrl.value as string // grab the fallback
       : 'ws://127.0.0.1:9944'; // nothing found, go local
       : 'ws://127.0.0.1:9944'; // nothing found, go local
+
+  return { apiUrl, uiMode };
 }
 }
 
 
-const apiUrl = getApiUrl();
+const { apiUrl, uiMode } = getInitSettings();
 
 
 // set the default as retrieved here
 // set the default as retrieved here
-settings.set({ apiUrl });
+settings.set({ apiUrl, uiMode });
 
 
 console.log('WS endpoint=', apiUrl);
 console.log('WS endpoint=', apiUrl);

+ 14 - 0
pioneer/packages/apps/webpack.base.config.js

@@ -48,6 +48,9 @@ function createWebpack (ENV, context) {
     return alias;
     return alias;
   }, {});
   }, {});
 
 
+  // Add @joystream/types as alias to automatically process any changes:
+  alias['@joystream/types'] = path.resolve(context, '../../../types/src');
+
   return {
   return {
     context,
     context,
     entry: ['@babel/polyfill', './src/index.tsx'],
     entry: ['@babel/polyfill', './src/index.tsx'],
@@ -69,6 +72,17 @@ function createWebpack (ENV, context) {
             }
             }
           ]
           ]
         },
         },
+        {
+          test: /\.s[ac]ss$/i,
+          use: [
+            // Creates `style` nodes from JS strings
+            'style-loader',
+            // Translates CSS into CommonJS
+            'css-loader',
+            // Compiles Sass to CSS
+            'sass-loader'
+          ]
+        },
         {
         {
           include: /node_modules/,
           include: /node_modules/,
           test: /\.css$/,
           test: /\.css$/,

+ 0 - 0
pioneer/packages/joy-election/.skip-build


+ 3 - 3
pioneer/packages/joy-election/package.json

@@ -7,9 +7,9 @@
   "author": "Joystream contributors",
   "author": "Joystream contributors",
   "maintainers": [],
   "maintainers": [],
   "dependencies": {
   "dependencies": {
-    "@babel/runtime": "^7.7.1",
-    "@polkadot/react-components": "0.37.0-beta.63",
-    "@polkadot/react-query": "0.37.0-beta.63",
+    "@babel/runtime": "^7.10.5",
+    "@polkadot/react-components": "0.51.1",
+    "@polkadot/react-query": "0.51.1",
     "@polkadot/joy-utils": "^0.1.1"
     "@polkadot/joy-utils": "^0.1.1"
   }
   }
 }
 }

+ 9 - 6
pioneer/packages/joy-election/src/Applicant.tsx

@@ -4,24 +4,25 @@ import { Table } from 'semantic-ui-react';
 
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { AccountId } from '@polkadot/types/interfaces';
 import { AccountId } from '@polkadot/types/interfaces';
 import { formatBalance } from '@polkadot/util';
 import { formatBalance } from '@polkadot/util';
 import CandidatePreview from './CandidatePreview';
 import CandidatePreview from './CandidatePreview';
 
 
 import translate from './translate';
 import translate from './translate';
-import { calcTotalStake } from '@polkadot/joy-utils/index';
+import { calcTotalStake } from '@polkadot/joy-utils/functions/misc';
 import { ElectionStake } from '@joystream/types/council';
 import { ElectionStake } from '@joystream/types/council';
 
 
 type Props = ApiProps & I18nProps & {
 type Props = ApiProps & I18nProps & {
   index: number;
   index: number;
   accountId: AccountId;
   accountId: AccountId;
   stake?: ElectionStake;
   stake?: ElectionStake;
+  isVotingStage: boolean;
 };
 };
 
 
 class Applicant extends React.PureComponent<Props> {
 class Applicant extends React.PureComponent<Props> {
   render () {
   render () {
-    const { index, accountId, stake } = this.props;
+    const { index, accountId, stake, isVotingStage } = this.props;
     const voteUrl = `/council/votes?applicantId=${accountId.toString()}`;
     const voteUrl = `/council/votes?applicantId=${accountId.toString()}`;
 
 
     return (
     return (
@@ -33,9 +34,11 @@ class Applicant extends React.PureComponent<Props> {
         <Table.Cell style={{ textAlign: 'right' }}>
         <Table.Cell style={{ textAlign: 'right' }}>
           {formatBalance(calcTotalStake(stake))}
           {formatBalance(calcTotalStake(stake))}
         </Table.Cell>
         </Table.Cell>
-        <Table.Cell>
-          <Link to={voteUrl} className='ui button primary inverted'>Vote</Link>
-        </Table.Cell>
+        { isVotingStage && (
+          <Table.Cell>
+            <Link to={voteUrl} className='ui button primary inverted'>Vote</Link>
+          </Table.Cell>
+        ) }
       </Table.Row>
       </Table.Row>
     );
     );
   }
   }

+ 44 - 24
pioneer/packages/joy-election/src/Applicants.tsx

@@ -1,49 +1,68 @@
 import React from 'react';
 import React from 'react';
-import { Table } from 'semantic-ui-react';
+import { Table, Message } from 'semantic-ui-react';
 import BN from 'bn.js';
 import BN from 'bn.js';
 
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { AccountId } from '@polkadot/types/interfaces';
 import { AccountId } from '@polkadot/types/interfaces';
+import { Option } from '@polkadot/types';
 import { formatNumber } from '@polkadot/util';
 import { formatNumber } from '@polkadot/util';
 
 
 import translate from './translate';
 import translate from './translate';
 import Applicant from './Applicant';
 import Applicant from './Applicant';
 import ApplyForm from './ApplyForm';
 import ApplyForm from './ApplyForm';
-import Section from '@polkadot/joy-utils/Section';
-import { queryToProp } from '@polkadot/joy-utils/index';
-import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
+import Section from '@polkadot/joy-utils/react/components/Section';
+import { queryToProp } from '@polkadot/joy-utils/functions/misc';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/react/hocs/accounts';
+import { ElectionStage } from '@joystream/types/src/council';
+import { RouteProps } from 'react-router-dom';
 
 
-type Props = ApiProps & I18nProps & MyAccountProps & {
+type Props = RouteProps & ApiProps & I18nProps & MyAccountProps & {
   candidacyLimit?: BN;
   candidacyLimit?: BN;
   applicants?: Array<AccountId>;
   applicants?: Array<AccountId>;
+  stage?: Option<ElectionStage>;
 };
 };
 
 
 class Applicants extends React.PureComponent<Props> {
 class Applicants extends React.PureComponent<Props> {
-  private renderTable = (applicants: Array<AccountId>) => (
-    <Table celled selectable compact>
-      <Table.Header>
-        <Table.Row>
-          <Table.HeaderCell>#</Table.HeaderCell>
-          <Table.HeaderCell>Applicant</Table.HeaderCell>
-          <Table.HeaderCell>Total stake</Table.HeaderCell>
-          <Table.HeaderCell style={{ width: '1%' }}>Actions</Table.HeaderCell>
-        </Table.Row>
-      </Table.Header>
-      <Table.Body>{applicants.map((accountId, index) => (
-        <Applicant key={index} index={index} accountId={accountId} />
-      ))}</Table.Body>
-    </Table>
-  )
+  private renderTable = (applicants: Array<AccountId>) => {
+    const isVotingStage = this.props.stage?.unwrapOr(undefined)?.isOfType('Voting') || false;
+
+    return (
+      <Table celled selectable compact>
+        <Table.Header>
+          <Table.Row>
+            <Table.HeaderCell>#</Table.HeaderCell>
+            <Table.HeaderCell>Applicant</Table.HeaderCell>
+            <Table.HeaderCell>Total stake</Table.HeaderCell>
+            { isVotingStage && (
+              <Table.HeaderCell style={{ width: '1%' }}>Actions</Table.HeaderCell>
+            ) }
+          </Table.Row>
+        </Table.Header>
+        <Table.Body>{applicants.map((accountId, index) => (
+          <Applicant key={index} index={index} accountId={accountId} isVotingStage={isVotingStage}/>
+        ))}</Table.Body>
+      </Table>
+    );
+  }
 
 
   render () {
   render () {
-    const { myAddress, applicants = [], candidacyLimit = new BN(0) } = this.props;
+    const { myAddress, applicants = [], candidacyLimit = new BN(0), stage } = this.props;
     const title = <span>Applicants <sup>{applicants.length}/{formatNumber(candidacyLimit)}</sup></span>;
     const title = <span>Applicants <sup>{applicants.length}/{formatNumber(candidacyLimit)}</sup></span>;
 
 
     return <>
     return <>
       <Section title='My application'>
       <Section title='My application'>
-        <ApplyForm myAddress={myAddress} />
+        { stage?.unwrapOr(undefined)?.isOfType('Announcing')
+          ? (
+            <ApplyForm myAddress={myAddress} />
+          )
+          : (
+            <Message warning>
+              Applying to council is only possible during <i><b>Announcing</b></i> stage.
+            </Message>
+          )
+        }
       </Section>
       </Section>
       <Section title={title}>
       <Section title={title}>
         {!applicants.length
         {!applicants.length
@@ -59,6 +78,7 @@ class Applicants extends React.PureComponent<Props> {
 export default translate(
 export default translate(
   withCalls<Props>(
   withCalls<Props>(
     queryToProp('query.councilElection.candidacyLimit'),
     queryToProp('query.councilElection.candidacyLimit'),
-    queryToProp('query.councilElection.applicants')
+    queryToProp('query.councilElection.applicants'),
+    queryToProp('query.councilElection.stage')
   )(withMyAccount(Applicants))
   )(withMyAccount(Applicants))
 );
 );

+ 19 - 21
pioneer/packages/joy-election/src/ApplyForm.tsx

@@ -3,21 +3,21 @@ import React from 'react';
 
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
 import { Labelled } from '@polkadot/react-components/index';
 import { Labelled } from '@polkadot/react-components/index';
 import { Balance } from '@polkadot/types/interfaces';
 import { Balance } from '@polkadot/types/interfaces';
 
 
 import translate from './translate';
 import translate from './translate';
-import TxButton from '@polkadot/joy-utils/TxButton';
-import InputStake from '@polkadot/joy-utils/InputStake';
+import TxButton from '@polkadot/joy-utils/react/components/TxButton';
+import InputStake from '@polkadot/joy-utils/react/components/InputStake';
 import { ElectionStake } from '@joystream/types/council';
 import { ElectionStake } from '@joystream/types/council';
-import { calcTotalStake, ZERO } from '@polkadot/joy-utils/index';
-import { MyAddressProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
+import { calcTotalStake, ZERO } from '@polkadot/joy-utils/functions/misc';
+import { MyAddressProps } from '@polkadot/joy-utils/react/hocs/accounts';
+import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards';
 
 
 type Props = ApiProps & I18nProps & MyAddressProps & {
 type Props = ApiProps & I18nProps & MyAddressProps & {
   minStake?: Balance;
   minStake?: Balance;
   alreadyStaked?: ElectionStake;
   alreadyStaked?: ElectionStake;
-  myBalance?: Balance;
 };
 };
 
 
 type State = {
 type State = {
@@ -48,15 +48,16 @@ class ApplyForm extends React.PureComponent<Props, State> {
           isValid={isStakeValid}
           isValid={isStakeValid}
           onChange={this.onChangeStake}
           onChange={this.onChangeStake}
         />
         />
-        <Labelled style={{ marginTop: '.5rem' }}>
-          <TxButton
-            size='large'
-            isDisabled={!isStakeValid}
-            label={buttonLabel}
-            params={[stake]}
-            tx='councilElection.apply'
-          />
-        </Labelled>
+        <div style={{ marginTop: '.5rem' }}>
+          <Labelled>
+            <TxButton
+              isDisabled={!isStakeValid}
+              label={buttonLabel}
+              params={[stake]}
+              tx='councilElection.apply'
+            />
+          </Labelled>
+        </div>
       </div>
       </div>
     );
     );
   }
   }
@@ -71,10 +72,9 @@ class ApplyForm extends React.PureComponent<Props, State> {
 
 
   private onChangeStake = (stake?: BN): void => {
   private onChangeStake = (stake?: BN): void => {
     stake = stake || ZERO;
     stake = stake || ZERO;
-    const { myBalance = ZERO } = this.props;
-    const isStakeLteBalance = stake.lte(myBalance);
     const isStakeGteMinStake = stake.add(this.alreadyStaked()).gte(this.minStake());
     const isStakeGteMinStake = stake.add(this.alreadyStaked()).gte(this.minStake());
-    const isStakeValid = !stake.isZero() && isStakeGteMinStake && isStakeLteBalance;
+    const isStakeValid = !stake.isZero() && isStakeGteMinStake;
+
     this.setState({ stake, isStakeValid });
     this.setState({ stake, isStakeValid });
   }
   }
 }
 }
@@ -88,8 +88,6 @@ export default withMulti(
     ['query.councilElection.minCouncilStake',
     ['query.councilElection.minCouncilStake',
       { propName: 'minStake' }],
       { propName: 'minStake' }],
     ['query.councilElection.applicantStakes',
     ['query.councilElection.applicantStakes',
-      { paramName: 'myAddress', propName: 'alreadyStaked' }],
-    ['query.balances.freeBalance',
-      { paramName: 'myAddress', propName: 'myBalance' }]
+      { paramName: 'myAddress', propName: 'alreadyStaked' }]
   )
   )
 );
 );

+ 2 - 2
pioneer/packages/joy-election/src/CandidatePreview.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import React from 'react';
-import AddressMini from '@polkadot/react-components/AddressMiniJoy';
-import MemberByAccount from '@polkadot/joy-utils/MemberByAccountPreview';
+import AddressMini from '@polkadot/react-components/AddressMini';
+import MemberByAccount from '@polkadot/joy-utils/react/components/MemberByAccountPreview';
 import { AccountId } from '@polkadot/types/interfaces';
 import { AccountId } from '@polkadot/types/interfaces';
 
 
 import styled from 'styled-components';
 import styled from 'styled-components';

+ 8 - 7
pioneer/packages/joy-election/src/Council.tsx

@@ -2,22 +2,22 @@ import React from 'react';
 
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Table } from 'semantic-ui-react';
 import { Table } from 'semantic-ui-react';
 import { formatBalance } from '@polkadot/util';
 import { formatBalance } from '@polkadot/util';
 import CouncilCandidate from './CandidatePreview';
 import CouncilCandidate from './CandidatePreview';
 
 
-import { calcBackersStake } from '@polkadot/joy-utils/index';
+import { calcBackersStake } from '@polkadot/joy-utils/functions/misc';
 import { Seat } from '@joystream/types/council';
 import { Seat } from '@joystream/types/council';
 import translate from './translate';
 import translate from './translate';
-import Section from '@polkadot/joy-utils/Section';
+import Section from '@polkadot/joy-utils/react/components/Section';
+import { RouteProps } from 'react-router-dom';
 
 
-type Props = ApiProps &
-I18nProps & {
+type Props = RouteProps & ApiProps & I18nProps & {
   council?: Seat[];
   council?: Seat[];
 };
 };
 
 
-type State = {};
+type State = Record<any, never>;
 
 
 class Council extends React.PureComponent<Props, State> {
 class Council extends React.PureComponent<Props, State> {
   state: State = {};
   state: State = {};
@@ -53,9 +53,10 @@ class Council extends React.PureComponent<Props, State> {
 
 
   render () {
   render () {
     const { council = [] } = this.props;
     const { council = [] } = this.props;
+
     // console.log({ council });
     // console.log({ council });
     return (
     return (
-      <Section title="Active council members">
+      <Section title='Active council members'>
         {!council.length ? <em>Council is not elected yet</em> : this.renderTable(council)}
         {!council.length ? <em>Council is not elected yet</em> : this.renderTable(council)}
       </Section>
       </Section>
     );
     );

+ 83 - 53
pioneer/packages/joy-election/src/Dashboard.tsx

@@ -3,18 +3,19 @@ import React from 'react';
 
 
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Option } from '@polkadot/types';
 import { Option } from '@polkadot/types';
 import { BlockNumber, Balance } from '@polkadot/types/interfaces';
 import { BlockNumber, Balance } from '@polkadot/types/interfaces';
-import { Bubble } from '@polkadot/react-components/index';
+import { Label, Icon } from 'semantic-ui-react';
 import { formatNumber, formatBalance } from '@polkadot/util';
 import { formatNumber, formatBalance } from '@polkadot/util';
 
 
-import Section from '@polkadot/joy-utils/Section';
-import { queryToProp } from '@polkadot/joy-utils/index';
+import Section from '@polkadot/joy-utils/react/components/Section';
+import { queryToProp } from '@polkadot/joy-utils/functions/misc';
 import { ElectionStage, Seat } from '@joystream/types/council';
 import { ElectionStage, Seat } from '@joystream/types/council';
 import translate from './translate';
 import translate from './translate';
+import { RouteProps } from 'react-router-dom';
 
 
-type Props = ApiProps & I18nProps & {
+type Props = RouteProps & ApiProps & I18nProps & {
   bestNumber?: BN;
   bestNumber?: BN;
 
 
   activeCouncil?: Seat[];
   activeCouncil?: Seat[];
@@ -34,7 +35,7 @@ type Props = ApiProps & I18nProps & {
   stage?: Option<ElectionStage>;
   stage?: Option<ElectionStage>;
 };
 };
 
 
-type State = {};
+type State = Record<any, never>;
 
 
 class Dashboard extends React.PureComponent<Props, State> {
 class Dashboard extends React.PureComponent<Props, State> {
   state: State = {};
   state: State = {};
@@ -45,12 +46,17 @@ class Dashboard extends React.PureComponent<Props, State> {
     const title = `Council ${activeCouncil.length > 0 ? '' : '(not elected)'}`;
     const title = `Council ${activeCouncil.length > 0 ? '' : '(not elected)'}`;
 
 
     return <Section title={title}>
     return <Section title={title}>
-      <Bubble label='Council members'>
-        {activeCouncil.length}
-      </Bubble>
-      <Bubble icon='flag checkered' label='Term ends at block #'>
-        {formatNumber(p.termEndsAt)}
-      </Bubble>
+      <Label.Group color='grey' size='large'>
+        <Label>
+          Council members
+          <Label.Detail>{activeCouncil.length}</Label.Detail>
+        </Label>
+        <Label>
+          <Icon name='flag checkered'/>
+          Term ends at block #
+          <Label.Detail>{formatNumber(p.termEndsAt)}</Label.Detail>
+        </Label>
+      </Label.Group>
     </Section>;
     </Section>;
   }
   }
 
 
@@ -59,13 +65,16 @@ class Dashboard extends React.PureComponent<Props, State> {
 
 
     let stageName: string | undefined;
     let stageName: string | undefined;
     let stageEndsAt: BlockNumber | undefined;
     let stageEndsAt: BlockNumber | undefined;
+
     if (stage && stage.isSome) {
     if (stage && stage.isSome) {
       const stageValue = stage.value as ElectionStage;
       const stageValue = stage.value as ElectionStage;
+
       stageEndsAt = stageValue.value as BlockNumber; // contained value
       stageEndsAt = stageValue.value as BlockNumber; // contained value
       stageName = stageValue.type; // name of Enum variant
       stageName = stageValue.type; // name of Enum variant
     }
     }
 
 
     let leftBlocks: BN | undefined;
     let leftBlocks: BN | undefined;
+
     if (stageEndsAt && bestNumber) {
     if (stageEndsAt && bestNumber) {
       leftBlocks = stageEndsAt.sub(bestNumber);
       leftBlocks = stageEndsAt.sub(bestNumber);
     }
     }
@@ -76,20 +85,28 @@ class Dashboard extends React.PureComponent<Props, State> {
     const title = <>Election (<span className={stateClass}>{stateText}</span>)</>;
     const title = <>Election (<span className={stateClass}>{stateText}</span>)</>;
 
 
     return <Section title={title}>
     return <Section title={title}>
-      <Bubble icon='target' label='Election round #'>
-        {formatNumber(round)}
-      </Bubble>
-      {isRunning && <>
-        <Bubble label='Stage'>
-          {stageName}
-        </Bubble>
-        <Bubble label='Blocks left'>
-          {formatNumber(leftBlocks)}
-        </Bubble>
-        <Bubble icon='flag checkered' label='Stage ends at block #'>
-          {formatNumber(stageEndsAt)}
-        </Bubble>
-      </>}
+      <Label.Group color='grey' size='large'>
+        <Label>
+          <Icon name='target'/>
+          Election round #
+          <Label.Detail>{formatNumber(round)}</Label.Detail>
+        </Label>
+        {isRunning && <>
+          <Label>
+            Stage
+            <Label.Detail>{stageName}</Label.Detail>
+          </Label>
+          <Label>
+            Blocks left
+            <Label.Detail>{formatNumber(leftBlocks)}</Label.Detail>
+          </Label>
+          <Label>
+            <Icon name='flag checkered'/>
+            Stage ends at block #
+            <Label.Detail>{formatNumber(stageEndsAt)}</Label.Detail>
+          </Label>
+        </>}
+      </Label.Group>
     </Section>;
     </Section>;
   }
   }
 
 
@@ -98,33 +115,46 @@ class Dashboard extends React.PureComponent<Props, State> {
     const isAutoStart = (p.autoStart || false).valueOf();
     const isAutoStart = (p.autoStart || false).valueOf();
 
 
     return <Section title='Configuration'>
     return <Section title='Configuration'>
-      <Bubble label='Auto-start elections'>
-        {isAutoStart ? 'Yes' : 'No'}
-      </Bubble>
-      <Bubble label='New term duration'>
-        {formatNumber(p.newTermDuration)}
-      </Bubble>
-      <Bubble label='Candidacy limit'>
-        {formatNumber(p.candidacyLimit)}
-      </Bubble>
-      <Bubble label='Council size'>
-        {formatNumber(p.councilSize)}
-      </Bubble>
-      <Bubble label='Min. council stake'>
-        {formatBalance(p.minCouncilStake)}
-      </Bubble>
-      <Bubble label='Min. voting stake'>
-        {formatBalance(p.minVotingStake)}
-      </Bubble>
-      <Bubble label='Announcing period'>
-        {formatNumber(p.announcingPeriod)} blocks
-      </Bubble>
-      <Bubble label='Voting period'>
-        {formatNumber(p.votingPeriod)} blocks
-      </Bubble>
-      <Bubble label='Revealing period'>
-        {formatNumber(p.revealingPeriod)} blocks
-      </Bubble>
+      <Label.Group color='grey' size='large'>
+        <Label>
+          Auto-start elections
+          <Label.Detail>{isAutoStart ? 'Yes' : 'No'}</Label.Detail>
+        </Label>
+        <Label>
+          New term duration
+          <Label.Detail>{formatNumber(p.newTermDuration)}</Label.Detail>
+        </Label>
+        <Label>
+          Candidacy limit
+          <Label.Detail>{formatNumber(p.candidacyLimit)}</Label.Detail>
+        </Label>
+        <Label>
+          Council size
+          <Label.Detail>{formatNumber(p.councilSize)}</Label.Detail>
+        </Label>
+        <Label>
+          Min. council stake
+          <Label.Detail>{formatBalance(p.minCouncilStake)}</Label.Detail>
+        </Label>
+        <Label>
+          Min. voting stake
+          <Label.Detail>{formatBalance(p.minVotingStake)}</Label.Detail>
+        </Label>
+      </Label.Group>
+      <Label.Group color='grey' size='large'>
+        <Label>
+          Announcing period
+          <Label.Detail>{formatNumber(p.announcingPeriod)} blocks</Label.Detail>
+        </Label>
+        <Label>
+          Voting period
+          <Label.Detail>{formatNumber(p.votingPeriod)} blocks</Label.Detail>
+        </Label>
+        <Label>
+          Revealing period
+          <Label.Detail>{formatNumber(p.revealingPeriod)} blocks</Label.Detail>
+        </Label>
+      </Label.Group>
     </Section>;
     </Section>;
   }
   }
 
 

+ 21 - 18
pioneer/packages/joy-election/src/Reveals.tsx

@@ -1,23 +1,23 @@
 import React from 'react';
 import React from 'react';
 
 
-import { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { withCalls, withMulti } from '@polkadot/react-api/hoc';
 import { AccountId } from '@polkadot/types/interfaces';
 import { AccountId } from '@polkadot/types/interfaces';
 import { Input, Labelled, InputAddress } from '@polkadot/react-components/index';
 import { Input, Labelled, InputAddress } from '@polkadot/react-components/index';
 
 
 import translate from './translate';
 import translate from './translate';
-import { nonEmptyStr, queryToProp, getUrlParam } from '@polkadot/joy-utils/index';
+import { nonEmptyStr, queryToProp, getUrlParam } from '@polkadot/joy-utils/functions/misc';
 import { accountIdsToOptions, hashVote } from './utils';
 import { accountIdsToOptions, hashVote } from './utils';
-import TxButton from '@polkadot/joy-utils/TxButton';
+import TxButton from '@polkadot/joy-utils/react/components/TxButton';
 import { findVoteByHash } from './myVotesStore';
 import { findVoteByHash } from './myVotesStore';
-import { withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
+import { withOnlyMembers } from '@polkadot/joy-utils/react/hocs/guards';
+import { RouteProps } from 'react-router-dom';
 
 
 // AppsProps is needed to get a location from the route.
 // AppsProps is needed to get a location from the route.
-type Props = AppProps & ApiProps & I18nProps & {
+type Props = RouteProps & ApiProps & I18nProps & {
   applicantId?: string | null;
   applicantId?: string | null;
   applicants?: AccountId[];
   applicants?: AccountId[];
-  location: any;
 };
 };
 
 
 type State = {
 type State = {
@@ -30,8 +30,9 @@ class RevealVoteForm extends React.PureComponent<Props, State> {
   constructor (props: Props) {
   constructor (props: Props) {
     super(props);
     super(props);
     let { applicantId, location } = this.props;
     let { applicantId, location } = this.props;
-    applicantId = applicantId || getUrlParam(location, 'applicantId');
-    const hashedVote = getUrlParam(location, 'hashedVote');
+
+    applicantId = applicantId || (location && getUrlParam(location, 'applicantId'));
+    const hashedVote = location && getUrlParam(location, 'hashedVote');
 
 
     this.state = {
     this.state = {
       applicantId,
       applicantId,
@@ -45,6 +46,7 @@ class RevealVoteForm extends React.PureComponent<Props, State> {
     const applicantOpts = accountIdsToOptions(this.props.applicants || []);
     const applicantOpts = accountIdsToOptions(this.props.applicants || []);
 
 
     const myVote = hashedVote ? findVoteByHash(hashedVote) : undefined;
     const myVote = hashedVote ? findVoteByHash(hashedVote) : undefined;
+
     if (myVote) {
     if (myVote) {
       // Try to substitue applicantId and salt from local sotre:
       // Try to substitue applicantId and salt from local sotre:
       if (!applicantId) applicantId = myVote.applicantId;
       if (!applicantId) applicantId = myVote.applicantId;
@@ -81,15 +83,16 @@ class RevealVoteForm extends React.PureComponent<Props, State> {
             onChange={this.onChangeSalt}
             onChange={this.onChangeSalt}
           />
           />
         </div>}
         </div>}
-        <Labelled style={{ marginTop: '.5rem' }}>
-          <TxButton
-            size='large'
-            isDisabled={!isVoteRevealed}
-            label='Reveal this vote'
-            params={[hashedVote, applicantId, salt]}
-            tx='councilElection.reveal'
-          />
-        </Labelled>
+        <div style={{ marginTop: '.5rem' }}>
+          <Labelled>
+            <TxButton
+              isDisabled={!isVoteRevealed}
+              label='Reveal this vote'
+              params={[hashedVote, applicantId, salt]}
+              tx='councilElection.reveal'
+            />
+          </Labelled>
+        </div>
       </div>
       </div>
     );
     );
   }
   }

+ 15 - 6
pioneer/packages/joy-election/src/SealedVote.tsx

@@ -1,38 +1,47 @@
 import React from 'react';
 import React from 'react';
 import { Link } from 'react-router-dom';
 import { Link } from 'react-router-dom';
-import { Table } from 'semantic-ui-react';
+import { Table, Message } from 'semantic-ui-react';
 
 
 import { I18nProps } from '@polkadot/react-components/types';
 import { I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { ApiProps } from '@polkadot/react-api/types';
-import { withCalls } from '@polkadot/react-api/with';
+import { withCalls } from '@polkadot/react-api/hoc';
 import { Hash } from '@polkadot/types/interfaces';
 import { Hash } from '@polkadot/types/interfaces';
 import { formatBalance } from '@polkadot/util';
 import { formatBalance } from '@polkadot/util';
 
 
 import translate from './translate';
 import translate from './translate';
-import { calcTotalStake } from '@polkadot/joy-utils/index';
+import { calcTotalStake } from '@polkadot/joy-utils/functions/misc';
 import { SealedVote } from '@joystream/types/council';
 import { SealedVote } from '@joystream/types/council';
-import AddressMini from '@polkadot/react-components/AddressMiniJoy';
+import AddressMini from '@polkadot/react-components/AddressMini';
 import CandidatePreview from './CandidatePreview';
 import CandidatePreview from './CandidatePreview';
 import { findVoteByHash } from './myVotesStore';
 import { findVoteByHash } from './myVotesStore';
 
 
 type Props = ApiProps & I18nProps & {
 type Props = ApiProps & I18nProps & {
   hash: Hash;
   hash: Hash;
   sealedVote?: SealedVote;
   sealedVote?: SealedVote;
+  isStageRevealing: boolean;
+  isMyVote: boolean;
 };
 };
 
 
 class Comp extends React.PureComponent<Props> {
 class Comp extends React.PureComponent<Props> {
   renderCandidateOrAction () {
   renderCandidateOrAction () {
-    const { hash, sealedVote } = this.props;
+    const { hash, sealedVote, isStageRevealing, isMyVote } = this.props;
+
     if (!sealedVote) {
     if (!sealedVote) {
       return <em>Unknown hashed vote: {hash.toHex()}</em>;
       return <em>Unknown hashed vote: {hash.toHex()}</em>;
     }
     }
 
 
     if (sealedVote.vote.isSome) {
     if (sealedVote.vote.isSome) {
       const candidateId = sealedVote.vote.unwrap();
       const candidateId = sealedVote.vote.unwrap();
+
       return <CandidatePreview accountId={candidateId} />;
       return <CandidatePreview accountId={candidateId} />;
-    } else {
+    } else if (isStageRevealing && isMyVote) {
       const revealUrl = `/council/reveals?hashedVote=${hash.toHex()}`;
       const revealUrl = `/council/reveals?hashedVote=${hash.toHex()}`;
+
       return <Link to={revealUrl} className='ui button primary inverted'>Reveal this vote</Link>;
       return <Link to={revealUrl} className='ui button primary inverted'>Reveal this vote</Link>;
+    } else if (isMyVote) {
+      return <Message warning>Wait until <i><b>Revealing</b></i> stage in order to reveal this vote.</Message>;
+    } else {
+      return <Message info>This vote has not been revealed yet.</Message>;
     }
     }
   }
   }
 
 

Some files were not shown because too many files changed in this diff