Browse Source

Merge branch 'giza-polkadot-api-update' into distributor-node

Leszek Wiesner 3 years ago
parent
commit
2d1ea4159f
100 changed files with 475 additions and 977 deletions
  1. 4 4
      cli/package.json
  2. 2 2
      metadata-protobuf/package.json
  3. 16 12
      package.json
  4. 0 1
      pioneer/README.md
  5. 2 5
      pioneer/package.json
  6. 0 3
      pioneer/packages/apps-electron/.gitignore
  7. 0 0
      pioneer/packages/apps-electron/.skip-build
  8. 0 0
      pioneer/packages/apps-electron/.skip-npm
  9. 0 3
      pioneer/packages/apps-electron/README.md
  10. 0 12
      pioneer/packages/apps-electron/appleEntitlements/entitlements.mac.plist
  11. BIN
      pioneer/packages/apps-electron/assets/icon.png
  12. 0 27
      pioneer/packages/apps-electron/package.json
  13. 0 12
      pioneer/packages/apps-electron/src/api/account-store-api.ts
  14. 0 9
      pioneer/packages/apps-electron/src/api/electron-main-api.ts
  15. 0 13
      pioneer/packages/apps-electron/src/api/global-exported-api.ts
  16. 0 19
      pioneer/packages/apps-electron/src/electron/autoUpdater.ts
  17. 0 23
      pioneer/packages/apps-electron/src/electron/contentSecurityPolicy.ts
  18. 0 27
      pioneer/packages/apps-electron/src/electron/index.ts
  19. 0 31
      pioneer/packages/apps-electron/src/electron/window.ts
  20. 0 56
      pioneer/packages/apps-electron/src/index.tsx
  21. 0 60
      pioneer/packages/apps-electron/src/main/account-store.spec.ts
  22. 0 47
      pioneer/packages/apps-electron/src/main/account-store.ts
  23. 0 7
      pioneer/packages/apps-electron/src/main/ipc-main-handler.ts
  24. 0 14
      pioneer/packages/apps-electron/src/main/register-ipc-handler.ts
  25. 0 15
      pioneer/packages/apps-electron/src/preload.ts
  26. 0 90
      pioneer/packages/apps-electron/src/renderer/remote-electron-store.spec.ts
  27. 0 41
      pioneer/packages/apps-electron/src/renderer/remote-electron-store.ts
  28. 0 55
      pioneer/packages/apps-electron/webpack.main.config.js
  29. 0 32
      pioneer/packages/apps-electron/webpack.renderer.config.js
  30. 1 1
      pioneer/packages/apps-routing/src/joy-roles.ts
  31. 2 2
      pioneer/packages/apps/src/SideBar/ChainInfo.tsx
  32. 1 1
      pioneer/packages/apps/src/translate.ts
  33. 1 1
      pioneer/packages/joy-election/src/VoteForm.tsx
  34. 1 1
      pioneer/packages/joy-forum/src/EditCategory.tsx
  35. 1 1
      pioneer/packages/joy-forum/src/EditReply.tsx
  36. 1 1
      pioneer/packages/joy-forum/src/EditThread.tsx
  37. 1 1
      pioneer/packages/joy-forum/src/Moderate.tsx
  38. 1 1
      pioneer/packages/joy-members/src/EditForm.tsx
  39. 6 6
      pioneer/packages/joy-proposals/src/Proposal/Body.tsx
  40. 1 1
      pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx
  41. 1 1
      pioneer/packages/joy-proposals/src/forms/GenericProposalForm.tsx
  42. 11 1
      pioneer/packages/joy-proposals/src/forms/GenericWorkingGroupProposalForm.tsx
  43. 12 3
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx
  44. 37 2
      pioneer/packages/joy-roles/src/tabs/WorkingGroup.tsx
  45. 5 2
      pioneer/packages/joy-roles/src/transport.substrate.ts
  46. 12 3
      pioneer/packages/joy-roles/src/working_groups.ts
  47. 62 106
      pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx
  48. 58 83
      pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx
  49. 0 12
      pioneer/packages/joy-tokenomics/src/Overview/index.tsx
  50. 113 0
      pioneer/packages/joy-tokenomics/src/tokenomicsGroupsData.ts
  51. 1 1
      pioneer/packages/joy-tokenomics/src/translate.ts
  52. 6 3
      pioneer/packages/joy-utils/src/consts/workingGroups.ts
  53. 1 1
      pioneer/packages/joy-utils/src/functions/misc.ts
  54. 1 1
      pioneer/packages/joy-utils/src/react/hocs/JoyEasyForms.tsx
  55. 4 3
      pioneer/packages/joy-utils/src/transport/base.ts
  56. 4 2
      pioneer/packages/joy-utils/src/transport/proposals.ts
  57. 8 2
      pioneer/packages/joy-utils/src/transport/tokenomics.ts
  58. 4 1
      pioneer/packages/joy-utils/src/types/tokenomics.ts
  59. 11 11
      pioneer/packages/page-accounts/src/Accounts/Account.tsx
  60. 2 2
      pioneer/packages/page-accounts/src/Accounts/index.tsx
  61. 2 2
      pioneer/packages/page-accounts/src/Contacts/Address.tsx
  62. 1 1
      pioneer/packages/page-accounts/src/translate.ts
  63. 1 1
      pioneer/packages/page-dashboard/src/translate.ts
  64. 1 1
      pioneer/packages/page-explorer/src/translate.ts
  65. 1 1
      pioneer/packages/page-extrinsics/src/translate.ts
  66. 1 1
      pioneer/packages/page-generic-asset/src/translate.ts
  67. 1 1
      pioneer/packages/page-js/src/snippets/storage-examples.ts
  68. 1 1
      pioneer/packages/page-js/src/translate.ts
  69. 1 1
      pioneer/packages/page-poll/src/translate.ts
  70. 1 1
      pioneer/packages/page-settings/src/translate.ts
  71. 1 1
      pioneer/packages/page-society/src/translate.ts
  72. 2 2
      pioneer/packages/page-staking/src/Payouts/index.tsx
  73. 2 2
      pioneer/packages/page-staking/src/Targets/Validator.tsx
  74. 1 1
      pioneer/packages/page-staking/src/translate.ts
  75. 2 1
      pioneer/packages/page-staking/src/util.ts
  76. 10 17
      pioneer/packages/page-storage/src/Query.tsx
  77. 2 2
      pioneer/packages/page-storage/src/Selection/Consts.tsx
  78. 1 1
      pioneer/packages/page-storage/src/Selection/Modules.tsx
  79. 1 1
      pioneer/packages/page-storage/src/translate.ts
  80. 2 2
      pioneer/packages/page-sudo/src/Sudo.tsx
  81. 1 1
      pioneer/packages/page-sudo/src/translate.ts
  82. 1 1
      pioneer/packages/page-toolbox/src/translate.ts
  83. 2 2
      pioneer/packages/react-api/package.json
  84. 0 17
      pioneer/packages/react-api/src/util/getEnvironment.ts
  85. 2 3
      pioneer/packages/react-api/src/util/intervalObservable.ts
  86. 5 5
      pioneer/packages/react-components/package.json
  87. 3 3
      pioneer/packages/react-components/src/Expander.tsx
  88. 4 4
      pioneer/packages/react-components/src/Extrinsic.tsx
  89. 1 1
      pioneer/packages/react-components/src/InputConsts/index.tsx
  90. 2 2
      pioneer/packages/react-components/src/InputConsts/options/key.tsx
  91. 1 1
      pioneer/packages/react-components/src/InputExtrinsic/options/method.tsx
  92. 4 3
      pioneer/packages/react-components/src/InputStorage/options/key.tsx
  93. 2 2
      pioneer/packages/react-components/src/ProposedAction.tsx
  94. 1 1
      pioneer/packages/react-components/src/Status/types.ts
  95. 1 1
      pioneer/packages/react-components/src/TxButton.tsx
  96. 0 1
      pioneer/packages/react-components/src/i18n/index.ts
  97. 1 1
      pioneer/packages/react-components/src/translate.ts
  98. 1 1
      pioneer/packages/react-hooks/src/translate.ts
  99. 2 1
      pioneer/packages/react-hooks/src/useAccountInfo.ts
  100. 12 8
      pioneer/packages/react-hooks/src/useCall.ts

+ 4 - 4
cli/package.json

@@ -18,7 +18,7 @@
     "@oclif/plugin-help": "^3.2.2",
     "@oclif/plugin-not-found": "^1.2.4",
     "@oclif/plugin-warn-if-update-available": "^1.7.0",
-    "@polkadot/api": "4.2.1",
+    "@polkadot/api": "5.9.1",
     "@types/cli-progress": "^3.9.1",
     "@types/fluent-ffmpeg": "^2.1.16",
     "@types/inquirer": "^6.5.0",
@@ -48,7 +48,7 @@
   "devDependencies": {
     "@oclif/dev-cli": "^1.22.2",
     "@oclif/test": "^1.2.5",
-    "@polkadot/ts": "^0.3.62",
+    "@polkadot/ts": "^0.4.8",
     "@types/chai": "^4.2.11",
     "@types/mocha": "^5.2.7",
     "@types/node": "^10.17.18",
@@ -60,8 +60,8 @@
     "json-schema-to-typescript": "^9.1.1",
     "mocha": "^5.2.0",
     "nyc": "^14.1.1",
-    "ts-node": "^8.8.2",
-    "typescript": "^3.8.3"
+    "ts-node": "^10.2.1",
+    "typescript": "^4.4.3"
   },
   "engines": {
     "node": ">=14.0.0",

+ 2 - 2
metadata-protobuf/package.json

@@ -53,7 +53,7 @@
     "eslint": "^7.6.0",
     "mocha": "^8.2.1",
     "prettier": "2.0.2",
-    "ts-node": "^8.8.1",
-    "typescript": "^4.1.3"
+    "ts-node": "^10.2.1",
+    "typescript": "^4.4.3"
   }
 }

+ 16 - 12
package.json

@@ -27,22 +27,26 @@
     "utils/api-scripts",
     "query-node",
     "query-node/mappings",
-    "query-node/generated/*",
+    "query-node/generated/graphql-server",
     "metadata-protobuf"
   ],
   "resolutions": {
-    "@polkadot/api": "4.2.1",
-    "@polkadot/api-contract": "4.2.1",
-    "@polkadot/keyring": "6.0.5",
-    "@polkadot/metadata": "4.2.1",
-    "@polkadot/types": "4.2.1",
-    "@polkadot/util": "6.0.5",
-    "@polkadot/util-crypto": "6.0.5",
-    "@polkadot/wasm-crypto": "^4.0.2",
+    "@polkadot/api": "5.9.1",
+    "@polkadot/api-contract": "5.9.1",
+    "@polkadot/keyring": "7.3.1",
+    "@polkadot/types": "5.9.1",
+    "@polkadot/types-known": "5.9.1",
+    "@polkadot/util": "7.3.1",
+    "@polkadot/util-crypto": "7.3.1",
+    "@polkadot/api-derive": "5.9.1",
+    "@polkadot/rpc-core": "5.9.1",
+    "@polkadot/rpc-provider": "5.9.1",
+    "@polkadot/x-global": "7.3.1",
+    "@polkadot/networks": "7.3.1",
     "babel-core": "^7.0.0-bridge.0",
-    "typescript": "^3.9.7",
-    "bn.js": "^5.1.2",
-    "rxjs": "^6.6.2",
+    "typescript": "^4.4.3",
+    "bn.js": "4.12.0",
+    "rxjs": "^7.4.0",
     "typeorm": "^0.2.31",
     "pg": "^8.4.0"
   },

+ 0 - 1
pioneer/README.md

@@ -11,7 +11,6 @@ This can be accessed as a hosted application via [https://testnet.joystream.org]
 The repo is split into a number of packages, each representing an application. These are -
 
 - [apps](packages/apps/) This is the main entry point. It handles the selection sidebar and routing to the specific application being displayed.
-- [apps-electron](packages/apps-electron/) Desktop app running [apps](packages/apps/).
 - [page-accounts](packages/page-accounts/) A basic account management app.
 - [page-address-book](packages/page-address-book/) A basic address management app.
 - [page-explorer](packages/page-explorer/) A simple block explorer. It only shows the most recent blocks, updating as they become available.

+ 2 - 5
pioneer/package.json

@@ -35,7 +35,7 @@
     "@babel/runtime": "^7.10.5",
     "@pinata/sdk": "^1.1.10",
     "@polkadot/dev": "^0.55.28",
-    "@polkadot/ts": "^0.3.62",
+    "@polkadot/ts": "^0.4.7",
     "@storybook/addon-actions": "^5.2.5",
     "@storybook/addon-console": "^1.2.1",
     "@storybook/addon-knobs": "^5.2.5",
@@ -57,9 +57,6 @@
     "concurrently": "^5.2.0",
     "devtron": "^1.4.0",
     "dnslink-cloudflare": "^2.0.4",
-    "electron": "^9.1.1",
-    "electron-builder": "^22.8.0",
-    "electron-builder-notarize": "^1.2.0",
     "eslint-plugin-header": "^3.0.0",
     "eslint-plugin-sort-destructure-keys": "^1.3.5",
     "i18next-scanner": "^2.11.0",
@@ -75,7 +72,7 @@
     "terser-webpack-plugin": "^3.0.7",
     "ts-jest": "^26.2.0",
     "tsconfig-paths-webpack-plugin": "^3.2.0",
-    "typescript": "^3.9.7",
+    "typescript": "^4.4.3",
     "webpack": "^4.44.0",
     "webpack-cli": "^3.3.12",
     "webpack-merge": "^4.2.2",

+ 0 - 3
pioneer/packages/apps-electron/.gitignore

@@ -1,3 +0,0 @@
-build
-node_modules
-release

+ 0 - 0
pioneer/packages/apps-electron/.skip-build


+ 0 - 0
pioneer/packages/apps-electron/.skip-npm


+ 0 - 3
pioneer/packages/apps-electron/README.md

@@ -1,3 +0,0 @@
-# @polkadot/apps-electron
-
-WARNING: This is not deemed stable yet for external use.

+ 0 - 12
pioneer/packages/apps-electron/appleEntitlements/entitlements.mac.plist

@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-	<dict>
-		<key>com.apple.security.cs.allow-jit</key>
-		<true/>
-		<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
-		<true/>
-		<key>com.apple.security.cs.allow-dyld-environment-variables</key>
-		<true/>
-	</dict>
-</plist>

BIN
pioneer/packages/apps-electron/assets/icon.png


+ 0 - 27
pioneer/packages/apps-electron/package.json

@@ -1,27 +0,0 @@
-{
-  "name": "@polkadot/apps-electron",
-  "main": "index.js",
-  "private": true,
-  "version": "0.51.1",
-  "dependencies": {
-    "@babel/core": "^7.10.5",
-    "@babel/polyfill": "^7.10.4",
-    "@polkadot/dev": "^0.55.28",
-    "@polkadot/react-components": "0.51.1",
-    "electron-log": "^4.2.2",
-    "electron-updater": "^4.3.4"
-  },
-  "devDependencies": {
-    "@types/react": "^16.9.43",
-    "@types/react-dom": "^16.9.8",
-    "@types/tmp": "^0.2.0",
-    "babel-loader": "^8.1.0",
-    "copy-webpack-plugin": "^6.0.3",
-    "html-webpack-plugin": "^4.3.0",
-    "react": "^16.13.1",
-    "react-dom": "^16.13.1",
-    "thread-loader": "^2.1.3",
-    "tmp": "^0.2.1",
-    "webpack": "^4.44.0"
-  }
-}

+ 0 - 12
pioneer/packages/apps-electron/src/api/account-store-api.ts

@@ -1,12 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { KeyringJson } from '@polkadot/ui-keyring/types';
-
-export interface AccountStoreApi {
-  all: () => Promise<{ key: string, value: KeyringJson }[]>
-  get: (key: string) => Promise<KeyringJson>
-  remove: (key: string) => Promise<void>
-  set: (key: string, value: KeyringJson) => Promise<void>
-}

+ 0 - 9
pioneer/packages/apps-electron/src/api/electron-main-api.ts

@@ -1,9 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { AccountStoreApi } from './account-store-api';
-
-export interface ElectronMainApi {
-  accountStore: AccountStoreApi
-}

+ 0 - 13
pioneer/packages/apps-electron/src/api/global-exported-api.ts

@@ -1,13 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { ElectronMainApi } from './electron-main-api';
-
-declare global {
-  interface Window {
-    ElectronMain: ElectronMainApi
-  }
-}
-
-export const electronMainApi = window.ElectronMain;

+ 0 - 19
pioneer/packages/apps-electron/src/electron/autoUpdater.ts

@@ -1,19 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { AppUpdater } from 'electron-updater';
-
-export async function setupAutoUpdater (): Promise<void> {
-  const { autoUpdater } = await import('electron-updater');
-
-  await setLogger(autoUpdater);
-  autoUpdater.checkForUpdatesAndNotify().catch(console.error);
-}
-
-async function setLogger (autoUpdater: AppUpdater): Promise<void> {
-  const log = await import('electron-log');
-
-  log.transports.file.level = 'debug';
-  autoUpdater.logger = log;
-}

+ 0 - 23
pioneer/packages/apps-electron/src/electron/contentSecurityPolicy.ts

@@ -1,23 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { HeadersReceivedResponse, session } from 'electron';
-
-export const setupContentSecurityPolicy = (environment: string): void => {
-  session.defaultSession.webRequest.onHeadersReceived((details, cb: (headersReceivedResponse: HeadersReceivedResponse) => void) => {
-    const headersReceivedResponse = {
-      responseHeaders: {
-        ...details.responseHeaders,
-        'Content-Security-Policy': [`default-src 'self' ${environment === 'development' ? "'unsafe-eval'" : ''};` +
-        " style-src-elem 'self' https://fonts.googleapis.com/css 'unsafe-inline';" +
-        " font-src data: 'self' https://fonts.gstatic.com;" +
-        " style-src 'unsafe-inline';" +
-        " connect-src 'self' wss:;" +
-        " img-src 'self' data:"]
-      }
-    };
-
-    cb(headersReceivedResponse);
-  });
-};

+ 0 - 27
pioneer/packages/apps-electron/src/electron/index.ts

@@ -1,27 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { app, shell } from 'electron';
-import { registerAccountStoreHandlers } from '../main/account-store';
-import { setupAutoUpdater } from './autoUpdater';
-import { setupContentSecurityPolicy } from './contentSecurityPolicy';
-import { createWindow } from './window';
-
-const ENV = process.env.NODE_ENV || 'production';
-
-const onReady = async () => {
-  registerAccountStoreHandlers();
-  setupContentSecurityPolicy(ENV);
-  await createWindow(ENV);
-  await setupAutoUpdater();
-};
-
-app.on('web-contents-created', (e, webContents) => {
-  webContents.on('new-window', (e, url) => {
-    e.preventDefault();
-    shell.openExternal(url).catch(console.error);
-  });
-});
-
-app.whenReady().then(onReady).catch(console.error);

+ 0 - 31
pioneer/packages/apps-electron/src/electron/window.ts

@@ -1,31 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { BrowserWindow, screen } from 'electron';
-import path from 'path';
-
-export function createWindow (environment: string): Promise<unknown> {
-  const { height, width } = screen.getPrimaryDisplay().workAreaSize;
-
-  const win = new BrowserWindow({
-    height,
-    webPreferences: {
-      contextIsolation: true,
-      enableRemoteModule: false,
-      nodeIntegration: false,
-      preload: path.join(__dirname, 'preload.js')
-    },
-    width
-  });
-
-  if (environment === 'development') {
-    win.webContents.openDevTools();
-
-    return win.loadURL('http://0.0.0.0:3000/');
-  }
-
-  const mainFilePath = path.resolve(__dirname, 'index.html');
-
-  return win.loadFile(mainFilePath);
-}

+ 0 - 56
pioneer/packages/apps-electron/src/index.tsx

@@ -1,56 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-// setup these right at front
-import '@polkadot/apps/initSettings';
-import 'semantic-ui-css/semantic.min.css';
-import '@polkadot/react-components/i18n';
-
-import React, { Suspense } from 'react';
-import ReactDOM from 'react-dom';
-import { HashRouter } from 'react-router-dom';
-import { ThemeProvider } from 'styled-components';
-import { Api } from '@polkadot/react-api';
-import Apps from '@polkadot/apps/Apps';
-import WindowDimensions from '@polkadot/apps/WindowDimensions';
-import Queue from '@polkadot/react-components/Status/Queue';
-import { BlockAuthors, Events } from '@polkadot/react-query';
-import settings from '@polkadot/ui-settings';
-
-import { electronMainApi } from './api/global-exported-api';
-import { RemoteElectronStore } from './renderer/remote-electron-store';
-
-const rootId = 'root';
-const rootElement = document.getElementById(rootId);
-const theme = { theme: settings.uiTheme };
-
-const store = new RemoteElectronStore(electronMainApi.accountStore);
-
-if (!rootElement) {
-  throw new Error(`Unable to find element with id '${rootId}'`);
-}
-
-ReactDOM.render(
-  <Suspense fallback='...'>
-    <ThemeProvider theme={theme}>
-      <Queue>
-        <Api
-          store={store}
-          url={settings.apiUrl}
-        >
-          <BlockAuthors>
-            <Events>
-              <HashRouter>
-                <WindowDimensions>
-                  <Apps />
-                </WindowDimensions>
-              </HashRouter>
-            </Events>
-          </BlockAuthors>
-        </Api>
-      </Queue>
-    </ThemeProvider>
-  </Suspense>,
-  rootElement
-);

+ 0 - 60
pioneer/packages/apps-electron/src/main/account-store.spec.ts

@@ -1,60 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { FileStore } from '@polkadot/ui-keyring/stores';
-import { accountStoreIpcHandler } from './account-store';
-import { KeyringJson } from '@polkadot/ui-keyring/types';
-import { IpcMainHandler } from './ipc-main-handler';
-import * as tmp from 'tmp';
-
-const exampleAccount = (address: string): KeyringJson => ({
-  address,
-  meta: {}
-});
-
-describe('Account store', () => {
-  let accountStore: IpcMainHandler;
-  let tmpDir: tmp.DirResult;
-
-  beforeEach(() => {
-    tmpDir = tmp.dirSync({ unsafeCleanup: true });
-    accountStore = accountStoreIpcHandler(new FileStore(tmpDir.name));
-  });
-
-  afterEach(() => {
-    tmpDir.removeCallback();
-  });
-
-  it('all returns empty array at first', () => {
-    expect(accountStore['account-store-all']()).toEqual([]);
-  });
-
-  it('after adding accounts, they become visible', async () => {
-    await accountStore['account-store-set']('1', exampleAccount('a'));
-    await accountStore['account-store-set']('2', exampleAccount('b'));
-
-    expect(accountStore['account-store-all']()).toEqual([{
-      key: '1', value: exampleAccount('a')
-    }, {
-      key: '2', value: exampleAccount('b')
-    }]);
-  });
-
-  it('get returns account if exists', async () => {
-    await accountStore['account-store-set']('1', exampleAccount('a'));
-    expect(await accountStore['account-store-get']('1')).toEqual(exampleAccount('a'));
-  });
-
-  it('get returns null if account does not exist', async () => {
-    expect(await accountStore['account-store-get']('1')).toEqual(null);
-  });
-
-  it('account disappears from list after it is removed', async () => {
-    await accountStore['account-store-set']('1', exampleAccount('a'));
-    await accountStore['account-store-remove']('1');
-
-    expect(await accountStore['account-store-get']('1')).toEqual(null);
-    expect(accountStore['account-store-all']()).toEqual([]);
-  });
-});

+ 0 - 47
pioneer/packages/apps-electron/src/main/account-store.ts

@@ -1,47 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { app } from 'electron';
-import { FileStore } from '@polkadot/ui-keyring/stores';
-import { KeyringJson } from '@polkadot/ui-keyring/types';
-import path from 'path';
-
-import { IpcMainHandler } from './ipc-main-handler';
-import { registerIpcHandler } from './register-ipc-handler';
-
-const ACCOUNTS_SUBFOLDER = 'polkadot-accounts';
-
-export const accountStoreIpcHandler = (fileStore: FileStore): IpcMainHandler => ({
-  'account-store-all': () => {
-    let result: { key: string, value: KeyringJson }[] = [];
-
-    const collect = (key: string, value: KeyringJson) => {
-      result = [...result, { key, value }];
-    };
-
-    fileStore.all(collect);
-
-    return result;
-  },
-  'account-store-get': async (key: string) => new Promise((resolve) => {
-    try {
-      fileStore.get(key, resolve);
-    } catch (err) {
-      resolve(null);
-    }
-  }),
-  'account-store-remove': async (key: string) => new Promise((resolve) =>
-    fileStore.remove(key, resolve)
-  ),
-  'account-store-set': async (key: string, value: KeyringJson) => new Promise((resolve) =>
-    fileStore.set(key, value, resolve)
-  )
-});
-
-export const registerAccountStoreHandlers = (): void => {
-  const defaultStorePath = path.join(app.getPath('userData'), ACCOUNTS_SUBFOLDER);
-  const fileStore = new FileStore(defaultStorePath);
-
-  registerIpcHandler(accountStoreIpcHandler(fileStore));
-};

+ 0 - 7
pioneer/packages/apps-electron/src/main/ipc-main-handler.ts

@@ -1,7 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-export type IpcMainHandler = {
-  [channel: string]: (...args: any[]) => Promise<void> | any
-}

+ 0 - 14
pioneer/packages/apps-electron/src/main/register-ipc-handler.ts

@@ -1,14 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { ipcMain } from 'electron';
-import { IpcMainHandler } from './ipc-main-handler';
-
-export const registerIpcHandler = (ipcHandler: IpcMainHandler): void => {
-  for (const [channel, listener] of Object.entries(ipcHandler)) {
-    ipcMain.handle(channel, (_, ...args): void => {
-      listener(...args);
-    });
-  }
-};

+ 0 - 15
pioneer/packages/apps-electron/src/preload.ts

@@ -1,15 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { KeyringJson } from '@polkadot/ui-keyring/types';
-import { contextBridge, ipcRenderer } from 'electron';
-
-contextBridge.exposeInMainWorld('ElectronMain', {
-  accountStore: {
-    all: () => ipcRenderer.invoke('account-store-all'),
-    get: (key: string) => ipcRenderer.invoke('account-store-get', key),
-    remove: (key: string) => ipcRenderer.invoke('account-store-remove', key),
-    set: (key: string, value: KeyringJson) => ipcRenderer.invoke('account-store-set', key, value)
-  }
-});

+ 0 - 90
pioneer/packages/apps-electron/src/renderer/remote-electron-store.spec.ts

@@ -1,90 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { RemoteElectronStore } from './remote-electron-store';
-
-describe('Remote Electron Store', () => {
-  const accountStore = {
-    all: jest.fn(),
-    get: jest.fn(),
-    remove: jest.fn(),
-    set: jest.fn()
-  };
-  const remoteStore = new RemoteElectronStore(accountStore);
-
-  beforeEach(() => {
-    accountStore.all.mockClear();
-    accountStore.get.mockClear();
-    accountStore.remove.mockClear();
-    accountStore.set.mockClear();
-  });
-
-  describe('all', () => {
-    it('calls callback for each returned account', async () => {
-      accountStore.all.mockResolvedValue([{
-        key: 1,
-        value: 'a'
-      }, {
-        key: 2,
-        value: 'b'
-      }]);
-      const cb = jest.fn();
-
-      remoteStore.all(cb);
-      await Promise.resolve();
-
-      expect(cb).nthCalledWith(1, 1, 'a');
-      expect(cb).nthCalledWith(2, 2, 'b');
-    });
-  });
-
-  describe('get', () => {
-    it('calls callback with returned account', async () => {
-      accountStore.get.mockResolvedValue('a');
-      const cb = jest.fn();
-
-      remoteStore.get('1', cb);
-      await Promise.resolve();
-
-      expect(accountStore.get).toBeCalledWith('1');
-      expect(cb).toBeCalledWith('a');
-    });
-
-    it('calls callback with null if no accounts found', async () => {
-      accountStore.get.mockResolvedValue(null);
-      const cb = jest.fn();
-
-      remoteStore.get('1', cb);
-      await Promise.resolve();
-
-      expect(cb).toBeCalledWith(null);
-    });
-  });
-
-  describe('remove', () => {
-    it('calls callback after success', async () => {
-      accountStore.remove.mockResolvedValue(null);
-      const cb = jest.fn();
-
-      remoteStore.remove('1', cb);
-      await Promise.resolve();
-
-      expect(accountStore.remove).toBeCalledWith('1');
-      expect(cb).toBeCalledTimes(1);
-    });
-  });
-
-  describe('set', () => {
-    it('calls callback after success', async () => {
-      accountStore.set.mockResolvedValue(null);
-      const cb = jest.fn();
-
-      remoteStore.set('1', 'a' as any, cb);
-      await Promise.resolve();
-
-      expect(accountStore.set).toBeCalledWith('1', 'a');
-      expect(cb).toBeCalledTimes(1);
-    });
-  });
-});

+ 0 - 41
pioneer/packages/apps-electron/src/renderer/remote-electron-store.ts

@@ -1,41 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-import { KeyringJson, KeyringStore } from '@polkadot/ui-keyring/types';
-import { AccountStoreApi } from '../api/account-store-api';
-
-export class RemoteElectronStore implements KeyringStore {
-  readonly #accountStore: AccountStoreApi;
-
-  constructor (accountStore: AccountStoreApi) {
-    this.#accountStore = accountStore;
-  }
-
-  all (cb: (key: string, value: KeyringJson) => void): void {
-    this.#accountStore.all()
-      .then((result: { key: string, value: KeyringJson }[]) => result?.forEach(({ key, value }) => cb(key, value)))
-      .catch((e: Error) => {
-        throw new Error(`error getting all accounts: ${e.message}`);
-      });
-  }
-
-  get (key: string, cb: (value: KeyringJson) => void): void {
-    this.#accountStore.get(key)
-      .then(cb).catch((e: Error) => {
-        throw new Error(`error storing account: ${e.message}`);
-      });
-  }
-
-  remove (key: string, cb: (() => void) | undefined): void {
-    this.#accountStore.remove(key).then(cb).catch((e: Error) => {
-      throw new Error(`error removing account: ${e.message}`);
-    });
-  }
-
-  set (key: string, value: KeyringJson, cb: (() => void) | undefined): void {
-    this.#accountStore.set(key, value).then(cb).catch((e: Error) => {
-      throw new Error(`error saving account: ${e.message}`);
-    });
-  }
-}

+ 0 - 55
pioneer/packages/apps-electron/webpack.main.config.js

@@ -1,55 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-/* eslint-disable camelcase */
-
-const TerserPlugin = require('terser-webpack-plugin');
-const path = require('path');
-
-const ENV = process.env.NODE_ENV || 'production';
-const isProd = ENV === 'production';
-
-function createWebpack () {
-  return [
-    {
-      entry: {
-        electron: './src/electron',
-        preload: './src/preload.ts'
-      },
-      mode: ENV,
-      module: {
-        rules: [
-          {
-            exclude: /(node_modules)/,
-            test: /\.(js|ts|tsx)$/,
-            use: [
-              require.resolve('thread-loader'),
-              {
-                loader: require.resolve('babel-loader'),
-                options: require('@polkadot/dev/config/babel')
-              }
-            ]
-          }
-        ]
-      },
-      node: {
-        __dirname: false
-      },
-      optimization: {
-        minimize: !!isProd,
-        minimizer: [new TerserPlugin()]
-      },
-      output: {
-        filename: '[name].js',
-        path: path.join(__dirname, '/build')
-      },
-      resolve: {
-        extensions: ['.js', '.jsx', '.json', '.ts', '.tsx']
-      },
-      target: 'electron-main'
-    }
-  ];
-}
-
-module.exports = createWebpack();

+ 0 - 32
pioneer/packages/apps-electron/webpack.renderer.config.js

@@ -1,32 +0,0 @@
-// Copyright 2017-2020 @polkadot/apps authors & contributors
-// This software may be modified and distributed under the terms
-// of the Apache-2.0 license. See the LICENSE file for details.
-
-/* eslint-disable camelcase */
-
-const path = require('path');
-const merge = require('webpack-merge');
-const CopyWebpackPlugin = require('copy-webpack-plugin');
-const HtmlWebpackPlugin = require('html-webpack-plugin');
-const baseConfig = require('@polkadot/apps/webpack.base.config');
-
-const ENV = process.env.NODE_ENV || 'production';
-const isProd = ENV === 'production';
-const context = __dirname;
-
-module.exports = merge(
-  baseConfig(ENV, context),
-  {
-    devtool: isProd ? 'none' : 'source-map',
-    plugins: [
-      // It must be placed before HtmlWebpackPlugin
-      new CopyWebpackPlugin({ patterns: [{ from: '../apps/public' }] }),
-      new HtmlWebpackPlugin({
-        PAGE_TITLE: 'Polkadot/Substrate Portal',
-        inject: true,
-        template: path.join(context, '../apps/public/index.html')
-      })
-    ],
-    target: 'web'
-  }
-);

+ 1 - 1
pioneer/packages/apps-routing/src/joy-roles.ts

@@ -7,7 +7,7 @@ export default function create (t: <T = string> (key: string, text: string, opti
     Component: Roles,
     display: {
       needsApi: [
-        'query.contentDirectoryWorkingGroup.mint',
+        'query.contentWorkingGroup.mint',
         'query.storageWorkingGroup.mint'
       ]
     },

+ 2 - 2
pioneer/packages/apps/src/SideBar/ChainInfo.tsx

@@ -19,8 +19,8 @@ interface Props {
 
 function ChainInfo ({ className = '', onClick }: Props): React.ReactElement<Props> {
   const { t } = useTranslation();
-  const { api } = useApi();
-  const runtimeVersion = useCall<RuntimeVersion>(api.rpc.state.subscribeRuntimeVersion, []);
+  const { api, isApiReady } = useApi();
+  const runtimeVersion = useCall<RuntimeVersion>(isApiReady && api.rpc.state.subscribeRuntimeVersion, []);
 
   return (
     <div

+ 1 - 1
pioneer/packages/apps/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<['apps', 'apps-routing']> {
   return useTranslationBase(['apps', 'apps-routing']);
 }

+ 1 - 1
pioneer/packages/joy-election/src/VoteForm.tsx

@@ -175,7 +175,7 @@ class Component extends React.PureComponent<Props, State> {
     this.setState({ isFormSubmitted: true });
   }
 
-  private onTxFailed: TxFailedCallback = (_txResult: SubmittableResult | null): void => {
+  private onTxFailed: TxFailedCallback = (_txResult: SubmittableResult | Error | null): void => {
     // TODO Possible UX improvement: tell a user that his vote hasn't been accepted.
   }
 

+ 1 - 1
pioneer/packages/joy-forum/src/EditCategory.tsx

@@ -89,7 +89,7 @@ const InnerForm = (props: FormProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
 
     if (txResult == null) {

+ 1 - 1
pioneer/packages/joy-forum/src/EditReply.tsx

@@ -85,7 +85,7 @@ const InnerForm = (props: FormProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
 
     if (txResult == null) {

+ 1 - 1
pioneer/packages/joy-forum/src/EditThread.tsx

@@ -94,7 +94,7 @@ const InnerForm = (props: FormProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
 
     if (txResult == null) {

+ 1 - 1
pioneer/packages/joy-forum/src/Moderate.tsx

@@ -71,7 +71,7 @@ const InnerForm = (props: FormProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
 
     if (txResult == null) {

+ 1 - 1
pioneer/packages/joy-members/src/EditForm.tsx

@@ -84,7 +84,7 @@ const InnerForm = (props: FormProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
 
     if (txResult == null) {

+ 6 - 6
pioneer/packages/joy-proposals/src/Proposal/Body.tsx

@@ -16,7 +16,7 @@ import { formatBalance } from '@polkadot/util';
 import PromiseComponent from '@polkadot/joy-utils/react/components/PromiseComponent';
 import ReactMarkdown from 'react-markdown';
 import { StakingPolicy } from '@joystream/types/hiring';
-import { WorkingGroup } from '@joystream/types/common';
+import { WorkingGroup, WorkingGroupKey } from '@joystream/types/common';
 import { ApplicationsDetailsByOpening } from '@polkadot/joy-utils/react/components/working-groups/ApplicationDetails';
 import { LeadInfoFromId } from '@polkadot/joy-utils/react/components/working-groups/LeadInfo';
 import { formatReward } from '@polkadot/joy-utils/functions/format';
@@ -269,7 +269,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
         : <ApplicationsDetailsByOpening
           openingId={openingId.toNumber()}
           acceptedIds={[succesfulApplicationId.toNumber()]}
-          group={workingGroup.type}/>,
+          group={workingGroup.type as WorkingGroupKey}/>,
       true
     )
   ],
@@ -280,7 +280,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -291,7 +291,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -302,7 +302,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
       'Lead',
       historical
         ? `#${(leadId as WorkerId).toNumber()}`
-        : <LeadInfoFromId group={(group as WorkingGroup).type} leadId={(leadId as WorkerId).toNumber()}/>,
+        : <LeadInfoFromId group={(group as WorkingGroup).type as WorkingGroupKey} leadId={(leadId as WorkerId).toNumber()}/>,
       true
     )
   ],
@@ -321,7 +321,7 @@ const paramParsers: { [k in ProposalType]: (params: SpecificProposalDetails<k>,
         'Lead',
         historical
           ? `#${leadId.toNumber()}`
-          : <LeadInfoFromId group={workingGroup.type} leadId={leadId.toNumber()}/>,
+          : <LeadInfoFromId group={workingGroup.type as WorkingGroupKey} leadId={leadId.toNumber()}/>,
         true
       )
     ];

+ 1 - 1
pioneer/packages/joy-proposals/src/Proposal/discussion/DiscussionPostForm.tsx

@@ -47,7 +47,7 @@ const DiscussionPostFormInner = (props: InnerProps) => {
     if (isValid) sendTx();
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
   };
 

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

@@ -151,7 +151,7 @@ export const GenericProposalForm: React.FunctionComponent<GenericFormInnerProps>
     setAfterSubmit(() => sendTx);
   };
 
-  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+  const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
     setSubmitting(false);
   };
 

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

@@ -45,7 +45,17 @@ export type FormInnerProps = ProposalFormInnerProps<FormContainerProps, FormValu
 
 const availableGroupsOptions = Object.keys(WorkingGroupDef)
   .filter((wgKey) => wgKey !== 'Gateway') // Gateway group not yet supported!
-  .map((wgKey) => ({ text: wgKey + ' Working Group', value: wgKey }));
+  .map((wgKey) => {
+    let text = `${wgKey} Working Group`;
+
+    if (wgKey.toLowerCase().includes('operations')) {
+      const workingGroupType = wgKey.slice('operations'.length);
+
+      text = `Operations Working Group ${workingGroupType}`;
+    }
+
+    return { text, value: wgKey };
+  });
 
 export const GenericWorkingGroupProposalForm: React.FunctionComponent<FormInnerProps> = (props) => {
   const {

+ 12 - 3
pioneer/packages/joy-roles/src/tabs/WorkingGroup.controller.tsx

@@ -9,14 +9,20 @@ import { AvailableGroups } from '../working_groups';
 import { WorkingGroupMembership,
   ContentCurators,
   StorageProviders,
-  OperationsGroup } from './WorkingGroup';
+  OperationsGroupAlpha,
+  OperationsGroupBeta,
+  OperationsGroupGamma,
+  Distribution } from './WorkingGroup';
 
 import styled from 'styled-components';
 
 type State = {
   curators?: WorkingGroupMembership;
   storageProviders?: WorkingGroupMembership;
-  operationsGroup?: WorkingGroupMembership;
+  operationsGroupAlpha?: WorkingGroupMembership;
+  operationsGroupBeta?: WorkingGroupMembership;
+  operationsGroupGamma?: WorkingGroupMembership;
+  distribution?: WorkingGroupMembership;
 }
 
 export class WorkingGroupsController extends Controller<State, ITransport> {
@@ -56,7 +62,10 @@ export const WorkingGroupsView = View<WorkingGroupsController, State>(
     <WorkingGroupsOverview>
       <ContentCurators {...state.curators}/>
       <StorageProviders {...state.storageProviders}/>
-      <OperationsGroup {...state.operationsGroup}/>
+      <OperationsGroupAlpha {...state.operationsGroupAlpha}/>
+      <OperationsGroupBeta {...state.operationsGroupBeta}/>
+      <OperationsGroupGamma {...state.operationsGroupGamma}/>
+      <Distribution {...state.distribution}/>
     </WorkingGroupsOverview>
   )
 );

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

@@ -73,6 +73,10 @@ type GroupOverviewProps = GroupOverviewOuterProps & {
   customBecomeLeadDesc?: string;
 }
 
+interface OperationsGroupProps extends GroupOverviewOuterProps{
+  group: WorkingGroups;
+}
+
 const GroupOverview = Loadable<GroupOverviewProps>(
   ['workers', 'leadStatus'],
   ({
@@ -142,9 +146,8 @@ export const StorageProviders = (props: GroupOverviewOuterProps) => (
   />
 );
 
-export const OperationsGroup = (props: GroupOverviewOuterProps) => (
+export const OperationsGroup = (props: OperationsGroupProps) => (
   <GroupOverview
-    group={WorkingGroups.Operations}
     description={
       <span>
         {"Operations Working Group encompases all the activites that don't require privilages on chain, for example:"}
@@ -159,6 +162,38 @@ export const OperationsGroup = (props: GroupOverviewOuterProps) => (
   />
 );
 
+export const OperationsGroupAlpha = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsAlpha}
+    {...props}
+  />
+);
+
+export const OperationsGroupBeta = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsBeta}
+    {...props}
+  />
+);
+
+export const OperationsGroupGamma = (props: GroupOverviewOuterProps) => (
+  <OperationsGroup
+    group={WorkingGroups.OperationsGamma}
+    {...props}
+  />
+);
+
+export const Distribution = (props: GroupOverviewOuterProps) => (
+  <GroupOverview
+    group={WorkingGroups.Distribution}
+    description={
+      'Distribution Working Group is responsible for running and maintaining distributor nodes' +
+      ' that deliver large volumes of upstream data to a large number of simultaneous end users.'
+    }
+    {...props}
+  />
+);
+
 const LeadSection = styled.div`
   margin-top: 1rem;
 `;

+ 5 - 2
pioneer/packages/joy-roles/src/transport.substrate.ts

@@ -51,8 +51,11 @@ type StakePair<T = Balance> = {
 
 const apiModuleByGroup = {
   [WorkingGroups.StorageProviders]: 'storageWorkingGroup',
-  [WorkingGroups.ContentCurators]: 'contentDirectoryWorkingGroup',
-  [WorkingGroups.Operations]: 'operationsWorkingGroup'
+  [WorkingGroups.ContentCurators]: 'contentWorkingGroup',
+  [WorkingGroups.OperationsAlpha]: 'operationsWorkingGroupAlpha',
+  [WorkingGroups.OperationsBeta]: 'operationsWorkingGroupBeta',
+  [WorkingGroups.OperationsGamma]: 'operationsWorkingGroupGamma',
+  [WorkingGroups.Distribution]: 'distributionWorkingGroup'
 } as const;
 
 export class Transport extends BaseTransport implements ITransport {

+ 12 - 3
pioneer/packages/joy-roles/src/working_groups.ts

@@ -1,17 +1,26 @@
 export enum WorkingGroups {
   ContentCurators = 'curators',
   StorageProviders = 'storageProviders',
-  Operations = 'operationsGroup'
+  OperationsAlpha = 'operationsGroupAlpha',
+  OperationsBeta = 'operationsGroupBeta',
+  OperationsGamma = 'operationsGroupGamma',
+  Distribution = 'distribution'
 }
 
 export const AvailableGroups: readonly WorkingGroups[] = [
   WorkingGroups.ContentCurators,
   WorkingGroups.StorageProviders,
-  WorkingGroups.Operations
+  WorkingGroups.OperationsAlpha,
+  WorkingGroups.OperationsBeta,
+  WorkingGroups.OperationsGamma,
+  WorkingGroups.Distribution
 ] as const;
 
 export const workerRoleNameByGroup: { [key in WorkingGroups]: string } = {
   [WorkingGroups.ContentCurators]: 'Content Curator',
   [WorkingGroups.StorageProviders]: 'Storage Provider',
-  [WorkingGroups.Operations]: 'Operations Group Worker'
+  [WorkingGroups.OperationsAlpha]: 'Operations Group Alpha Worker',
+  [WorkingGroups.OperationsBeta]: 'Operations Group Beta Worker',
+  [WorkingGroups.OperationsGamma]: 'Operations Group Gamma Worker',
+  [WorkingGroups.Distribution]: 'Distribution'
 };

+ 62 - 106
pioneer/packages/joy-tokenomics/src/Overview/SpendingAndStakeDistributionTable.tsx

@@ -5,7 +5,7 @@ import { useWindowDimensions } from '../../../joy-utils/src/react/hooks';
 
 import { TokenomicsData, StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
 
-import { COLORS } from './index';
+import { NON_WORKING_GROUPS, WORKING_GROUPS } from '../tokenomicsGroupsData';
 
 const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
 
@@ -121,25 +121,24 @@ const SpendingAndStakeTableRow: React.FC<{
   );
 };
 
-type TokenomicsGroup =
-  'validators' |
-  'council' |
-  'storageProviders' |
-  'contentCurators' |
-  'operations'
+type TokenomicsGroup = typeof WORKING_GROUPS[number]['groupType'] | typeof NON_WORKING_GROUPS[number]['groupType'];
 
 const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
   const { width } = useWindowDimensions();
 
   const displayStatusData = (group: TokenomicsGroup, dataType: 'rewardsPerWeek' | 'totalStake', lead = false): string | undefined => {
-    if ((group === 'storageProviders' || group === 'contentCurators') && lead) {
+    if (WORKING_GROUPS.map(({ groupType }) => groupType).includes(group as any) && lead) {
       return statusData === null
         ? 'Data currently unavailable...'
-        : (data && statusData) && `${(data[group].lead[dataType] * Number(statusData.price)).toFixed(2)}`;
+        : data &&
+            statusData &&
+            `${(
+              data[group as typeof WORKING_GROUPS[number]['groupType']].lead[dataType] * Number(statusData.price)
+            ).toFixed(2)}`;
     } else {
       return statusData === null
         ? 'Data currently unavailable...'
-        : (data && statusData) && `${(data[group][dataType] * Number(statusData.price)).toFixed(2)}`;
+        : data && statusData && `${(data[group][dataType] * Number(statusData.price)).toFixed(2)}`;
     }
   };
 
@@ -160,102 +159,59 @@ const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; status
       </Table.Header>
 
       <Table.Body>
-        <SpendingAndStakeTableRow
-          role={width <= 1050 ? 'Validators' : 'Validators (Nominators)'}
-          helpContent='The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).'
-          numberOfActors={data && `${data.validators.number} (${data.validators.nominators.number})`}
-          groupEarning={data && `${Math.round(data.validators.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('validators', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.validators.rewardsShare * 100)}`}
-          groupStake={data && `${data.validators.totalStake}`}
-          groupStakeDollar={displayStatusData('validators', 'totalStake')}
-          stakeShare={data && `${round(data.validators.stakeShare * 100)}`}
-          color={COLORS.VALIDATOR}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Council' : 'Council Members'}
-          helpContent='The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).'
-          numberOfActors={data && `${data.council.number}`}
-          groupEarning={data && `${Math.round(data.council.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('council', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.council.rewardsShare * 100)}`}
-          groupStake={data && `${data.council.totalStake}`}
-          groupStakeDollar={displayStatusData('council', 'totalStake')}
-          stakeShare={data && `${round(data.council.stakeShare * 100)}`}
-          color={COLORS.COUNCIL_MEMBER}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Storage' : 'Storage Providers'}
-          helpContent='The current Storage Providers, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.storageProviders.number}`}
-          groupEarning={data && `${Math.round(data.storageProviders.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.storageProviders.rewardsShare * 100)}`}
-          groupStake={data && `${data.storageProviders.totalStake}`}
-          groupStakeDollar={displayStatusData('storageProviders', 'totalStake')}
-          stakeShare={data && `${round(data.storageProviders.stakeShare * 100)}`}
-          color={COLORS.STORAGE_PROVIDER}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'S. Lead' : width <= 1050 ? 'Storage Lead' : 'Storage Provider Lead'}
-          helpContent='Current Storage Provider Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.storageProviders.lead.number}`}
-          groupEarning={data && `${Math.round(data.storageProviders.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.storageProviders.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.storageProviders.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('storageProviders', 'totalStake', true)}
-          stakeShare={data && `${round(data.storageProviders.lead.stakeShare * 100)}`}
-          color={COLORS.STORAGE_LEAD}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'Curators' : 'Content Curators'}
-          helpContent='The current Content Curators, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.contentCurators.number}`}
-          groupEarning={data && `${Math.round(data.contentCurators.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.contentCurators.rewardsShare * 100)}`}
-          groupStake={data && `${data.contentCurators.totalStake}`}
-          groupStakeDollar={displayStatusData('contentCurators', 'totalStake')}
-          stakeShare={data && `${round(data.contentCurators.stakeShare * 100)}`}
-          color={COLORS.CONTENT_CURATOR}
-        />
-        <SpendingAndStakeTableRow
-          role={width <= 1015 ? 'C. Lead' : 'Curators Lead'}
-          helpContent='Current Content Curators Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.contentCurators.lead.number}`}
-          groupEarning={data && `${Math.round(data.contentCurators.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.contentCurators.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.contentCurators.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('contentCurators', 'totalStake', true)}
-          stakeShare={data && `${round(data.contentCurators.lead.stakeShare * 100)}`}
-          color={COLORS.CURATOR_LEAD}
-        />
-        <SpendingAndStakeTableRow
-          role='Operations'
-          helpContent='The current Operations members, and the sum of their projected rewards and stakes.'
-          numberOfActors={data && `${data.operations.number}`}
-          groupEarning={data && `${Math.round(data.operations.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('operations', 'rewardsPerWeek')}
-          earningShare={data && `${round(data.operations.rewardsShare * 100)}`}
-          groupStake={data && `${data.operations.totalStake}`}
-          groupStakeDollar={displayStatusData('operations', 'totalStake')}
-          stakeShare={data && `${round(data.operations.stakeShare * 100)}`}
-          color={COLORS.OPERATIONS}
-        />
-        <SpendingAndStakeTableRow
-          role='Operations Lead'
-          helpContent='Current Operations Lead, and their projected reward and stake.'
-          numberOfActors={data && `${data.operations.lead.number}`}
-          groupEarning={data && `${Math.round(data.operations.lead.rewardsPerWeek)}`}
-          groupEarningDollar={displayStatusData('operations', 'rewardsPerWeek', true)}
-          earningShare={data && `${round(data.operations.lead.rewardsShare * 100)}`}
-          groupStake={data && `${data.operations.lead.totalStake}`}
-          groupStakeDollar={displayStatusData('operations', 'totalStake', true)}
-          stakeShare={data && `${round(data.operations.lead.stakeShare * 100)}`}
-          color={COLORS.OPERATIONS_LEAD}
-        />
+        {NON_WORKING_GROUPS.map(({ groupType, titleCutoff, shortTitle, title, helpText, color }) => {
+          let numberOfActors = data && `${data[groupType].number}`;
+
+          if (groupType === 'validators' && data) {
+            numberOfActors = `${data.validators.number} (${data.validators.nominators.number})`;
+          }
+
+          return (
+            <SpendingAndStakeTableRow
+              key={groupType}
+              role={width <= titleCutoff ? shortTitle : title}
+              helpContent={helpText}
+              numberOfActors={numberOfActors}
+              groupEarning={data && `${Math.round(data[groupType].rewardsPerWeek)}`}
+              groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek')}
+              earningShare={data && `${round(data[groupType].rewardsShare * 100)}`}
+              groupStake={data && `${data[groupType].totalStake}`}
+              groupStakeDollar={displayStatusData(groupType, 'totalStake')}
+              stakeShare={data && `${round(data[groupType].stakeShare * 100)}`}
+              color={color}
+            />
+          );
+        })}
+        {WORKING_GROUPS.map(({ groupType, titleCutoff, shortTitle, title, helpText, color, lead }) => {
+          return (
+            <React.Fragment key={groupType}>
+              <SpendingAndStakeTableRow
+                role={width <= titleCutoff ? shortTitle : title}
+                helpContent={helpText}
+                numberOfActors={data && `${data[groupType].number}`}
+                groupEarning={data && `${Math.round(data[groupType].rewardsPerWeek)}`}
+                groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek')}
+                earningShare={data && `${round(data[groupType].rewardsShare * 100)}`}
+                groupStake={data && `${data[groupType].totalStake}`}
+                groupStakeDollar={displayStatusData(groupType, 'totalStake')}
+                stakeShare={data && `${round(data[groupType].stakeShare * 100)}`}
+                color={color}
+              />
+              <SpendingAndStakeTableRow
+                role={width <= lead.titleCutoff ? lead.shortTitle : lead.title}
+                helpContent={lead.helpText}
+                numberOfActors={data && `${data[groupType].lead.number}`}
+                groupEarning={data && `${Math.round(data[groupType].lead.rewardsPerWeek)}`}
+                groupEarningDollar={displayStatusData(groupType, 'rewardsPerWeek', true)}
+                earningShare={data && `${round(data[groupType].lead.rewardsShare * 100)}`}
+                groupStake={data && `${data[groupType].lead.totalStake}`}
+                groupStakeDollar={displayStatusData(groupType, 'totalStake', true)}
+                stakeShare={data && `${round(data[groupType].lead.stakeShare * 100)}`}
+                color={lead.color}
+              />
+            </React.Fragment>
+          );
+        })}
         <SpendingAndStakeTableRow
           role='TOTAL'
           active={true}

+ 58 - 83
pioneer/packages/joy-tokenomics/src/Overview/TokenomicsCharts.tsx

@@ -4,7 +4,8 @@ import PieChart from '../../../react-components/src/Chart/PieChart';
 import styled from 'styled-components';
 
 import { TokenomicsData } from '@polkadot/joy-utils/src/types/tokenomics';
-import { COLORS } from './index';
+
+import { WORKING_GROUPS, NON_WORKING_GROUPS } from '../tokenomicsGroupsData';
 
 const StyledPieChart = styled(PieChart)`
   width:15rem;
@@ -29,88 +30,62 @@ const ChartContainer = styled('div')`
 const TokenomicsCharts: React.FC<{data?: TokenomicsData; className?: string}> = ({ data, className }) => {
   return (
     <div className={className}>
-      {data ? <ChartContainer>
-        <StyledPieChart
-          values={[{
-            colors: [COLORS.VALIDATOR],
-            label: 'Validators',
-            value: data.validators.rewardsShare * 100
-          }, {
-            colors: [COLORS.COUNCIL_MEMBER],
-            label: 'Council',
-            value: data.council.rewardsShare * 100
-          }, {
-            colors: [COLORS.STORAGE_PROVIDER],
-            label: 'Storage Providers',
-            value: data.storageProviders.rewardsShare * 100
-          }, {
-            colors: [COLORS.STORAGE_LEAD],
-            label: 'Storage Lead',
-            value: data.storageProviders.lead.rewardsShare * 100
-          }, {
-            colors: [COLORS.CONTENT_CURATOR],
-            label: 'Content Curators',
-            value: data.contentCurators.rewardsShare * 100
-          }, {
-            colors: [COLORS.CURATOR_LEAD],
-            label: 'Content Curators Lead',
-            value: data.contentCurators.lead.rewardsShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS],
-            label: 'Operations',
-            value: data.operations.rewardsShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS_LEAD],
-            label: 'Operations Lead',
-            value: data.operations.lead.rewardsShare * 100
-          }
-          ]} />
-        <Label as='div'>
-          <Icon name='money' />
-          <span style={{ fontWeight: 600 }}>Spending</span>
-        </Label>
-      </ChartContainer> : <Icon name='circle notched' loading/>}
-      {data ? <ChartContainer>
-        <StyledPieChart
-          values={[{
-            colors: [COLORS.VALIDATOR],
-            label: 'Validators',
-            value: data.validators.stakeShare * 100
-          }, {
-            colors: [COLORS.COUNCIL_MEMBER],
-            label: 'Council',
-            value: data.council.stakeShare * 100
-          }, {
-            colors: [COLORS.STORAGE_PROVIDER],
-            label: 'Storage Providers',
-            value: data.storageProviders.stakeShare * 100
-          }, {
-            colors: [COLORS.STORAGE_LEAD],
-            label: 'Storage Lead',
-            value: data.storageProviders.lead.stakeShare * 100
-          }, {
-            colors: [COLORS.CONTENT_CURATOR],
-            label: 'Content Curators',
-            value: data.contentCurators.stakeShare * 100
-          }, {
-            colors: [COLORS.CURATOR_LEAD],
-            label: 'Content Curators Lead',
-            value: data.contentCurators.lead.stakeShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS],
-            label: 'Operations',
-            value: data.operations.stakeShare * 100
-          }, {
-            colors: [COLORS.OPERATIONS_LEAD],
-            label: 'Operations Lead',
-            value: data.operations.lead.stakeShare * 100
-          }
-          ]} />
-        <Label as='div'>
-          <Icon name='block layout' />
-          <span style={{ fontWeight: 600 }}>Stake</span>
-        </Label>
-      </ChartContainer> : <Icon name='circle notched' loading/>}
+      {data ? (
+        <ChartContainer>
+          <StyledPieChart
+            values={[
+              ...WORKING_GROUPS.map(({ color, title, groupType, lead }) => [{
+                colors: [color],
+                label: title,
+                value: data[groupType].rewardsShare * 100
+              }, {
+                colors: [lead.color],
+                label: lead.title,
+                value: data[groupType].lead.rewardsShare * 100
+              }]).flat(),
+              ...NON_WORKING_GROUPS.map(({ color, shortTitle, groupType }) => ({
+                colors: [color],
+                label: shortTitle,
+                value: data[groupType].rewardsShare * 100
+              }))
+            ]}
+          />
+          <Label as='div'>
+            <Icon name='money' />
+            <span style={{ fontWeight: 600 }}>Spending</span>
+          </Label>
+        </ChartContainer>
+      ) : (
+        <Icon name='circle notched' loading />
+      )}
+      {data ? (
+        <ChartContainer>
+          <StyledPieChart
+            values={[
+              ...WORKING_GROUPS.map(({ color, title, groupType, lead }) => [{
+                colors: [color],
+                label: title,
+                value: data[groupType].stakeShare * 100
+              }, {
+                colors: [lead.color],
+                label: lead.title,
+                value: data[groupType].lead.stakeShare * 100
+              }]).flat(),
+              ...NON_WORKING_GROUPS.map(({ color, shortTitle, groupType }) => ({
+                colors: [color],
+                label: shortTitle,
+                value: data[groupType].stakeShare * 100
+              }))
+            ]}
+          />
+          <Label as='div'>
+            <Icon name='block layout' />
+            <span style={{ fontWeight: 600 }}>Stake</span>
+          </Label>
+        </ChartContainer>
+      ) : (
+        <Icon name='circle notched' loading />
+      )}
     </div>
   );
 };

+ 0 - 12
pioneer/packages/joy-tokenomics/src/Overview/index.tsx

@@ -38,17 +38,6 @@ const StyledTokenomicsCharts = styled(TokenomicsCharts)`
   }
 `;
 
-const COLORS = {
-  VALIDATOR: '#ff9800',
-  COUNCIL_MEMBER: '#ffc107',
-  STORAGE_PROVIDER: '#ffeb3b',
-  STORAGE_LEAD: '#cddc39',
-  CONTENT_CURATOR: '#8bc34a',
-  CURATOR_LEAD: '#4caf50',
-  OPERATIONS: '#009688',
-  OPERATIONS_LEAD: '#00bcd4'
-};
-
 const Overview: React.FC = () => {
   const transport = useTransport();
   const [statusDataValue, statusDataError] = usePromise<StatusServerData | undefined>(() => fetch('https://status.joystream.org/status').then((res) => res.json().then((data) => data as StatusServerData)), undefined, []);
@@ -68,4 +57,3 @@ const Overview: React.FC = () => {
 };
 
 export default Overview;
-export { COLORS };

+ 113 - 0
pioneer/packages/joy-tokenomics/src/tokenomicsGroupsData.ts

@@ -0,0 +1,113 @@
+export const NON_WORKING_GROUPS = [
+  {
+    groupType: 'validators' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Validators',
+    title: 'Validators (Nominators)',
+    helpText:
+      'The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).',
+    color: '#ff9800'
+  },
+  {
+    groupType: 'council' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Council',
+    title: 'Council Members',
+    helpText:
+      'The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).',
+    color: '#ffc107'
+  }
+];
+
+export const WORKING_GROUPS = [
+  {
+    groupType: 'storageProviders' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Storage',
+    title: 'Storage Providers',
+    helpText: 'The current Storage Providers, and the sum of their projected rewards and stakes.',
+    color: '#ffeb3b',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'S. Lead',
+      title: 'Storage Lead',
+      helpText: 'Current Storage Provider Lead, and their projected reward and stake.',
+      color: '#cddc39'
+    }
+  },
+  {
+    groupType: 'contentCurators' as const,
+    titleCutoff: 1015,
+    shortTitle: 'Curators',
+    title: 'Content Curators',
+    helpText: 'The current Content Curators, and the sum of their projected rewards and stakes.',
+    color: '#8bc34a',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'C. Lead',
+      title: 'Curators Lead',
+      helpText: 'Current Content Curators Lead, and their projected reward and stake.',
+      color: '#4caf50'
+    }
+  },
+  {
+    groupType: 'operationsAlpha' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations A.',
+    title: 'Operations Alpha',
+    helpText: 'The current Operations Group Alpha members, and the sum of their projected rewards and stakes.',
+    color: '#009688',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations A. Lead',
+      title: 'Operations Alpha Lead',
+      helpText: 'Current Operations Group Alpha Lead, and their projected reward and stake.',
+      color: '#00bcd4'
+    }
+  },
+  {
+    groupType: 'operationsBeta' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations B.',
+    title: 'Operations Beta',
+    helpText: 'The current Operations Group Beta members, and the sum of their projected rewards and stakes.',
+    color: '#03a9f4',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations B. Lead',
+      title: 'Operations Beta Lead',
+      helpText: 'Current Operations Group Beta Lead, and their projected reward and stake.',
+      color: '#2196f3'
+    }
+  },
+  {
+    groupType: 'operationsGamma' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Operations G.',
+    title: 'Operations Gamma',
+    helpText: 'The current Operations Group Gamma members, and the sum of their projected rewards and stakes.',
+    color: '#3f51b5',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Operations G. Lead',
+      title: 'Operations Gamma Lead',
+      helpText: 'Current Operations Group Gamma Lead, and their projected reward and stake.',
+      color: '#673ab7'
+    }
+  },
+  {
+    groupType: 'distribution' as const,
+    titleCutoff: 1050,
+    shortTitle: 'Distribution',
+    title: 'Distribution',
+    helpText: 'The current Distribution Group members, and the sum of their projected rewards and stakes.',
+    color: '#9c27b0',
+    lead: {
+      titleCutoff: 1015,
+      shortTitle: 'Distr. Lead',
+      title: 'Distribution Lead',
+      helpText: 'Current Distribution Group Lead, and their projected reward and stake.',
+      color: '#e91e63'
+    }
+  }
+];

+ 1 - 1
pioneer/packages/joy-tokenomics/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'joy-tokenomics'> {
   return useTranslationBase('joy-tokenomics');
 }

+ 6 - 3
pioneer/packages/joy-utils/src/consts/workingGroups.ts

@@ -1,7 +1,10 @@
 import { WorkingGroupKey } from '@joystream/types/common';
 export const apiModuleByGroup: { [k in WorkingGroupKey]: string } = {
   Storage: 'storageWorkingGroup',
-  Content: 'contentDirectoryWorkingGroup',
-  Operations: 'operationsWorkingGroup',
-  Gateway: 'gatewayWorkingGroup'
+  Content: 'contentWorkingGroup',
+  OperationsAlpha: 'operationsWorkingGroupAlpha',
+  OperationsBeta: 'operationsWorkingGroupBeta',
+  OperationsGamma: 'operationsWorkingGroupGamma',
+  Gateway: 'gatewayWorkingGroup',
+  Distribution: 'distributionWorkingGroup'
 };

+ 1 - 1
pioneer/packages/joy-utils/src/functions/misc.ts

@@ -168,7 +168,7 @@ export function bytesToString (bytes: Bytes) {
 }
 
 // Based on: https://fettblog.eu/typescript-hasownproperty/
-export function isObjectWithProperties<X extends unknown, Y extends PropertyKey[]> (input: X, ...props: Y): input is X & Record<Y[number], unknown> {
+export function isObjectWithProperties<X, Y extends PropertyKey[]> (input: X, ...props: Y): input is X & Record<Y[number], unknown> {
   return typeof input === 'object' && input !== null && props.every((prop) => prop in input);
 }
 

+ 1 - 1
pioneer/packages/joy-utils/src/react/hocs/JoyEasyForms.tsx

@@ -154,7 +154,7 @@ export function withEasyForm<OuterProps, FormValues>
         setSubmitting(false);
       };
 
-      const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | null) => {
+      const onTxFailed: TxFailedCallback = (txResult: SubmittableResult | Error | null) => {
         setSubmitting(false);
 
         if (txResult === null) {

+ 4 - 3
pioneer/packages/joy-utils/src/transport/base.ts

@@ -1,14 +1,15 @@
 import { ApiPromise } from '@polkadot/api';
 import { UInt } from '@polkadot/types/codec';
-import { Codec, CodecArg } from '@polkadot/types/types';
+import { Codec } from '@polkadot/types/types';
 import { QueryableStorageEntry } from '@polkadot/api/types/storage';
 import { APIQueryCache } from './APIQueryCache';
 
 export async function entriesByIds<IDType extends UInt, ValueType extends Codec> (
   apiMethod: QueryableStorageEntry<'promise'>,
-  firstKey?: CodecArg // First key in case of double maps
+  firstKey?: unknown // First key in case of double maps
 ): Promise<[IDType, ValueType][]> {
-  const entries: [IDType, ValueType][] = (await apiMethod.entries<ValueType>(firstKey))
+  const storageEntries = firstKey ? await apiMethod.entries<ValueType>(firstKey) : await apiMethod.entries<ValueType>();
+  const entries: [IDType, ValueType][] = storageEntries
     .map(([storageKey, value]) => ([
       // If double-map (first key is provided), we map entries by second key
       storageKey.args[firstKey !== undefined ? 1 : 0] as IDType,

+ 4 - 2
pioneer/packages/joy-utils/src/transport/proposals.ts

@@ -19,6 +19,7 @@ import { ThreadId, PostId } from '@joystream/types/common';
 import { Proposal, ProposalId, VoteKind, DiscussionThread, DiscussionPost, ProposalDetails, ProposalStatus } from '@joystream/types/proposals';
 import { MemberId } from '@joystream/types/members';
 import { u32, Bytes, Null } from '@polkadot/types/';
+import { Callback, Codec } from '@polkadot/types/types';
 import { BalanceOf } from '@polkadot/types/interfaces';
 
 import { bytesToString } from '../functions/misc';
@@ -26,6 +27,7 @@ import _ from 'lodash';
 import { metadata as proposalsConsts, apiMethods as proposalsApiMethods } from '../consts/proposals';
 
 import { ApiPromise } from '@polkadot/api';
+import { UnsubscribePromise } from '@polkadot/api/types';
 import MembersTransport from './members';
 import ChainTransport from './chain';
 import CouncilTransport from './council';
@@ -247,8 +249,8 @@ export default class ProposalsTransport extends BaseTransport {
     return Promise.all(ProposalTypes.map((type) => this.parametersFromProposalType(type)));
   }
 
-  async subscribeProposal (id: number|ProposalId, callback: () => void) {
-    return this.api.query.proposalsEngine.proposals(id, callback);
+  async subscribeProposal (id: number|ProposalId, callback: Callback<Proposal>): UnsubscribePromise {
+    return (this.api.query.proposalsEngine.proposals as { <T extends Codec>(arg: unknown, callback: Callback<T>): UnsubscribePromise })<Proposal>(id, callback);
   }
 
   async discussion (id: number|ProposalId): Promise<ParsedDiscussion | null> {

+ 8 - 2
pioneer/packages/joy-utils/src/transport/tokenomics.ts

@@ -228,7 +228,10 @@ export default class TokenomicsTransport extends BaseTransport {
     const workingGroupsData = {
       storageProviders: await this.getWorkingGroupData('Storage'),
       curators: await this.getWorkingGroupData('Content'),
-      operations: await this.getWorkingGroupData('Operations')
+      operationsAlpha: await this.getWorkingGroupData('OperationsAlpha'),
+      operationsBeta: await this.getWorkingGroupData('OperationsBeta'),
+      operationsGamma: await this.getWorkingGroupData('OperationsGamma'),
+      distribution: await this.getWorkingGroupData('Distribution')
     };
     const { numberOfValidators, numberOfNominators, totalValidatorStake, validatorRewardsPerWeek, totalIssuance } =
       await this.getValidatorData();
@@ -293,7 +296,10 @@ export default class TokenomicsTransport extends BaseTransport {
       },
       storageProviders: resolveGroupData(workingGroupsData.storageProviders),
       contentCurators: resolveGroupData(workingGroupsData.curators),
-      operations: resolveGroupData(workingGroupsData.operations)
+      operationsAlpha: resolveGroupData(workingGroupsData.operationsAlpha),
+      operationsBeta: resolveGroupData(workingGroupsData.operationsBeta),
+      operationsGamma: resolveGroupData(workingGroupsData.operationsGamma),
+      distribution: resolveGroupData(workingGroupsData.distribution)
     };
   }
 }

+ 4 - 1
pioneer/packages/joy-utils/src/types/tokenomics.ts

@@ -37,7 +37,10 @@ export type TokenomicsData = {
   };
   storageProviders: WorkingGroupTokenomicsData;
   contentCurators: WorkingGroupTokenomicsData;
-  operations: WorkingGroupTokenomicsData;
+  operationsAlpha: WorkingGroupTokenomicsData;
+  operationsBeta: WorkingGroupTokenomicsData;
+  operationsGamma: WorkingGroupTokenomicsData;
+  distribution: WorkingGroupTokenomicsData;
 }
 
 export type StatusServerData = {

+ 11 - 11
pioneer/packages/page-accounts/src/Accounts/Account.tsx

@@ -18,7 +18,7 @@ import { AddressInfo, AddressMini, AddressSmall, Badge, Button, CryptoType, Forg
 import { useAccountInfo, useApi, useCall, useToggle } from '@polkadot/react-hooks';
 import { Option } from '@polkadot/types';
 import keyring from '@polkadot/ui-keyring';
-import { BN_ZERO, formatBalance, formatNumber } from '@polkadot/util';
+import { BN_ZERO, formatBalance, formatNumber, isFunction } from '@polkadot/util';
 
 import { useTranslation } from '../translate';
 import { createMenuGroup } from '../util';
@@ -361,9 +361,9 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
         )}
       </td>
       <td className='address ui--media-1400'>
-        {meta.parentAddress && (
+        {meta.parentAddress ? (
           <AddressMini value={meta.parentAddress} />
-        )}
+        ) : ''}
       </td>
       <td className='number'>
         <CryptoType accountId={address} />
@@ -407,7 +407,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
             vertical
           >
             {createMenuGroup([
-              api.api.tx.identity?.setIdentity && (
+              isFunction(api.api.tx.identity?.setIdentity) && (
                 <Menu.Item
                   key='identityMain'
                   onClick={toggleIdentityMain}
@@ -415,7 +415,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                   {t('Set on-chain identity')}
                 </Menu.Item>
               ),
-              api.api.tx.identity?.setSubs && identity?.display && (
+              isFunction(api.api.tx.identity?.setSubs) && identity?.display && (
                 <Menu.Item
                   key='identitySub'
                   onClick={toggleIdentitySub}
@@ -423,7 +423,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                   {t('Set on-chain sub-identities')}
                 </Menu.Item>
               ),
-              api.api.tx.democracy?.unlock && democracyUnlockTx && (
+              isFunction(api.api.tx.democracy?.unlock) && democracyUnlockTx && (
                 <Menu.Item
                   key='clearDemocracy'
                   onClick={_clearDemocracyLocks}
@@ -431,7 +431,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                   {t('Clear expired democracy locks')}
                 </Menu.Item>
               ),
-              api.api.tx.vesting?.vest && vestingVestTx && (
+              isFunction(api.api.tx.vesting?.vest) && vestingVestTx && (
                 <Menu.Item
                   key='vestingVest'
                   onClick={_vestingVest}
@@ -484,7 +484,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                 </Menu.Item>
               )
             ])}
-            {api.api.tx.recovery?.createRecovery && createMenuGroup([
+            {isFunction(api.api.tx.recovery?.createRecovery) && createMenuGroup([
               !recoveryInfo && (
                 <Menu.Item
                   key='makeRecoverable'
@@ -500,7 +500,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                 {t('Initiate recovery for another')}
               </Menu.Item>
             ])}
-            {api.api.tx.multisig?.asMulti && isMultisig && createMenuGroup([
+            { isFunction(api.api.tx.multisig?.asMulti) && isMultisig && createMenuGroup([
               <Menu.Item
                 disabled={!multiInfos || !multiInfos.length}
                 key='multisigApprovals'
@@ -509,7 +509,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                 {t('Multisig approvals')}
               </Menu.Item>
             ])}
-            {api.api.query.democracy?.votingOf && delegation?.accountDelegated && createMenuGroup([
+            { isFunction(api.api.query.democracy?.votingOf) && delegation?.accountDelegated && createMenuGroup([
               <Menu.Item
                 key='changeDelegate'
                 onClick={toggleDelegate}
@@ -523,7 +523,7 @@ function Account ({ account: { address, meta }, className = '', delegation, filt
                 {t('Undelegate')}
               </Menu.Item>
             ])}
-            {api.api.query.democracy?.votingOf && !delegation?.accountDelegated && createMenuGroup([
+            {isFunction(api.api.query.democracy?.votingOf) && !delegation?.accountDelegated && createMenuGroup([
               <Menu.Item
                 key='delegate'
                 onClick={toggleDelegate}

+ 2 - 2
pioneer/packages/page-accounts/src/Accounts/index.tsx

@@ -14,7 +14,7 @@ import { getLedger, isLedger } from '@polkadot/react-api';
 import { useApi, useAccounts, useCall, useFavorites, useIpfs, useToggle } from '@polkadot/react-hooks';
 import { FormatBalance } from '@polkadot/react-query';
 import { Button, Input, Table } from '@polkadot/react-components';
-import { BN_ZERO } from '@polkadot/util';
+import { BN_ZERO, isFunction } from '@polkadot/util';
 
 import { useTranslation } from '../translate';
 import CreateModal from './modals/Create';
@@ -81,7 +81,7 @@ function Overview ({ className = '', onStatusChange }: Props): React.ReactElemen
   }, [allAccounts, favorites]);
 
   useEffect(() => {
-    if (api.query.democracy?.votingOf && !delegations?.length) {
+    if (isFunction(api.query.democracy?.votingOf) && !delegations?.length) {
       return;
     }
 

+ 2 - 2
pioneer/packages/page-accounts/src/Contacts/Address.tsx

@@ -11,7 +11,7 @@ import styled from 'styled-components';
 import { AddressSmall, AddressInfo, Button, ChainLock, Icon, LinkExternal, Forget, Menu, Popup, Tags } from '@polkadot/react-components';
 import { useApi, useCall } from '@polkadot/react-hooks';
 import keyring from '@polkadot/ui-keyring';
-import { BN_ZERO, formatNumber } from '@polkadot/util';
+import { BN_ZERO, formatNumber, isFunction } from '@polkadot/util';
 
 import Transfer from '../Accounts/modals/Transfer';
 import { useTranslation } from '../translate';
@@ -58,7 +58,7 @@ function Address ({ address, className = '', filter, isFavorite, toggleFavorite
   useEffect((): void => {
     const { identity, nickname } = info || {};
 
-    if (api.api.query.identity && api.api.query.identity.identityOf) {
+    if (api.api.query.identity && isFunction(api.api.query.identity.identityOf)) {
       if (identity?.display) {
         setAccName(identity.display);
       }

+ 1 - 1
pioneer/packages/page-accounts/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-accounts'> {
   return useTranslationBase('app-accounts');
 }

+ 1 - 1
pioneer/packages/page-dashboard/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-dashboard'> {
   return useTranslationBase('app-dashboard');
 }

+ 1 - 1
pioneer/packages/page-explorer/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-explorer'> {
   return useTranslationBase('app-explorer');
 }

+ 1 - 1
pioneer/packages/page-extrinsics/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-extrinsics'> {
   return useTranslationBase('app-extrinsics');
 }

+ 1 - 1
pioneer/packages/page-generic-asset/src/translate.ts

@@ -4,7 +4,7 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse, withTranslation } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-generic-asset'> {
   return useTranslationBase('app-generic-asset');
 }
 

+ 1 - 1
pioneer/packages/page-js/src/snippets/storage-examples.ts

@@ -53,7 +53,7 @@ api.query.system.events((events) => {
     const types = event.typeDef;
     // show what we are busy with
     console.log(event.section + ':' + event.method + '::' + 'phase=' + phase.toString());
-    console.log(event.meta.documentation.toString());
+    console.log(event.meta.docs.toString());
     // loop through each of the parameters, displaying the type and data
     event.data.forEach((data, index) => {
       console.log(types[index].type + ';' + data.toString());

+ 1 - 1
pioneer/packages/page-js/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-js'> {
   return useTranslationBase('app-js');
 }

+ 1 - 1
pioneer/packages/page-poll/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-poll'> {
   return useTranslationBase('app-poll');
 }

+ 1 - 1
pioneer/packages/page-settings/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-settings'> {
   return useTranslationBase('app-settings');
 }

+ 1 - 1
pioneer/packages/page-society/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-society'> {
   return useTranslationBase('app-society');
 }

+ 2 - 2
pioneer/packages/page-staking/src/Payouts/index.tsx

@@ -184,7 +184,7 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement<P
 
   return (
     <div className={className}>
-      {api.tx.staking.payoutStakers && (
+      {isFunction(api.tx.staking.payoutStakers) && (
         <Button.Group>
           <PayToggle
             onChange={setEraSelectionIndex}
@@ -215,7 +215,7 @@ function Payouts ({ className = '', isInElection }: Props): React.ReactElement<P
           />
         ))}
       </Table>
-      {api.tx.staking.payoutStakers && !isLoadingRewards && validators && (validators.length !== 0) && (
+      {isFunction(api.tx.staking.payoutStakers) && !isLoadingRewards && validators && (validators.length !== 0) && (
         <Table
           header={headerValidators}
           isFixed

+ 2 - 2
pioneer/packages/page-staking/src/Targets/Validator.tsx

@@ -10,7 +10,7 @@ import { ApiPromise } from '@polkadot/api';
 import { AddressSmall, Badge, Checkbox, Icon } from '@polkadot/react-components';
 import { FormatBalance } from '@polkadot/react-query';
 import { useApi, useCall } from '@polkadot/react-hooks';
-import { formatNumber } from '@polkadot/util';
+import { formatNumber, isFunction } from '@polkadot/util';
 import keyring from '@polkadot/ui-keyring';
 
 import MaxBadge from '../MaxBadge';
@@ -34,7 +34,7 @@ function checkIdentity (api: ApiPromise, accountInfo: DeriveAccountInfo): boolea
 
   const { accountId, identity, nickname } = accountInfo;
 
-  if (api.query.identity && api.query.identity.identityOf) {
+  if (api.query.identity && isFunction(api.query.identity.identityOf)) {
     hasIdentity = !!(identity?.display && identity.display.toString());
   } else if (nickname) {
     hasIdentity = !!nickname.toString();

+ 1 - 1
pioneer/packages/page-staking/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-staking'> {
   return useTranslationBase('app-staking');
 }

+ 2 - 1
pioneer/packages/page-staking/src/util.ts

@@ -5,6 +5,7 @@
 import { DeriveAccountInfo } from '@polkadot/api-derive/types';
 
 import { ApiPromise } from '@polkadot/api';
+import { isFunction } from '@polkadot/util';
 import keyring from '@polkadot/ui-keyring';
 
 export function checkVisibility (api: ApiPromise, address: string, filterName: string, withIdentity: boolean, accountInfo?: DeriveAccountInfo): boolean {
@@ -17,7 +18,7 @@ export function checkVisibility (api: ApiPromise, address: string, filterName: s
 
       if (!withIdentity && (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName))) {
         isVisible = true;
-      } else if (api.query.identity && api.query.identity.identityOf) {
+      } else if (api.query.identity && isFunction(api.query.identity.identityOf)) {
         isVisible = (!!identity?.display && identity.display.toLowerCase().includes(filterLower)) ||
           (!!identity?.displayParent && identity.displayParent.toLowerCase().includes(filterLower));
       } else if (nickname) {

+ 10 - 17
pioneer/packages/page-storage/src/Query.tsx

@@ -15,6 +15,7 @@ import { withCallDiv } from '@polkadot/react-api/hoc';
 import valueToText from '@polkadot/react-params/valueToText';
 import { Option, Raw } from '@polkadot/types';
 import { isU8a, u8aToHex, u8aToString, compactStripLength } from '@polkadot/util';
+import { registry } from '@joystream/types';
 
 interface Props {
   className?: string;
@@ -52,7 +53,7 @@ function keyToName (isConst: boolean, _key: Uint8Array | QueryableStorageEntry<'
 }
 
 function typeToString ({ creator: { meta: { modifier, type } } }: QueryableStorageEntry<'promise'>): string {
-  const _type = unwrapStorageType(type);
+  const _type = unwrapStorageType(registry, type);
 
   return modifier.isOptional
     ? `Option<${_type}>`
@@ -105,22 +106,14 @@ function getCachedComponent (query: QueryTypes): CacheInstance {
             ? 1
             : 2;
 
-        if ((values.length === allCount) || (type.isMap && type.asMap.linked.isTrue)) {
-          // render function to create an element for the query results which is plugged to the api
-          renderHelper = withCallDiv('subscribe', {
-            paramName: 'params',
-            paramValid: true,
-            params: [key, ...values],
-            withIndicator: true
-          });
-        } else {
-          renderHelper = withCallDiv('subscribe', {
-            paramName: 'params',
-            paramValid: true,
-            params: [key.entries, ...values],
-            withIndicator: true
-          });
-        }
+        renderHelper = withCallDiv('subscribe', {
+          paramName: 'params',
+          paramValid: true,
+          params: values.length === allCount
+            ? [key, ...values]
+            : [key.entries, ...values],
+          withIndicator: true
+        });
       }
 
       type = key.creator && key.creator.meta

+ 2 - 2
pioneer/packages/page-storage/src/Selection/Consts.tsx

@@ -2,7 +2,7 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { ConstantCodec } from '@polkadot/metadata/decorate/types';
+import { ConstantCodec } from '@polkadot/types/metadata/decorate/types';
 import { ConstValue } from '@polkadot/react-components/InputConsts/types';
 import { ComponentProps as Props } from '../types';
 
@@ -40,7 +40,7 @@ function Consts ({ onAdd }: Props): React.ReactElement<Props> {
       <div className='storage--actionrow-value'>
         <InputConsts
           defaultValue={defaultValue}
-          help={meta?.documentation.join(' ')}
+          help={meta?.docs.join(' ')}
           label={t<string>('selected constant query')}
           onChange={setValue}
         />

+ 1 - 1
pioneer/packages/page-storage/src/Selection/Modules.tsx

@@ -142,7 +142,7 @@ function Modules ({ onAdd }: Props): React.ReactElement<Props> {
       <div className='storage--actionrow-value'>
         <InputStorage
           defaultValue={api.query.timestamp.now}
-          help={meta?.documentation.join(' ')}
+          help={meta?.docs.join(' ')}
           label={t<string>('selected state query')}
           onChange={_onChangeKey}
         />

+ 1 - 1
pioneer/packages/page-storage/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-storage'> {
   return useTranslationBase('app-storage');
 }

+ 2 - 2
pioneer/packages/page-sudo/src/Sudo.tsx

@@ -9,7 +9,7 @@ import React, { useCallback, useState } from 'react';
 import styled from 'styled-components';
 import { Button, Icon, Extrinsic, Toggle, TxButton, InputNumber } from '@polkadot/react-components';
 import { useApi, useToggle } from '@polkadot/react-hooks';
-import { BN_ZERO } from '@polkadot/util';
+import { BN_ZERO, isFunction } from '@polkadot/util';
 
 import { useTranslation } from './translate';
 
@@ -56,7 +56,7 @@ function Sudo ({ className, isMine, sudoKey }: Props): React.ReactElement<Props>
             value={weight}
           />
         )}
-        {api.tx.sudo.sudoUncheckedWeight && (
+        {isFunction(api.tx.sudo.sudoUncheckedWeight) && (
           <Toggle
             className='sudoToggle'
             label={

+ 1 - 1
pioneer/packages/page-sudo/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-sudo'> {
   return useTranslationBase('app-sudo');
 }

+ 1 - 1
pioneer/packages/page-toolbox/src/translate.ts

@@ -4,6 +4,6 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'app-toolbox'> {
   return useTranslationBase('app-toolbox');
 }

+ 2 - 2
pioneer/packages/react-api/package.json

@@ -31,8 +31,8 @@
   "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-reactive#readme",
   "dependencies": {
     "@babel/runtime": "^7.10.5",
-    "@polkadot/api": "4.2.1",
-    "@polkadot/extension-dapp": "^0.32.0-beta.10",
+    "@polkadot/api": "5.9.1",
+    "@polkadot/extension-dapp": "^0.39.4-11",
     "rxjs-compat": "^6.6.0"
   }
 }

+ 0 - 17
pioneer/packages/react-api/src/util/getEnvironment.ts

@@ -4,23 +4,6 @@
 
 import { Environment } from '../types';
 
-// https://github.com/electron/electron/issues/2288
-function isElectron () {
-  if (process?.versions?.electron) {
-    return true;
-  }
-
-  if (window?.process?.type === 'renderer') {
-    return true;
-  }
-
-  return navigator?.userAgent?.indexOf('Electron') >= 0;
-}
-
 export default function getEnvironment (): Environment {
-  if (isElectron()) {
-    return 'app';
-  }
-
   return 'web';
 }

+ 2 - 3
pioneer/packages/react-api/src/util/intervalObservable.ts

@@ -4,12 +4,11 @@
 
 import { CallState } from '../types';
 
-import { Subscription } from 'rxjs';
-import { interval } from 'rxjs/observable/interval';
+import { interval, Subscription } from 'rxjs';
 
 const interval$ = interval(500);
 
-export default function intervalObservable<T, Props, State extends CallState> (that: React.Component<Props, State>): Subscription {
+export default function intervalObservable<Props, State extends CallState> (that: React.Component<Props, State>): Subscription {
   return interval$.subscribe((): void => {
     const elapsed = Date.now() - ((that.state.callUpdatedAt as number) || 0);
     const callUpdated = elapsed <= 1500;

+ 5 - 5
pioneer/packages/react-components/package.json

@@ -14,15 +14,15 @@
     "@fortawesome/fontawesome-svg-core": "^1.2.30",
     "@fortawesome/free-solid-svg-icons": "^5.14.0",
     "@fortawesome/react-fontawesome": "^0.1.11",
-    "@polkadot/keyring": "^6.0.5",
+    "@polkadot/keyring": "7.3.1",
     "@polkadot/react-api": "0.51.1",
     "@polkadot/react-identicon": "^0.57.3",
     "@polkadot/react-qr": "^0.57.3",
     "@polkadot/react-query": "0.51.1",
-    "@polkadot/ui-keyring": "^0.72.1",
-    "@polkadot/ui-settings": "^0.72.1",
-    "@polkadot/util": "^6.0.5",
-    "@polkadot/util-crypto": "^6.0.5",
+    "@polkadot/ui-keyring": "^0.85",
+    "@polkadot/ui-settings": "^0.85",
+    "@polkadot/util": "7.3.1",
+    "@polkadot/util-crypto": "7.3.1",
     "chart.js": "^2.9.3",
     "codeflask": "^1.4.1",
     "i18next": "^19.6.3",

+ 3 - 3
pioneer/packages/react-components/src/Expander.tsx

@@ -11,7 +11,7 @@ import { useTranslation } from './translate';
 import Icon from './Icon';
 
 interface Meta {
-  documentation: Text[];
+  docs: Text[];
 }
 
 export interface Props {
@@ -26,11 +26,11 @@ export interface Props {
 }
 
 function formatMeta (meta?: Meta): React.ReactNode | null {
-  if (!meta || !meta.documentation.length) {
+  if (!meta || !meta.docs.length) {
     return null;
   }
 
-  const strings = meta.documentation.map((doc) => doc.toString().trim());
+  const strings = meta.docs.map((doc) => doc.toString().trim());
   const firstEmpty = strings.findIndex((doc) => !doc.length);
 
   return (

+ 4 - 4
pioneer/packages/react-components/src/Extrinsic.tsx

@@ -7,7 +7,7 @@ import { RawParam } from '@polkadot/react-params/types';
 import { TypeDef } from '@polkadot/types/types';
 
 import React, { useCallback, useEffect, useState } from 'react';
-import { GenericCall, getTypeDef } from '@polkadot/types';
+import { getTypeDef } from '@polkadot/types';
 import Params from '@polkadot/react-params';
 import { isUndefined } from '@polkadot/util';
 
@@ -37,7 +37,7 @@ interface CallState {
 }
 
 function getParams ({ meta }: SubmittableExtrinsicFunction<'promise'>): { name: string; type: TypeDef }[] {
-  return GenericCall.filterOrigin(meta).map((arg): { name: string; type: TypeDef } => ({
+  return meta.args.map((arg): { name: string; type: TypeDef } => ({
     name: arg.name.toString(),
     type: getTypeDef(arg.type.toString())
   }));
@@ -65,7 +65,7 @@ function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label
       try {
         method = extrinsic.fn(...values.map(({ value }): any => value));
       } catch (error) {
-        onError && onError(error);
+        onError && onError(error as Error);
       }
     } else {
       onError && onError(null);
@@ -85,7 +85,7 @@ function ExtrinsicDisplay ({ defaultValue, isDisabled, isError, isPrivate, label
     <div className='extrinsics--Extrinsic'>
       <InputExtrinsic
         defaultValue={defaultValue}
-        help={meta?.documentation.join(' ')}
+        help={meta?.docs.join(' ')}
         isDisabled={isDisabled}
         isError={isError}
         isPrivate={isPrivate}

+ 1 - 1
pioneer/packages/react-components/src/InputConsts/index.tsx

@@ -2,7 +2,7 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { ConstantCodec } from '@polkadot/metadata/decorate/types';
+import { ConstantCodec } from '@polkadot/types/metadata/decorate/types';
 import { DropdownOptions } from '../util/types';
 import { ConstValue, ConstValueBase } from './types';
 

+ 2 - 2
pioneer/packages/react-components/src/InputConsts/options/key.tsx

@@ -2,7 +2,7 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { ConstantCodec } from '@polkadot/metadata/decorate/types';
+import { ConstantCodec } from '@polkadot/types/metadata/decorate/types';
 import { DropdownOptions, DropdownOption } from '../../util/types';
 
 import React from 'react';
@@ -35,7 +35,7 @@ export default function createOptions (api: ApiPromise, sectionName: string): Dr
             className='ui--DropdownLinked-Item-text'
             key={`${sectionName}_${value}:text`}
           >
-            {(method.meta.documentation[0] || method.meta.name).toString()}
+            {(method.meta.docs[0] || method.meta.name).toString()}
           </div>
         ],
         value

+ 1 - 1
pioneer/packages/react-components/src/InputExtrinsic/options/method.tsx

@@ -38,7 +38,7 @@ export default function createOptions (api: ApiPromise, sectionName: string): Dr
             className='ui--DropdownLinked-Item-text'
             key={`${sectionName}_${value}:text`}
           >
-            {(method.meta.documentation[0] || value).toString()}
+            {(method.meta.docs[0] || value).toString()}
           </div>
         ],
         value

+ 4 - 3
pioneer/packages/react-components/src/InputStorage/options/key.tsx

@@ -8,6 +8,7 @@ import React from 'react';
 
 import { ApiPromise } from '@polkadot/api';
 import { unwrapStorageType } from '@polkadot/types/primitive/StorageKey';
+import { registry } from '@joystream/types';
 
 export default function createOptions (api: ApiPromise, sectionName: string): DropdownOptions {
   const section = api.query[sectionName];
@@ -28,8 +29,8 @@ export default function createOptions (api: ApiPromise, sectionName: string): Dr
           ? `${type.asDoubleMap.key1.toString()}, ${type.asDoubleMap.key2.toString()}`
           : '';
       const output = method.meta.modifier.isOptional
-        ? `Option<${unwrapStorageType(type)}>`
-        : unwrapStorageType(type);
+        ? `Option<${unwrapStorageType(registry, type)}>`
+        : unwrapStorageType(registry, type);
 
       return {
         className: 'ui--DropdownLinked-Item',
@@ -45,7 +46,7 @@ export default function createOptions (api: ApiPromise, sectionName: string): Dr
             className='ui--DropdownLinked-Item-text'
             key={`${sectionName}_${value}:text`}
           >
-            {(method.meta.documentation[0] || method.meta.name).toString()}
+            {(method.meta.docs[0] || method.meta.name).toString()}
           </div>
         ],
         value

+ 2 - 2
pioneer/packages/react-components/src/ProposedAction.tsx

@@ -54,9 +54,9 @@ function ProposedAction ({ asInset, className = '', expandNested, idNumber, inse
   const { meta, method, section } = registry.findMetaCall(proposal.callIndex);
 
   const header = `#${stringId}: ${section}.${method}`;
-  const documentation = meta?.documentation
+  const documentation = meta?.docs
     ? (
-      <summary>{meta.documentation.join(' ')}</summary>
+      <summary>{meta.docs.join(' ')}</summary>
     )
     : null;
   const params = (isTreasuryProposalVote(proposal) && expandNested)

+ 1 - 1
pioneer/packages/react-components/src/Status/types.ts

@@ -27,7 +27,7 @@ export type SignerCallback = (id: number, result: SignerResult | null) => void;
 
 export type TxCallback = (status: SubmittableResult) => void;
 
-export type TxFailedCallback = (status: SubmittableResult | null) => void;
+export type TxFailedCallback = (status: Error | SubmittableResult | null) => void;
 
 export interface QueueTx extends AccountInfo {
   error?: Error;

+ 1 - 1
pioneer/packages/react-components/src/TxButton.tsx

@@ -22,7 +22,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon,
   const needsAccount = !isUnsigned && !accountId;
 
   const _onFailed = useCallback(
-    (result: SubmittableResult | null): void => {
+    (result: SubmittableResult | Error | null): void => {
       setIsSending(false);
 
       onFailed && onFailed(result);

+ 0 - 1
pioneer/packages/react-components/src/i18n/index.ts

@@ -41,7 +41,6 @@ i18next
     ns: [
       'apps',
       'apps-config',
-      'apps-electron',
       'apps-routing',
       'app-accounts',
       'app-claims',

+ 1 - 1
pioneer/packages/react-components/src/translate.ts

@@ -4,7 +4,7 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse, withTranslation } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'react-components'> {
   return useTranslationBase('react-components');
 }
 

+ 1 - 1
pioneer/packages/react-hooks/src/translate.ts

@@ -4,7 +4,7 @@
 
 import { useTranslation as useTranslationBase, UseTranslationResponse, withTranslation } from 'react-i18next';
 
-export function useTranslation (): UseTranslationResponse {
+export function useTranslation (): UseTranslationResponse<'react-hooks'> {
   return useTranslationBase('react-hooks');
 }
 

+ 2 - 1
pioneer/packages/react-hooks/src/useAccountInfo.ts

@@ -9,6 +9,7 @@ import { AddressFlags, AddressIdentity, UseAccountInfo } from './types';
 
 import { useCallback, useEffect, useState } from 'react';
 import keyring from '@polkadot/ui-keyring';
+import { isFunction } from '@polkadot/util';
 
 import useAccounts from './useAccounts';
 import useAddresses from './useAddresses';
@@ -68,7 +69,7 @@ export default function useAccountInfo (value: string | null, isContract = false
 
     let name;
 
-    if (api.query.identity && api.query.identity.identityOf) {
+    if (api.query.identity && isFunction(api.query.identity.identityOf)) {
       if (identity?.display) {
         name = identity.display;
       }

+ 12 - 8
pioneer/packages/react-hooks/src/useCall.ts

@@ -2,22 +2,26 @@
 // This software may be modified and distributed under the terms
 // of the Apache-2.0 license. See the LICENSE file for details.
 
-import { Codec } from '@polkadot/types/types';
-import { CallOptions, CallParam, CallParams } from './types';
+import type { RpcPromiseResult } from '@polkadot/api/types';
+import type { StorageEntryTypeLatest } from '@polkadot/types/interfaces';
+import type { AnyFunction, Codec } from '@polkadot/types/types';
+import type { CallOptions, CallParam, CallParams } from './types';
+import type { MountedRef } from './useIsMountedRef';
 
 import { useEffect, useRef, useState } from 'react';
+
 import { isNull, isUndefined } from '@polkadot/util';
 
-import useIsMountedRef, { MountedRef } from './useIsMountedRef';
+import useIsMountedRef from './useIsMountedRef';
 
 type TrackFnResult = Promise<() => void>;
 
-interface TrackFn {
+export type TrackFn = RpcPromiseResult<AnyFunction> | QueryTrackFn;
+
+interface QueryTrackFn {
   (...params: CallParam[]): TrackFnResult;
   meta?: {
-    type: {
-      isDoubleMap: boolean;
-    };
+    type?: StorageEntryTypeLatest;
   };
 }
 
@@ -65,7 +69,7 @@ function subscribe <T> (mountedRef: MountedRef, tracker: TrackerRef, fn: TrackFn
 
   setTimeout((): void => {
     if (mountedRef.current) {
-      if (fn && (!fn.meta || !fn.meta.type?.isDoubleMap || validParams.length === 2)) {
+      if (fn && (!(fn as QueryTrackFn).meta?.type?.isDoubleMap || validParams.length === 2)) {
         // swap to acive mode and reset our count
         tracker.current.isActive = true;
         tracker.current.count = 0;

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