Browse Source

Merge remote-tracking branch 'pioneer-upstream/monorepo-migration' into pioneer-migration

Leszek Wiesner 4 years ago
parent
commit
aafa210851
100 changed files with 6972 additions and 0 deletions
  1. 1 0
      pioneer/.123trigger
  2. 1 0
      pioneer/.babelrc.js
  3. 3 0
      pioneer/.codeclimate.yml
  4. 1 0
      pioneer/.dockerignore
  5. 10 0
      pioneer/.editorconfig
  6. 4 0
      pioneer/.eslintignore
  7. 16 0
      pioneer/.eslintrc.js
  8. 71 0
      pioneer/.github/workflows/pr-any.yml
  9. 32 0
      pioneer/.github/workflows/push-master.yml
  10. 25 0
      pioneer/.gitignore
  11. 159 0
      pioneer/.gitlab-ci.yml
  12. 0 0
      pioneer/.npmignore
  13. 1 0
      pioneer/.nvmrc
  14. 3 0
      pioneer/.storybook/addons.ts
  15. 19 0
      pioneer/.storybook/config.tsx
  16. 4 0
      pioneer/.storybook/style.css
  17. 81 0
      pioneer/.storybook/webpack.config.js
  18. 13 0
      pioneer/.travis.yml
  19. 2 0
      pioneer/.yarnclean
  20. 19 0
      pioneer/BOUNTIES.md
  21. 124 0
      pioneer/CHANGELOG.md
  22. 45 0
      pioneer/CONTRIBUTING.md
  23. 26 0
      pioneer/Dockerfile
  24. 201 0
      pioneer/LICENSE
  25. 54 0
      pioneer/README.md
  26. 4 0
      pioneer/babel.config.js
  27. 33 0
      pioneer/deployment.extras.yml
  28. 60 0
      pioneer/deployment.template.yml
  29. 24 0
      pioneer/gh-pages-refresh.sh
  30. 89 0
      pioneer/i18next-scanner.config.js
  31. 72 0
      pioneer/img/pioneer_new.svg
  32. 17 0
      pioneer/jest.config.js
  33. 14 0
      pioneer/lerna.json
  34. 85 0
      pioneer/package.json
  35. 201 0
      pioneer/packages/app-123code/LICENSE
  36. 22 0
      pioneer/packages/app-123code/README.md
  37. 16 0
      pioneer/packages/app-123code/package.json
  38. 49 0
      pioneer/packages/app-123code/src/AccountSelector.tsx
  39. 28 0
      pioneer/packages/app-123code/src/Summary.tsx
  40. 65 0
      pioneer/packages/app-123code/src/SummaryBar.tsx
  41. 47 0
      pioneer/packages/app-123code/src/Transfer.tsx
  42. 38 0
      pioneer/packages/app-123code/src/index.tsx
  43. 7 0
      pioneer/packages/app-123code/src/translate.ts
  44. 201 0
      pioneer/packages/app-accounts/LICENSE
  45. 5 0
      pioneer/packages/app-accounts/README.md
  46. 23 0
      pioneer/packages/app-accounts/package.json
  47. 26 0
      pioneer/packages/app-accounts/scripts/vanitygen.js
  48. 227 0
      pioneer/packages/app-accounts/src/Account.tsx
  49. 105 0
      pioneer/packages/app-accounts/src/Banner.tsx
  50. 51 0
      pioneer/packages/app-accounts/src/MemoForm.tsx
  51. 104 0
      pioneer/packages/app-accounts/src/Overview.tsx
  52. 104 0
      pioneer/packages/app-accounts/src/Vanity/Match.tsx
  53. 328 0
      pioneer/packages/app-accounts/src/Vanity/index.tsx
  54. 7 0
      pioneer/packages/app-accounts/src/Vanity/translate.ts
  55. 26 0
      pioneer/packages/app-accounts/src/bipWorker.ts
  56. 89 0
      pioneer/packages/app-accounts/src/index.tsx
  57. 44 0
      pioneer/packages/app-accounts/src/md/basic.md
  58. 144 0
      pioneer/packages/app-accounts/src/modals/Backup.tsx
  59. 183 0
      pioneer/packages/app-accounts/src/modals/ChangePass.tsx
  60. 193 0
      pioneer/packages/app-accounts/src/modals/Create.tsx
  61. 60 0
      pioneer/packages/app-accounts/src/modals/CreateConfirmation.tsx
  62. 240 0
      pioneer/packages/app-accounts/src/modals/Derive.tsx
  63. 180 0
      pioneer/packages/app-accounts/src/modals/Import.tsx
  64. 119 0
      pioneer/packages/app-accounts/src/modals/Qr.tsx
  65. 178 0
      pioneer/packages/app-accounts/src/modals/Transfer.tsx
  66. 7 0
      pioneer/packages/app-accounts/src/translate.ts
  67. 29 0
      pioneer/packages/app-accounts/src/types.ts
  68. 66 0
      pioneer/packages/app-accounts/src/vanitygen/calculate.ts
  69. 133 0
      pioneer/packages/app-accounts/src/vanitygen/cli.ts
  70. 31 0
      pioneer/packages/app-accounts/src/vanitygen/generate.ts
  71. 23 0
      pioneer/packages/app-accounts/src/vanitygen/index.ts
  72. 7 0
      pioneer/packages/app-accounts/src/vanitygen/regex.ts
  73. 31 0
      pioneer/packages/app-accounts/src/vanitygen/sort.ts
  74. 33 0
      pioneer/packages/app-accounts/src/vanitygen/types.d.ts
  75. 201 0
      pioneer/packages/app-address-book/LICENSE
  76. 1 0
      pioneer/packages/app-address-book/README.md
  77. 16 0
      pioneer/packages/app-address-book/package.json
  78. 153 0
      pioneer/packages/app-address-book/src/Address.tsx
  79. 62 0
      pioneer/packages/app-address-book/src/MemoByAccount.tsx
  80. 63 0
      pioneer/packages/app-address-book/src/Overview.tsx
  81. 62 0
      pioneer/packages/app-address-book/src/index.tsx
  82. 6 0
      pioneer/packages/app-address-book/src/md/basic.md
  83. 135 0
      pioneer/packages/app-address-book/src/modals/Create.tsx
  84. 7 0
      pioneer/packages/app-address-book/src/translate.ts
  85. 15 0
      pioneer/packages/app-address-book/src/types.ts
  86. 201 0
      pioneer/packages/app-claims/LICENSE
  87. 1 0
      pioneer/packages/app-claims/README.md
  88. 17 0
      pioneer/packages/app-claims/package.json
  89. 107 0
      pioneer/packages/app-claims/src/Claim.tsx
  90. 259 0
      pioneer/packages/app-claims/src/index.tsx
  91. 8 0
      pioneer/packages/app-claims/src/secp256k1.d.ts
  92. 7 0
      pioneer/packages/app-claims/src/translate.ts
  93. 19 0
      pioneer/packages/app-claims/src/util.spec.ts
  94. 111 0
      pioneer/packages/app-claims/src/util.ts
  95. 201 0
      pioneer/packages/app-contracts/LICENSE
  96. 3 0
      pioneer/packages/app-contracts/README.md
  97. 17 0
      pioneer/packages/app-contracts/package.json
  98. 196 0
      pioneer/packages/app-contracts/src/ABI.tsx
  99. 113 0
      pioneer/packages/app-contracts/src/Codes/Add.tsx
  100. 208 0
      pioneer/packages/app-contracts/src/Codes/Code.tsx

+ 1 - 0
pioneer/.123trigger

@@ -0,0 +1 @@
+5

+ 1 - 0
pioneer/.babelrc.js

@@ -0,0 +1 @@
+module.exports = require('./babel.config.js');

+ 3 - 0
pioneer/.codeclimate.yml

@@ -0,0 +1,3 @@
+exclude_patterns:
+- "**/*.spec.js"
+- "**/*.spec.ts"

+ 1 - 0
pioneer/.dockerignore

@@ -0,0 +1 @@
+node_modules

+ 10 - 0
pioneer/.editorconfig

@@ -0,0 +1,10 @@
+root = true
+[*]
+indent_style=space
+indent_size=2
+tab_width=2
+end_of_line=lf
+charset=utf-8
+trim_trailing_whitespace=true
+max_line_length=120
+insert_final_newline=true

+ 4 - 0
pioneer/.eslintignore

@@ -0,0 +1,4 @@
+**/build/*
+**/coverage/*
+**/node_modules/*
+i18next-scanner.config.js

+ 16 - 0
pioneer/.eslintrc.js

@@ -0,0 +1,16 @@
+const base = require('@polkadot/dev-react/config/eslint');
+
+// add override for any (a metric ton of them, initial conversion)
+module.exports = {
+  ...base,
+  parserOptions: {
+    ...base.parserOptions,
+    project: [
+      './tsconfig.json'
+    ]
+  },
+  rules: {
+    ...base.rules,
+    '@typescript-eslint/no-explicit-any': 'off'
+  }
+};

+ 71 - 0
pioneer/.github/workflows/pr-any.yml

@@ -0,0 +1,71 @@
+name: PR
+on: [pull_request]
+
+jobs:
+  lint:
+    name: Linting
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: lint
+      run: |
+        yarn install --frozen-lockfile
+        yarn lint
+
+  test:
+    name: Testing
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: test
+      run: |
+        yarn install --frozen-lockfile
+        yarn test
+
+  build_code:
+    name: Build Code
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      run: |
+        yarn install --frozen-lockfile
+        yarn build:code
+
+  build_i18n:
+    name: Build i18n
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      run: |
+        yarn install --frozen-lockfile
+        yarn build:i18n

+ 32 - 0
pioneer/.github/workflows/push-master.yml

@@ -0,0 +1,32 @@
+name: Master
+on:
+  push:
+    branches:
+      - master
+
+jobs:
+  build_code:
+    name: Build Code
+    if: "! contains(github.event.head_commit.message, '[CI Skip]')"
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        node-version: [12.x]
+    steps:
+    - uses: actions/checkout@v1
+      with:
+        token: ${{ secrets.GH_PAT }}
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v1
+      with:
+        node-version: ${{ matrix.node-version }}
+    - name: build
+      env:
+        CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
+        GH_PAGES_SRC: packages/apps/build
+        GH_PAT: ${{ secrets.GH_PAT }}
+        NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
+      run: |
+        yarn install --frozen-lockfile
+        yarn polkadot-dev-ghact-build
+        yarn polkadot-dev-ghact-docs

+ 25 - 0
pioneer/.gitignore

@@ -0,0 +1,25 @@
+build/
+coverage/
+node_modules/
+tmp/
+.idea/
+.vscode/
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+.npmrc
+cc-test-reporter
+package-lock.json
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+!patches/**
+.idea/
+
+# Built Joystream types:
+packages/joy-types/lib/
+
+# Storybook
+storybook-static/

+ 159 - 0
pioneer/.gitlab-ci.yml

@@ -0,0 +1,159 @@
+image: roffe/kubectl:latest
+variables:
+  CI_REGISTRY: parity.azurecr.io
+  CI_REGISTRY_USER: parity
+  AUTO_DEVOPS_DOMAIN: poc-3.polkadot.io
+
+.kubernetes: &kubernetes
+  tags:
+    - kubernetes
+
+stages:
+  - dockerize
+  - test
+  - review
+  - staging
+  - production
+  - cleanup
+
+before_script:
+  - export DOCKER_IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH_SLUG
+  - export DOCKER_TAG=$CI_COMMIT_REF_SLUG-$VERSION
+  - export DOCKER_IMAGE_FULL_NAME=$DOCKER_IMAGE:$DOCKER_TAG
+
+dockerize:
+  stage: dockerize
+  environment:
+    name: infrastructure_build
+  tags:
+    - kubernetes-parity-build
+  image: docker:git
+  services:
+    - docker:dind
+  variables:
+    DOCKER_DRIVER: overlay2
+    DOCKER_HOST: tcp://localhost:2375
+  script:
+    - echo $DOCKER_IMAGE
+    - echo $DOCKER_TAG
+    - echo $DOCKER_IMAGE_FULL_NAME
+    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+    - docker build -t "$DOCKER_IMAGE_FULL_NAME" .
+    - docker push "$DOCKER_IMAGE_FULL_NAME"
+  only:
+    - master
+
+review:
+  stage: review
+  <<: *kubernetes
+  script:
+    - setup_kubernetes
+    - deploy
+  environment:
+    name: review/$CI_COMMIT_REF_NAME
+    url: https://$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN
+    on_stop: stop_review
+  only:
+    refs:
+      - branches
+    kubernetes: active
+  except:
+    - master
+
+stop_review:
+  stage: cleanup
+  <<: *kubernetes
+  variables:
+    GIT_STRATEGY: none
+  script:
+    - setup_kubernetes
+    - delete
+  environment:
+    name: review/$CI_COMMIT_REF_NAME
+    action: stop
+  when: manual
+  allow_failure: true
+  only:
+    refs:
+      - branches
+    kubernetes: active
+  except:
+    - master
+
+staging:
+  stage: staging
+  <<: *kubernetes
+  script:
+    - setup_kubernetes
+    - deploy
+  environment:
+    name: staging
+    url: https://staging.$AUTO_DEVOPS_DOMAIN
+  only:
+    refs:
+      - master
+    kubernetes: active
+
+production:
+  stage: production
+  <<: *kubernetes
+  script:
+    - setup_kubernetes
+    - deploy
+  environment:
+    name: production
+    url: https://$AUTO_DEVOPS_DOMAIN
+  when: manual
+  only:
+    refs:
+      - master
+    kubernetes: active
+
+# ---------------------------------------------------------------------------
+.auto_devops: &auto_devops |
+  # Auto DevOps variables and functions
+  [[ "$TRACE" ]] && set -x
+  export DOCKER_IMAGE=$CI_REGISTRY/$CI_PROJECT_PATH_SLUG
+  export DOCKER_TAG=$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHA
+  export DOCKER_IMAGE_FULL_NAME=$DOCKER_IMAGE:$DOCKER_TAG
+
+  export AUTODEVOPS_HOST=$(echo $CI_ENVIRONMENT_URL | awk -F/ '{print $3}')
+
+  function build() {
+    if [[ -n "$CI_REGISTRY_USER" ]]; then
+      echo "Logging to GitLab Container Registry with CI credentials..."
+      docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" "$CI_REGISTRY"
+      echo ""
+    fi
+
+    echo "Building Dockerfile-based application..."
+    docker build -t "$DOCKER_IMAGE_FULL_NAME" .
+
+    echo "Pushing to GitLab Container Registry..."
+    docker push "$DOCKER_IMAGE_FULL_NAME"
+    echo ""
+  }
+
+  function setup_kubernetes() {
+    kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
+    kubectl create secret -n "$KUBE_NAMESPACE" \
+      docker-registry gitlab-registry \
+      --docker-server="$CI_REGISTRY" \
+      --docker-username="$CI_REGISTRY_USER" \
+      --docker-password="$CI_REGISTRY_PASSWORD" \
+      --docker-email="$GITLAB_USER_EMAIL" \
+      -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
+  }
+
+  function deploy() {
+    cat ./deployment.template.yml | envsubst | kubectl apply -n "$KUBE_NAMESPACE" -f -
+  }
+
+  function delete() {
+    kubectl -n "$KUBE_NAMESPACE" delete "deploy/$CI_ENVIRONMENT_SLUG-backend"
+    kubectl -n "$KUBE_NAMESPACE" delete "svc/$CI_ENVIRONMENT_SLUG-service"
+    kubectl -n "$KUBE_NAMESPACE" delete "ing/$CI_ENVIRONMENT_SLUG-ingress"
+  }
+
+before_script:
+  - *auto_devops

+ 0 - 0
pioneer/.npmignore


+ 1 - 0
pioneer/.nvmrc

@@ -0,0 +1 @@
+10.13.0

+ 3 - 0
pioneer/.storybook/addons.ts

@@ -0,0 +1,3 @@
+import '@storybook/addon-knobs/register';
+import '@storybook/addon-actions/register';
+import '@storybook/addon-storysource/register';

+ 19 - 0
pioneer/.storybook/config.tsx

@@ -0,0 +1,19 @@
+import React from 'react'
+import { configure, addDecorator } from '@storybook/react';
+import '@storybook/addon-console';
+import StoryRouter from 'storybook-react-router';
+
+import GlobalStyle from '@polkadot/react-components/styles';
+import 'semantic-ui-css/semantic.min.css'
+import './style.css'
+
+addDecorator(StoryRouter());
+
+addDecorator(story => (
+  <div className='StorybookRoot'>
+    <GlobalStyle />
+    {story()}
+  </div>
+));
+
+configure(require.context('../packages', true, /\.stories\.tsx?$/), module)

+ 4 - 0
pioneer/.storybook/style.css

@@ -0,0 +1,4 @@
+.StorybookRoot {
+  background-color: #fafafa;
+  padding: 1rem 5rem;
+}

+ 81 - 0
pioneer/.storybook/webpack.config.js

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

+ 13 - 0
pioneer/.travis.yml

@@ -0,0 +1,13 @@
+language: node_js
+node_js:
+  - "12"
+cache:
+  yarn: true
+  directories:
+    - node_modules
+before_install:
+  - curl -o- -L https://yarnpkg.com/install.sh | bash
+  - export PATH=$HOME/.yarn/bin:$PATH
+script:
+  - yarn
+  - yarn build

+ 2 - 0
pioneer/.yarnclean

@@ -0,0 +1,2 @@
+@types/react-native
+

+ 19 - 0
pioneer/BOUNTIES.md

@@ -0,0 +1,19 @@
+# Bounties
+
+From time-to-time we will add bounties for features.
+
+These are generously provided by the [Web3 Foundation](https://web3.foundation/) and as such employees of Parity or those of the W3F are ineligible for them. (This includes people being by either Party for development or community work, related or un-related to polkadot-js).
+
+The idea is that these bounties should be left open to community participation, so only if there is no outside interest for a specific issue, should those directly or indirectly paid by the W3F for work, attempt to close an issue. (in which case it will be "un-bountied")
+
+Current bounties are tracked by the [!bounty](https://github.com/polkadot-js/apps/labels/%21bounty) label.
+
+## Process
+
+Once listed, the normal [Gitcoin](https://gitcoin.co/) process kicks in. This means application, work and payment is managed by this tool. The values for bounties are determined by the size estimation done by the team.
+
+## Some small requests
+
+Please don't start work on an issue until you have been approved via the gitcoin interface. We generally love enthusiasm and code in the repo, however short-cutting the process does create some issues for the management of the bounties. We certainly don't want to be playing favorites if 2 PRs for the same issue are created at the same time. And in cases where somebody else has been approved and an unapproved PR comes in... well, it gets really murky.
+
+When making changes, please do not force push in your PRs, especially not after a review has been started. We will clone your repo and work from that, doing a simple `pull` on a force-pushed branch ends up being, well, less than simple. We squash merge all PRs, so you do not clutter up the history by using stock-standard pushes to your branch.

+ 124 - 0
pioneer/CHANGELOG.md

@@ -0,0 +1,124 @@
+# 0.36.1
+
+- Api 0.95.1, Util 1.6.1, Extension 0.13.1
+- Support latest contracts ABI (via API), incl. rework of contracts UI
+- Support for Kusama CC2
+- Support for Edgeware mainnet
+- Experimental Ledger support
+- Display forks on explorer (limited to Babe)
+- Change settings to have Save as well as Save & Reload (depending on changes made)
+- Updates to struct & enum rendering (as per extrinsic app)
+- Backup, Password change & Delete don't show for built-in dev accounts
+- Add commissions to the staking overview
+- UI theme update
+- A large number of components refactored for React functional components
+- Allow dismiss of all notifications (via bounty)
+- Migrate all buttons to have icons (via bounty)
+- Proposal submission via modal (via bounty)
+- i18n string extraction (via bounty)
+- adjust signature validity (via bounty)
+- Make the network selection clickable on network name (via bounty)
+- ... and a number of cleanups all around
+
+# 0.35.1
+
+- Api 0.91.1, Util 1.2.1, Extension 0.10.1
+- Support for accounts added via Qr (for instance, the Parity Signer)
+- Support for accounts tied to specific chains (instead of just available to all)
+- GenericAsset app transfers
+- Support for Edgeware with default types
+- Display received heartbeats for validators
+- Allow optional params (really as optional) in RPC toolbox
+- Add Polkascan for Kusama
+- Fix account derivation with `///password`
+- Lots of component & maintainability cleanups
+
+# 0.34.1
+
+- Kusama support
+- Full support for Substrate 2.x & Polkadot 0.5.0 networks
+- Lots of UI updated to support both Substrate 1.x & 2.x chains
+- Add of claims app for Kusama (and Polkadot)
+- Basic Council, Parachains & Treasury apps
+- Moved ui-* packages to react-*
+
+# 0.33.1
+
+- Allow for externally injected accounts (i.e. via extension, polkadot-js & SingleSource)
+- Links to extrnisics & addresses on Polkascan
+- Rework Account & Address layouts with cards
+- Transfer can happen from any point (via Transfer modal)
+- Use new api.derive functions
+- Introduce multi support (most via api.derive.*)
+- Update all account and address modals
+- Add seconding of proposals
+- Staking updates, including un-bonding & withdrawals
+- Update explorer with global query on hash/blocks
+- Add filters on the staking page
+- Vanitygen now supports sr25519 as well
+- Fixes for importing of old JSON
+- Latest @polkadot/util & @polkadot/api
+- A large number of optimizations and smaller fixes
+
+# 0.32.1
+
+- Support for Substrate 1.0 release & metadata v4
+- @polkadot/api 0.77.1
+
+# 0.31.1
+
+- Cleanups, fixes and features around the poc-4 staking module
+- Number of UI enhancements
+
+# 0.30.1
+
+- Staking page indicator for offline nodes (count & block)
+- Rework page tabs and content layouts
+- Cleanup of all UI summary headers
+- Emberic Elem support (replaces Dried Danta)
+
+# 0.29.1
+
+- @polkadot/util & @polkadot/api 0.75.1
+
+# 0.28.1
+
+- Support for substrate 1.0-rc
+
+# 0.27.1
+
+- Bring in new staking & nominating functions
+- Swap default keyring accounts (on creation) to sr25519
+- New faster crypto algorithms
+- Misc. bug fixes all around
+
+# 0.26.1
+
+- Swap keyring to HDKD derivation, mnemonic keys are now not backwards compatible with those created earlier. (Defaults are still for ed25519)
+- Swap crypto to new WASM-backed version (and remove libsodium dependency)
+- UI to allow for derived keys for ed25519 and sr25519
+- New mobile-friendly sidebar
+- Fix issues with nominating (old non-bonds interface)
+
+# 0.25.1
+
+- Swap to publishing -beta.x on merge (non-breaking testing)
+
+ # 0.24.1
+
+ Storage now handles Option type properly
+
+ # 0.23.1
+
+ JavaScript console introduced
+
+# 0.22.1
+
+- Use new Compact<Index> transaction format - this requires the latest binaries from either Polkadot or Substrate
+
+# 0.21.1
+
+- PoC-3 support with latest Substrate master & Polkadot master
+- Add support for Charred Cherry (Substrate) and Alexander (Polkadot) testnets
+- Too many changes to mention, master now only supports latest PoC-3 iteration
+- Use https://poc-2.polkadot.io if access is required to PoC-2 era networks

+ 45 - 0
pioneer/CONTRIBUTING.md

@@ -0,0 +1,45 @@
+# Contributing
+
+## What?
+
+Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit.
+A project is more like an open wiki than a standard guarded open source project.
+
+## Rules
+
+There are a few basic ground-rules for contributors (including the maintainer(s) of the project):
+
+1. **No `--force` pushes** or modifying the Git history in any way. If you need to rebase, ensure you do it in your own repo.
+2. **Non-master branches**, prefixed with a short name moniker (e.g. `<initials>-<feature>`) must be used for ongoing work.
+3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors.
+4. A pull-request *must not be merged until CI* has finished successfully.
+
+#### Merging pull requests once CI is successful:
+- A pull request with no large change to logic that is an urgent fix may be merged after a non-author contributor has reviewed it well.
+- No PR should be merged until all reviews' comments are addressed.
+
+#### Reviewing pull requests:
+When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in:
+
+- Buggy behaviour.
+- Undue maintenance burden.
+- Breaking with house coding style.
+- Pessimisation (i.e. reduction of speed as measured in the projects benchmarks).
+- Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on).
+- Uselessness (i.e. it does not strictly add a feature or fix a known issue).
+
+#### Reviews may not be used as an effective veto for a PR because:
+- There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix.
+- It does not fit well with some other contributors' longer-term vision for the project.
+
+## Releases
+
+Declaring formal releases remains the prerogative of the project maintainer(s).
+
+## Changes to this arrangement
+
+This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change.
+
+## Heritage
+
+These contributing guidelines are modified from the "OPEN Open Source Project" guidelines for the Level project: [https://github.com/Level/community/blob/master/CONTRIBUTING.md](https://github.com/Level/community/blob/master/CONTRIBUTING.md)

+ 26 - 0
pioneer/Dockerfile

@@ -0,0 +1,26 @@
+FROM ubuntu:18.04 as builder
+
+# Install any needed packages
+RUN apt-get update && apt-get install -y curl git gnupg
+
+# install nodejs
+RUN curl -sL https://deb.nodesource.com/setup_10.x | bash -
+RUN apt-get install -y nodejs
+
+WORKDIR /app
+RUN git clone https://github.com/polkadot-js/apps
+
+WORKDIR /app/apps
+RUN npm install yarn -g
+RUN yarn
+RUN NODE_ENV=production yarn build
+
+FROM ubuntu:18.04
+
+RUN apt-get update && apt-get -y install nginx
+
+COPY --from=builder /app/apps/packages/apps/build /var/www/html
+
+EXPOSE 80
+
+CMD ["nginx", "-g", "daemon off;"]

+ 201 - 0
pioneer/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 54 - 0
pioneer/README.md

@@ -0,0 +1,54 @@
+<p align="center"><img src="img/pioneer_new.svg"></p>
+
+![Content Directory](https://user-images.githubusercontent.com/4144334/67765742-bbfab280-fa44-11e9-8b13-494b1bfb6014.jpeg)
+
+A Portal into the Joystream network. Provides a view and interaction layer from a browser.
+
+This can be accessed as a hosted application via [https://testnet.joystream.org](https://testnet.joystream.org).
+
+## overview
+
+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.
+- [app-accounts](packages/app-accounts/) A basic account management app.
+- [app-address-book](packages/app-address-book/) A basic address management app.
+- [app-explorer](packages/app-explorer/) A simple block explorer. It only shows the most recent blocks, updating as they become available.
+- [app-extrinsics](packages/app-extrinsics/) Submission of extrinsics to a node.
+- [app-js](packages/app-js/) An online code editor with [@polkadot-js/api](https://github.com/polkadot-js/api/tree/master/packages/api) access to the currently connected node.
+- [app-settings](packages/app-settings/) A basic settings management app, allowing choice of language, node to connect to, and theme
+- [app-staking](packages/app-staking/) A basic staking management app, allowing staking and nominations.
+- [app-storage](packages/app-storage/) A simple node storage query application. Multiple queries can be queued and updates as new values become available.
+- [app-toolbox](packages/app-toolbox/) Submission of raw data to RPC endpoints and utility hashing functions.
+- [app-transfer](packages/app-transfer/) A basic account management app, allowing transfer of Units/DOTs between accounts.
+
+In addition the following libraries are also included in the repo. These are to be moved to the [@polkadot/ui](https://github.com/polkadot-js/ui/) repository once it reaches a base level of stability and usability. (At this point with the framework being tested on the apps above, it makes development easier having it close)
+
+- [react-components](packages/react-components/) A reactive (using RxJS) application framework with a number of useful shared components.
+- [react-signer](packages/react-signer/) Signer implementation for apps.
+- [react-query](packages/react-query) Base components that use the RxJS Observable APIs
+
+## development
+
+Contributions are welcome!
+
+To start off, this repo (along with others in the [@polkadot](https://github.com/polkadot-js/) family) uses yarn workspaces to organise the code. As such, after cloning dependencies _should_ be installed via `yarn`, not via npm, the latter will result in broken dependencies.
+
+To get started -
+
+1. Clone the repo locally, via `git clone https://github.com/joystream/apps <optional local path>`
+2. Ensure that you have a recent LTS version of Node.js, for development purposes [Node >=10.13.0](https://nodejs.org/en/) is recommended.
+3. Ensure that you have a recent version of Yarn, for development purposes [Yarn >=1.10.1](https://yarnpkg.com/docs/install) is required.
+4. Install the dependencies by running `yarn`
+5. Ready! Now you can launch the UI (assuming you have a local Polkadot Node running), via `yarn run start`
+6. Access the UI via [http://localhost:3000](http://localhost:3000)
+
+### Storybook
+
+There is a [StoryBook](https://storybook.js.org) implementation, the UI for which can be started with `yarn storybook` and then accessed in a browser via http://localhost:3001 (and the server will open a new browser tab by default when it starts).
+
+Story code can be placed anywhere in the `packages` directory, and will be detected as long as the file name ends in `.stories.tsx. Stories should be defined in the [Component Story Format (CSF)](https://storybook.js.org/docs/formats/component-story-format) for consistency.
+
+There are several StoryBook addons available, the most useful of which is [Knobs](https://www.npmjs.com/package/@storybook/addon-knobs), which allows props to be altered in real time.
+
+Note that currently StoryBook only allows for stateless components; it has no connection to polkadot.js or any Substrate node. This means that existing components, which are often tightly coupled with the Polkadot API, cannot be used in storybook.

+ 4 - 0
pioneer/babel.config.js

@@ -0,0 +1,4 @@
+module.exports = {
+  extends: '@polkadot/dev-react/config/babel',
+  sourceType: 'unambiguous',
+};

+ 33 - 0
pioneer/deployment.extras.yml

@@ -0,0 +1,33 @@
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: production-ingress-substrate-ui
+  namespace: poc3-122
+  annotations:
+    kubernetes.io/ingress.class: traefik
+    traefik.frontend.entryPoints: "https,http"
+spec:
+  rules:
+  - host: substrate-ui.parity.io
+    http:
+      paths:
+      - backend:
+          serviceName: production-service
+          servicePort: 80
+---
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: production-ingress-substrate-ui-light
+  namespace: poc3-122
+  annotations:
+    kubernetes.io/ingress.class: traefik
+    traefik.frontend.entryPoints: "https,http"
+spec:
+  rules:
+  - host: substrate-ui-light.parity.io
+    http:
+      paths:
+      - backend:
+          serviceName: production-service
+          servicePort: 80

+ 60 - 0
pioneer/deployment.template.yml

@@ -0,0 +1,60 @@
+---
+apiVersion: v1
+data:
+# AZURE_DOCKER_REGISTRY_CONFIG is base64 of this:
+# {"auths":{"parity.azurecr.io":{"username":"parity","password":"<password>","email":"admin@parity.io","auth":"<base64 of user+passwoed>"}}}
+  .dockerconfigjson: $AZURE_DOCKER_REGISTRY_CONFIG
+kind: Secret
+metadata:
+  name: azure-docker-registry-key
+type: kubernetes.io/dockerconfigjson
+---
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+  name: $CI_ENVIRONMENT_SLUG-backend
+spec:
+  replicas: $REPLICAS
+  template:
+    metadata:
+      labels:
+        app: $CI_ENVIRONMENT_SLUG
+        component: backend
+    spec:
+      containers:
+        - name: $CI_ENVIRONMENT_SLUG-backend
+          image: $DOCKER_IMAGE_FULL_NAME
+          imagePullPolicy: Always
+          ports:
+          - containerPort: 80
+      imagePullSecrets:
+        - name: azure-docker-registry-key
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: $CI_ENVIRONMENT_SLUG-service
+spec:
+  selector:
+    app: $CI_ENVIRONMENT_SLUG
+  ports:
+    - name: http
+      port: 80
+      targetPort: 80
+      protocol: TCP
+---
+apiVersion: extensions/v1beta1
+kind: Ingress
+metadata:
+  name: $CI_ENVIRONMENT_SLUG-ingress
+  annotations:
+    kubernetes.io/ingress.class: traefik
+    traefik.frontend.entryPoints: "https,http"
+spec:
+  rules:
+  - host: $AUTODEVOPS_HOST
+    http:
+      paths:
+      - backend:
+          serviceName: $CI_ENVIRONMENT_SLUG-service
+          servicePort: 80

+ 24 - 0
pioneer/gh-pages-refresh.sh

@@ -0,0 +1,24 @@
+#!/bin/bash
+
+exit 0
+
+# checkout latest
+git fetch
+git checkout gh-pages
+git pull
+git checkout --orphan gh-pages-temp
+
+# cleanup
+rm -rf node_modules
+rm -rf coverage
+rm -rf packages
+rm -rf test
+
+# add
+git add -A
+git commit -am "refresh history"
+
+# danger, force new
+git branch -D gh-pages
+git branch -m gh-pages
+git push -f origin gh-pages

+ 89 - 0
pioneer/i18next-scanner.config.js

@@ -0,0 +1,89 @@
+const fs = require('fs');
+const path = require('path');
+const typescript = require('typescript');
+
+module.exports = {
+  input: [
+    'packages/*/src/**/*.{ts,tsx}',
+    // Use ! to filter out files or directories
+    '!packages/*/src/**/*.spec.{ts,tsx}',
+    '!packages/*/src/i18n/**',
+    '!**/node_modules/**'
+  ],
+  output: './',
+  options: {
+    debug: true,
+    func: {
+      list: ['t', 'i18next.t', 'i18n.t'],
+      extensions: ['.tsx']
+    },
+    trans: {
+      component: 'Trans'
+    },
+    lngs: ['en'],
+    defaultLng: 'en',
+    ns: [
+      'app-123code',
+      'app-accounts',
+      'app-address-book',
+      'app-claims',
+      'app-contracts',
+      'app-council',
+      'app-dashboard',
+      'app-democracy',
+      'app-explorer',
+      'app-extrinsics',
+      'app-generic-asset',
+      'app-js',
+      'app-parachains',
+      'app-settings',
+      'app-staking',
+      'app-storage',
+      'app-sudo',
+      'app-toolbox',
+      'app-transfer',
+      'app-treasury',
+      'apps',
+      'apps-routing',
+      'react-api',
+      'react-components',
+      'react-params',
+      'react-query',
+      'react-signer',
+      'ui'
+    ],
+    defaultNs: 'ui',
+    resource: {
+      loadPath: 'packages/apps/public/locales/{{lng}}/{{ns}}.json',
+      savePath: 'packages/apps/public/locales/{{lng}}/{{ns}}.json',
+      jsonIndent: 2,
+      lineEnding: '\n'
+    },
+    nsSeparator: false, // namespace separator
+    keySeparator: false // key separator
+  },
+  transform: function transform (file, enc, done) {
+    const { ext } = path.parse(file.path);
+
+    if (ext === '.tsx') {
+      const content = fs.readFileSync(file.path, enc);
+
+      const { outputText } = typescript.transpileModule(content, {
+        compilerOptions: {
+          target: 'es2018'
+        },
+        fileName: path.basename(file.path)
+      });
+
+      const parserHandler = (key, options) => {
+        options.defaultValue = key;
+        options.ns = /packages\/(.*?)\/src/g.exec(file.path)[1];
+        this.parser.set(key, options);
+      };
+
+      this.parser.parseFuncFromString(outputText, parserHandler);
+    }
+
+    done();
+  }
+};

File diff suppressed because it is too large
+ 72 - 0
pioneer/img/pioneer_new.svg


+ 17 - 0
pioneer/jest.config.js

@@ -0,0 +1,17 @@
+/* eslint-disable @typescript-eslint/no-var-requires */
+const config = require('@polkadot/dev-react/config/jest');
+const findPackages = require('./scripts/findPackages');
+
+const internalModules = findPackages().reduce((modules, { dir, name }) => {
+  modules[`${name}(.*)$`] = `<rootDir>/packages/${dir}/src/$1`;
+
+  return modules;
+}, {});
+
+module.exports = Object.assign({}, config, {
+  moduleNameMapper: {
+    ...internalModules,
+    '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
+    '\\.(css|less)$': 'empty/object'
+  }
+});

+ 14 - 0
pioneer/lerna.json

@@ -0,0 +1,14 @@
+{
+  "lerna": "2.11.0",
+  "npmClient": "yarn",
+  "useWorkspaces": true,
+  "command": {
+    "publish": {
+      "allowBranch": "master"
+    }
+  },
+  "packages": [
+    "packages/*"
+  ],
+  "version": "0.37.0-beta.63"
+}

+ 85 - 0
pioneer/package.json

@@ -0,0 +1,85 @@
+{
+  "version": "0.37.0-beta.63",
+  "private": true,
+  "engines": {
+    "node": ">=10.13.0",
+    "yarn": "^1.10.1"
+  },
+  "homepage": ".",
+  "workspaces": [
+    "packages/*"
+  ],
+  "resolutions": {
+    "@polkadot/api": "^0.96.1",
+    "@polkadot/api-contract": "^0.96.1",
+    "@polkadot/keyring": "^1.7.0-beta.5",
+    "@polkadot/types": "^0.96.1",
+    "@polkadot/util": "^1.7.0-beta.5",
+    "@polkadot/util-crypto": "^1.7.0-beta.5",
+    "babel-core": "^7.0.0-bridge.0",
+    "typescript": "^3.7.2"
+  },
+  "scripts": {
+    "analyze": "yarn run build && cd packages/apps && yarn run source-map-explorer build/main.*.js",
+    "build": "yarn run build:code && yarn run build:i18n",
+    "build:code": "NODE_ENV=production polkadot-dev-build-ts",
+    "build:i18n": "i18next-scanner --config i18next-scanner.config.js",
+    "docs": "echo \"skipping docs\"",
+    "clean": "polkadot-dev-clean-build",
+    "clean:i18n": "rm -rf packages/apps/public/locales/en && mkdir -p packages/apps/public/locales/en",
+    "lint": "eslint --ext .js,.jsx,.ts,.tsx . && tsc --noEmit --pretty",
+    "postinstall": "polkadot-dev-yarn-only",
+    "test": "echo \"skipping tests\"",
+    "vanitygen": "node packages/app-accounts/scripts/vanitygen.js",
+    "start": "cd packages/apps && webpack --config webpack.config.js",
+    "generate-schemas": "json2ts -i packages/joy-types/src/schemas/role.schema.json -o packages/joy-types/src/schemas/role.schema.ts",
+    "build-storybook": "build-storybook -c .storybook",
+    "storybook": "start-storybook -s ./packages/apps/public -p 3001"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.7.0",
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/dev-react": "^0.32.0-beta.13",
+    "@polkadot/ts": "^0.1.84",
+    "@storybook/addon-knobs": "^5.2.5",
+    "@storybook/addon-storysource": "^5.2.5",
+    "@types/jest": "^24.0.22",
+    "@types/react-router-dom": "^5.1.4",
+    "@types/yup": "^0.26.36",
+    "autoprefixer": "^9.7.1",
+    "empty": "^0.10.1",
+    "html-loader": "^0.5.5",
+    "i18next-scanner": "^2.10.3",
+    "json-schema-to-typescript": "^7.1.0",
+    "markdown-loader": "^5.1.0",
+    "postcss": "^7.0.21",
+    "postcss-clean": "^1.1.0",
+    "postcss-flexbugs-fixes": "^4.1.0",
+    "postcss-import": "^12.0.0",
+    "postcss-loader": "^3.0.0",
+    "postcss-nested": "^4.2.1",
+    "postcss-sass": "^0.4.1",
+    "postcss-simple-vars": "^5.0.0",
+    "precss": "^4.0.0",
+    "source-map-explorer": "^2.0.1",
+    "storybook-react-router": "^1.0.8",
+    "ts-jest": "^24.1.0",
+    "tsconfig-paths-webpack-plugin": "^3.2.0"
+  },
+  "dependencies": {
+    "@polkadot/ui-settings": "^0.47.0-beta.3",
+    "@storybook/addon-actions": "^5.2.5",
+    "@storybook/addon-console": "^1.2.1",
+    "@storybook/react": "^5.2.5",
+    "@types/lodash": "^4.14.138",
+    "@types/marked": "^0.7.0",
+    "ajv": "^6.10.2",
+    "css-loader": "^3.2.0",
+    "less": "^3.10.3",
+    "less-loader": "^5.0.0",
+    "lodash": "^4.17.15",
+    "node-sass": "^4.13.0",
+    "sass-loader": "^8.0.0",
+    "style-loader": "^1.0.0"
+  }
+}

+ 201 - 0
pioneer/packages/app-123code/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 22 - 0
pioneer/packages/app-123code/README.md

@@ -0,0 +1,22 @@
+# @polkadot/app-123code
+
+A simple template to get started with adding an "app" to this UI. It contains the bare minimum for a nicely hackable app (if you just want to code _somewhere_) and the steps needed to create, add and register an new app that appears in the UI.
+
+## adding an app
+
+If you want to add a new app to the UI, this is the place to start.
+
+1. Duplicate this `app-123code` folder and give it an appropriate name, in this case we will select `app-example` to keep things clear.
+2. Edit the `apps-example/package.json` app description, i.e. the name, author and relevant overview.
+
+And we have the basic app source setup, time to get the tooling correct.
+
+3. Add the new app to the TypeScript config in root, `tsconfig.json`, i.e. an entry such as `"@polkadot/app-example/*": [ "packages/app-example/src/*" ],`
+
+At this point the app should be buildable, but not quite reachable. The final step is to add it to the actual sidebar in `apps`.
+
+4. In `apps-routing/src` duplicate the `123code.ts` file to `example.ts` and edit it with the appropriate information, including the hash link, name and icon (any icon name from semantic-ui-react/font-awesome 4 should be appropriate).
+5. In the above description file, the `isHidden` field needs to be toggled to make it appear - the base template is hidden by default.
+6. Finally add the `template` to the `apps-routing/src/index.ts` file at the appropriate place for both full and light mode (either optional)
+
+Yes. After all that we have things hooked up. Run `yarn start` and your new app (non-coded) should show up. Now start having fun and building something great.

+ 16 - 0
pioneer/packages/app-123code/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "@polkadot/app-123code",
+  "version": "0.37.0-beta.63",
+  "description": "A basic app that shows the ropes on customisation",
+  "main": "index.js",
+  "scripts": {},
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "maintainers": [
+    "Jaco Greeff <jacogr@gmail.com>"
+  ],
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
+  }
+}

+ 49 - 0
pioneer/packages/app-123code/src/AccountSelector.tsx

@@ -0,0 +1,49 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { Bubble, InputAddress } from '@polkadot/react-components';
+import { AccountIndex, Balance, Nonce } from '@polkadot/react-query';
+
+interface Props {
+  className?: string;
+  onChange: (accountId: string | null) => void;
+}
+
+function AccountSelector ({ className, onChange }: Props): React.ReactElement<Props> {
+  const [accountId, setAccountId] = useState<string | null>(null);
+
+  useEffect((): void => onChange(accountId), [accountId]);
+
+  return (
+    <section className={`template--AccountSelector ui--row ${className}`}>
+      <InputAddress
+        className='medium'
+        label='my default account'
+        onChange={setAccountId}
+        type='account'
+      />
+      <div className='medium'>
+        <Bubble color='teal' icon='address card' label='index'>
+          <AccountIndex params={accountId} />
+        </Bubble>
+        <Bubble color='yellow' icon='adjust' label='balance'>
+          <Balance params={accountId} />
+        </Bubble>
+        <Bubble color='yellow' icon='target' label='transactions'>
+          <Nonce params={accountId} />
+        </Bubble>
+      </div>
+    </section>
+  );
+}
+
+export default styled(AccountSelector)`
+  align-items: flex-end;
+
+  .summary {
+    text-align: center;
+  }
+`;

+ 28 - 0
pioneer/packages/app-123code/src/Summary.tsx

@@ -0,0 +1,28 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 { BareProps } from '@polkadot/react-components/types';
+
+import React from 'react';
+import styled from 'styled-components';
+
+interface Props extends BareProps {
+  children: React.ReactNode;
+}
+
+function Summary ({ children, className, style }: Props): React.ReactElement<Props> {
+  return (
+    <div
+      className={className}
+      style={style}
+    >
+      {children}
+    </div>
+  );
+}
+
+export default styled(Summary)`
+  opacity: 0.5;
+  padding: 1rem 1.5rem;
+`;

+ 65 - 0
pioneer/packages/app-123code/src/SummaryBar.tsx

@@ -0,0 +1,65 @@
+/* eslint-disable @typescript-eslint/camelcase */
+// Copyright 2017-2019 @polkadot/app-123code 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 { AccountId } from '@polkadot/types/interfaces';
+import { BareProps, I18nProps } from '@polkadot/react-components/types';
+
+import BN from 'bn.js';
+import React, { useContext } from 'react';
+import { ApiContext, withCalls } from '@polkadot/react-api';
+import { Bubble, IdentityIcon } from '@polkadot/react-components';
+import { formatBalance, formatNumber } from '@polkadot/util';
+
+import translate from './translate';
+
+interface Props extends BareProps, I18nProps {
+  balances_totalIssuance?: BN;
+  chain_bestNumber?: BN;
+  chain_bestNumberLag?: BN;
+  staking_validators?: AccountId[];
+}
+
+function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_validators }: Props): React.ReactElement<Props> {
+  const { api, systemChain, systemName, systemVersion } = useContext(ApiContext);
+
+  return (
+    <summary>
+      <div>
+        <Bubble icon='tty' label='node'>
+          {systemName} v{systemVersion}
+        </Bubble>
+        <Bubble icon='chain' label='chain'>
+          {systemChain}
+        </Bubble>
+        <Bubble icon='code' label='runtime'>
+          {api.runtimeVersion.implName} v{api.runtimeVersion.implVersion}
+        </Bubble>
+        <Bubble icon='bullseye' label='best #'>
+          {formatNumber(chain_bestNumber)} ({formatNumber(chain_bestNumberLag)} lag)
+        </Bubble>
+        {staking_validators && (
+          <Bubble icon='chess queen' label='validators'>{
+            staking_validators.map((accountId, index): React.ReactNode => (
+              <IdentityIcon key={index} value={accountId} size={20} />
+            ))
+          }</Bubble>
+        )}
+        <Bubble icon='circle' label='total tokens'>
+          {formatBalance(balances_totalIssuance)}
+        </Bubble>
+      </div>
+    </summary>
+  );
+}
+
+// inject the actual API calls automatically into props
+export default translate(
+  withCalls<Props>(
+    'derive.chain.bestNumber',
+    'derive.chain.bestNumberLag',
+    'derive.staking.validators',
+    'query.balances.totalIssuance'
+  )(SummaryBar)
+);

+ 47 - 0
pioneer/packages/app-123code/src/Transfer.tsx

@@ -0,0 +1,47 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 BN from 'bn.js';
+import React, { useState } from 'react';
+import { Button, InputAddress, InputBalance, TxButton } from '@polkadot/react-components';
+
+import Summary from './Summary';
+
+interface Props {
+  accountId?: string | null;
+}
+
+export default function Transfer ({ accountId }: Props): React.ReactElement<Props> {
+  const [amount, setAmount] = useState<BN | undefined | null>(null);
+  const [recipientId, setRecipientId] = useState<string | null>(null);
+
+  return (
+    <section>
+      <h1>transfer</h1>
+      <div className='ui--row'>
+        <div className='large'>
+          <InputAddress
+            label='recipient address for this transfer'
+            onChange={setRecipientId}
+            type='all'
+          />
+          <InputBalance
+            label='amount to transfer'
+            onChange={setAmount}
+          />
+          <Button.Group>
+            <TxButton
+              accountId={accountId}
+              icon='send'
+              label='make transfer'
+              params={[recipientId, amount]}
+              tx='balances.transfer'
+            />
+          </Button.Group>
+        </div>
+        <Summary className='small'>Make a transfer from any account you control to another account. Transfer fees and per-transaction fees apply and will be calculated upon submission.</Summary>
+      </div>
+    </section>
+  );
+}

+ 38 - 0
pioneer/packages/app-123code/src/index.tsx

@@ -0,0 +1,38 @@
+// Copyright 2017-2019 @polkadot/app-123code authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+// some types, AppProps for the app and I18nProps to indicate
+// translatable strings. Generally the latter is quite "light",
+// `t` is inject into props (see the HOC export) and `t('any text')
+// does the translation
+import { AppProps, I18nProps } from '@polkadot/react-components/types';
+
+// external imports (including those found in the packages/*
+// of this repo)
+import React, { useState } from 'react';
+
+// local imports and components
+import AccountSelector from './AccountSelector';
+import SummaryBar from './SummaryBar';
+import Transfer from './Transfer';
+import translate from './translate';
+
+// define our internal types
+interface Props extends AppProps, I18nProps {}
+
+function App ({ className }: Props): React.ReactElement<Props> {
+  const [accountId, setAccountId] = useState<string | null>(null);
+
+  return (
+    // in all apps, the main wrapper is setup to allow the padding
+    // and margins inside the application. (Just from a consistent pov)
+    <main className={className}>
+      <SummaryBar />
+      <AccountSelector onChange={setAccountId} />
+      <Transfer accountId={accountId} />
+    </main>
+  );
+}
+
+export default translate(App);

+ 7 - 0
pioneer/packages/app-123code/src/translate.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 { withTranslation } from 'react-i18next';
+
+export default withTranslation(['app-123code']);

+ 201 - 0
pioneer/packages/app-accounts/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 5 - 0
pioneer/packages/app-accounts/README.md

@@ -0,0 +1,5 @@
+# @polkadot/app-accounts
+
+## vanity
+
+Running `yarn run vanitygen --match <string>` runs the generator as a Node CLI app. (Orders of a magnitude faster due to the use of libsoldium bindings)

+ 23 - 0
pioneer/packages/app-accounts/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "@polkadot/app-accounts",
+  "version": "0.37.0-beta.63",
+  "main": "index.js",
+  "repository": "https://github.com/polkadot-js/apps.git",
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "maintainers": [
+    "Jaco Greeff <jacogr@gmail.com>"
+  ],
+  "contributors": [],
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63",
+    "@polkadot/react-qr": "^0.47.0-beta.3",
+    "@types/file-saver": "^2.0.0",
+    "@types/yargs": "^13.0.2",
+    "babel-plugin-module-resolver": "^3.1.1",
+    "detect-browser": "^4.8.0",
+    "file-saver": "^2.0.0",
+    "yargs": "^14.2.0"
+  }
+}

+ 26 - 0
pioneer/packages/app-accounts/scripts/vanitygen.js

@@ -0,0 +1,26 @@
+#!/usr/bin/env node
+/* eslint-disable @typescript-eslint/no-var-requires */
+// Copyright 2017-2019 @polkadot/app-accounts authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+const fs = require('fs');
+const path = require('path');
+
+const [compiled] = ['../vanitygen/cli.js']
+  .map((file) => path.join(__dirname, file))
+  .filter((file) => fs.existsSync(file));
+
+if (compiled) {
+  require(compiled);
+} else {
+  require('@babel/register')({
+    extensions: ['.js', '.ts'],
+    plugins: [
+      ['module-resolver', {
+        alias: {}
+      }]
+    ]
+  });
+  require('../src/vanitygen/cli.ts');
+}

+ 227 - 0
pioneer/packages/app-accounts/src/Account.tsx

@@ -0,0 +1,227 @@
+// Copyright 2017-2019 @polkadot/app-staking 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 { ActionStatus } from '@polkadot/react-components/Status/types';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import React, { useState, useEffect } from 'react';
+import { Popup } from 'semantic-ui-react';
+import styled from 'styled-components';
+import { AddressCard, AddressInfo, Button, ChainLock, Forget, Menu } from '@polkadot/react-components';
+import keyring from '@polkadot/ui-keyring';
+
+import Backup from './modals/Backup';
+import ChangePass from './modals/ChangePass';
+import Derive from './modals/Derive';
+import Transfer from './modals/Transfer';
+import translate from './translate';
+
+interface Props extends I18nProps {
+  address: string;
+  className?: string;
+}
+
+function Account ({ address, className, t }: Props): React.ReactElement<Props> {
+  const [genesisHash, setGenesisHash] = useState<string | null>(null);
+  const [isBackupOpen, setIsBackupOpen] = useState(false);
+  const [{ isDevelopment, isEditable, isExternal }, setFlags] = useState({ isDevelopment: false, isEditable: false, isExternal: false });
+  const [isDeriveOpen, setIsDeriveOpen] = useState(false);
+  const [isForgetOpen, setIsForgetOpen] = useState(false);
+  const [isPasswordOpen, setIsPasswordOpen] = useState(false);
+  const [isSettingPopupOpen, setIsSettingPopupOpen] = useState(false);
+  const [isTransferOpen, setIsTransferOpen] = useState(false);
+
+  useEffect((): void => {
+    const account = keyring.getAccount(address);
+
+    setGenesisHash((account && account.meta.genesisHash) || null);
+    setFlags({
+      isDevelopment: (account && account.meta.isTesting) || false,
+      isEditable: (account && !(account.meta.isInjected || account.meta.isHardware)) || false,
+      isExternal: (account && account.meta.isExternal) || false
+    });
+  }, [address]);
+
+  const _toggleBackup = (): void => setIsBackupOpen(!isBackupOpen);
+  const _toggleDerive = (): void => setIsDeriveOpen(!isDeriveOpen);
+  const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
+  const _togglePass = (): void => setIsPasswordOpen(!isPasswordOpen);
+  const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
+  const _toggleSettingPopup = (): void => setIsSettingPopupOpen(!isSettingPopupOpen);
+  const _onForget = (): void => {
+    if (!address) {
+      return;
+    }
+
+    const status: Partial<ActionStatus> = {
+      account: address,
+      action: 'forget'
+    };
+
+    try {
+      keyring.forgetAccount(address);
+      status.status = 'success';
+      status.message = t('account forgotten');
+    } catch (error) {
+      status.status = 'error';
+      status.message = error.message;
+    }
+  };
+  const _onGenesisChange = (genesisHash: string | null): void => {
+    const account = keyring.getPair(address);
+
+    account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
+
+    setGenesisHash(genesisHash);
+  };
+
+  // FIXME It is a bit heavy-handled switching of being editable here completely
+  // (and removing the tags, however the keyring cannot save these)
+  return (
+    <AddressCard
+      buttons={
+        <div className='accounts--Account-buttons buttons'>
+          <div className='actions'>
+            {isEditable && !isDevelopment && (
+              <Button
+                isNegative
+                onClick={_toggleForget}
+                icon='trash'
+                size='small'
+                tooltip={t('Forget this account')}
+              />
+            )}
+            {isEditable && !isExternal && !isDevelopment && (
+              <>
+                <Button
+                  icon='cloud download'
+                  isPrimary
+                  onClick={_toggleBackup}
+                  size='small'
+                  tooltip={t('Create a backup file for this account')}
+                />
+                <Button
+                  icon='key'
+                  isPrimary
+                  onClick={_togglePass}
+                  size='small'
+                  tooltip={t("Change this account's password")}
+                />
+              </>
+            )}
+            <Button
+              icon='paper plane'
+              isPrimary
+              label={t('send')}
+              onClick={_toggleTransfer}
+              size='small'
+              tooltip={t('Send funds from this account')}
+            />
+            {isEditable && !isExternal && (
+              <Popup
+                onClose={_toggleSettingPopup}
+                open={isSettingPopupOpen}
+                position='bottom left'
+                trigger={
+                  <Button
+                    icon='setting'
+                    onClick={_toggleSettingPopup}
+                    size='small'
+                  />
+                }
+              >
+                <Menu
+                  vertical
+                  text
+                  onClick={_toggleSettingPopup}
+                >
+                  <Menu.Item onClick={_toggleDerive}>
+                    {t('Derive account from source')}
+                  </Menu.Item>
+                  <Menu.Item disabled>
+                    {t('Change on-chain nickname')}
+                  </Menu.Item>
+                </Menu>
+              </Popup>
+            )}
+          </div>
+          {isEditable && !isExternal && (
+            <div className='others'>
+              <ChainLock
+                genesisHash={genesisHash}
+                onChange={_onGenesisChange}
+              />
+            </div>
+          )}
+        </div>
+      }
+      className={className}
+      isEditable={isEditable}
+      type='account'
+      value={address}
+      withExplorer
+      withIndexOrAddress={false}
+      withTags
+    >
+      {address && (
+        <>
+          {isBackupOpen && (
+            <Backup
+              address={address}
+              key='modal-backup-account'
+              onClose={_toggleBackup}
+            />
+          )}
+          {isDeriveOpen && (
+            <Derive
+              from={address}
+              key='modal-derive-account'
+              onClose={_toggleDerive}
+            />
+          )}
+          {isForgetOpen && (
+            <Forget
+              address={address}
+              onForget={_onForget}
+              key='modal-forget-account'
+              onClose={_toggleForget}
+            />
+          )}
+          {isPasswordOpen && (
+            <ChangePass
+              address={address}
+              key='modal-change-pass'
+              onClose={_togglePass}
+            />
+          )}
+          {isTransferOpen && (
+            <Transfer
+              key='modal-transfer'
+              onClose={_toggleTransfer}
+              senderId={address}
+            />
+          )}
+        </>
+      )}
+      <AddressInfo
+        address={address}
+        withBalance
+        withExtended
+      />
+    </AddressCard>
+  );
+}
+
+export default translate(
+  styled(Account)`
+    .accounts--Account-buttons {
+      text-align: right;
+
+      .others {
+        margin-right: 0.125rem;
+        margin-top: 0.25rem;
+      }
+    }
+  `
+);

+ 105 - 0
pioneer/packages/app-accounts/src/Banner.tsx

@@ -0,0 +1,105 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+
+import { detect } from 'detect-browser';
+import React from 'react';
+import styled from 'styled-components';
+import { isWeb3Injected } from '@polkadot/extension-dapp';
+import { stringUpperFirst } from '@polkadot/util';
+
+import translate from './translate';
+
+// it would have been really good to import this from detect, however... not exported
+type Browser = 'chrome' | 'firefox';
+
+interface Extension {
+  desc: string;
+  link: string;
+  name: string;
+}
+
+interface Props extends I18nProps {
+  className?: string;
+}
+
+const available: Record<Browser, Extension[]> = {
+  chrome: [],
+  firefox: []
+};
+
+[
+  {
+    browsers: {
+      chrome: 'https://chrome.google.com/webstore/detail/polkadot%7Bjs%7D-extension/mopnmbcafieddcagagdcbnhejhlodfdd',
+      firefox: 'https://addons.mozilla.org/en-US/firefox/addon/polkadot-js-extension/'
+    },
+    desc: 'Basic account injection and signer',
+    name: 'polkadot-js extension'
+  }
+].forEach(({ browsers, desc, name }): void => {
+  Object.entries(browsers).forEach(([browser, link]): void => {
+    available[browser as Browser].push({ link, desc, name });
+  });
+});
+
+const browserInfo = detect();
+const browserName: Browser | null = (browserInfo && (browserInfo.name as Browser)) || null;
+const isSupported = browserName && Object.keys(available).includes(browserName);
+
+function Banner ({ className, t }: Props): React.ReactElement<Props> | null {
+  if (isWeb3Injected || !isSupported || !browserName) {
+    return null;
+  }
+
+  return (
+    <div className={className}>
+      <div className='box'>
+        <div className='info'>
+          <p>{t('It is recommended that you create/store your accounts securely and externally from the app. On {{yourBrowser}} the following browser extensions are available for use -', {
+            replace: {
+              yourBrowser: stringUpperFirst(browserName)
+            }
+          })}</p>
+          <ul>{available[browserName].map(({ desc, name, link }): React.ReactNode => (
+            <li key={name}>
+              <a
+                href={link}
+                rel='noopener noreferrer'
+                target='_blank'
+              >
+                {name}
+              </a> ({desc})
+            </li>
+          ))
+          }</ul>
+          <p>{t('Accounts injected from any of these extensions will appear in this application and be available for use. The above list is updated as more extensions with external signing capability become available.')}&nbsp;<a
+            href='https://github.com/polkadot-js/extension'
+            rel='noopener noreferrer'
+            target='_blank'
+          >{t('Learn more...')}</a></p>
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default translate(
+  styled(Banner)`
+    padding: 0 0.5rem 0.5rem;
+
+    .box {
+      background: #fff6e5;
+      border-left: 0.25rem solid darkorange;
+      border-radius: 0 0.25rem 0.25rem 0;
+      box-sizing: border-box;
+      padding: 1rem 1.5rem;
+
+      .info {
+        max-width: 50rem;
+      }
+    }
+  `
+);

+ 51 - 0
pioneer/packages/app-accounts/src/MemoForm.tsx

@@ -0,0 +1,51 @@
+import React from 'react';
+import { Labelled } from '@polkadot/react-components/index';
+
+import MemoEdit from '@polkadot/joy-utils/memo/MemoEdit';
+import TxButton from '@polkadot/joy-utils/TxButton';
+import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
+import { Text } from '@polkadot/types';
+
+type Props = MyAccountProps & {};
+
+type State = {
+  memo: string,
+  modified: boolean,
+};
+
+class Component extends React.PureComponent<Props, State> {
+
+  state: State = {
+    memo: '',
+    modified: false,
+  };
+
+  render () {
+    const { myAddress } = this.props;
+    const { memo, modified } = this.state;
+    return (
+      <>
+        <MemoEdit accountId={myAddress || ''} onChange={this.onChangeMemo} onReset={this.onResetMemo} />
+        <Labelled style={{ marginTop: '.5rem' }}>
+          <TxButton
+            size='large'
+            isDisabled={!modified}
+            label='Update memo'
+            params={[new Text(memo)]}
+            tx='memo.updateMemo'
+          />
+        </Labelled>
+      </>
+    );
+  }
+
+  onChangeMemo = (memo: string): void => {
+    this.setState({ memo, modified: true });
+  }
+
+  onResetMemo = (memo: string): void => {
+    this.setState({ memo, modified: false });
+  }
+}
+
+export default withMyAccount(Component);

+ 104 - 0
pioneer/packages/app-accounts/src/Overview.tsx

@@ -0,0 +1,104 @@
+// Copyright 2017-2019 @polkadot/app-staking 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 { I18nProps } from '@polkadot/react-components/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+import { ComponentProps } from './types';
+
+import React, { useState } from 'react';
+import keyring from '@polkadot/ui-keyring';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { getLedger, isLedger, withMulti, withObservable } from '@polkadot/react-api';
+import { Button, CardGrid } from '@polkadot/react-components';
+
+import CreateModal from './modals/Create';
+import ImportModal from './modals/Import';
+import Account from './Account';
+import translate from './translate';
+
+interface Props extends ComponentProps, I18nProps {
+  accounts?: SubjectInfo[];
+}
+
+// query the ledger for the address, adding it to the keyring
+async function queryLedger (): Promise<void> {
+  const ledger = getLedger();
+
+  try {
+    const { address } = await ledger.getAddress();
+
+    keyring.addHardware(address, 'ledger', { name: 'ledger' });
+  } catch (error) {
+    console.error(error);
+  }
+}
+
+function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [isCreateOpen, setIsCreateOpen] = useState(false);
+  const [isImportOpen, setIsImportOpen] = useState(false);
+  const emptyScreen = !(isCreateOpen || isImportOpen) && accounts && (Object.keys(accounts).length === 0);
+
+  const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
+  const _toggleImport = (): void => setIsImportOpen(!isImportOpen);
+
+  return (
+    <CardGrid
+      buttons={
+        <Button.Group>
+          <Button
+            icon='add'
+            isPrimary
+            label={t('Add account')}
+            onClick={_toggleCreate}
+          />
+          <Button.Or />
+          <Button
+            icon='sync'
+            isPrimary
+            label={t('Restore JSON')}
+            onClick={_toggleImport}
+          />
+         {isLedger() && (
+            <>
+              <Button.Or />
+              <Button
+                icon='question'
+                isPrimary
+                label={t('Query Ledger')}
+                onClick={queryLedger}
+              />
+            </>
+          )}
+        </Button.Group>
+      }
+      isEmpty={emptyScreen}
+      emptyText={t('No account yet?')}
+    >
+      {isCreateOpen && (
+        <CreateModal
+          onClose={_toggleCreate}
+          onStatusChange={onStatusChange}
+        />
+      )}
+      {isImportOpen && (
+        <ImportModal
+          onClose={_toggleImport}
+          onStatusChange={onStatusChange}
+        />
+      )}
+     {accounts && Object.keys(accounts).map((address): React.ReactNode => (
+        <Account
+          address={address}
+          key={address}
+        />
+      ))}
+    </CardGrid>
+  );
+}
+
+export default withMulti(
+  Overview,
+  translate,
+  withObservable(accountObservable.subject, { propName: 'accounts' })
+);

+ 104 - 0
pioneer/packages/app-accounts/src/Vanity/Match.tsx

@@ -0,0 +1,104 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { BareProps } from '@polkadot/react-components/types';
+
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { Button, IdentityIcon } from '@polkadot/react-components';
+import { u8aToHex } from '@polkadot/util';
+
+interface Props extends BareProps {
+  address: string;
+  count: number;
+  offset: number;
+  onCreateToggle: (seed: string) => void;
+  onRemove: (address: string) => void;
+  seed: Uint8Array;
+}
+
+function Match ({ address, className, count, offset, onCreateToggle, onRemove, seed }: Props): React.ReactElement<Props> {
+  const [hexSeed, setHexSeed] = useState('');
+  const _onCreate = (): void => onCreateToggle(hexSeed);
+  const _onRemove = (): void => onRemove(address);
+
+  useEffect((): void => {
+    setHexSeed(u8aToHex(seed));
+  }, [seed]);
+
+  return (
+    <div className={className}>
+      <div className='vanity--Match-item'>
+        <IdentityIcon
+          className='vanity--Match-icon'
+          size={48}
+          value={address}
+        />
+        <div className='vanity--Match-data'>
+          <div className='vanity--Match-addr'>
+            <span className='no'>{address.slice(0, offset)}</span><span className='yes'>{address.slice(offset, count + offset)}</span><span className='no'>{address.slice(count + offset)}</span>
+          </div>
+          <div className='vanity--Match-seed'>
+            {hexSeed}
+          </div>
+        </div>
+        <div className='vanity--Match-buttons'>
+          <Button
+            icon='plus'
+            isPrimary
+            onClick={_onCreate}
+            size='tiny'
+          />
+          <Button
+            icon='close'
+            isNegative
+            onClick={_onRemove}
+            size='tiny'
+          />
+        </div>
+      </div>
+    </div>
+  );
+}
+
+export default styled(Match)`
+  text-align: center;
+
+  &:hover {
+    background: #f9f9f9;
+  }
+
+  .vanity--Match-addr {
+    font-size: 1.5rem;
+    padding: 0 1rem;
+
+    .no {
+      color: inherit;
+    }
+
+    .yes {
+      color: red;
+    }
+  }
+
+  .vanity--Match-buttons,
+  .vanity--Match-data,
+  .vanity--Match-icon {
+    display: inline-block;
+    vertical-align: middle;
+  }
+
+  .vanity--Match-item {
+    display: inline-block;
+    font-family: monospace;
+    margin: 0 auto;
+    padding: 0.5em;
+    position: relative;
+  }
+
+  .vanity--Match-seed {
+    opacity: 0.45;
+    padding: 0 1rem;
+  }
+`;

+ 328 - 0
pioneer/packages/app-accounts/src/Vanity/index.tsx

@@ -0,0 +1,328 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+import { KeypairType } from '@polkadot/util-crypto/types';
+import { GeneratorMatches, GeneratorMatch, GeneratorResult } from '../vanitygen/types';
+import { ComponentProps } from '../types';
+
+import React from 'react';
+import styled from 'styled-components';
+import { Button, Dropdown, Input, TxComponent } from '@polkadot/react-components';
+import uiSettings from '@polkadot/ui-settings';
+
+import CreateModal from '../modals/Create';
+import generator from '../vanitygen';
+import matchRegex from '../vanitygen/regex';
+import generatorSort from '../vanitygen/sort';
+import Match from './Match';
+import translate from './translate';
+
+interface Props extends ComponentProps, I18nProps {}
+
+interface State {
+  createSeed: string | null;
+  elapsed: number;
+  isMatchValid: boolean;
+  isRunning: boolean;
+  keyCount: 0;
+  keyTime: 0;
+  match: string;
+  matches: GeneratorMatches;
+  startAt: number;
+  type: KeypairType;
+  withCase: boolean;
+  withHex: boolean;
+}
+
+const DEFAULT_MATCH = 'Some';
+const BOOL_OPTIONS = [
+  { text: 'No', value: false },
+  { text: 'Yes', value: true }
+];
+
+class VanityApp extends TxComponent<Props, State> {
+  private results: GeneratorResult[] = [];
+
+  public state: State = {
+    createSeed: null,
+    elapsed: 0,
+    isMatchValid: true,
+    isRunning: false,
+    keyCount: 0,
+    keyTime: 0,
+    match: DEFAULT_MATCH,
+    matches: [],
+    startAt: 0,
+    type: 'ed25519',
+    withCase: true,
+    withHex: true
+  };
+
+  private _isActive = false;
+
+  public componentWillUnmount (): void {
+    this._isActive = false;
+  }
+
+  public render (): React.ReactNode {
+    const { className, onStatusChange } = this.props;
+    const { createSeed, type } = this.state;
+
+    return (
+      <div className={className}>
+        {this.renderOptions()}
+        {this.renderButtons()}
+        {this.renderStats()}
+        {this.renderMatches()}
+        {createSeed && (
+          <CreateModal
+            onClose={this.closeCreate}
+            onStatusChange={onStatusChange}
+            seed={createSeed}
+            type={type}
+          />
+        )}
+      </div>
+    );
+  }
+
+  private renderButtons (): React.ReactNode {
+    const { t } = this.props;
+    const { isMatchValid, isRunning } = this.state;
+
+    return (
+      <Button.Group>
+        <Button
+          icon={
+            isRunning
+              ? 'stop'
+              : 'sign-in'
+          }
+          isDisabled={!isMatchValid}
+          isPrimary={!isRunning}
+          onClick={this.toggleStart}
+          label={
+            isRunning
+              ? t('Stop generation')
+              : t('Start generation')
+          }
+          ref={this.button}
+        />
+      </Button.Group>
+    );
+  }
+
+  private renderMatches (): React.ReactNode {
+    const { matches } = this.state;
+
+    return (
+      <div className='vanity--App-matches'>
+        {matches.map((match): React.ReactNode => (
+          <Match
+            {...match}
+            key={match.address}
+            onCreateToggle={this.onCreateToggle}
+            onRemove={this.onRemove}
+          />
+        ))}
+      </div>
+    );
+  }
+
+  private renderOptions (): React.ReactNode {
+    const { t } = this.props;
+    const { isMatchValid, isRunning, match, type, withCase } = this.state;
+
+    return (
+      <>
+        <div className='ui--row'>
+          <Input
+            autoFocus
+            className='medium'
+            help={t('Type here what you would like your address to contain. This tool will generate the keys and show the associated addresses that best match your search. You can use "?" as a wildcard for a character.')}
+            isDisabled={isRunning}
+            isError={!isMatchValid}
+            label={t('Search for')}
+            onChange={this.onChangeMatch}
+            onEnter={this.submit}
+            value={match}
+          />
+          <Dropdown
+            className='medium'
+            help={t('Should the search be case sensitive, e.g if you select "no" your search for "Some" may return addresses containing "somE" or "sOme"...')}
+            isDisabled={isRunning}
+            label={t('case sensitive')}
+            options={BOOL_OPTIONS}
+            onChange={this.onChangeCase}
+            value={withCase}
+          />
+        </div>
+        <div className='ui--row'>
+          <Dropdown
+            className='medium'
+            defaultValue={type}
+            help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
+            label={t('keypair crypto type')}
+            onChange={this.onChangeType}
+            options={uiSettings.availableCryptos}
+          />
+        </div>
+      </>
+    );
+  }
+
+  private renderStats (): React.ReactNode {
+    const { t } = this.props;
+    const { elapsed, keyCount } = this.state;
+
+    if (!keyCount) {
+      return null;
+    }
+
+    const secs = elapsed / 1000;
+
+    return (
+      <div className='vanity--App-stats'>
+        {t('Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)', {
+          replace: {
+            avg: (keyCount / secs).toFixed(3),
+            count: keyCount,
+            elapsed: secs.toFixed(2)
+          }
+        })}
+      </div>
+    );
+  }
+
+  private checkMatches (): void {
+    const results = this.results;
+
+    this.results = [];
+
+    if (results.length === 0 || !this._isActive) {
+      return;
+    }
+
+    this.setState(
+      ({ keyCount, keyTime, matches, startAt }: State): Pick<State, never> => {
+        let newKeyCount = keyCount;
+        let newKeyTime = keyTime;
+        const newMatches = results
+          .reduce((result, { elapsed, found }): GeneratorMatch[] => {
+            newKeyCount += found.length;
+            newKeyTime += elapsed;
+
+            return result.concat(found);
+          }, matches)
+          .sort(generatorSort)
+          .slice(0, 25);
+        const elapsed = Date.now() - startAt;
+
+        return {
+          elapsed,
+          matches: newMatches,
+          keyCount: newKeyCount,
+          keyTime: newKeyTime
+        };
+      }
+    );
+  }
+
+  private executeGeneration = (): void => {
+    if (!this.state.isRunning) {
+      this.checkMatches();
+
+      return;
+    }
+
+    setTimeout((): void => {
+      if (this._isActive) {
+        if (this.results.length === 25) {
+          this.checkMatches();
+        }
+
+        const { match, type, withCase, withHex } = this.state;
+
+        this.results.push(
+          generator({
+            match,
+            runs: 10,
+            type,
+            withCase,
+            withHex
+          })
+        );
+
+        this.executeGeneration();
+      }
+    }, 0);
+  }
+
+  private onCreateToggle = (createSeed: string): void => {
+    this.setState({ createSeed });
+  }
+
+  private onChangeCase = (withCase: boolean): void => {
+    this.setState({ withCase });
+  }
+
+  private onChangeMatch = (match: string): void => {
+    this.setState({
+      isMatchValid:
+        matchRegex.test(match) &&
+        (match.length !== 0) &&
+        (match.length < 31),
+      match
+    });
+  }
+
+  private onChangeType = (type: KeypairType): void => {
+    this.setState({ type });
+  }
+
+  private onRemove = (address: string): void => {
+    this.setState(
+      ({ matches }: State): Pick<State, never> => ({
+        matches: matches.filter((item): boolean =>
+          item.address !== address
+        )
+      })
+    );
+  }
+
+  private toggleStart = (): void => {
+    this.setState(
+      ({ isRunning, keyCount, keyTime, startAt }: State): Pick<State, never> => {
+        this._isActive = !isRunning;
+
+        return {
+          isRunning: this._isActive,
+          keyCount: this._isActive ? 0 : keyCount,
+          keyTime: this._isActive ? 0 : keyTime,
+          startAt: this._isActive ? Date.now() : startAt
+        };
+      },
+      this.executeGeneration
+    );
+  }
+
+  private closeCreate = (): void => {
+    this.setState({ createSeed: null });
+  }
+}
+
+export default translate(
+  styled(VanityApp)`
+    .vanity--App-matches {
+      padding: 1em 0;
+    }
+
+    .vanity--App-stats {
+      padding: 1em 0 0 0;
+      opacity: 0.45;
+      text-align: center;
+    }
+  `
+);

+ 7 - 0
pioneer/packages/app-accounts/src/Vanity/translate.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { withTranslation } from 'react-i18next';
+
+export default withTranslation(['app-accounts']);

+ 26 - 0
pioneer/packages/app-accounts/src/bipWorker.ts

@@ -0,0 +1,26 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { cryptoWaitReady, mnemonicGenerate, mnemonicToMiniSecret, naclKeypairFromSeed, schnorrkelKeypairFromSeed } from '@polkadot/util-crypto';
+
+const ctx: Worker = self as unknown as Worker;
+
+cryptoWaitReady().catch((): void => {
+  // ignore
+});
+
+ctx.onmessage = async ({ data: { pairType } }): Promise<void> => {
+  await cryptoWaitReady();
+
+  const seed = mnemonicGenerate();
+  const miniSecret = mnemonicToMiniSecret(seed);
+  const { publicKey } = pairType === 'sr25519'
+    ? schnorrkelKeypairFromSeed(miniSecret)
+    : naclKeypairFromSeed(miniSecret);
+
+  ctx.postMessage({
+    publicKey,
+    seed
+  });
+};

+ 89 - 0
pioneer/packages/app-accounts/src/index.tsx

@@ -0,0 +1,89 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { ComponentProps, LocationProps } from './types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+
+import React, { useEffect, useState } from 'react';
+import { Route, Switch } from 'react-router';
+import accountObservable from '@polkadot/ui-keyring/observable/accounts';
+import { HelpOverlay, Tabs } from '@polkadot/react-components';
+import { withMulti, withObservable } from '@polkadot/react-api';
+
+import basicMd from './md/basic.md';
+import Overview from './Overview';
+import translate from './translate';
+import Vanity from './Vanity';
+
+import MemoForm from './MemoForm';
+
+interface Props extends AppProps, I18nProps {
+  allAccounts?: SubjectInfo;
+  location: any;
+}
+
+function AccountsApp ({ allAccounts = {}, basePath, location, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [hidden, setHidden] = useState<string[]>(['vanity']);
+
+  useEffect((): void => {
+    setHidden(
+      Object.keys(allAccounts).length !== 0
+        ? []
+        : ['vanity']
+    );
+  }, [allAccounts]);
+
+  const _renderComponent = (Component: React.ComponentType<ComponentProps>): (props: LocationProps) => React.ReactNode => {
+    // eslint-disable-next-line react/display-name
+    return ({ match }: LocationProps): React.ReactNode => {
+      return (
+        <Component
+          basePath={basePath}
+          location={location}
+          match={match}
+          onStatusChange={onStatusChange}
+        />
+      );
+    };
+  };
+
+  return (
+    <main className='accounts--App'>
+      <HelpOverlay md={basicMd} />
+      <header>
+        <Tabs
+          basePath={basePath}
+          hidden={hidden}
+          items={[
+            {
+              isRoot: true,
+              name: 'overview',
+              text: t('My accounts')
+            },
+            {
+              name: 'vanity',
+              text: t('Vanity address')
+            },
+            {
+              name: 'memo',
+              text: t('My memo')
+            }
+          ]}
+        />
+      </header>
+      <Switch>
+        <Route path={`${basePath}/vanity`} render={_renderComponent(Vanity)} />
+        <Route path={`${basePath}/memo`} component={MemoForm} />
+        <Route render={_renderComponent(Overview)} />
+      </Switch>
+    </main>
+  );
+}
+
+export default withMulti(
+  AccountsApp,
+  translate,
+  withObservable(accountObservable.subject, { propName: 'allAccounts' })
+);

+ 44 - 0
pioneer/packages/app-accounts/src/md/basic.md

@@ -0,0 +1,44 @@
+# Account
+
+An account is identified by its public address on the network. It is totally fine to give away this address, this is also the only information needed to receive funds. The network will **not** know about the name you give to this account in this application.
+
+# Balances
+
+The balances for each account is broken down into a number of areas, giving an overview of the totals, transferable and bonded funds as well as the funds currently being unbonded or redeemable. These are -
+
+- **total**: The overall amount of funds in the account, this includes the vested balance, available for transfer and locked.
+- **available**: The funds that can be transferred or bonded, i.e. the funds that are available for any transaction.
+- **bonded**: The funds bonded for validating or nominating. They are locked and cannot be transferred, although it can be unlocked for future actions.
+- **redeemable**: The funds that can get redeemed, e.g made available for withdrawal, by clicking on the "lock" icon.
+- **unbonding**: The funds that are being unbonded. The funds will be redeemable after the bonding period has passed. These funds can still be slashed. The information icon tells the amount of blocks left before the funds can be redeemed.
+
+# Security
+
+The public address is generated from a private key, itself generated from a seed or a mnemonic phrase. The seed or the mnemonic phrase should **never be shared with anybody** as they give access to your funds. It must be stored securely.
+The password needed to create an account is used to encrypt your private key. You must choose a strong and unique password.
+This password is also used to encrypt the private key in the backup file downloaded upon account creation. Thanks to this file together with your account password, you can recover your account.
+
+# Account recovery
+
+You can recover an account from its:
+- seed or mnemonic:
+  Click on the "Add account" button, type your seed or mnemonic in the associated field.
+
+- backup file (also called JSON keystore file) and the account's password:
+  Click on "Restore JSON" button. Upload your backup file and type in the password associated.
+
+# Minimum allowed balance
+
+Accounts with a balance lower than the minimal amount, 100 milliUnits (miliDOTs for Polkadot) as of writing are considered as nonexistent for the network. If an account's balance ever drops below this amount, it is removed from the network. In this application, it will still be visible, but with a balance of 0.
+
+For a fund transfer to a **new account** (read an account with a balance of 0), if the amount transferred is less than the minimum allowed balance, then the transfer will "succeed" but the destination account will not be created (read its balance will remain 0); this essentially burns the transfer balance from the sender, because the receiver's balance never exceed the minimum allowed balance.
+
+If the receiver already exists (read it has a balance greater than 0), it is perfectly possible to transfer very low amounts.
+
+# Cryptography
+
+Substrate and Polkadot use Schnorrkel/Ristretto x25519 ("sr25519") as its key derivation and signing algorithm.
+
+Sr25519 is based on the same underlying Curve25519 as its EdDSA counterpart, Ed25519. However, it uses Schnorr signatures instead of the EdDSA scheme. Schnorr signatures bring some noticeable benefits over the ECDSA/EdDSA schemes. For one, it is more efficient and still retains the same feature set and security assumptions. Additionally, it allows for native multisignature through signature aggregation.
+
+If you wish to validate, the `session` account needs to use "ed25519" cryptography.

+ 144 - 0
pioneer/packages/app-accounts/src/modals/Backup.tsx

@@ -0,0 +1,144 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+
+import FileSaver from 'file-saver';
+import React from 'react';
+import { AddressRow, Button, Modal, Password, TxComponent } from '@polkadot/react-components';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import keyring from '@polkadot/ui-keyring';
+import { isPasswordValid } from '@polkadot/joy-utils/accounts';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  onClose: () => void;
+  address: string;
+}
+
+interface State {
+  isPassValid: boolean;
+  password: string;
+}
+
+class Backup extends TxComponent<Props, State> {
+  public state: State = {
+    isPassValid: true,
+    password: ''
+  };
+
+  public render (): React.ReactNode {
+    const { t } = this.props;
+
+    return (
+      <Modal
+        className='app--accounts-Modal'
+        dimmer='inverted'
+        open
+      >
+        <Modal.Header>{t('Backup account')}</Modal.Header>
+        {this.renderContent()}
+        {this.renderButtons()}
+      </Modal>
+    );
+  }
+
+  private renderButtons (): React.ReactNode {
+    const { onClose, t } = this.props;
+    const { isPassValid } = this.state;
+
+    return (
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='download'
+            isDisabled={!isPassValid}
+            label={t('Download')}
+            onClick={this.doBackup}
+            ref={this.button}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    );
+  }
+
+  private renderContent (): React.ReactNode {
+    const { address, t } = this.props;
+    const { isPassValid, password } = this.state;
+
+    return (
+      <Modal.Content>
+        <AddressRow
+          isInline
+          value={address}
+        >
+          <p>{t('An encrypted backup file will be created once you have pressed the "Download" button. This can be used to re-import your account on any other machine.')}</p>
+          <p>{t('Save this backup file in a secure location. Additionally, the password associated with this account is needed together with this backup file in order to restore your account.')}</p>
+          <div>
+            <Password
+              help={t('The account password as specified when creating the account. This is used to encrypt the backup file and subsequently decrypt it when restoring the account.')}
+              isError={!isPassValid}
+              label={t('password')}
+              onChange={this.onChangePass}
+              onEnter={this.submit}
+              tabIndex={0}
+              value={password}
+            />
+          </div>
+        </AddressRow>
+      </Modal.Content>
+    );
+  }
+
+  private doBackup = (): void => {
+    const { onClose, address, t } = this.props;
+    const { password } = this.state;
+
+    if (!address) {
+      return;
+    }
+
+    const status: Partial<ActionStatus> = {
+      action: 'backup'
+    };
+
+    try {
+      const addressKeyring = address && keyring.getPair(address);
+      const json = addressKeyring && keyring.backupAccount(addressKeyring, password);
+      const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
+
+      status.account = address;
+      status.status = blob ? 'success' : 'error';
+      status.message = t('account backed up');
+
+      FileSaver.saveAs(blob, `${address}.json`);
+    } catch (error) {
+      this.setState({ isPassValid: true });
+      console.error(error);
+
+      status.status = 'error';
+      status.message = error.message;
+      return;
+    }
+
+    onClose();
+  }
+
+  private onChangePass = (password: string): void => {
+    this.setState({
+      isPassValid: isPasswordValid(password),
+      password
+    });
+  }
+}
+
+export default translate(Backup);

+ 183 - 0
pioneer/packages/app-accounts/src/modals/ChangePass.tsx

@@ -0,0 +1,183 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+
+import React from 'react';
+import { AddressRow, Button, Modal, Password, TxComponent } from '@polkadot/react-components';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import keyring from '@polkadot/ui-keyring';
+import { isPasswordValid } from '@polkadot/joy-utils/accounts';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  address: string;
+  onClose: () => void;
+}
+
+interface State {
+  isNewValid: boolean;
+  isOldValid: boolean;
+  newPass: string;
+  oldPass: string;
+}
+
+class ChangePass extends TxComponent<Props, State> {
+  public state: State = {
+    isNewValid: false,
+    isOldValid: false,
+    newPass: '',
+    oldPass: ''
+  };
+
+  public render (): React.ReactNode {
+    const { t } = this.props;
+
+    return (
+      <Modal
+        className='app--accounts-Modal'
+        dimmer='inverted'
+        open
+      >
+        <Modal.Header>{t('Change account password')}</Modal.Header>
+        {this.renderContent()}
+        {this.renderButtons()}
+      </Modal>
+    );
+  }
+
+  private renderButtons (): React.ReactNode {
+    const { onClose, t } = this.props;
+    const { isNewValid, isOldValid } = this.state;
+
+    return (
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='sign-in'
+            isDisabled={!isNewValid || !isOldValid}
+            isPrimary
+            label={t('Change')}
+            onClick={this.doChange}
+            ref={this.button}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    );
+  }
+
+  private renderContent (): React.ReactNode {
+    const { address, t } = this.props;
+    const { isNewValid, isOldValid, newPass, oldPass } = this.state;
+
+    return (
+      <Modal.Content>
+        <AddressRow
+          isInline
+          value={address}
+        >
+          <p>{t('This will apply to any future use of this account as stored on this browser. Ensure that you securely store this new password and that it is strong and unique to the account.')}</p>
+          <div>
+            <Password
+              autoFocus
+              help={t('The existing account password as specified when this account was created or when it was last changed.')}
+              isError={!isOldValid}
+              label={t('your current password')}
+              onChange={this.onChangeOld}
+              tabIndex={1}
+              value={oldPass}
+            />
+            <Password
+              help={t('The new account password. Once set, all future account unlocks will be performed with this new password.')}
+              isError={!isNewValid}
+              label={t('your new password')}
+              onChange={this.onChangeNew}
+              onEnter={this.submit}
+              tabIndex={2}
+              value={newPass}
+            />
+          </div>
+        </AddressRow>
+      </Modal.Content>
+    );
+  }
+
+  private doChange = (): void => {
+    const { address, onClose, t } = this.props;
+    const { newPass, oldPass } = this.state;
+    const status: Partial<ActionStatus> = {
+      action: 'changePassword'
+    };
+
+    try {
+      const account = address && keyring.getPair(address);
+
+      if (!account) {
+        status.message = t(`No keypair found for this address ${address}`);
+
+        return;
+      }
+
+      try {
+        if (!account.isLocked) {
+          account.lock();
+        }
+
+        account.decodePkcs8(oldPass);
+      } catch (error) {
+        this.setState({ isOldValid: false });
+        status.message = error.message;
+
+        return;
+      }
+
+      try {
+        keyring.encryptAccount(account, newPass);
+        status.account = address;
+        status.status = 'success';
+        status.message = t('password changed');
+      } catch (error) {
+        this.setState({ isNewValid: false });
+        status.status = 'error';
+        status.message = error.message;
+
+        return;
+      }
+    } catch (error) {
+      status.message = error.message;
+
+      return;
+    }
+
+    onClose();
+  }
+
+  private onChangeNew = (newPass: string): void => {
+    this.setState({
+      isNewValid: this.validatePass(newPass),
+      newPass
+    });
+  }
+
+  private onChangeOld = (oldPass: string): void => {
+    this.setState({
+      isOldValid: this.validatePass(oldPass),
+      oldPass
+    });
+  }
+
+  private validatePass (password: string): boolean {
+    return isPasswordValid(password);
+  }
+}
+
+export default translate(ChangePass);

+ 193 - 0
pioneer/packages/app-accounts/src/modals/Create.tsx

@@ -0,0 +1,193 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+import { KeypairType } from '@polkadot/util-crypto/types';
+import { ModalProps } from '../types';
+
+import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
+import { generateSeed, updateAddress, createAccount, isPasswordValid, AddressState, SeedType } from '@polkadot/joy-utils/accounts';
+import React, { useContext, useState } from 'react';
+import styled from 'styled-components';
+import { ApiContext } from '@polkadot/react-api';
+import { AddressRow, Button, Dropdown, Input, Modal, Password } from '@polkadot/react-components';
+import uiSettings from '@polkadot/ui-settings';
+
+import translate from '../translate';
+import CreateConfirmation from './CreateConfirmation';
+
+interface Props extends ModalProps, I18nProps {
+  seed?: string;
+  type?: KeypairType;
+}
+
+function Create ({ className, onClose, onStatusChange, seed: propsSeed, t, type: propsType }: Props): React.ReactElement<Props> {
+  const { isDevelopment } = useContext(ApiContext);
+  const [{ address, deriveError, derivePath, isSeedValid, pairType, seed, seedType }, setAddress] = useState<AddressState>(generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', propsType));
+  const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
+  const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
+  const isValid = !!address && !deriveError && isNameValid && isPassValid && isSeedValid;
+
+  const _onChangePass = (password: string): void =>
+    setPassword({ isPassValid: isPasswordValid(password), password });
+  const _onChangeDerive = (newDerivePath: string): void =>
+    setAddress(updateAddress(seed, newDerivePath, seedType, pairType));
+  const _onChangeSeed = (newSeed: string): void =>
+    setAddress(updateAddress(newSeed, derivePath, seedType, pairType));
+  const _onChangePairType = (newPairType: KeypairType): void =>
+    setAddress(updateAddress(seed, derivePath, seedType, newPairType));
+  const _selectSeedType = (newSeedType: SeedType): void => {
+    if (newSeedType !== seedType) {
+      setAddress(generateSeed(null, derivePath, newSeedType, pairType));
+    }
+  };
+  const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name });
+  const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen);
+  const context = useMyAccount()
+
+  const _onCommit = (): void => {
+    if (!isValid) {
+      return;
+    }
+
+    const status = createAccount(`${seed}${derivePath}`, pairType, name, password, t('created account'));
+    context.set(status.account as string)
+
+    _toggleConfirmation();
+    onStatusChange(status);
+    onClose();
+  };
+
+  return (
+    <Modal
+      className={className}
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Add an account via seed')}</Modal.Header>
+      {address && isConfirmationOpen && (
+        <CreateConfirmation
+          address={address}
+          name={name}
+          onCommit={_onCommit}
+          onClose={_toggleConfirmation}
+        />
+      )}
+      <Modal.Content>
+        <AddressRow
+          defaultName={name}
+          noDefaultNameOpacity
+          value={isSeedValid ? address : ''}
+        >
+          <Input
+            autoFocus
+            className='full'
+            help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
+            isError={!isNameValid}
+            label={t('name')}
+            onChange={_onChangeName}
+            onEnter={_onCommit}
+            placeholder={t('new account')}
+            value={name}
+          />
+          <Input
+            className='full'
+            help={t('The private key for your account is derived from this seed. This seed must be kept secret as anyone in its possession has access to the funds of this account. If you validate, use the seed of the session account as the "--key" parameter of your node.')}
+            isAction
+            isError={!isSeedValid}
+            isReadOnly={seedType === 'dev'}
+            label={
+              seedType === 'bip'
+                ? t('mnemonic seed')
+                : seedType === 'dev'
+                  ? t('development seed')
+                  : t('seed (hex or string)')
+            }
+            onChange={_onChangeSeed}
+            onEnter={_onCommit}
+            value={seed}
+          >
+            <Dropdown
+              isButton
+              defaultValue={seedType}
+              onChange={_selectSeedType}
+              options={
+                (
+                  isDevelopment
+                    ? [{ value: 'dev', text: t('Development') }]
+                    : []
+                ).concat(
+                  { value: 'bip', text: t('Mnemonic') },
+                  { value: 'raw', text: t('Raw seed') }
+                )
+              }
+            />
+          </Input>
+          <Password
+            className='full'
+            help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
+            isError={!isPassValid}
+            label={t('password')}
+            onChange={_onChangePass}
+            onEnter={_onCommit}
+            value={password}
+          />
+          <details
+            className='accounts--Creator-advanced'
+            open
+          >
+            <summary>{t('Advanced creation options')}</summary>
+            <Dropdown
+              defaultValue={pairType}
+              help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
+              label={t('keypair crypto type')}
+              onChange={_onChangePairType}
+              options={uiSettings.availableCryptos}
+            />
+            <Input
+              className='full'
+              help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>///<password>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. The "///password" is optional and should only occur once.')}
+              isError={!!deriveError}
+              label={t('secret derivation path')}
+              onChange={_onChangeDerive}
+              onEnter={_onCommit}
+              placeholder={t('//hard/soft///password')}
+              value={derivePath}
+            />
+            {deriveError && (
+              <article className='error'>{deriveError}</article>
+            )}
+          </details>
+        </AddressRow>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='plus'
+            isDisabled={!isValid}
+            isPrimary
+            label={t('Save')}
+            onClick={_toggleConfirmation}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(
+  styled(Create)`
+    .accounts--Creator-advanced {
+      margin-top: 1rem;
+    }
+  `
+);

+ 60 - 0
pioneer/packages/app-accounts/src/modals/CreateConfirmation.tsx

@@ -0,0 +1,60 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+
+import React from 'react';
+import { AddressRow, Button, Modal } from '@polkadot/react-components';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  address: string;
+  name: string;
+  onClose: () => void;
+  onCommit: () => void;
+}
+
+function CreateConfirmation ({ address, name, onClose, onCommit, t }: Props): React.ReactElement<Props> | null {
+  return (
+    <Modal
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>
+        {t('Important notice')}
+      </Modal.Header>
+      <Modal.Content>
+        <AddressRow
+          defaultName={name}
+          isInline
+          noDefaultNameOpacity
+          value={address}
+        >
+          <p>{t('We will provide you with a generated backup file after your account is created. As long as you have access to your account you can always download this file later by clicking on "Backup" button from the Accounts section.')}</p>
+          <p>{t('Please make sure to save this file in a secure location as it is required, together with your password, to restore your account.')}</p>
+        </AddressRow>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='plus'
+            isPrimary
+            label={t('Create and backup account')}
+            onClick={onCommit}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(CreateConfirmation);

+ 240 - 0
pioneer/packages/app-accounts/src/modals/Derive.tsx

@@ -0,0 +1,240 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { KeyringPair } from '@polkadot/keyring/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { I18nProps } from '@polkadot/react-components/types';
+import { KeypairType } from '@polkadot/util-crypto/types';
+
+import React, { useContext, useEffect, useState } from 'react';
+import { AddressRow, Button, Input, InputAddress, Modal, Password, StatusContext } from '@polkadot/react-components';
+import { useDebounce } from '@polkadot/react-components/hooks';
+import keyring from '@polkadot/ui-keyring';
+import { isPasswordValid } from '@polkadot/joy-utils/accounts';
+import { keyExtractPath } from '@polkadot/util-crypto';
+
+import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
+
+import translate from '../translate';
+import { downloadAccount } from '@polkadot/joy-utils/accounts';
+import CreateConfirmation from './CreateConfirmation';
+
+interface Props extends I18nProps {
+  from: string;
+  onClose: () => void;
+}
+
+interface DerivedAddress {
+  address: string | null;
+  deriveError: string | null;
+}
+
+function deriveValidate (suri: string, pairType: KeypairType): string | null {
+  try {
+    const { path } = keyExtractPath(suri);
+
+    // we don't allow soft for ed25519
+    if (pairType === 'ed25519' && path.some(({ isSoft }): boolean => isSoft)) {
+      return 'Soft derivation paths are not allowed on ed25519';
+    }
+  } catch (error) {
+    return error.message;
+  }
+
+  return null;
+}
+
+function createAccount (source: KeyringPair, suri: string, name: string, password: string, success: string): ActionStatus {
+  // we will fill in all the details below
+  const status = { action: 'create' } as ActionStatus;
+
+  try {
+    const derived = source.derive(suri);
+
+    derived.setMeta({ ...derived.meta, name, tags: [] });
+
+    const result = keyring.addPair(derived, password || '');
+    const { address } = result.pair;
+
+    status.account = address;
+    status.status = 'success';
+    status.message = success;
+
+    downloadAccount(result);
+  } catch (error) {
+    status.status = 'error';
+    status.message = error.message;
+  }
+
+  return status;
+}
+
+function Derive ({ className, from, onClose, t }: Props): React.ReactElement {
+  const { queueAction } = useContext(StatusContext);
+  const [source] = useState(keyring.getPair(from));
+  const [{ address, deriveError }, setDerived] = useState<DerivedAddress>({ address: null, deriveError: null });
+  const [isConfirmationOpen, setIsConfirmationOpen] = useState(false);
+  const [isLocked, setIsLocked] = useState(source.isLocked);
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
+  const [{ isPassValid, password }, setPassword] = useState({ isPassValid: true, password: '' });
+  const [rootPass, setRootPass] = useState('');
+  const [suri, setSuri] = useState('');
+  const debouncedSuri = useDebounce(suri);
+  const isValid = !!address && !deriveError && isNameValid && isPassValid;
+  const context = useMyAccount()
+
+  useEffect((): void => {
+    setIsLocked(source.isLocked);
+  }, [source]);
+
+  useEffect((): void => {
+    setDerived((): DerivedAddress => {
+      let address: string | null = null;
+      const deriveError = deriveValidate(debouncedSuri, source.type);
+
+      if (!deriveError) {
+        const result = source.derive(suri);
+
+        address = result.address;
+      }
+
+      return { address, deriveError };
+    });
+  }, [debouncedSuri]);
+
+  const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name });
+  const _onChangePass = (password: string): void => setPassword({ isPassValid: isPasswordValid(password), password });
+  const _toggleConfirmation = (): void => setIsConfirmationOpen(!isConfirmationOpen);
+  const _onUnlock = (): void => {
+    try {
+      source.decodePkcs8(rootPass);
+    } catch (error) {
+      console.error(error);
+    }
+
+    setIsLocked(source.isLocked);
+  };
+
+  const _onCommit = (): void => {
+    if (!isValid) {
+      return;
+    }
+
+    const status = createAccount(source, suri, name, password, t('created account'));
+    context.set(status.account as string)
+
+    _toggleConfirmation();
+    queueAction(status);
+    onClose();
+  };
+
+  const sourceStatic = (
+    <InputAddress
+      help={t('The selected account to perform the derivation on.')}
+      isDisabled
+      label={t('derive root account')}
+      value={from}
+    />
+  );
+
+  return (
+    <Modal
+      className={className}
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Derive account from pair')}</Modal.Header>
+      {address && isConfirmationOpen && (
+        <CreateConfirmation
+          address={address}
+          name={name}
+          onCommit={_onCommit}
+          onClose={_toggleConfirmation}
+        />
+      )}
+      <Modal.Content>
+        {isLocked && (
+          <>
+            {sourceStatic}
+            <Password
+              autoFocus
+              help={t('The password to unlock the selected account.')}
+              label={t('password')}
+              onChange={setRootPass}
+              value={rootPass}
+            />
+          </>
+        )}
+        {!isLocked && (
+          <AddressRow
+            defaultName={name}
+            noDefaultNameOpacity
+            value={deriveError ? '' : address}
+          >
+            {sourceStatic}
+            <Input
+              autoFocus
+              help={t('You can set a custom derivation path for this account using the following syntax "/<soft-key>//<hard-key>///<password>". The "/<soft-key>" and "//<hard-key>" may be repeated and mixed`. The "///password" is optional and should only occur once.')}
+              label={t('derivation path')}
+              onChange={setSuri}
+              placeholder={t('//hard/soft')}
+            />
+            <Input
+              className='full'
+              help={t('Name given to this account. You can edit it. To use the account to validate or nominate, it is a good practice to append the function of the account in the name, e.g "name_you_want - stash".')}
+              isError={!isNameValid}
+              label={t('name')}
+              onChange={_onChangeName}
+              onEnter={_onCommit}
+              placeholder={t('new account')}
+              value={name}
+            />
+            <Password
+              className='full'
+              help={t('This password is used to encrypt your private key. It must be strong and unique! You will need it to sign transactions with this account. You can recover this account using this password together with the backup file (generated in the next step).')}
+              isError={!isPassValid}
+              label={t('password')}
+              onChange={_onChangePass}
+              onEnter={_onCommit}
+              value={password}
+            />
+          </AddressRow>
+        )}
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          {isLocked
+            ? (
+              <Button
+                icon='lock'
+                isDisabled={!rootPass}
+                isPrimary
+                label={t('Unlock')}
+                onClick={_onUnlock}
+              />
+            )
+            : (
+              <Button
+                icon='plus'
+                isDisabled={!isValid}
+                isPrimary
+                label={t('Save')}
+                onClick={_toggleConfirmation}
+              />
+            )
+          }
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(Derive);

+ 180 - 0
pioneer/packages/app-accounts/src/modals/Import.tsx

@@ -0,0 +1,180 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { KeyringPair$Json } from '@polkadot/keyring/types';
+import { I18nProps } from '@polkadot/react-components/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { ModalProps } from '../types';
+
+import React from 'react';
+import { MyAccountContext } from '@polkadot/joy-utils/MyAccountContext';
+import { isPasswordValid } from '@polkadot/joy-utils/accounts';
+import { AddressRow, Button, InputAddress, InputFile, Modal, Password, TxComponent } from '@polkadot/react-components';
+import { isHex, isObject, u8aToString } from '@polkadot/util';
+import keyring from '@polkadot/ui-keyring';
+
+import translate from '../translate';
+
+interface Props extends ModalProps, I18nProps {}
+
+interface State {
+  address: string | null;
+  isFileValid: boolean;
+  isPassValid: boolean;
+  json: KeyringPair$Json | null;
+  password: string;
+}
+
+class Import extends TxComponent<Props, State> {
+  static contextType = MyAccountContext;
+  context!: React.ContextType<typeof MyAccountContext>;
+
+  public state: State = {
+    address: null,
+    isFileValid: false,
+    isPassValid: true,
+    json: null,
+    password: ''
+  };
+
+  public render (): React.ReactNode {
+    const { onClose, t } = this.props;
+    const { isFileValid, isPassValid } = this.state;
+
+    return (
+      <Modal
+        dimmer='inverted'
+        open
+      >
+        <Modal.Header>{t('Add via backup file')}</Modal.Header>
+        {this.renderInput()}
+        <Modal.Actions>
+          <Button.Group>
+            <Button
+              icon='cancel'
+              isNegative
+              label={t('Cancel')}
+              onClick={onClose}
+            />
+            <Button.Or />
+            <Button
+              icon='sync'
+              isDisabled={!isFileValid || !isPassValid}
+              isPrimary
+              onClick={this.onSave}
+              label={t('Restore')}
+              ref={this.button}
+            />
+          </Button.Group>
+        </Modal.Actions>
+      </Modal>
+    );
+  }
+
+  private renderInput (): React.ReactNode {
+    const { t } = this.props;
+    const { address, isFileValid, isPassValid, json, password } = this.state;
+    const acceptedFormats = ['application/json', 'text/plain'].join(', ');
+
+    return (
+      <Modal.Content>
+        <AddressRow
+          defaultName={isFileValid && json ? json.meta.name : null}
+          noDefaultNameOpacity
+          value={isFileValid && address ? address : null}
+        >
+          <InputFile
+            accept={acceptedFormats}
+            className='full'
+            help={t('Select the JSON key file that was downloaded when you created the account. This JSON file contains your private key encrypted with your password.')}
+            isError={!isFileValid}
+            label={t('backup file')}
+            onChange={this.onChangeFile}
+            withLabel
+          />
+          <Password
+            autoFocus
+            className='full'
+            help={t('Type the password chosen at the account creation. It was used to encrypt your account\'s private key in the backup file.')}
+            isError={!isPassValid}
+            label={t('password')}
+            onChange={this.onChangePass}
+            onEnter={this.submit}
+            value={password}
+          />
+        </AddressRow>
+      </Modal.Content>
+    );
+  }
+
+  private onChangeFile = (file: Uint8Array): void => {
+    try {
+      const json = JSON.parse(u8aToString(file));
+      const publicKey = keyring.decodeAddress(json.address, true);
+      const address = keyring.encodeAddress(publicKey);
+      const isFileValid = publicKey.length === 32 && isHex(json.encoded) && isObject(json.meta) && (
+        Array.isArray(json.encoding.content)
+          ? json.encoding.content[0] === 'pkcs8'
+          : json.encoding.content === 'pkcs8'
+      );
+
+      this.setState({
+        address,
+        isFileValid,
+        json
+      });
+    } catch (error) {
+      this.setState({
+        address: null,
+        isFileValid: false,
+        json: null
+      });
+      console.error(error);
+    }
+  }
+
+  private onChangePass = (password: string): void => {
+    this.setState({
+      isPassValid: isPasswordValid(password),
+      password
+    });
+  }
+
+  private onSave = (): void => {
+    const { onClose, onStatusChange, t } = this.props;
+    const { json, password } = this.state;
+
+    if (!json) {
+      return;
+    }
+
+    const status: Partial<ActionStatus> = { action: 'restore' };
+
+    try {
+      const pair = keyring.restoreAccount(json, password);
+      const { address } = pair;
+
+      status.status = pair ? 'success' : 'error';
+      status.account = address;
+      status.message = t('account restored');
+
+      InputAddress.setLastValue('account', address);
+      this.context.set(address)
+    } catch (error) {
+      this.setState({ isPassValid: true });
+
+      status.status = 'error';
+      status.message = error.message;
+      console.error(error);
+    }
+
+    onStatusChange(status as ActionStatus);
+
+    if (status.status !== 'error') {
+      onClose();
+    }
+  }
+}
+
+export default translate(Import);

+ 119 - 0
pioneer/packages/app-accounts/src/modals/Qr.tsx

@@ -0,0 +1,119 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
+import { ModalProps } from '../types';
+
+import React, { useState } from 'react';
+import styled from 'styled-components';
+import { AddressRow, Button, Input, InputAddress, Modal } from '@polkadot/react-components';
+import { QrScanAddress } from '@polkadot/react-qr';
+import keyring from '@polkadot/ui-keyring';
+
+import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
+
+import translate from '../translate';
+
+interface Scanned {
+  address: string;
+  genesisHash: string;
+}
+
+interface Props extends I18nProps, ModalProps {
+  className?: string;
+}
+
+function QrModal ({ className, onClose, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
+  const [scanned, setScanned] = useState<Scanned | null>(null);
+  const context = useMyAccount()
+
+  const _onNameChange = (name: string): void => setName({ isNameValid: !!name.trim(), name });
+
+  const _onSave = (): void => {
+    if (!scanned || !isNameValid) {
+      return;
+    }
+
+    const { address, genesisHash } = scanned;
+
+    keyring.addExternal(address, { genesisHash, name: name.trim() });
+    InputAddress.setLastValue('account', address);
+    context.set(address)
+
+    onStatusChange({
+      account: address,
+      action: 'create',
+      message: t('created account'),
+      status: 'success'
+    });
+    onClose();
+  };
+
+  return (
+    <Modal
+      className={className}
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Add account via Qr')}</Modal.Header>
+      <Modal.Content>
+        {
+          scanned
+            ? (
+              <>
+                <AddressRow
+                  defaultName={name}
+                  noDefaultNameOpacity
+                  value={scanned.address}
+                />
+                <Input
+                  autoFocus
+                  className='full'
+                  help={t('Name given to this account. You can change it at any point in the future.')}
+                  isError={!isNameValid}
+                  label={t('name')}
+                  onChange={_onNameChange}
+                  onEnter={_onSave}
+                  value={name}
+                />
+              </>
+            )
+            : (
+              <div className='qr-wrapper'>
+                <QrScanAddress onScan={setScanned} />
+              </div>
+            )
+        }
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <Button
+            icon='sign-in'
+            isDisabled={!scanned || !isNameValid}
+            isPrimary
+            onClick={_onSave}
+            label={t('Create')}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(
+  styled(QrModal)`
+    .qr-wrapper {
+      margin: 0 auto;
+      max-width: 30rem;
+    }
+  `
+);

+ 178 - 0
pioneer/packages/app-accounts/src/modals/Transfer.tsx

@@ -0,0 +1,178 @@
+/* eslint-disable @typescript-eslint/camelcase */
+// Copyright 2017-2019 @polkadot/app-accounts 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 { SubmittableExtrinsic } from '@polkadot/api/promise/types';
+import { DerivedFees } from '@polkadot/api-derive/types';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import BN from 'bn.js';
+import React, { useContext, useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { Button, InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
+import { Available } from '@polkadot/react-query';
+import Checks from '@polkadot/react-signer/Checks';
+import { ApiContext } from '@polkadot/react-api';
+
+import translate from '../translate';
+
+interface Props extends I18nProps {
+  balances_fees?: DerivedFees;
+  className?: string;
+  onClose: () => void;
+  recipientId?: string;
+  senderId?: string;
+}
+
+const ZERO = new BN(0);
+
+// TODO Re-enable when we have proper fee calculation (incl. weights)
+// async function calcMax (api: ApiPromise, balances_fees: DerivedFees | undefined, senderId: string, recipientId: string): Promise<BN> {
+//   let maxBalance = new BN(1);
+
+//   if (!balances_fees) {
+//     return maxBalance;
+//   }
+
+//   const { transferFee, transactionBaseFee, transactionByteFee, creationFee } = balances_fees;
+
+//   const [senderNonce, senderBalances, recipientBalances] = await Promise.all([
+//     api.query.system.accountNonce<Index>(senderId),
+//     api.derive.balances.all(senderId),
+//     api.derive.balances.all(recipientId)
+//   ]);
+
+//   let prevMax = new BN(0);
+
+//   // something goes screwy here when we move this out of the component :(
+//   let extrinsic: any;
+
+//   while (!prevMax.eq(maxBalance)) {
+//     prevMax = maxBalance;
+//     extrinsic = api.tx.balances.transfer(senderNonce, prevMax);
+
+//     const txLength = calcTxLength(extrinsic, senderNonce);
+//     const fees = transactionBaseFee
+//       .add(transactionByteFee.mul(txLength))
+//       .add(transferFee)
+//       .add(recipientBalances.availableBalance.isZero() ? creationFee : ZERO);
+
+//     maxBalance = bnMax(senderBalances.availableBalance.sub(fees), ZERO);
+//   }
+
+//   return maxBalance;
+// }
+
+function Transfer ({ className, onClose, recipientId: propRecipientId, senderId: propSenderId, t }: Props): React.ReactElement<Props> {
+  const { api } = useContext(ApiContext);
+  const [amount, setAmount] = useState<BN | undefined>(new BN(0));
+  const [extrinsic, setExtrinsic] = useState<SubmittableExtrinsic | null>(null);
+  const [hasAvailable, setHasAvailable] = useState(true);
+  const [maxBalance] = useState(new BN(0));
+  const [recipientId, setRecipientId] = useState<string | null>(propRecipientId || null);
+  const [senderId, setSenderId] = useState<string | null>(propSenderId || null);
+
+  useEffect((): void => {
+    if (senderId && recipientId) {
+      setExtrinsic(api.tx.balances.transfer(recipientId, amount || ZERO));
+
+      // We currently have not enabled the max functionality - we don't take care of weights
+      // calcMax(api, balances_fees, senderId, recipientId)
+      //   .then(([maxBalance]): void => setMaxBalance(maxBalance))
+      //   .catch((error: Error): void => console.error(error));
+    }
+  }, [amount, recipientId, senderId]);
+
+  const transferrable = <span className='label'>{t('transferrable ')}</span>;
+
+  return (
+    <Modal
+      className='app--accounts-Modal'
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Send funds')}</Modal.Header>
+      <Modal.Content>
+        <div className={className}>
+          <InputAddress
+            defaultValue={propSenderId}
+            help={t('The account you will send funds from.')}
+            isDisabled={!!propSenderId}
+            label={t('send from account')}
+            labelExtra={<Available label={transferrable} params={senderId} />}
+            onChange={setSenderId}
+            type='account'
+          />
+          <InputAddress
+            defaultValue={propRecipientId}
+            help={t('Select a contact or paste the address you want to send funds to.')}
+            isDisabled={!!propRecipientId}
+            label={t('send to address')}
+            labelExtra={<Available label={transferrable} params={recipientId} />}
+            onChange={setRecipientId}
+            type='allPlus'
+          />
+          <InputBalance
+            help={t('Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.')}
+            isError={!hasAvailable}
+            label={t('amount')}
+            maxValue={maxBalance}
+            onChange={setAmount}
+            withMax
+          />
+          <Checks
+            accountId={senderId}
+            extrinsic={extrinsic}
+            isSendable
+            onChange={setHasAvailable}
+          />
+        </div>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            label={t('Cancel')}
+            onClick={onClose}
+          />
+          <Button.Or />
+          <TxButton
+            accountId={senderId}
+            extrinsic={extrinsic}
+            icon='send'
+            isDisabled={!hasAvailable}
+            isPrimary
+            label={t('Make Transfer')}
+            onStart={onClose}
+            withSpinner={false}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(
+  styled(Transfer)`
+    article.padded {
+      box-shadow: none;
+      margin-left: 2rem;
+    }
+
+    .balance {
+      margin-bottom: 0.5rem;
+      text-align: right;
+      padding-right: 1rem;
+
+      .label {
+        opacity: 0.7;
+      }
+    }
+
+    label.with-help {
+      flex-basis: 10rem;
+    }
+  `
+);

+ 7 - 0
pioneer/packages/app-accounts/src/translate.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { withTranslation } from 'react-i18next';
+
+export default withTranslation(['app-accounts']);

+ 29 - 0
pioneer/packages/app-accounts/src/types.ts

@@ -0,0 +1,29 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { AppProps } from '@polkadot/react-components/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+
+import { WithTranslation } from 'react-i18next';
+
+export interface LocationProps {
+  location: any;
+  match: {
+    params: Record<string, string>;
+  };
+}
+
+export interface BareProps {
+  className?: string;
+  style?: Record<string, any>;
+}
+
+export type I18nProps = BareProps & WithTranslation;
+
+export type ComponentProps = AppProps & LocationProps;
+
+export interface ModalProps {
+  onClose: () => void;
+  onStatusChange: (status: ActionStatus) => void;
+}

+ 66 - 0
pioneer/packages/app-accounts/src/vanitygen/calculate.ts

@@ -0,0 +1,66 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { GeneratorCalculation, GeneratorOptions } from './types';
+
+const MAX_OFFSET = 5;
+
+function calculateAtOne (atOffset: number, test: string[], address: string): GeneratorCalculation {
+  return {
+    count: test.reduce((count, c, index): number => {
+      if (index === count) {
+        count += (c === '?' || c === address.charAt(index + atOffset)) ? 1 : 0;
+      }
+
+      return count;
+    }, 0),
+    offset: atOffset
+  };
+}
+
+function calculateAt (atOffset: number, test: string[][], address: string): GeneratorCalculation {
+  let bestCount = 0;
+  let bestOffset = 1;
+
+  for (let i = 0; i < test.length; i++) {
+    const { count, offset } = calculateAtOne(atOffset, test[i], address);
+
+    if (count > bestCount) {
+      bestCount = count;
+      bestOffset = offset;
+    }
+  }
+
+  return {
+    count: bestCount,
+    offset: bestOffset
+  };
+}
+
+export default function calculate (test: string[][], _address: string, { atOffset = -1, withCase = false }: GeneratorOptions): GeneratorCalculation {
+  const address = withCase
+    ? _address
+    : _address.toLowerCase();
+
+  if (atOffset > 0) {
+    return calculateAt(atOffset, test, address);
+  }
+
+  let bestCount = 0;
+  let bestOffset = 1;
+
+  for (let index = 0; index < MAX_OFFSET; index++) {
+    const { count, offset } = calculateAt(index, test, address);
+
+    if (count > bestCount) {
+      bestCount = count;
+      bestOffset = offset;
+    }
+  }
+
+  return {
+    count: bestCount,
+    offset: bestOffset
+  };
+}

+ 133 - 0
pioneer/packages/app-accounts/src/vanitygen/cli.ts

@@ -0,0 +1,133 @@
+#!/usr/bin/env node
+// Copyright 2017-2019 @polkadot/app-accounts 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 { KeypairType } from '@polkadot/util-crypto/types';
+import { GeneratorOptions } from './types';
+
+import yargs from 'yargs';
+import chalk from 'chalk';
+import { u8aToHex } from '@polkadot/util';
+import { cryptoWaitReady, setSS58Format } from '@polkadot/util-crypto';
+
+import generator from '.';
+import matchRegex from './regex';
+
+interface Best {
+  address: string;
+  count: number;
+  mnemonic?: string;
+  offset: number;
+  seed?: Uint8Array;
+  withCase?: boolean;
+}
+
+const { match, mnemonic, network, type, withCase } = yargs
+  .option('match', {
+    default: 'Test',
+    type: 'string'
+  })
+  .option('mnemonic', {
+    default: false,
+    type: 'boolean'
+  })
+  .option('network', {
+    choices: ['substrate', 'polkadot', 'kusama'],
+    default: 'substrate'
+  })
+  .option('type', {
+    choices: ['ed25519', 'sr25519'],
+    default: 'sr25519'
+  })
+  .option('withCase', {
+    default: false,
+    type: 'boolean'
+  })
+  .argv;
+
+const INDICATORS = ['|', '/', '-', '\\'];
+const NUMBER_REGEX = new RegExp('(\\d+?)(?=(\\d{3})+(?!\\d)|$)', 'g');
+
+const options: GeneratorOptions = {
+  match,
+  network,
+  runs: 50,
+  type: type as KeypairType,
+  withCase,
+  withHex: !mnemonic
+};
+const startAt = Date.now();
+let best: Best = {
+  address: '',
+  count: -1,
+  offset: 65536
+};
+let total = 0;
+let indicator = -1;
+const tests = options.match.split(',');
+
+tests.forEach((test): void => {
+  if (!matchRegex.test(test)) {
+    console.error("Invalid character found in match string, allowed is '1-9' (no '0'), 'A-H, J-N & P-Z' (no 'I' or 'O'), 'a-k & m-z' (no 'l') and '?' (wildcard)");
+    process.exit(-1);
+  }
+});
+
+switch (network) {
+  case 'kusama':
+    setSS58Format(2);
+    break;
+
+  case 'polkadot':
+    setSS58Format(0);
+    break;
+
+  default:
+    setSS58Format(42);
+    break;
+}
+
+console.log(options);
+
+function showProgress (): void {
+  const elapsed = (Date.now() - startAt) / 1000;
+
+  indicator++;
+
+  if (indicator === INDICATORS.length) {
+    indicator = 0;
+  }
+
+  process.stdout.write(`\r[${INDICATORS[indicator]}] ${(total.toString().match(NUMBER_REGEX) || []).join(',')} keys in ${(elapsed).toFixed(2)}s (${(total / elapsed).toFixed(0)} keys/s)`);
+}
+
+function showBest (): void {
+  const { address, count, mnemonic, offset, seed } = best;
+
+  console.log(`\r::: ${address.slice(0, offset)}${chalk.cyan(address.slice(offset, count + offset))}${address.slice(count + offset)} <= ${u8aToHex(seed)} (count=${count}, offset=${offset})${mnemonic ? '\n                                                        ' + mnemonic : ''}`);
+}
+
+cryptoWaitReady()
+  .then((): void => {
+    while (true) {
+      const nextBest = generator(options).found.reduce((best, match): Best => {
+        if ((match.count > best.count) || ((match.count === best.count) && (match.offset <= best.offset))) {
+          return match;
+        }
+
+        return best;
+      }, best);
+
+      total += options.runs;
+
+      if (nextBest.address !== best.address) {
+        best = nextBest;
+        showBest();
+        showProgress();
+      } else if ((total % (options.withHex ? 1000 : 100)) === 0) {
+        showProgress();
+      }
+    }
+  })
+  .catch((error: Error): void => console.error(error));

+ 31 - 0
pioneer/packages/app-accounts/src/vanitygen/generate.ts

@@ -0,0 +1,31 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { GeneratorMatch, GeneratorOptions } from './types';
+
+import { encodeAddress, mnemonicGenerate, naclKeypairFromSeed, randomAsU8a, schnorrkelKeypairFromSeed, mnemonicToMiniSecret } from '@polkadot/util-crypto';
+
+import calculate from './calculate';
+
+export default function generator (test: string[][], options: GeneratorOptions): GeneratorMatch {
+  const mnemonic = options.withHex
+    ? undefined
+    : mnemonicGenerate(12);
+  const seed = mnemonic
+    ? mnemonicToMiniSecret(mnemonic)
+    : randomAsU8a();
+  const pair = options.type === 'sr25519'
+    ? schnorrkelKeypairFromSeed(seed)
+    : naclKeypairFromSeed(seed);
+  const address = encodeAddress(pair.publicKey);
+  const { count, offset } = calculate(test, address, options);
+
+  return {
+    address,
+    count,
+    mnemonic,
+    offset,
+    seed
+  };
+}

+ 23 - 0
pioneer/packages/app-accounts/src/vanitygen/index.ts

@@ -0,0 +1,23 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { GeneratorMatches, GeneratorResult, GeneratorOptions } from './types';
+
+import generate from './generate';
+
+export default function generator (options: GeneratorOptions): GeneratorResult {
+  const { match, runs = 10, withCase = false } = options;
+  const test = (withCase ? match : match.toLowerCase()).split(',').map((c): string[] => c.split(''));
+  const startAt = Date.now();
+  const found: GeneratorMatches = [];
+
+  while (found.length !== runs) {
+    found.push(generate(test, options));
+  }
+
+  return {
+    elapsed: Date.now() - startAt,
+    found
+  };
+}

+ 7 - 0
pioneer/packages/app-accounts/src/vanitygen/regex.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-accounts authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+const regex = new RegExp('^[1-9A-HJ-NP-Za-km-z]*$', '');
+
+export default regex;

+ 31 - 0
pioneer/packages/app-accounts/src/vanitygen/sort.ts

@@ -0,0 +1,31 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { GeneratorMatch } from './types';
+
+function numberSort (a: number, b: number): number {
+  if (a > b) {
+    return -1;
+  } else if (a < b) {
+    return 1;
+  }
+
+  return 0;
+}
+
+export default function sort (a: GeneratorMatch, b: GeneratorMatch): number {
+  const countResult = numberSort(a.count, b.count);
+
+  if (countResult !== 0) {
+    return countResult;
+  }
+
+  const positionResult = numberSort(b.offset, a.offset);
+
+  if (positionResult !== 0) {
+    return positionResult;
+  }
+
+  return a.address.localeCompare(b.address);
+}

+ 33 - 0
pioneer/packages/app-accounts/src/vanitygen/types.d.ts

@@ -0,0 +1,33 @@
+// Copyright 2017-2019 @polkadot/app-accounts 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 { KeypairType } from '@polkadot/util-crypto/types';
+
+export interface GeneratorCalculation {
+  count: number;
+  offset: number;
+}
+
+export interface GeneratorMatch extends GeneratorCalculation {
+  address: string;
+  mnemonic?: string;
+  seed: Uint8Array;
+}
+
+export type GeneratorMatches = GeneratorMatch[];
+
+export interface GeneratorOptions {
+  atOffset?: number;
+  match: string;
+  network?: string;
+  runs: number;
+  type: KeypairType;
+  withCase?: boolean;
+  withHex?: boolean;
+}
+
+export interface GeneratorResult {
+  elapsed: number;
+  found: GeneratorMatches;
+}

+ 201 - 0
pioneer/packages/app-address-book/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 1 - 0
pioneer/packages/app-address-book/README.md

@@ -0,0 +1 @@
+# @polkadot/app-address-book

+ 16 - 0
pioneer/packages/app-address-book/package.json

@@ -0,0 +1,16 @@
+{
+  "name": "@polkadot/app-address-book",
+  "version": "0.37.0-beta.63",
+  "main": "index.js",
+  "repository": "https://github.com/polkadot-js/apps.git",
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "maintainers": [
+    "Jaco Greeff <jacogr@gmail.com>"
+  ],
+  "contributors": [],
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
+  }
+}

+ 153 - 0
pioneer/packages/app-address-book/src/Address.tsx

@@ -0,0 +1,153 @@
+// Copyright 2017-2019 @polkadot/app-staking 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 { KeyringAddress } from '@polkadot/ui-keyring/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import React, { useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { AddressCard, AddressInfo, Button, ChainLock, Forget } from '@polkadot/react-components';
+import keyring from '@polkadot/ui-keyring';
+
+import Transfer from '@polkadot/app-accounts/modals/Transfer';
+
+import translate from './translate';
+
+interface Props extends I18nProps {
+  address: string;
+  className?: string;
+}
+
+const WITH_BALANCE = { available: true, bonded: true, free: true, locked: true, reserved: true, total: true };
+const WITH_EXTENDED = { nonce: true };
+
+const isEditable = true;
+
+function Address ({ address, className, t }: Props): React.ReactElement<Props> {
+  const [current, setCurrent] = useState<KeyringAddress | null>(null);
+  const [genesisHash, setGenesisHash] = useState<string | null>(null);
+  const [isForgetOpen, setIsForgetOpen] = useState(false);
+  const [isTransferOpen, setIsTransferOpen] = useState(false);
+
+  useEffect((): void => {
+    const current = keyring.getAddress(address);
+
+    setCurrent(current || null);
+    setGenesisHash((current && current.meta.genesisHash) || null);
+  }, []);
+
+  const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
+  const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
+  const _onForget = (): void => {
+    if (address) {
+      const status: Partial<ActionStatus> = {
+        account: address,
+        action: 'forget'
+      };
+
+      try {
+        keyring.forgetAddress(address);
+        status.status = 'success';
+        status.message = t('address forgotten');
+      } catch (error) {
+        status.status = 'error';
+        status.message = error.message;
+      }
+    }
+  };
+  const _onGenesisChange = (genesisHash: string | null): void => {
+    setGenesisHash(genesisHash);
+
+    const account = keyring.getAddress(address);
+
+    account && keyring.saveAddress(address, { ...account.meta, genesisHash });
+
+    setGenesisHash(genesisHash);
+  };
+
+  return (
+    <AddressCard
+      buttons={
+        <div className='addresses--Address-buttons buttons'>
+          <div className='actions'>
+            {isEditable && (
+              <Button
+                isNegative
+                onClick={_toggleForget}
+                icon='trash'
+                key='forget'
+                size='small'
+                tooltip={t('Forget this address')}
+              />
+            )}
+            <Button
+              icon='paper plane'
+              isPrimary
+              key='deposit'
+              label={t('deposit')}
+              onClick={_toggleTransfer}
+              size='small'
+              tooltip={t('Send funds to this address')}
+            />
+          </div>
+          {isEditable && (
+            <div className='others'>
+              <ChainLock
+                genesisHash={genesisHash}
+                onChange={_onGenesisChange}
+              />
+            </div>
+          )}
+        </div>
+      }
+      className={className}
+      isEditable={isEditable}
+      type='address'
+      value={address}
+      withExplorer
+      withIndexOrAddress={false}
+      withTags
+    >
+      {address && current && (
+        <>
+          {isForgetOpen && (
+            <Forget
+              address={current.address}
+              onForget={_onForget}
+              key='modal-forget-account'
+              mode='address'
+              onClose={_toggleForget}
+            />
+          )}
+          {isTransferOpen && (
+            <Transfer
+              key='modal-transfer'
+              onClose={_toggleTransfer}
+              recipientId={address}
+            />
+          )}
+        </>
+      )}
+      <AddressInfo
+        address={address}
+        withBalance={WITH_BALANCE}
+        withExtended={WITH_EXTENDED}
+      />
+    </AddressCard>
+  );
+}
+
+export default translate(
+  styled(Address)`
+    .addresses--Address-buttons {
+      text-align: right;
+
+      .others {
+        margin-right: 0.125rem;
+        margin-top: 0.25rem;
+      }
+    }
+  `
+);

+ 62 - 0
pioneer/packages/app-address-book/src/MemoByAccount.tsx

@@ -0,0 +1,62 @@
+import React from 'react';
+import { Form } from 'semantic-ui-react';
+import { History } from 'history';
+
+import MemoView from '@polkadot/joy-utils/memo/MemoView';
+import { RouteComponentProps } from 'react-router';
+
+type Props = RouteComponentProps & {
+  history: History,
+  match: {
+    params: {
+      accountId?: string
+    }
+  }
+};
+
+type State = {
+  loaded: boolean,
+  accountInput?: string
+};
+
+export default class Component extends React.PureComponent<Props, State> {
+
+  static getDerivedStateFromProps (props: Props, currentState: State): State | null {
+    const { match: { params: { accountId } } } = props;
+    const { loaded } = currentState;
+    // console.log('getDerivedStateFromProps', props);
+    if (!loaded && accountId) {
+      return { loaded: true, accountInput: accountId };
+    }
+    return null;
+  }
+
+  state: State = {
+    loaded: false,
+    accountInput: ''
+  };
+
+  render () {
+    const { match: { params: { accountId } } } = this.props;
+    const { accountInput } = this.state;
+    return <>
+      <Form onSubmit={this.findMemo}>
+        <Form.Input
+          value={accountInput}
+          placeholder='Key address'
+          onChange={e => this.onChangeAccount(e.target.value)}
+          action={{ icon: 'search', content: 'Find memo', onClick: this.findMemo }}
+        />
+      </Form>
+      {accountId && <MemoView accountId={accountId} preview={false} style={{ marginTop: '1rem' }} />}
+    </>;
+  }
+
+  private onChangeAccount = (accountInput: string): void => {
+    this.setState({ accountInput });
+  }
+
+  private findMemo = () => {
+    this.props.history.push('/addressbook/memo/' + this.state.accountInput);
+  }
+}

+ 63 - 0
pioneer/packages/app-address-book/src/Overview.tsx

@@ -0,0 +1,63 @@
+// Copyright 2017-2019 @polkadot/app-address-book 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 { I18nProps } from '@polkadot/react-components/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+import { ComponentProps } from './types';
+
+import React, { useState } from 'react';
+import { Button, CardGrid } from '@polkadot/react-components';
+import addressObservable from '@polkadot/ui-keyring/observable/addresses';
+import { withMulti, withObservable } from '@polkadot/react-api';
+
+import CreateModal from './modals/Create';
+import Address from './Address';
+import translate from './translate';
+
+interface Props extends ComponentProps, I18nProps {
+  addresses?: SubjectInfo[];
+}
+
+function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [isCreateOpen, setIsCreateOpen] = useState(false);
+  const emptyScreen = !isCreateOpen && (!addresses || Object.keys(addresses).length === 0);
+
+  const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
+
+  return (
+    <CardGrid
+      buttons={
+        <Button.Group>
+          <Button
+            icon='add'
+            isPrimary
+            label={t('Add contact')}
+            onClick={_toggleCreate}
+          />
+        </Button.Group>
+      }
+      isEmpty={emptyScreen}
+      emptyText={t('No contacts found.')}
+    >
+      {isCreateOpen && (
+        <CreateModal
+          onClose={_toggleCreate}
+          onStatusChange={onStatusChange}
+        />
+      )}
+      {addresses && Object.keys(addresses).map((address): React.ReactNode => (
+        <Address
+          address={address}
+          key={address}
+        />
+      ))}
+    </CardGrid>
+  );
+}
+
+export default withMulti(
+  Overview,
+  translate,
+  withObservable(addressObservable.subject, { propName: 'addresses' })
+);

+ 62 - 0
pioneer/packages/app-address-book/src/index.tsx

@@ -0,0 +1,62 @@
+// Copyright 2017-2019 @polkadot/app-address-book 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 { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
+import { ComponentProps } from './types';
+
+import React from 'react';
+import { Route, Switch } from 'react-router';
+import { HelpOverlay } from '@polkadot/react-components';
+import Tabs from '@polkadot/react-components/Tabs';
+
+import basicMd from './md/basic.md';
+import Overview from './Overview';
+import translate from './translate';
+import MemoByAccount from './MemoByAccount';
+
+interface Props extends AppProps, I18nProps {
+  allAddresses?: SubjectInfo;
+  location: any;
+}
+
+function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
+    // eslint-disable-next-line react/display-name
+    return (): React.ReactNode =>
+      <Component
+        basePath={basePath}
+        location={location}
+        onStatusChange={onStatusChange}
+      />;
+  };
+
+  return (
+    <main className='address-book--App'>
+      <HelpOverlay md={basicMd} />
+      <header>
+        <Tabs
+          basePath={basePath}
+          items={[
+            {
+              isRoot: true,
+              name: 'overview',
+              text: t('My contacts')
+            },
+            {
+              name: 'memo',
+              text: t('View memo')
+            }
+          ]}
+        />
+      </header>
+      <Switch>
+        <Route path={`${basePath}/memo/:accountId?`} component={MemoByAccount} />
+        <Route render={_renderComponent(Overview)} />
+      </Switch>
+    </main>
+  );
+}
+
+export default translate(AddressBookApp);

+ 6 - 0
pioneer/packages/app-address-book/src/md/basic.md

@@ -0,0 +1,6 @@
+# Address book
+
+You can store and get quick access to the most commonly used address, such as a friends' account.
+Any contact you create in this interface will be reflected in the application.
+
+You can edit the name of a contact by clicking on it. To remove a contact from the list, click on the trash icon to "Forget" it.

+ 135 - 0
pioneer/packages/app-address-book/src/modals/Create.tsx

@@ -0,0 +1,135 @@
+// Copyright 2017-2019 @polkadot/app-address-book 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 { I18nProps } from '@polkadot/react-components/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+import { ModalProps } from '../types';
+
+import React, { useState } from 'react';
+
+import { AddressRow, Button, Input, InputAddress, Modal } from '@polkadot/react-components';
+import keyring from '@polkadot/ui-keyring';
+
+import translate from '../translate';
+
+interface Props extends ModalProps, I18nProps {}
+
+function Create ({ onClose, onStatusChange, t }: Props): React.ReactElement<Props> {
+  const [{ isNameValid, name }, setName] = useState<{ isNameValid: boolean; name: string }>({ isNameValid: false, name: '' });
+  const [{ address, isAddressExisting, isAddressValid }, setAddress] = useState<{ address: string; isAddressExisting: boolean; isAddressValid: boolean }>({ address: '', isAddressExisting: false, isAddressValid: false });
+  const isValid = isAddressValid && isNameValid;
+
+  const _onChangeAddress = (input: string): void => {
+    let address = '';
+    let isAddressValid = true;
+    let isAddressExisting = false;
+
+    try {
+      address = keyring.encodeAddress(
+        keyring.decodeAddress(input)
+      );
+      isAddressValid = keyring.isAvailable(address);
+
+      if (!isAddressValid) {
+        const old = keyring.getAddress(address);
+
+        if (old) {
+          const newName = old.meta.name || name;
+
+          isAddressExisting = true;
+          isAddressValid = true;
+
+          setName({ isNameValid: !!(newName || '').trim(), name: newName });
+        }
+      }
+    } catch (error) {
+      isAddressValid = false;
+    }
+
+    setAddress({ address: address || input, isAddressExisting, isAddressValid });
+  };
+  const _onChangeName = (name: string): void => setName({ isNameValid: !!name.trim(), name });
+  const _onCommit = (): void => {
+    const status = { action: 'create' } as ActionStatus;
+
+    if (!isValid) {
+      return;
+    }
+
+    try {
+      keyring.saveAddress(address, { name: name.trim(), genesisHash: keyring.genesisHash, tags: [] });
+
+      status.account = address;
+      status.status = address ? 'success' : 'error';
+      status.message = isAddressExisting
+        ? t('address edited')
+        : t('address created');
+
+      InputAddress.setLastValue('address', address);
+    } catch (error) {
+      status.status = 'error';
+      status.message = error.message;
+    }
+
+    onStatusChange(status);
+    onClose();
+  };
+
+  return (
+    <Modal
+      dimmer='inverted'
+      open
+    >
+      <Modal.Header>{t('Add an address')}</Modal.Header>
+      <Modal.Content>
+        <AddressRow
+          defaultName={name}
+          noDefaultNameOpacity
+          value={address}
+        >
+          <Input
+            autoFocus
+            className='full'
+            help={t('Paste here the address of the contact you want to add to your address book.')}
+            isError={!isAddressValid}
+            label={t('address')}
+            onChange={_onChangeAddress}
+            onEnter={_onCommit}
+            placeholder={t('new address')}
+            value={address}
+          />
+          <Input
+            className='full'
+            help={t('Type the name of your contact. This name will be used across all the apps. It can be edited later on.')}
+            isError={!isNameValid}
+            label={t('name')}
+            onChange={_onChangeName}
+            onEnter={_onCommit}
+            value={name}
+          />
+        </AddressRow>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button.Group>
+          <Button
+            icon='cancel'
+            isNegative
+            onClick={onClose}
+            label={t('Cancel')}
+          />
+          <Button.Or />
+          <Button
+            icon='save'
+            isDisabled={!isValid}
+            isPrimary
+            onClick={_onCommit}
+            label={t('Save')}
+          />
+        </Button.Group>
+      </Modal.Actions>
+    </Modal>
+  );
+}
+
+export default translate(Create);

+ 7 - 0
pioneer/packages/app-address-book/src/translate.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-address-book 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 { withTranslation } from 'react-i18next';
+
+export default withTranslation(['app-address-book']);

+ 15 - 0
pioneer/packages/app-address-book/src/types.ts

@@ -0,0 +1,15 @@
+// Copyright 2017-2019 @polkadot/app-address-book 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 { AppProps } from '@polkadot/react-components/types';
+import { ActionStatus } from '@polkadot/react-components/Status/types';
+
+export interface ComponentProps extends AppProps {
+  location: any;
+}
+
+export interface ModalProps {
+  onClose: () => void;
+  onStatusChange: (status: ActionStatus) => void;
+}

+ 201 - 0
pioneer/packages/app-claims/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 1 - 0
pioneer/packages/app-claims/README.md

@@ -0,0 +1 @@
+# @polkadot/app-claims

+ 17 - 0
pioneer/packages/app-claims/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "@polkadot/app-claims",
+  "version": "0.37.0-beta.63",
+  "description": "An app for claiming Polkadot tokens",
+  "main": "index.js",
+  "scripts": {},
+  "author": "Keith Ingram <keith@parity.io>",
+  "maintainers": [
+    "Keith Ingram <keith@parity.io>",
+    "Jaco Greeff <jacogr@gmail.com>"
+  ],
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
+  }
+}

+ 107 - 0
pioneer/packages/app-claims/src/Claim.tsx

@@ -0,0 +1,107 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 { Option } from '@polkadot/types';
+import { BalanceOf, EthereumAddress } from '@polkadot/types/interfaces';
+import { I18nProps } from '@polkadot/react-components/types';
+
+import React, { useContext, useEffect, useState } from 'react';
+import styled from 'styled-components';
+import { ApiContext } from '@polkadot/react-api';
+import { Button, Card } from '@polkadot/react-components';
+import { formatBalance } from '@polkadot/util';
+
+import translate from './translate';
+import { addrToChecksum } from './util';
+
+interface Props extends I18nProps {
+  button: React.ReactNode;
+  ethereumAddress: EthereumAddress | null;
+}
+
+function Claim ({ button, className, ethereumAddress, t }: Props): React.ReactElement<Props> | null {
+  const { api } = useContext(ApiContext);
+  const [claimValue, setClaimValue] = useState<BalanceOf | null>(null);
+  const [claimAddress, setClaimAddress] = useState<EthereumAddress | null>(null);
+  const [isBusy, setIsBusy] = useState(false);
+
+  const _fetchClaim = (address: EthereumAddress): void => {
+    setIsBusy(true);
+
+    api.query.claims
+      .claims<Option<BalanceOf>>(address)
+      .then((claim): void => {
+        setClaimValue(claim.unwrapOr(null));
+        setIsBusy(false);
+      })
+      .catch((): void => setIsBusy(false));
+  };
+
+  useEffect((): void => {
+    if (ethereumAddress !== claimAddress) {
+      setClaimAddress(ethereumAddress);
+      ethereumAddress && _fetchClaim(ethereumAddress);
+    }
+  }, [ethereumAddress]);
+
+  if (isBusy || !claimAddress) {
+    return null;
+  }
+
+  const hasClaim = claimValue && claimValue.gten(0);
+
+  return (
+    <Card
+      isError={!hasClaim}
+      isSuccess={!!hasClaim}
+    >
+      <div className={className}>
+        {t('Your Ethereum account')}
+        <h3>{addrToChecksum(claimAddress.toString())}</h3>
+        {hasClaim && claimValue
+          ? (
+            <>
+              {t('has a valid claim for')}
+              <h2>{formatBalance(claimValue)}</h2>
+              <Button.Group>{button}</Button.Group>
+            </>
+          )
+          : (
+            <>
+              {t('does not appear to have a valid claim. Please double check that you have signed the transaction correctly on the correct ETH account.')}
+            </>
+          )}
+      </div>
+    </Card>
+  );
+}
+
+export default translate(
+  styled(Claim)`
+    font-size: 1.15rem;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    min-height: 12rem;
+    align-items: center;
+    margin: 0 1rem;
+
+    h3 {
+      font-family: monospace;
+      font-size: 1.5rem;
+      max-width: 100%;
+      margin: 0.5rem;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+
+    h2 {
+      margin: 0.5rem 0 2rem;
+      font-family: monospace;
+      font-size: 2.5rem;
+      font-weight: 200;
+    }
+  `
+);

+ 259 - 0
pioneer/packages/app-claims/src/index.tsx

@@ -0,0 +1,259 @@
+/* eslint-disable @typescript-eslint/camelcase */
+// Copyright 2017-2019 @polkadot/app-123code 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 { Compact } from '@polkadot/types';
+import { Balance, EcdsaSignature, EthereumAddress } from '@polkadot/types/interfaces';
+import { AppProps, I18nProps } from '@polkadot/react-components/types';
+import { ApiProps } from '@polkadot/react-api/types';
+
+import React from 'react';
+import { Trans } from 'react-i18next';
+import styled from 'styled-components';
+import CopyToClipboard from 'react-copy-to-clipboard';
+import { withApi, withMulti } from '@polkadot/react-api';
+import { Button, Card, Columar, Column, InputAddress, Tooltip } from '@polkadot/react-components';
+import { InputNumber } from '@polkadot/react-components/InputNumber';
+import TxModal, { TxModalState, TxModalProps } from '@polkadot/react-components/TxModal';
+import { u8aToHex, u8aToString } from '@polkadot/util';
+import { decodeAddress } from '@polkadot/util-crypto';
+
+import ClaimDisplay from './Claim';
+import { recoverFromJSON } from './util';
+
+import translate from './translate';
+
+enum Step {
+  Account = 0,
+  Sign = 1,
+  Claim = 2,
+}
+
+interface Props extends AppProps, ApiProps, I18nProps, TxModalProps {}
+
+interface State extends TxModalState {
+  didCopy: boolean;
+  ethereumAddress: EthereumAddress | null;
+  claim?: Balance | null;
+  signature?: EcdsaSignature | null;
+  step: Step;
+}
+
+const Payload = styled.pre`
+  cursor: copy;
+  font-family: monospace;
+  border: 1px dashed #c2c2c2;
+  background: #fafafa;
+  padding: 1rem;
+  width: 100%;
+  margin: 1rem 0;
+  white-space: normal;
+  word-break: break-all;
+`;
+
+const Signature = styled.textarea`
+  font-family: monospace;
+  padding: 1rem;
+  border: 1px solid rgba(34, 36, 38, 0.15);
+  border-radius: 0.25rem;
+  margin: 1rem 0;
+  resize: none;
+  width: 100%;
+
+  &::placeholder {
+    color: rgba(0, 0, 0, 0.5);
+  }
+
+  &:-ms-input-placeholder {
+    color: rgba(0, 0, 0, 0.5);
+  }
+
+  &::-ms-input-placeholder {
+    color: rgba(0, 0, 0, 0.5);
+  }
+`;
+
+class App extends TxModal<Props, State> {
+  constructor (props: Props) {
+    super(props);
+
+    this.defaultState = {
+      ...this.defaultState,
+      claim: null,
+      didCopy: false,
+      ethereumAddress: null,
+      signature: null,
+      step: 0
+    };
+    this.state = this.defaultState;
+  }
+
+  public componentDidUpdate (): void {
+    if (this.state.didCopy) {
+      setTimeout((): void => {
+        this.setState({ didCopy: false });
+      }, 1000);
+    }
+  }
+
+  public render (): React.ReactNode {
+    const { api, systemChain = '', t } = this.props;
+    const { accountId, didCopy, ethereumAddress, signature, step } = this.state;
+
+    const payload = accountId
+      ? (
+        u8aToString(Compact.stripLengthPrefix(api.consts.claims.prefix.toU8a(true))) +
+        u8aToHex(decodeAddress(accountId), -1, false)
+      )
+      : '';
+
+    return (
+      <main>
+        <header />
+        <h1>
+          <Trans>claim your <em>{InputNumber.units}</em> tokens</Trans>
+        </h1>
+        <Columar>
+          <Column>
+            <Card withBottomMargin>
+              <h3>{t('1. Select your {{chain}} account', {
+                replace: {
+                  chain: systemChain
+                }
+              })}</h3>
+              <InputAddress
+                defaultValue={this.state.accountId}
+                help={t('The account you want to claim to.')}
+                label={t('claim to account')}
+                onChange={this.onChangeAccount}
+                type='all'
+              />
+              {(step === Step.Account) && (
+                <Button.Group>
+                  <Button
+                    icon='sign-in'
+                    isPrimary
+                    onClick={this.setStep(Step.Sign)}
+                    label={t('Continue')}
+                  />
+                </Button.Group>
+              )}
+            </Card>
+            {(step >= Step.Sign && !!accountId) && (
+              <Card>
+                <h3>{t('2. Sign ETH transaction')}</h3>
+                <CopyToClipboard
+                  onCopy={this.onCopy}
+                  text={payload}
+                >
+                  <Payload
+                    data-for='tx-payload'
+                    data-tip
+                  >
+                    {payload}
+                  </Payload>
+                </CopyToClipboard>
+                <Tooltip
+                  place='right'
+                  text={didCopy ? t('copied') : t('click to copy')}
+                  trigger='tx-payload'
+                />
+                <div>
+                  {t('Copy the above string and sign an Ethereum transaction with the account you used during the pre-sale in the wallet of your choice, using the string as the payload, and then paste the transaction signature object below')}
+                  :
+                </div>
+                <Signature
+                  onChange={this.onChangeSignature}
+                  placeholder='{\n  "address": "0x ...",\n  "msg": "Pay KSMs to the Kusama account: ...",\n  "sig": "0x ...",\n  "version": "2"\n}'
+                  rows={10}
+                />
+                {(step === Step.Sign) && (
+                  <Button.Group>
+                    <Button
+                      icon='sign-in'
+                      isDisabled={!accountId || !signature}
+                      isPrimary
+                      onClick={this.setStep(Step.Claim)}
+                      label={t('Confirm claim')}
+                    />
+                  </Button.Group>
+                )}
+              </Card>
+            )}
+          </Column>
+          <Column showEmptyText={false}>
+            {(step >= Step.Claim) && (
+              <ClaimDisplay
+                button={this.renderTxButton()}
+                ethereumAddress={ethereumAddress}
+              />
+            )}
+          </Column>
+        </Columar>
+      </main>
+    );
+  }
+
+  protected isDisabled = (): boolean => {
+    const { accountId, signature } = this.state;
+
+    return !accountId || !signature;
+  }
+
+  protected isUnsigned = (): boolean => true;
+
+  protected submitLabel = (): React.ReactNode => this.props.t('Redeem');
+
+  protected txMethod = (): string => 'claims.claim';
+
+  protected txParams = (): [string | null, EcdsaSignature | null] => {
+    const { accountId, signature } = this.state;
+
+    return [
+      accountId ? accountId.toString() : null,
+      signature || null
+    ];
+  }
+
+  protected onChangeAccount = (accountId: string | null): void => {
+    this.setState(({ step }: State): Pick<State, never> => {
+      return {
+        ...(
+          step > Step.Account
+            ? this.defaultState
+            : {}
+        ),
+        accountId
+      };
+    });
+  }
+
+  protected onChangeSignature = (event: React.SyntheticEvent<Element>): void => {
+    const { value: signatureJson } = event.target as HTMLInputElement;
+
+    this.setState(({ step }: State): Pick<State, never> => ({
+      ...(
+        step > Step.Sign
+          ? { step: Step.Sign }
+          : {}
+      ),
+      ...recoverFromJSON(signatureJson)
+    }));
+  }
+
+  private onCopy = (): void => {
+    this.setState({ didCopy: true });
+  }
+
+  private setStep = (step: Step): () => void =>
+    (): void => {
+      this.setState({ step });
+    }
+}
+
+export default withMulti(
+  App,
+  translate,
+  withApi
+);

+ 8 - 0
pioneer/packages/app-claims/src/secp256k1.d.ts

@@ -0,0 +1,8 @@
+// Copyright 2017-2019 @polkadot/app-123code authors & contributors
+// This software may be modified and distributed under the terms
+// of the Apache-2.0 license. See the LICENSE file for details.
+
+declare module 'secp256k1/elliptic' {
+  export function publicKeyConvert (publicKey: Buffer, expanded: boolean): Buffer;
+  export function recover (msgHash: Buffer, signature: Buffer, recovery: number): Buffer;
+}

+ 7 - 0
pioneer/packages/app-claims/src/translate.ts

@@ -0,0 +1,7 @@
+// Copyright 2017-2019 @polkadot/app-123code 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 { withTranslation } from 'react-i18next';
+
+export default withTranslation(['app-claims']);

+ 19 - 0
pioneer/packages/app-claims/src/util.spec.ts

@@ -0,0 +1,19 @@
+// Copyright 2017-2019 @polkadot/react-components 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 { hexToU8a } from '@polkadot/util';
+
+import { publicToAddr } from './util';
+
+describe('publicToAddr', (): void => {
+  it('converts a publicKey to address', (): void => {
+    expect(
+      publicToAddr(
+        hexToU8a(
+          '0x836b35a026743e823a90a0ee3b91bf615c6a757e2b60b9e1dc1826fd0dd16106f7bc1e8179f665015f43c6c81f39062fc2086ed849625c06e04697698b21855e'
+        )
+      )
+    ).toEqual('0x0BED7ABd61247635c1973eB38474A2516eD1D884');
+  });
+});

+ 111 - 0
pioneer/packages/app-claims/src/util.ts

@@ -0,0 +1,111 @@
+/* eslint-disable @typescript-eslint/camelcase */
+// Copyright 2017-2019 @polkadot/app-123code 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 { EthereumAddress, EcdsaSignature } from '@polkadot/types/interfaces';
+
+import secp256k1 from 'secp256k1/elliptic';
+import { createType } from '@polkadot/types';
+import { assert, hexToU8a, stringToU8a, u8aToBuffer, u8aConcat } from '@polkadot/util';
+import { keccakAsHex, keccakAsU8a } from '@polkadot/util-crypto';
+
+interface RecoveredSignature {
+  error: Error | null;
+  ethereumAddress: EthereumAddress | null;
+  signature: EcdsaSignature | null;
+}
+
+interface SignatureParts {
+  recovery: number;
+  signature: Buffer;
+}
+
+// converts an Ethereum address to a checksum representation
+export function addrToChecksum (_address: string): string {
+  const address = _address.toLowerCase();
+  const hash = keccakAsHex(address.substr(2)).substr(2);
+  let result = '0x';
+
+  for (let n = 0; n < 40; n++) {
+    result = `${result}${
+      parseInt(hash[n], 16) > 7
+        ? address[n + 2].toUpperCase()
+        : address[n + 2]
+    }`;
+  }
+
+  return result;
+}
+
+// convert a give public key to an Ethereum address (the last 20 bytes of an _exapnded_ key keccack)
+export function publicToAddr (publicKey: Uint8Array): string {
+  return addrToChecksum(`0x${keccakAsHex(publicKey).slice(-40)}`);
+}
+
+// hash a message for use in signature recovery, adding the standard Ethereum header
+export function hashMessage (message: string): Buffer {
+  const expanded = stringToU8a(`\x19Ethereum Signed Message:\n${message.length.toString()}${message}`);
+  const hashed = keccakAsU8a(expanded);
+
+  return u8aToBuffer(hashed);
+}
+
+// split is 65-byte signature into the r, s (combined) and recovery number (derived from v)
+export function sigToParts (_signature: string): SignatureParts {
+  const signature = hexToU8a(_signature);
+
+  assert(signature.length === 65, `Invalid signature length, expected 65 found ${signature.length}`);
+
+  let v = signature[64];
+
+  if (v < 27) {
+    v += 27;
+  }
+
+  const recovery = v - 27;
+
+  assert(recovery === 0 || recovery === 1, 'Invalid signature v value');
+
+  return {
+    recovery,
+    signature: u8aToBuffer(signature.slice(0, 64))
+  };
+}
+
+// recover an address from a given message and a recover/signature combination
+export function recoverAddress (message: string, { recovery, signature }: SignatureParts): string {
+  const msgHash = hashMessage(message);
+  const senderPubKey = secp256k1.recover(msgHash, signature, recovery);
+
+  return publicToAddr(
+    secp256k1.publicKeyConvert(senderPubKey, false).slice(1)
+  );
+}
+
+// recover an address from a signature JSON (as supplied by e.g. MyCrypto)
+export function recoverFromJSON (signatureJson: string | null): RecoveredSignature {
+  try {
+    const { msg, sig } = JSON.parse(signatureJson || '{}');
+
+    if (!msg || !sig) {
+      throw new Error('Invalid signature object');
+    }
+
+    const parts = sigToParts(sig);
+
+    return {
+      error: null,
+      ethereumAddress: createType('EthereumAddress', recoverAddress(msg, parts)),
+      signature: createType('EcdsaSignature', u8aConcat(parts.signature, new Uint8Array([parts.recovery])))
+    };
+  } catch (error) {
+    console.error(error);
+
+    return {
+      error,
+      ethereumAddress: null,
+      signature: null
+    };
+  }
+}

+ 201 - 0
pioneer/packages/app-contracts/LICENSE

@@ -0,0 +1,201 @@
+                              Apache License
+                        Version 2.0, January 2004
+                    http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+  "License" shall mean the terms and conditions for use, reproduction,
+  and distribution as defined by Sections 1 through 9 of this document.
+
+  "Licensor" shall mean the copyright owner or entity authorized by
+  the copyright owner that is granting the License.
+
+  "Legal Entity" shall mean the union of the acting entity and all
+  other entities that control, are controlled by, or are under common
+  control with that entity. For the purposes of this definition,
+  "control" means (i) the power, direct or indirect, to cause the
+  direction or management of such entity, whether by contract or
+  otherwise, or (ii) ownership of fifty percent (50%) or more of the
+  outstanding shares, or (iii) beneficial ownership of such entity.
+
+  "You" (or "Your") shall mean an individual or Legal Entity
+  exercising permissions granted by this License.
+
+  "Source" form shall mean the preferred form for making modifications,
+  including but not limited to software source code, documentation
+  source, and configuration files.
+
+  "Object" form shall mean any form resulting from mechanical
+  transformation or translation of a Source form, including but
+  not limited to compiled object code, generated documentation,
+  and conversions to other media types.
+
+  "Work" shall mean the work of authorship, whether in Source or
+  Object form, made available under the License, as indicated by a
+  copyright notice that is included in or attached to the work
+  (an example is provided in the Appendix below).
+
+  "Derivative Works" shall mean any work, whether in Source or Object
+  form, that is based on (or derived from) the Work and for which the
+  editorial revisions, annotations, elaborations, or other modifications
+  represent, as a whole, an original work of authorship. For the purposes
+  of this License, Derivative Works shall not include works that remain
+  separable from, or merely link (or bind by name) to the interfaces of,
+  the Work and Derivative Works thereof.
+
+  "Contribution" shall mean any work of authorship, including
+  the original version of the Work and any modifications or additions
+  to that Work or Derivative Works thereof, that is intentionally
+  submitted to Licensor for inclusion in the Work by the copyright owner
+  or by an individual or Legal Entity authorized to submit on behalf of
+  the copyright owner. For the purposes of this definition, "submitted"
+  means any form of electronic, verbal, or written communication sent
+  to the Licensor or its representatives, including but not limited to
+  communication on electronic mailing lists, source code control systems,
+  and issue tracking systems that are managed by, or on behalf of, the
+  Licensor for the purpose of discussing and improving the Work, but
+  excluding communication that is conspicuously marked or otherwise
+  designated in writing by the copyright owner as "Not a Contribution."
+
+  "Contributor" shall mean Licensor and any individual or Legal Entity
+  on behalf of whom a Contribution has been received by Licensor and
+  subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  copyright license to reproduce, prepare Derivative Works of,
+  publicly display, publicly perform, sublicense, and distribute the
+  Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+  this License, each Contributor hereby grants to You a perpetual,
+  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+  (except as stated in this section) patent license to make, have made,
+  use, offer to sell, sell, import, and otherwise transfer the Work,
+  where such license applies only to those patent claims licensable
+  by such Contributor that are necessarily infringed by their
+  Contribution(s) alone or by combination of their Contribution(s)
+  with the Work to which such Contribution(s) was submitted. If You
+  institute patent litigation against any entity (including a
+  cross-claim or counterclaim in a lawsuit) alleging that the Work
+  or a Contribution incorporated within the Work constitutes direct
+  or contributory patent infringement, then any patent licenses
+  granted to You under this License for that Work shall terminate
+  as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+  Work or Derivative Works thereof in any medium, with or without
+  modifications, and in Source or Object form, provided that You
+  meet the following conditions:
+
+  (a) You must give any other recipients of the Work or
+      Derivative Works a copy of this License; and
+
+  (b) You must cause any modified files to carry prominent notices
+      stating that You changed the files; and
+
+  (c) You must retain, in the Source form of any Derivative Works
+      that You distribute, all copyright, patent, trademark, and
+      attribution notices from the Source form of the Work,
+      excluding those notices that do not pertain to any part of
+      the Derivative Works; and
+
+  (d) If the Work includes a "NOTICE" text file as part of its
+      distribution, then any Derivative Works that You distribute must
+      include a readable copy of the attribution notices contained
+      within such NOTICE file, excluding those notices that do not
+      pertain to any part of the Derivative Works, in at least one
+      of the following places: within a NOTICE text file distributed
+      as part of the Derivative Works; within the Source form or
+      documentation, if provided along with the Derivative Works; or,
+      within a display generated by the Derivative Works, if and
+      wherever such third-party notices normally appear. The contents
+      of the NOTICE file are for informational purposes only and
+      do not modify the License. You may add Your own attribution
+      notices within Derivative Works that You distribute, alongside
+      or as an addendum to the NOTICE text from the Work, provided
+      that such additional attribution notices cannot be construed
+      as modifying the License.
+
+  You may add Your own copyright statement to Your modifications and
+  may provide additional or different license terms and conditions
+  for use, reproduction, or distribution of Your modifications, or
+  for any such Derivative Works as a whole, provided Your use,
+  reproduction, and distribution of the Work otherwise complies with
+  the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+  any Contribution intentionally submitted for inclusion in the Work
+  by You to the Licensor shall be under the terms and conditions of
+  this License, without any additional terms or conditions.
+  Notwithstanding the above, nothing herein shall supersede or modify
+  the terms of any separate license agreement you may have executed
+  with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+  names, trademarks, service marks, or product names of the Licensor,
+  except as required for reasonable and customary use in describing the
+  origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+  agreed to in writing, Licensor provides the Work (and each
+  Contributor provides its Contributions) on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+  implied, including, without limitation, any warranties or conditions
+  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+  PARTICULAR PURPOSE. You are solely responsible for determining the
+  appropriateness of using or redistributing the Work and assume any
+  risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+  whether in tort (including negligence), contract, or otherwise,
+  unless required by applicable law (such as deliberate and grossly
+  negligent acts) or agreed to in writing, shall any Contributor be
+  liable to You for damages, including any direct, indirect, special,
+  incidental, or consequential damages of any character arising as a
+  result of this License or out of the use or inability to use the
+  Work (including but not limited to damages for loss of goodwill,
+  work stoppage, computer failure or malfunction, or any and all
+  other commercial damages or losses), even if such Contributor
+  has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+  the Work or Derivative Works thereof, You may choose to offer,
+  and charge a fee for, acceptance of support, warranty, indemnity,
+  or other liability obligations and/or rights consistent with this
+  License. However, in accepting such obligations, You may act only
+  on Your own behalf and on Your sole responsibility, not on behalf
+  of any other Contributor, and only if You agree to indemnify,
+  defend, and hold each Contributor harmless for any liability
+  incurred by, or claims asserted against, such Contributor by reason
+  of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+  To apply the Apache License to your work, attach the following
+  boilerplate notice, with the fields enclosed by brackets "[]"
+  replaced with your own identifying information. (Don't include
+  the brackets!)  The text should be enclosed in the appropriate
+  comment syntax for the file format. We also recommend that a
+  file or class name and description of purpose be included on the
+  same "printed page" as the copyright notice for easier
+  identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.

+ 3 - 0
pioneer/packages/app-contracts/README.md

@@ -0,0 +1,3 @@
+# @polkadot/app-contracts
+
+Substrate contracts - code deployment, instance creation and messages.

+ 17 - 0
pioneer/packages/app-contracts/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "@polkadot/app-contracts",
+  "version": "0.37.0-beta.63",
+  "description": "Deployment and management of substrate contracts",
+  "main": "index.js",
+  "scripts": {},
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "maintainers": [
+    "Jaco Greeff <jacogr@gmail.com>"
+  ],
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@babel/runtime": "^7.7.1",
+    "@polkadot/api-contract": "^0.96.1",
+    "@polkadot/react-components": "^0.37.0-beta.63"
+  }
+}

+ 196 - 0
pioneer/packages/app-contracts/src/ABI.tsx

@@ -0,0 +1,196 @@
+// Copyright 2017-2019 @polkadot/app-contracts 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 { I18nProps } from '@polkadot/react-components/types';
+
+import React from 'react';
+import styled from 'styled-components';
+import { Abi } from '@polkadot/api-contract';
+import { InputFile, Labelled, Messages } from '@polkadot/react-components';
+import { u8aToString } from '@polkadot/util';
+
+import translate from './translate';
+
+interface Props extends I18nProps {
+  className?: string;
+  contractAbi?: Abi | null;
+  errorText?: string | null;
+  help?: React.ReactNode;
+  isError?: boolean;
+  isDisabled?: boolean;
+  isRequired?: boolean;
+  label?: React.ReactNode;
+  onChange: (json: string | null, contractAbi: Abi | null) => void;
+  onRemove?: () => void;
+  onRemoved?: () => void;
+  onSelect?: () => void;
+  onSelectConstructor?: (constructorIndex?: number) => void;
+}
+
+interface State {
+  contractAbi: Abi | null;
+  errorText: string | null;
+  isAbiValid: boolean;
+  isEmpty: boolean;
+  isError: boolean;
+}
+
+class ABI extends React.PureComponent<Props, State> {
+  public state: State = {
+    contractAbi: null,
+    errorText: null,
+    isAbiValid: false,
+    isEmpty: true,
+    isError: false
+  };
+
+  constructor (props: Props) {
+    super(props);
+
+    const { contractAbi, isError, isRequired } = this.props;
+    const isAbiValid = !!contractAbi;
+
+    this.state = {
+      contractAbi: contractAbi || null,
+      errorText: null,
+      isAbiValid,
+      isEmpty: !isAbiValid,
+      isError: isError || (isRequired && !isAbiValid) || false
+    };
+  }
+
+  public static getDerivedStateFromProps ({ contractAbi }: Props): Pick<State, never> | null {
+    if (contractAbi) {
+      return {
+        contractAbi,
+        isAbiValid: true,
+        isError: false
+      };
+    }
+    return null;
+  }
+
+  public render (): React.ReactNode {
+    const { className } = this.props;
+    const { contractAbi, isAbiValid } = this.state;
+
+    return (
+      <div className={className}>
+        {
+          (contractAbi && isAbiValid)
+            ? this.renderMessages()
+            : this.renderInputFile()
+        }
+      </div>
+    );
+  }
+
+  private renderInputFile (): React.ReactNode {
+    const { className, help, isDisabled, isRequired, label, t } = this.props;
+    const { isAbiValid, isEmpty, isError, errorText } = this.state;
+
+    return (
+      <div className={className}>
+        <InputFile
+          help={help}
+          isDisabled={isDisabled}
+          isError={!isAbiValid && (isRequired || isError)}
+          label={label}
+          onChange={this.onChange}
+          placeholder={
+            !isEmpty && !isAbiValid
+              ? (
+                <>
+                  {t('invalid ABI file selected')}
+                  {!!errorText && (
+                    <>
+                      {' — '}
+                      {t(errorText)}
+                    </>
+                  )}
+                </>
+              )
+              : t('click to select or drag and drop a JSON ABI file')
+          }
+        />
+      </div>
+    );
+  }
+
+  private renderMessages (): React.ReactNode {
+    const { help, isDisabled, label, onRemove, onSelectConstructor } = this.props;
+    const { contractAbi } = this.state;
+
+    if (!contractAbi) {
+      return null;
+    }
+
+    return (
+      <Labelled
+        label={label}
+        help={help}
+        withLabel={!!label}
+      >
+        <Messages
+          contractAbi={contractAbi}
+          onRemove={onRemove || this.onRemove}
+          isLabelled={!!label}
+          isRemovable={!isDisabled}
+          onSelectConstructor={onSelectConstructor}
+          withConstructors
+        />
+      </Labelled>
+    );
+  }
+
+  private onChange = (u8a: Uint8Array): void => {
+    const { onChange, t } = this.props;
+    const json = u8aToString(u8a);
+    try {
+      const abi = JSON.parse(json);
+
+      if (abi.deploy || abi.messages) {
+        throw new Error(t('You are using an ABI with an outdated format. Please generate a new one.'));
+      }
+
+      const contractAbi = new Abi(abi);
+
+      this.setState({
+        contractAbi,
+        isAbiValid: true,
+        isEmpty: false,
+        isError: false
+      }, (): void => onChange(json, contractAbi));
+    } catch (error) {
+      console.error(error);
+
+      this.setState({
+        isAbiValid: false,
+        isEmpty: false,
+        isError: true,
+        errorText: error
+      }, (): void => onChange(null, null));
+    }
+  }
+
+  private onRemove = (): void => {
+    const { onChange, onRemoved } = this.props;
+
+    this.setState(
+      {
+        contractAbi: null,
+        isAbiValid: false,
+        isEmpty: true
+      },
+      (): void => {
+        onChange(null, null);
+        onRemoved && onRemoved();
+      }
+    );
+  }
+}
+
+export default translate(styled(ABI as React.ComponentClass<Props, State>)`
+  min-height: 4rem;
+`);

+ 113 - 0
pioneer/packages/app-contracts/src/Codes/Add.tsx

@@ -0,0 +1,113 @@
+// Copyright 2017-2019 @polkadot/app-contracts 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 React from 'react';
+import { createType } from '@polkadot/types';
+import { Button, Input } from '@polkadot/react-components';
+
+import ContractModal, { ContractModalProps as Props, ContractModalState } from '../Modal';
+import ValidateCode from './ValidateCode';
+import store from '../store';
+import translate from '../translate';
+
+interface State extends ContractModalState {
+  codeHash: string;
+  isBusy: boolean;
+  isCodeValid: boolean;
+}
+
+class Add extends ContractModal<Props, State> {
+  constructor (props: Props) {
+    super(props);
+    this.defaultState = {
+      ...this.defaultState,
+      codeHash: '',
+      isBusy: false,
+      isCodeValid: false
+    };
+    this.state = this.defaultState;
+    this.headerText = props.t('Add an existing code hash');
+  }
+
+  protected renderContent = (): React.ReactNode => {
+    const { t } = this.props;
+    const { codeHash, isBusy, isCodeValid } = this.state;
+
+    return (
+      <>
+        <Input
+          autoFocus
+          help={t('The code hash for the on-chain deployed code.')}
+          isDisabled={isBusy}
+          isError={!isCodeValid}
+          label={t('code hash')}
+          onChange={this.onChangeHash}
+          onEnter={this.submit}
+          value={codeHash}
+        />
+        <ValidateCode
+          codeHash={codeHash}
+          onChange={this.onValidateCode}
+        />
+        {this.renderInputName()}
+        {this.renderInputAbi()}
+      </>
+    );
+  }
+
+  protected renderButtons = (): React.ReactNode => {
+    const { t } = this.props;
+    const { isBusy, isCodeValid, isNameValid } = this.state;
+    const isValid = !isBusy && isCodeValid && isNameValid;
+
+    return (
+      <Button.Group>
+        {this.renderCancelButton()}
+        <Button
+          icon='save'
+          isDisabled={!isValid}
+          isPrimary
+          label={t('Save')}
+          onClick={this.onSave}
+          ref={this.button}
+        />
+      </Button.Group>
+    );
+  }
+
+  private onChangeHash = (codeHash: string): void => {
+    this.setState({ codeHash, isCodeValid: false });
+  }
+
+  private onValidateCode = (isCodeValid: boolean): void => {
+    this.setState({ isCodeValid });
+  }
+
+  private onSave = (): void => {
+    const { abi, codeHash, name, tags } = this.state;
+
+    if (!codeHash || !name) {
+      return;
+    }
+
+    this.setState({ isBusy: true }, (): void => {
+      store
+        .saveCode(createType('Hash', codeHash), { abi, name, tags })
+        .then((): void => {
+          this.setState(
+            { isBusy: false },
+            (): void => this.onClose()
+          );
+        })
+        .catch((error): void => {
+          console.error('Unable to save code', error);
+          this.setState({ isBusy: false });
+        });
+    });
+
+    // this.redirect();
+  }
+}
+
+export default translate(Add);

+ 208 - 0
pioneer/packages/app-contracts/src/Codes/Code.tsx

@@ -0,0 +1,208 @@
+// Copyright 2017-2019 @polkadot/app-staking 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 { I18nProps } from '@polkadot/react-components/types';
+import { CodeStored } from '../types';
+
+import React from 'react';
+import styled from 'styled-components';
+import { RouteComponentProps } from 'react-router';
+import { withRouter } from 'react-router-dom';
+import { Button, Card, CodeRow, Forget } from '@polkadot/react-components';
+
+import ABI from '../ABI';
+import RemoveABI from '../RemoveABI';
+
+import contracts from '../store';
+import translate from '../translate';
+
+interface Props extends I18nProps, RouteComponentProps<{}> {
+  code: CodeStored;
+  showDeploy: (codeHash?: string, constructorIndex?: number) => () => void;
+}
+
+interface State {
+  isAbiOpen: boolean;
+  isForgetOpen: boolean;
+  isRemoveABIOpen: boolean;
+}
+
+const CodeCard = styled(Card)`
+  && {
+    max-width: 100%;
+    min-width: 100%;
+  }
+`;
+
+class Code extends React.PureComponent<Props, State> {
+  public state: State = {
+    isAbiOpen: false,
+    isForgetOpen: false,
+    isRemoveABIOpen: false
+  };
+
+  public render (): React.ReactNode {
+    const { code, code: { contractAbi }, t } = this.props;
+    const { isAbiOpen } = this.state;
+
+    const abi = (
+      <ABI
+        contractAbi={contractAbi}
+        onChange={this.onChangeABI}
+        onRemove={this.toggleRemoveABI}
+        onSelectConstructor={this.onDeployConstructor}
+      />
+    );
+
+    return (
+      <CodeCard>
+        {this.renderModals()}
+        <CodeRow
+          buttons={this.renderButtons()}
+          code={code}
+          isEditable
+          withTags
+        >
+          {contractAbi
+            ? (
+              <details
+                onClick={this.toggleAbi}
+                open={isAbiOpen}
+              >
+                <summary>{t('ABI')}</summary>
+                {abi}
+              </details>
+            )
+            : abi
+          }
+        </CodeRow>
+      </CodeCard>
+    );
+  }
+
+  private renderButtons (): React.ReactNode {
+    const { code: { json: { codeHash } }, showDeploy, t } = this.props;
+
+    return (
+      <>
+        <Button
+          isNegative
+          onClick={this.toggleForget}
+          icon='trash'
+          size='small'
+          tooltip={t('Forget this code hash')}
+        />
+        <Button
+          icon='cloud upload'
+          isPrimary
+          label={t('deploy')}
+          onClick={showDeploy(codeHash)}
+          size='small'
+          tooltip={t('Deploy this code hash as a smart contract')}
+        />
+      </>
+    );
+  }
+
+  private renderModals (): React.ReactNode {
+    const { code } = this.props;
+    const { isForgetOpen, isRemoveABIOpen } = this.state;
+
+    if (!code) {
+      return null;
+    }
+
+    const modals = [];
+
+    if (isForgetOpen) {
+      modals.push(
+        <Forget
+          code={code}
+          key='modal-forget-account'
+          mode='code'
+          onClose={this.toggleForget}
+          onForget={this.onForget}
+        />
+      );
+    }
+
+    if (isRemoveABIOpen) {
+      modals.push(
+        <RemoveABI
+          code={code}
+          key='modal-remove-abi'
+          onClose={this.toggleRemoveABI}
+          onRemove={this.onChangeABI}
+        />
+      );
+    }
+
+    return modals;
+  }
+
+  private toggleAbi = (event: React.MouseEvent): () => void => {
+    return (): void => {
+      event.preventDefault();
+      const { isAbiOpen } = this.state;
+
+      this.setState({
+        isAbiOpen: !isAbiOpen
+      });
+    };
+  }
+
+  private toggleForget = (): void => {
+    const { isForgetOpen } = this.state;
+
+    this.setState({
+      isForgetOpen: !isForgetOpen
+    });
+  }
+
+  private toggleRemoveABI = (): void => {
+    const { isRemoveABIOpen } = this.state;
+
+    this.setState({
+      isRemoveABIOpen: !isRemoveABIOpen
+    });
+  }
+
+  private onDeployConstructor = (constructorIndex = 0): void => {
+    const { code: { json: { codeHash } }, showDeploy } = this.props;
+
+    codeHash && showDeploy && showDeploy(codeHash, constructorIndex)();
+  }
+
+  private onForget = (): void => {
+    const { code: { json: { codeHash } } } = this.props;
+
+    if (!codeHash) {
+      return;
+    }
+
+    try {
+      contracts.forgetCode(codeHash);
+    } catch (error) {
+      console.error(error);
+    } finally {
+      this.toggleForget();
+    }
+  }
+
+  private onChangeABI = (abi: string | null = null): void => {
+    const { code: { json: { codeHash } } } = this.props;
+
+    this.setState(
+      { isAbiOpen: true },
+      (): void => {
+        contracts.saveCode(
+          codeHash,
+          { abi }
+        );
+      }
+    );
+  }
+}
+
+export default translate(withRouter(Code));

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