├── .editorconfig ├── .github ├── issue_template.md └── workflows │ ├── auto-approve.yml │ ├── auto-merge.yml │ ├── lock.yml │ ├── pr-any.yml │ └── push-master.yml ├── .gitignore ├── .mailmap ├── .npmignore ├── .prettierignore ├── .prettierrc.cjs ├── .vscode └── settings.json ├── .yarn ├── plugins │ └── .keep └── releases │ └── yarn-4.5.1.cjs ├── .yarnrc.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── 404.html └── index.html ├── eslint.config.js ├── package.json ├── packages ├── react-identicon │ ├── README.md │ ├── demo.png │ ├── index.html │ ├── package.json │ ├── src │ │ ├── Identicon.tsx │ │ ├── bundle.ts │ │ ├── icons │ │ │ ├── Beachball.tsx │ │ │ ├── Empty.tsx │ │ │ ├── Ethereum.tsx │ │ │ ├── Jdenticon.tsx │ │ │ ├── Polkadot.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── packageDetect.ts │ │ ├── packageInfo.ts │ │ ├── styled.ts │ │ └── types.ts │ └── tsconfig.build.json ├── react-qr │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Display.tsx │ │ ├── DisplayAddress.tsx │ │ ├── DisplayPayload.tsx │ │ ├── NetworkSpecs.tsx │ │ ├── Scan.tsx │ │ ├── ScanAddress.tsx │ │ ├── ScanSignature.tsx │ │ ├── bundle.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── packageDetect.ts │ │ ├── packageInfo.ts │ │ ├── qrcode.ts │ │ ├── styled.ts │ │ ├── util.spec.ts │ │ └── util.ts │ ├── tsconfig.build.json │ └── tsconfig.spec.json ├── reactnative-identicon │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Identicon.tsx │ │ ├── bundle.ts │ │ ├── icons │ │ │ ├── Empty.tsx │ │ │ ├── Polkadot.tsx │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── packageDetect.ts │ │ ├── packageInfo.ts │ │ └── types.ts │ └── tsconfig.build.json ├── ui-keyring │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Base.ts │ │ ├── Keyring.ts │ │ ├── bundle.ts │ │ ├── defaults.ts │ │ ├── index.ts │ │ ├── json.d.ts │ │ ├── observable │ │ │ ├── accounts.ts │ │ │ ├── addresses.ts │ │ │ ├── contracts.ts │ │ │ ├── env.ts │ │ │ ├── genericSubject.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── options │ │ │ ├── index.ts │ │ │ ├── item.ts │ │ │ └── types.ts │ │ ├── packageDetect.ts │ │ ├── packageInfo.ts │ │ ├── stores │ │ │ ├── Browser.ts │ │ │ ├── File.ts │ │ │ └── index.ts │ │ └── types.ts │ ├── tsconfig.build.json │ └── tsconfig.spec.json ├── ui-settings │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Settings.ts │ │ ├── bundle.ts │ │ ├── defaults │ │ │ ├── chains.ts │ │ │ ├── crypto.ts │ │ │ ├── endpoints.ts │ │ │ ├── index.ts │ │ │ ├── ledger.ts │ │ │ ├── ss58.ts │ │ │ ├── type.ts │ │ │ └── ui.ts │ │ ├── index.ts │ │ ├── packageDetect.ts │ │ ├── packageInfo.ts │ │ └── types.ts │ └── tsconfig.build.json ├── ui-shared │ ├── README.md │ ├── package.json │ ├── src │ │ ├── bundle.ts │ │ ├── icons │ │ │ ├── beachball │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ ├── colors.spec.ts │ │ │ │ ├── colors.ts │ │ │ │ ├── container.spec.ts │ │ │ │ ├── container.ts │ │ │ │ ├── defaults.ts │ │ │ │ ├── demo.ts │ │ │ │ ├── index.spec.ts │ │ │ │ ├── index.ts │ │ │ │ ├── seeder.spec.ts │ │ │ │ ├── seeder.ts │ │ │ │ ├── shape │ │ │ │ │ ├── circle.spec.ts │ │ │ │ │ ├── circle.ts │ │ │ │ │ └── square.ts │ │ │ │ ├── svg │ │ │ │ │ ├── circle.spec.ts │ │ │ │ │ ├── circle.ts │ │ │ │ │ ├── element.spec.ts │ │ │ │ │ ├── element.ts │ │ │ │ │ ├── rect.spec.ts │ │ │ │ │ ├── rect.ts │ │ │ │ │ ├── svg.spec.ts │ │ │ │ │ └── svg.ts │ │ │ │ └── types.ts │ │ │ ├── index.ts │ │ │ ├── polkadot.spec.ts │ │ │ ├── polkadot.ts │ │ │ └── types.ts │ │ ├── index.ts │ │ ├── packageDetect.ts │ │ └── packageInfo.ts │ ├── tsconfig.build.json │ └── tsconfig.spec.json └── vue-identicon │ ├── README.md │ ├── index.html │ ├── package.json │ ├── src │ ├── Identicon.ts │ ├── bundle.ts │ ├── icons │ │ ├── Beachball.ts │ │ ├── Empty.ts │ │ ├── Jdenticon.ts │ │ ├── Polkadot.ts │ │ └── index.ts │ ├── index.ts │ ├── packageDetect.ts │ ├── packageInfo.ts │ └── util.ts │ └── tsconfig.build.json ├── rollup.config.js ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.eslint.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=space 4 | indent_size=2 5 | tab_width=2 6 | end_of_line=lf 7 | charset=utf-8 8 | trim_trailing_whitespace=true 9 | max_line_length=120 10 | insert_final_newline=true 11 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | * **I'm submitting a ...** 24 | 25 | 32 | 33 | - [ ] Bug report 34 | - [ ] Feature request 35 | - [ ] Support request 36 | - [ ] Other 37 | 38 | 39 | * **What is the current behavior and expected behavior?** 40 | 41 | 49 | 50 | 51 | * **What is the motivation for changing the behavior?** 52 | 53 | 61 | 62 | 63 | * **Please tell us about your environment:** 64 | 65 | 74 | 75 | - Version: 76 | - Environment: 77 | 78 | - [ ] Node.js 79 | - [ ] Browser 80 | - [ ] Other (limited support for other environments) 81 | 82 | - Language: 83 | 84 | - [ ] JavaScript 85 | - [ ] TypeScript (include tsc --version) 86 | - [ ] Other 87 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: bot 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | approve: 9 | if: "! startsWith(github.event.head_commit.message, '[CI Skip]') && (!github.event.pull_request || github.event.pull_request.head.repo.full_name == github.repository)" 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: jacogr/action-approve@795afd1dd096a2071d7ec98740661af4e853b7da 13 | with: 14 | authors: jacogr, TarikGul 15 | labels: -auto 16 | token: ${{ secrets.GH_PAT_BOT }} 17 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: bot 2 | 3 | on: 4 | pull_request: 5 | types: [labeled] 6 | 7 | jobs: 8 | merge: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: jacogr/action-merge@d2d64b4545acd93b0a9575177d3d215ae3f92029 12 | with: 13 | checks: pr (build),pr (lint),pr (test) 14 | labels: -auto 15 | strategy: squash 16 | token: ${{ secrets.GH_PAT_BOT }} 17 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: 'Lock Threads' 2 | 3 | on: 4 | schedule: 5 | - cron: '25 2/3 * * *' 6 | 7 | jobs: 8 | lock: 9 | runs-on: ubuntu-latest 10 | env: 11 | YARN_ENABLE_SCRIPTS: false 12 | steps: 13 | - uses: dessant/lock-threads@c1b35aecc5cdb1a34539d14196df55838bb2f836 14 | with: 15 | github-token: ${{ secrets.GH_PAT_BOT }} 16 | issue-inactive-days: '7' 17 | issue-comment: > 18 | This thread has been automatically locked since there has not been 19 | any recent activity after it was closed. Please open a new issue 20 | if you think you have a related problem or query. 21 | pr-inactive-days: '2' 22 | pr-comment: > 23 | This pull request has been automatically locked since there 24 | has not been any recent activity after it was closed. 25 | Please open a new issue for related bugs. 26 | -------------------------------------------------------------------------------- /.github/workflows/pr-any.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | on: [pull_request] 3 | 4 | jobs: 5 | pr: 6 | continue-on-error: true 7 | strategy: 8 | matrix: 9 | step: ['lint', 'test', 'build'] 10 | runs-on: ubuntu-latest 11 | env: 12 | YARN_ENABLE_SCRIPTS: false 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 'lts/*' 18 | - name: ${{ matrix.step }} 19 | run: | 20 | yarn install --immutable 21 | yarn ${{ matrix.step }} 22 | -------------------------------------------------------------------------------- /.github/workflows/push-master.yml: -------------------------------------------------------------------------------- 1 | name: Master 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | master: 9 | if: "! startsWith(github.event.head_commit.message, '[CI Skip]')" 10 | strategy: 11 | matrix: 12 | step: ['build:release'] 13 | runs-on: ubuntu-latest 14 | env: 15 | YARN_ENABLE_SCRIPTS: false 16 | CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 17 | GH_PAT: ${{ secrets.GH_PAT_BOT }} 18 | GH_RELEASE_GITHUB_API_TOKEN: ${{ secrets.GH_PAT_BOT }} 19 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | token: ${{ secrets.GH_PAT_BOT }} 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 'lts/*' 28 | - name: ${{ matrix.step }} 29 | run: | 30 | yarn install --immutable 31 | yarn ${{ matrix.step }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | build-*/ 3 | coverage/ 4 | node_modules/ 5 | tmp/ 6 | .idea 7 | .DS_Store 8 | .env.local 9 | .env.development.local 10 | .env.test.local 11 | .env.production.local 12 | .npmrc 13 | .yarn/* 14 | !.yarn/releases 15 | !.yarn/plugins 16 | .pnp.* 17 | cc-test-reporter 18 | package-lock.json 19 | npm-debug.log* 20 | tsconfig.*buildinfo 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Jaco 2 | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> 3 | github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Github Actions 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polkadot-js/ui/648c379c28625a3398c6614c9b9557f6ddcfbbbb/.npmignore -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | coverage 3 | packages 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | module.exports = require('@polkadot/dev/config/prettier.cjs'); 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true, 3 | "eslint.experimental.useFlatConfig": true 4 | } 5 | -------------------------------------------------------------------------------- /.yarn/plugins/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polkadot-js/ui/648c379c28625a3398c6614c9b9557f6ddcfbbbb/.yarn/plugins/.keep -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: false 4 | 5 | enableImmutableInstalls: false 6 | 7 | enableProgressBars: false 8 | 9 | logFilters: 10 | - code: YN0013 11 | level: discard 12 | 13 | nodeLinker: node-modules 14 | 15 | yarnPath: .yarn/releases/yarn-4.5.1.cjs 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## What? 4 | 5 | Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit. 6 | A project is more like an open wiki than a standard guarded open source project. 7 | 8 | ## Rules 9 | 10 | There are a few basic ground-rules for contributors (including the maintainer(s) of the project): 11 | 12 | 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. 13 | 2. **Non-master branches**, prefixed with a short name moniker (e.g. `-`) must be used for ongoing work. 14 | 3. **All modifications** must be made in a **pull-request** to solicit feedback from other contributors. 15 | 4. A pull-request *must not be merged until CI* has finished successfully. 16 | 17 | #### Merging pull requests once CI is successful: 18 | - 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. 19 | - No PR should be merged until all reviews' comments are addressed. 20 | 21 | #### Reviewing pull requests: 22 | 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: 23 | 24 | - Buggy behaviour. 25 | - Undue maintenance burden. 26 | - Breaking with house coding style. 27 | - Pessimisation (i.e. reduction of speed as measured in the projects benchmarks). 28 | - Feature reduction (i.e. it removes some aspect of functionality that a significant minority of users rely on). 29 | - Uselessness (i.e. it does not strictly add a feature or fix a known issue). 30 | 31 | #### Reviews may not be used as an effective veto for a PR because: 32 | - There exists a somewhat cleaner/better/faster way of accomplishing the same feature/fix. 33 | - It does not fit well with some other contributors' longer-term vision for the project. 34 | 35 | ## Releases 36 | 37 | Declaring formal releases remains the prerogative of the project maintainer(s). 38 | 39 | ## Changes to this arrangement 40 | 41 | 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. 42 | 43 | ## Heritage 44 | 45 | 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) 46 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | 735 Jaco Adjust Vue peerDependency, >= 2.7 for defineComponent (#782) 2 | 38 Tarik Gul 3.14.1 (#835) 3 | 7 YJ fix #224: decrease frame size to 1024, set multipart bool (#225) 4 | 4 Antoine Estienne Refactor logic to better use prefix (#491) 5 | 4 kwingram25 fix ff api url issue (#289) 6 | 4 Thibaut Sardan store hardware wallets (#431) 7 | 3 Luke Sugiura remove language options (#244) 8 | 3 Stefanie Doll Replace isUndefined util with explicit typeof check (#107) 9 | 2 Arjun Porwal 3.13.1 (#832) 10 | 2 Drew Stone Add Edgeware testnet and mainnet updated endpoints (#274) 11 | 2 Hanwen Cheng allow qr to scan seed qr (#330) 12 | 2 Nikos Kontakis Fix undefined process error that may appear when using ui (#524) 13 | 2 Valentin Fernandez 3.12.2 14 | 2 Viki Val Add compatibility layer for H160 addresses (#789) 15 | 2 Vlad Proshchavaiev Add Subsocial to ss58 defaults (#358) 16 | 2 Xiliang Chen Update UNfrastructure node label (#160) 17 | 2 雪霁 Batch export patch up (#455) 18 | 1 Adam Dossa Add Polymesh Ledger Integration (#427) 19 | 1 Alex Siman Fix typo in README: stiorage -> storage (#69) 20 | 1 Alexander Krupenkin Added ECDSA crypto type (#313) 21 | 1 Andrei Eres Fix raising error if process has not defined yet (#548) 22 | 1 Axel Chalon Simplify KeyringAddress interface (#144) 23 | 1 Cameron Franz react-native example app port (#223) 24 | 1 carumusan Add kulupu (#342) 25 | 1 Derek Colley Remove redundant `div` wrappers in vue-identicon (#643) 26 | 1 Harry Liu Update README.md (#166) 27 | 1 Jake Naviasky Add Edgeware chain prefix to settings. (#317) 28 | 1 Jeeyong Um Fix outdated links in README.md (#772) 29 | 1 Logan Saether Propose W3F node (#188) 30 | 1 Lovesh Harchandani Allow Ledger support for Dock (#425) 31 | 1 Luke Schoen Fix typo in heading (#114) 32 | 1 Nantian fix bigint type (#510) 33 | 1 Nikhil Ranjan Updated to valid javascript (#271) 34 | 1 omahs Fix: typos (#709) 35 | 1 Ryan Lee fix: qr-code (#801) 36 | 1 Shawn Tabrizi Add a new `notification` setting (#498) 37 | 1 Wei Tang Add Kulupu to the network selection list (#254) 38 | 1 WoeOm add darwinia ss58 (#379) 39 | 1 yuhui1208 [Stores] make FileStroe read key safely (#338) 40 | 1 Yuri Add webpack 5 support (#557) 41 | 1 Zen feat: adapt Vue 3.x (#755) 42 | 1 菩提 fix some bugs (#260) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/ui 2 | 3 | Basic browser and framework agnostic UI components for creating apps using the polkadot{.js} libraries 4 | 5 | ## overview 6 | 7 | The following UI components are currently available - 8 | 9 | - [react-identicon](packages/react-identicon/) React identity icon generator with address as input 10 | - [reactnative-identicon](packages/reactnative-identicon/) React Native identity icon generator with address as input 11 | - [vue-identicon](packages/vue-identicon/) Vue identity icon generator with address as input 12 | - [react-qr](packages/react-qr/) QR code generator/reader for [uos](https://github.com/maciejhirsz/uos) (Substrate/Polkadot only) 13 | 14 | Additionally some shared libraries, that are not dependent on any framework - 15 | 16 | - [ui-keyring](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/keyring](https://github.com/polkadot-js/common/) library 17 | - [ui-settings](packages/ui-settings/) A browser local storage wrapper for app settings & configuration 18 | - [ui-shared](packages/ui-shared) Shared logic that is used across UI components, e.g. for icon generation 19 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polkadot-js/ui/648c379c28625a3398c6614c9b9557f6ddcfbbbb/docs/.nojekyll -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirecting to https://polkadot.js.org/docs/ 8 | 9 | 10 | Redirecting to https://polkadot.js.org/docs/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Redirecting to https://polkadot.js.org/docs/ 8 | 9 | 10 | Redirecting to https://polkadot.js.org/docs/ 11 | 12 | 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import baseConfig from '@polkadot/dev/config/eslint'; 5 | 6 | export default [ 7 | ...baseConfig 8 | ]; 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "engines": { 5 | "node": ">=18.14" 6 | }, 7 | "homepage": "https://github.com/polkadot-js/ui#readme", 8 | "license": "Apache-2.0", 9 | "packageManager": "yarn@4.5.1", 10 | "private": true, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/polkadot-js/ui.git" 14 | }, 15 | "sideEffects": false, 16 | "type": "module", 17 | "version": "3.14.1", 18 | "versions": { 19 | "git": "3.14.1", 20 | "npm": "3.14.1" 21 | }, 22 | "workspaces": [ 23 | "packages/*" 24 | ], 25 | "scripts": { 26 | "build": "polkadot-dev-build-ts", 27 | "build:release": "polkadot-ci-ghact-build", 28 | "build:rollup": "polkadot-exec-rollup --config", 29 | "clean": "polkadot-dev-clean-build", 30 | "lint": "polkadot-dev-run-lint", 31 | "postinstall": "polkadot-dev-yarn-only", 32 | "test": "polkadot-dev-run-test --env browser", 33 | "test:one": "polkadot-dev-run-test --env browser" 34 | }, 35 | "devDependencies": { 36 | "@polkadot/dev": "^0.83.2", 37 | "@polkadot/x-bundle": "^13.5.1", 38 | "@types/node": "^22.10.5", 39 | "react": "^18.2.0", 40 | "react-dom": "^18.2.0", 41 | "react-is": "^18.2.0", 42 | "react-native": "^0.73.1" 43 | }, 44 | "resolutions": { 45 | "typescript": "^5.5.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/react-identicon/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/react-identicon 2 | 3 | A generic identity icon that can render icons based on the theme, be it Substrate or Polkadot 4 | 5 | ## Usage Examples 6 | 7 | To install the component, do `yarn add @polkadot/react-identicon` 8 | 9 | Inside a React component, you can now render any account with the associated icon - 10 | 11 | ```javascript 12 | import Identicon from '@polkadot/react-identicon'; 13 | 14 | ... 15 | render () { 16 | // address is an ss58-encoded address or publicKey (hex string or Uint8Array) 17 | const { address } = this.props; 18 | // size (optional) is a number, indicating the size (in pixels, 64 as default) 19 | const size = 32; 20 | // theme (optional), depicts the type of icon, one of 21 | // 'polkadot', 'substrate' (default), 'beachball' or 'jdenticon' 22 | const theme = 'polkadot'; 23 | 24 | // standard className & style props are also available 25 | return ( 26 | 31 | ); 32 | } 33 | ... 34 | ``` 35 | -------------------------------------------------------------------------------- /packages/react-identicon/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polkadot-js/ui/648c379c28625a3398c6614c9b9557f6ddcfbbbb/packages/react-identicon/demo.png -------------------------------------------------------------------------------- /packages/react-identicon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/react-identicon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Renders an SVG picture representing an address", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/react-identicon#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/react-identicon", 11 | "repository": { 12 | "directory": "packages/react-identicon", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "@polkadot/keyring": "^13.5.1", 25 | "@polkadot/ui-settings": "3.14.1", 26 | "@polkadot/ui-shared": "3.14.1", 27 | "@polkadot/util": "^13.5.1", 28 | "@polkadot/util-crypto": "^13.5.1", 29 | "ethereum-blockies-base64": "^1.0.2", 30 | "jdenticon": "3.2.0", 31 | "react-copy-to-clipboard": "^5.1.0", 32 | "styled-components": "^6.1.1", 33 | "tslib": "^2.8.1" 34 | }, 35 | "devDependencies": { 36 | "@types/react-copy-to-clipboard": "^5.0.7", 37 | "@types/react-dom": "^18.2.18", 38 | "xmlserializer": "^0.6.1" 39 | }, 40 | "peerDependencies": { 41 | "@polkadot/keyring": "*", 42 | "@polkadot/util": "*", 43 | "@polkadot/util-crypto": "*", 44 | "react": "*", 45 | "react-dom": "*", 46 | "react-is": "*" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/react-identicon/src/Identicon.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Prefix } from '@polkadot/util-crypto/address/types'; 5 | import type { IdentityProps as Props, Props as ComponentProps } from './types.js'; 6 | 7 | import React from 'react'; 8 | import CopyToClipboard from 'react-copy-to-clipboard'; 9 | 10 | import { ICON_DEFAULT_HOST, settings } from '@polkadot/ui-settings'; 11 | import { isHex, isU8a, u8aToHex } from '@polkadot/util'; 12 | import { decodeAddress, encodeAddress, ethereumEncode } from '@polkadot/util-crypto'; 13 | 14 | import { Beachball, Empty, Ethereum, Jdenticon, Polkadot } from './icons/index.js'; 15 | import { styled } from './styled.js'; 16 | 17 | const Fallback = Beachball; 18 | 19 | interface State { 20 | address: string; 21 | publicKey: string; 22 | } 23 | 24 | const DEFAULT_SIZE = 64; 25 | const Components: Record> = { 26 | beachball: Beachball, 27 | empty: Empty, 28 | ethereum: Ethereum, 29 | jdenticon: Jdenticon, 30 | polkadot: Polkadot, 31 | substrate: Jdenticon 32 | }; 33 | 34 | class BaseIcon extends React.PureComponent { 35 | public override state: State = { 36 | address: '', 37 | publicKey: '0x' 38 | }; 39 | 40 | private static prefix?: Prefix = undefined; 41 | 42 | public static setDefaultPrefix (prefix: Prefix): void { 43 | BaseIcon.prefix = prefix; 44 | } 45 | 46 | public static getDerivedStateFromProps ({ prefix = BaseIcon.prefix, theme, value }: Props, prevState: State): State | null { 47 | if (theme === 'ethereum') { 48 | const address = isU8a(value) 49 | ? ethereumEncode(value) 50 | : value || ''; 51 | 52 | return { address, publicKey: '' }; 53 | } 54 | 55 | try { 56 | const address = isU8a(value) || isHex(value) 57 | ? encodeAddress(value, prefix) 58 | : (value || ''); 59 | const publicKey = u8aToHex(decodeAddress(address, false, prefix)); 60 | 61 | return address === prevState.address 62 | ? null 63 | : { 64 | address, 65 | publicKey 66 | }; 67 | } catch { 68 | return { 69 | address: '', 70 | publicKey: '0x' 71 | }; 72 | } 73 | } 74 | 75 | public override render (): React.ReactNode { 76 | const { address } = this.state; 77 | const wrapped = this.getWrapped(this.state, this.props); 78 | 79 | return !address 80 | ? wrapped 81 | : ( 82 | 86 | {wrapped} 87 | 88 | ); 89 | } 90 | 91 | private getWrapped ({ address, publicKey }: State, { Custom }: Props): React.ReactNode { 92 | const { className = '', isAlternative, isHighlight, size = DEFAULT_SIZE, style = {}, theme = settings.icon } = this.props; 93 | const Component = !address 94 | ? Empty 95 | : Custom || Components[theme === 'default' ? ICON_DEFAULT_HOST : theme] || Fallback; 96 | 97 | return ( 98 | 103 | 110 | 111 | ); 112 | } 113 | 114 | private onCopy = (): void => { 115 | const { onCopy } = this.props; 116 | const { address } = this.state; 117 | 118 | if (address && onCopy) { 119 | onCopy(address); 120 | } 121 | }; 122 | } 123 | 124 | function Icon (props: Props): React.ReactElement { 125 | return ; 126 | } 127 | 128 | const StyledDiv = styled.div` 129 | cursor: copy; 130 | display: inline-block; 131 | line-height: 0; 132 | 133 | > .container { 134 | position: relative; 135 | 136 | > div, 137 | > svg { 138 | position: relative; 139 | } 140 | 141 | &.highlight:before { 142 | position: absolute; 143 | top: 0; 144 | left: 0; 145 | right: 0; 146 | bottom: 0; 147 | border-radius: 50%; 148 | box-shadow: 0 0 5px 2px #aaa; 149 | content: ''; 150 | } 151 | } 152 | `; 153 | 154 | export const Identicon = React.memo(Icon); 155 | -------------------------------------------------------------------------------- /packages/react-identicon/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './icons/index.js'; 5 | export { Identicon } from './Identicon.js'; 6 | export { packageInfo } from './packageInfo.js'; 7 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/Beachball.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Props } from '../types.js'; 5 | 6 | import React, { useCallback } from 'react'; 7 | 8 | import { beachballIcon } from '@polkadot/ui-shared'; 9 | 10 | function Identicon ({ address, className = '', size, style = {} }: Props): React.ReactElement { 11 | const updateElem = useCallback( 12 | (node: HTMLDivElement): void => { 13 | node?.appendChild( 14 | beachballIcon(address, { isAlternative: false, size }) 15 | ); 16 | }, 17 | [address, size] 18 | ); 19 | 20 | return ( 21 |
26 | ); 27 | } 28 | 29 | export const Beachball = React.memo(Identicon); 30 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/Empty.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Props } from '../types.js'; 5 | 6 | import React from 'react'; 7 | 8 | function Identicon ({ className = '', size, style = {} }: Props): React.ReactElement { 9 | return ( 10 | 17 | ); 18 | } 19 | 20 | export const Empty = React.memo(Identicon); 21 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/Ethereum.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Props } from '../types.js'; 5 | 6 | import makeBlockie from 'ethereum-blockies-base64'; 7 | import React, { useMemo } from 'react'; 8 | 9 | import { styled } from '../styled.js'; 10 | 11 | interface ImgProps { 12 | size: number; 13 | } 14 | 15 | function Identicon ({ address, className = '', size, style = {} }: Props): React.ReactElement { 16 | const imgSrc = useMemo( 17 | () => makeBlockie(address), 18 | [address] 19 | ); 20 | 21 | return ( 22 | 28 | ); 29 | } 30 | 31 | const StyledImg = styled.img(({ size }) => ` 32 | display: block; 33 | height: ${size}px; 34 | width: ${size}px; 35 | `); 36 | 37 | export const Ethereum = React.memo(Identicon); 38 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/Jdenticon.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Props } from '../types.js'; 5 | 6 | import * as jdenticon from 'jdenticon'; 7 | import React, { useMemo } from 'react'; 8 | 9 | function Identicon ({ className = '', publicKey, size, style = {} }: Props): React.ReactElement { 10 | const html = useMemo( 11 | () => ({ __html: jdenticon.toSvg(publicKey.substring(2), size) }), 12 | [publicKey, size] 13 | ); 14 | 15 | return ( 16 |
21 | ); 22 | } 23 | 24 | export const Jdenticon = React.memo(Identicon); 25 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/Polkadot.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2018 Paritytech via paritytech/oo7/polkadot-identicon 5 | 6 | // This has been converted from the original version that can be found at 7 | // 8 | // https://github.com/paritytech/oo7/blob/251ba2b7c45503b68eab4320c270b5afa9bccb60/packages/polkadot-identicon/src/index.jsx 9 | // 10 | // Here we have done the following to convert the component - 11 | // - Converted the code to TypeScript 12 | // - Removed the oo7 dependencies (since not initialised properly, it makes calls to wrong endpoints) 13 | // - Remove encoding functionality, these are catered for in the base 14 | // - Remove copy functionality (this is catered from in the base components) 15 | // - Split calculations into relevant functions 16 | // - Move constants to file-level 17 | // - Overall it is now just a static component, expecting an address as an input value 18 | 19 | import type { Circle } from '@polkadot/ui-shared/icons/types'; 20 | import type { Props } from '../types.js'; 21 | 22 | import React, { useMemo } from 'react'; 23 | 24 | import { polkadotIcon } from '@polkadot/ui-shared'; 25 | 26 | function renderCircle ({ cx, cy, fill, r }: Circle, key: number): React.ReactNode { 27 | return ( 28 | 35 | ); 36 | } 37 | 38 | function Identicon ({ address, className = '', isAlternative = false, size, style = {} }: Props): React.ReactElement { 39 | const circles = useMemo( 40 | () => polkadotIcon(address, { isAlternative }), 41 | [address, isAlternative] 42 | ); 43 | 44 | return ( 45 | 54 | {circles.map(renderCircle)} 55 | 56 | ); 57 | } 58 | 59 | export const Polkadot = React.memo(Identicon); 60 | -------------------------------------------------------------------------------- /packages/react-identicon/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { Beachball } from './Beachball.js'; 5 | export { Empty } from './Empty.js'; 6 | export { Ethereum } from './Ethereum.js'; 7 | export { Jdenticon } from './Jdenticon.js'; 8 | export { Polkadot } from './Polkadot.js'; 9 | -------------------------------------------------------------------------------- /packages/react-identicon/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | import { Identicon } from './bundle.js'; 7 | 8 | export * from './bundle.js'; 9 | 10 | export default Identicon; 11 | -------------------------------------------------------------------------------- /packages/react-identicon/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { packageInfo as settingsInfo } from '@polkadot/ui-settings/packageInfo'; 8 | import { packageInfo as sharedInfo } from '@polkadot/ui-shared/packageInfo'; 9 | import { detectPackage } from '@polkadot/util'; 10 | 11 | import { packageInfo } from './packageInfo.js'; 12 | 13 | detectPackage(packageInfo, null, [settingsInfo, sharedInfo]); 14 | -------------------------------------------------------------------------------- /packages/react-identicon/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/react-identicon', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/react-identicon/src/styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { styled } from 'styled-components'; 5 | -------------------------------------------------------------------------------- /packages/react-identicon/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/react-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type React from 'react'; 5 | import type { Prefix } from '@polkadot/util-crypto/address/types'; 6 | 7 | export interface BaseProps { 8 | className?: string; 9 | style?: React.CSSProperties; 10 | } 11 | 12 | export interface Props extends BaseProps { 13 | address: string; 14 | isAlternative?: boolean | undefined; 15 | publicKey: string; 16 | size: number; 17 | } 18 | 19 | export interface IdentityProps extends BaseProps { 20 | Custom?: React.ComponentType; 21 | isAlternative?: boolean; 22 | isHighlight?: boolean; 23 | onCopy?: (value: string) => void; 24 | prefix?: Prefix; 25 | size?: number; 26 | theme?: IconTheme; 27 | value?: string | Uint8Array | null; 28 | } 29 | 30 | export type IconTheme = 'beachball' | 'empty' | 'ethereum' | 'jdenticon' | 'polkadot' | 'substrate'; 31 | -------------------------------------------------------------------------------- /packages/react-identicon/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "references": [ 9 | { "path": "../ui-shared/tsconfig.build.json" }, 10 | { "path": "../ui-settings/tsconfig.build.json" } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/react-qr/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/react-qr 2 | 3 | A Qr-code component that allows for the transfer of addresses and transaction payloads to and from external readers. It implements the [Universal Offline Signatures](https://github.com/maciejhirsz/uos) specification to read and generated QR codes. 4 | -------------------------------------------------------------------------------- /packages/react-qr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Generates and reads QR codes", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/react-qr#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/react-qr", 11 | "repository": { 12 | "directory": "packages/react-qr", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "@polkadot/ui-settings": "3.14.1", 25 | "@polkadot/util": "^13.5.1", 26 | "@polkadot/util-crypto": "^13.5.1", 27 | "@zxing/browser": "^0.1.5", 28 | "@zxing/library": "^0.21.2", 29 | "qrcode-generator": "^1.4.4", 30 | "styled-components": "^6.1.1", 31 | "tslib": "^2.8.1" 32 | }, 33 | "peerDependencies": { 34 | "@polkadot/util": "*", 35 | "@polkadot/util-crypto": "*", 36 | "react": "*", 37 | "react-dom": "*", 38 | "react-is": "*" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/react-qr/src/Display.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React, { useEffect, useMemo, useRef, useState } from 'react'; 5 | 6 | import { objectSpread } from '@polkadot/util'; 7 | import { xxhashAsHex } from '@polkadot/util-crypto'; 8 | 9 | import { qrcode } from './qrcode.js'; 10 | import { styled } from './styled.js'; 11 | import { createFrames, createImgSize } from './util.js'; 12 | 13 | interface Props { 14 | className?: string | undefined; 15 | size?: string | number | undefined; 16 | skipEncoding?: boolean; 17 | style?: React.CSSProperties | undefined; 18 | timerDelay?: number | undefined; 19 | value: Uint8Array; 20 | } 21 | 22 | interface FrameState { 23 | frames: Uint8Array[]; 24 | frameIdx: number; 25 | image: string | null; 26 | valueHash: string | null; 27 | } 28 | 29 | interface TimerState { 30 | timerDelay: number; 31 | timerId: ReturnType | null; 32 | } 33 | 34 | const DEFAULT_FRAME_DELAY = 2750; 35 | const TIMER_INC = 500; 36 | 37 | function getDataUrl (value: Uint8Array): string { 38 | const qr = qrcode(0, 'M'); 39 | 40 | // HACK See our qrcode stringToBytes override as used internally. This 41 | // will only work for the case where we actually pass `Bytes` in here 42 | qr.addData(value as unknown as string, 'Byte'); 43 | qr.make(); 44 | 45 | return qr.createDataURL(16, 0); 46 | } 47 | 48 | function Display ({ className = '', size, skipEncoding, style = {}, timerDelay = DEFAULT_FRAME_DELAY, value }: Props): React.ReactElement | null { 49 | const [{ image }, setFrameState] = useState({ frameIdx: 0, frames: [], image: null, valueHash: null }); 50 | const timerRef = useRef({ timerDelay, timerId: null }); 51 | 52 | const containerStyle = useMemo( 53 | () => createImgSize(size), 54 | [size] 55 | ); 56 | 57 | // run on initial load to setup the global timer and provide and unsubscribe 58 | useEffect((): () => void => { 59 | const nextFrame = () => setFrameState((state): FrameState => { 60 | // when we have a single frame, we only ever fire once 61 | if (state.frames.length <= 1) { 62 | return state; 63 | } 64 | 65 | let frameIdx = state.frameIdx + 1; 66 | 67 | // when we overflow, skip to the first and slightly increase the delay between frames 68 | if (frameIdx === state.frames.length) { 69 | frameIdx = 0; 70 | timerRef.current.timerDelay = timerRef.current.timerDelay + TIMER_INC; 71 | } 72 | 73 | // only encode the frames on demand, not above as part of the 74 | // state derivation - in the case of large payloads, this should 75 | // be slightly more responsive on initial load 76 | const newState = objectSpread({}, state, { 77 | frameIdx, 78 | image: getDataUrl(state.frames[frameIdx]) 79 | }); 80 | 81 | // set the new timer last 82 | timerRef.current.timerId = setTimeout(nextFrame, timerRef.current.timerDelay); 83 | 84 | return newState; 85 | }); 86 | 87 | timerRef.current.timerId = setTimeout(nextFrame, timerRef.current.timerDelay); 88 | 89 | return (): void => { 90 | // eslint-disable-next-line react-hooks/exhaustive-deps 91 | timerRef.current.timerId && clearTimeout(timerRef.current.timerId); 92 | }; 93 | // eslint-disable-next-line react-hooks/exhaustive-deps 94 | }, []); 95 | 96 | useEffect((): void => { 97 | setFrameState((state): FrameState => { 98 | const valueHash = xxhashAsHex(value); 99 | 100 | if (valueHash === state.valueHash) { 101 | return state; 102 | } 103 | 104 | const frames: Uint8Array[] = skipEncoding 105 | ? [value] 106 | : createFrames(value); 107 | 108 | // encode on demand 109 | return { 110 | frameIdx: 0, 111 | frames, 112 | image: getDataUrl(frames[0]), 113 | valueHash 114 | }; 115 | }); 116 | }, [skipEncoding, value]); 117 | 118 | if (!image) { 119 | return null; 120 | } 121 | 122 | return ( 123 | 127 |
131 | 132 |
133 |
134 | ); 135 | } 136 | 137 | const StyledDiv = styled.div` 138 | .ui--qr-Display { 139 | height: 100%; 140 | width: 100%; 141 | 142 | img, 143 | svg { 144 | background: white; 145 | height: auto !important; 146 | max-height: 100%; 147 | max-width: 100%; 148 | width: auto !important; 149 | } 150 | } 151 | `; 152 | 153 | export const QrDisplay = React.memo(Display); 154 | -------------------------------------------------------------------------------- /packages/react-qr/src/DisplayAddress.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React, { useMemo } from 'react'; 5 | 6 | import { QrDisplay } from './Display.js'; 7 | import { createAddressPayload } from './util.js'; 8 | 9 | interface Props { 10 | address: string; 11 | genesisHash: string; 12 | className?: string; 13 | size?: string | number; 14 | style?: React.CSSProperties; 15 | timerDelay?: number; 16 | } 17 | 18 | function DisplayAddress ({ address, className, genesisHash, size, style, timerDelay }: Props): React.ReactElement | null { 19 | const data = useMemo( 20 | () => createAddressPayload(address, genesisHash), 21 | [address, genesisHash] 22 | ); 23 | 24 | if (!data) { 25 | return null; 26 | } 27 | 28 | return ( 29 | 37 | ); 38 | } 39 | 40 | export const QrDisplayAddress = React.memo(DisplayAddress); 41 | -------------------------------------------------------------------------------- /packages/react-qr/src/DisplayPayload.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import React, { useMemo } from 'react'; 5 | 6 | import { QrDisplay } from './Display.js'; 7 | import { createSignPayload } from './util.js'; 8 | 9 | interface Props { 10 | address: string; 11 | className?: string; 12 | cmd: number; 13 | delay?: number; 14 | genesisHash: Uint8Array | string; 15 | payload: Uint8Array; 16 | size?: string | number; 17 | style?: React.CSSProperties; 18 | timerDelay?: number; 19 | } 20 | 21 | function DisplayPayload ({ address, className, cmd, genesisHash, payload, size, style, timerDelay }: Props): React.ReactElement | null { 22 | const data = useMemo( 23 | () => createSignPayload(address, cmd, payload, genesisHash), 24 | [address, cmd, payload, genesisHash] 25 | ); 26 | 27 | if (!data) { 28 | return null; 29 | } 30 | 31 | return ( 32 | 39 | ); 40 | } 41 | 42 | export const QrDisplayPayload = React.memo(DisplayPayload); 43 | -------------------------------------------------------------------------------- /packages/react-qr/src/NetworkSpecs.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { NetworkSpecsStruct } from '@polkadot/ui-settings/types'; 5 | 6 | import React, { useMemo } from 'react'; 7 | 8 | import { QrDisplay } from './Display.js'; 9 | import { encodeString } from './util.js'; 10 | 11 | interface Props { 12 | className?: string; 13 | networkSpecs: NetworkSpecsStruct; 14 | size?: string | number; 15 | style?: React.CSSProperties; 16 | } 17 | 18 | function DisplayNetworkSpecs ({ className, networkSpecs, size, style }: Props): React.ReactElement | null { 19 | const data = useMemo( 20 | () => encodeString(JSON.stringify(networkSpecs)), 21 | [networkSpecs] 22 | ); 23 | 24 | if (!data) { 25 | return null; 26 | } 27 | 28 | return ( 29 | 36 | ); 37 | } 38 | 39 | export const QrNetworkSpecs = React.memo(DisplayNetworkSpecs); 40 | -------------------------------------------------------------------------------- /packages/react-qr/src/Scan.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { BrowserQRCodeReader, type IScannerControls } from '@zxing/browser'; 5 | import React, { useCallback, useEffect, useMemo, useRef } from 'react'; 6 | 7 | import { styled } from './styled.js'; 8 | import { createImgSize } from './util.js'; 9 | 10 | interface Props { 11 | className?: string | undefined; 12 | delay?: number; 13 | onError?: undefined | ((error: Error) => void); 14 | onScan: (data: string) => void; 15 | size?: string | number | undefined; 16 | style?: React.CSSProperties | undefined; 17 | } 18 | 19 | const DEFAULT_DELAY = 150; 20 | 21 | const DEFAULT_ERROR = (error: Error): void => { 22 | console.error('@polkadot/react-qr:Scan', error.message); 23 | }; 24 | 25 | function Scan ({ className = '', delay = DEFAULT_DELAY, onError = DEFAULT_ERROR, onScan, size, style = {} }: Props): React.ReactElement { 26 | const videoRef = useRef(null); 27 | const controlsRef = useRef(null); 28 | 29 | const containerStyle = useMemo( 30 | () => createImgSize(size), 31 | [size] 32 | ); 33 | 34 | const _onError = useCallback( 35 | (error: Error) => onError(error), 36 | [onError] 37 | ); 38 | 39 | useEffect(() => { 40 | const codeReader = new BrowserQRCodeReader(); 41 | 42 | const startScanning = async () => { 43 | try { 44 | const videoInputDevices = await BrowserQRCodeReader.listVideoInputDevices(); 45 | const selectedDeviceId = videoInputDevices[0].deviceId; 46 | 47 | controlsRef.current = await codeReader.decodeFromVideoDevice( 48 | selectedDeviceId, 49 | videoRef.current ?? undefined, 50 | (result, error) => { 51 | if (result) { 52 | onScan(result.getText()); 53 | } 54 | 55 | if (error && !(error instanceof Error)) { 56 | _onError(new Error(error)); 57 | } 58 | } 59 | ); 60 | } catch (error) { 61 | _onError(error instanceof Error ? error : new Error('Unknown error occurred')); 62 | } 63 | }; 64 | 65 | // eslint-disable-next-line @typescript-eslint/no-misused-promises 66 | const timeoutId = setTimeout(startScanning, delay); 67 | 68 | return () => { 69 | clearTimeout(timeoutId); 70 | 71 | if (controlsRef.current) { 72 | controlsRef.current.stop(); 73 | } 74 | }; 75 | }, [onScan, _onError, delay]); 76 | 77 | return ( 78 | 82 | 88 | ); 89 | } 90 | 91 | const StyledDiv = styled.div` 92 | .ui--qr-Scan { 93 | display: inline-block; 94 | height: 100%; 95 | transform: matrix(-1, 0, 0, 1, 0, 0); 96 | width: 100%; 97 | } 98 | `; 99 | 100 | export const QrScan = React.memo(Scan); 101 | -------------------------------------------------------------------------------- /packages/react-qr/src/ScanAddress.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { HexString } from '@polkadot/util/types'; 5 | 6 | import React, { useCallback } from 'react'; 7 | 8 | import { decodeAddress } from '@polkadot/util-crypto'; 9 | 10 | import { ADDRESS_PREFIX, SEED_PREFIX } from './constants.js'; 11 | import { QrScan } from './Scan.js'; 12 | 13 | interface ScanType { 14 | isAddress: boolean; 15 | content: string; 16 | genesisHash: HexString | null; 17 | name?: string | undefined; 18 | } 19 | 20 | interface Props { 21 | className?: string; 22 | onError?: (error: Error) => void; 23 | onScan: (scanned: ScanType) => void; 24 | size?: string | number; 25 | style?: React.CSSProperties; 26 | isEthereum?: boolean 27 | } 28 | 29 | function ScanAddress ({ className, isEthereum, onError, onScan, size, style }: Props): React.ReactElement { 30 | const _onScan = useCallback( 31 | (data: string | null): void => { 32 | if (data) { 33 | try { 34 | let prefix: string; 35 | let content: string; 36 | let genesisHash: string | null; 37 | let name: string[]; 38 | 39 | if (!isEthereum) { 40 | [prefix, content, genesisHash, ...name] = data.split(':'); 41 | } else { 42 | [prefix, content, ...name] = data.split(':'); 43 | genesisHash = null; 44 | content = content.substring(0, 42); 45 | } 46 | 47 | const expectedPrefix = (isEthereum ? 'ethereum' : ADDRESS_PREFIX); 48 | const isValidPrefix = (prefix === expectedPrefix) || (prefix === SEED_PREFIX); 49 | 50 | if (!isValidPrefix) { 51 | throw new Error(`Invalid prefix received, expected '${expectedPrefix} or ${SEED_PREFIX}' , found '${prefix}'`); 52 | } 53 | 54 | const isAddress = prefix === expectedPrefix; 55 | 56 | if (isAddress && !isEthereum) { 57 | decodeAddress(content); 58 | } 59 | 60 | onScan({ content, genesisHash: genesisHash as HexString, isAddress, name: name?.length ? name.join(':') : undefined }); 61 | } catch (error) { 62 | onError && onError(error as Error); 63 | 64 | console.error('@polkadot/react-qr:QrScanAddress', (error as Error).message, data); 65 | } 66 | } 67 | }, 68 | [onScan, onError, isEthereum] 69 | ); 70 | 71 | return ( 72 | 79 | ); 80 | } 81 | 82 | export const QrScanAddress = React.memo(ScanAddress); 83 | -------------------------------------------------------------------------------- /packages/react-qr/src/ScanSignature.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { HexString } from '@polkadot/util/types'; 5 | 6 | import React, { useCallback } from 'react'; 7 | 8 | import { QrScan } from './Scan.js'; 9 | 10 | interface ScanType { 11 | signature: HexString; 12 | } 13 | 14 | interface Props { 15 | className?: string; 16 | onError?: (error: Error) => void; 17 | onScan: (scanned: ScanType) => void; 18 | size?: string | number; 19 | style?: React.CSSProperties; 20 | } 21 | 22 | function ScanSignature ({ className, onError, onScan, size, style }: Props): React.ReactElement { 23 | const _onScan = useCallback( 24 | (signature: string | null) => signature && onScan({ signature: `0x${signature}` }), 25 | [onScan] 26 | ); 27 | 28 | return ( 29 | 36 | ); 37 | } 38 | 39 | export const QrScanSignature = React.memo(ScanSignature); 40 | -------------------------------------------------------------------------------- /packages/react-qr/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { QrDisplayAddress } from './DisplayAddress.js'; 5 | export { QrDisplayPayload } from './DisplayPayload.js'; 6 | export { QrNetworkSpecs } from './NetworkSpecs.js'; 7 | export { packageInfo } from './packageInfo.js'; 8 | export { QrScanAddress } from './ScanAddress.js'; 9 | export { QrScanSignature } from './ScanSignature.js'; 10 | -------------------------------------------------------------------------------- /packages/react-qr/src/constants.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export const ADDRESS_PREFIX = 'substrate'; 5 | export const SEED_PREFIX = 'secret'; 6 | export const FRAME_SIZE = 1024; 7 | export const SUBSTRATE_ID = new Uint8Array([0x53]); 8 | export const CRYPTO_SR25519 = new Uint8Array([0x01]); 9 | export const CMD_SIGN_TX = new Uint8Array([0]); 10 | export const CMD_SIGN_TX_HASH = new Uint8Array([1]); 11 | export const CMD_SIGN_IMMORTAL_TX = new Uint8Array([2]); 12 | export const CMD_SIGN_MSG = new Uint8Array([3]); 13 | -------------------------------------------------------------------------------- /packages/react-qr/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | export * from './bundle.js'; 7 | -------------------------------------------------------------------------------- /packages/react-qr/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { detectPackage } from '@polkadot/util'; 8 | 9 | import { packageInfo } from './packageInfo.js'; 10 | 11 | detectPackage(packageInfo, null, []); 12 | -------------------------------------------------------------------------------- /packages/react-qr/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/react-qr', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/react-qr/src/qrcode.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import _qrcode from 'qrcode-generator'; 5 | 6 | // A small hurdle to jump through, just to get the default/default correct (as generated) 7 | const qrcode: typeof _qrcode = _qrcode; 8 | 9 | // HACK The default function take string -> number[], the Uint8array is compatible 10 | // with that signature and the use thereof 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access 12 | (qrcode as any).stringToBytes = (data: Uint8Array): Uint8Array => 13 | data; 14 | 15 | export { qrcode }; 16 | -------------------------------------------------------------------------------- /packages/react-qr/src/styled.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { styled } from 'styled-components'; 5 | -------------------------------------------------------------------------------- /packages/react-qr/src/util.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import { u8aConcat, u8aToHex, u8aToString } from '@polkadot/util'; 7 | import { randomAsU8a } from '@polkadot/util-crypto'; 8 | 9 | import { createAddressPayload, createFrames, createSignPayload, decodeString, encodeNumber, encodeString } from './util.js'; 10 | 11 | describe('util', (): void => { 12 | describe('Uint8Array <-> string', (): void => { 13 | let u8a: Uint8Array; 14 | let str: string; 15 | 16 | beforeEach((): void => { 17 | u8a = new Uint8Array(256); 18 | 19 | for (let i = 0; i < 256; i++) { 20 | u8a[i] = i; 21 | } 22 | 23 | u8a = u8aConcat(u8a, randomAsU8a(4096)); 24 | str = decodeString(u8a); 25 | }); 26 | 27 | it('decodes into string', (): void => { 28 | expect(str).toHaveLength(u8a.length); 29 | }); 30 | 31 | it('have encode <-> decode', (): void => { 32 | expect(encodeString(str)).toEqual(u8a); 33 | }); 34 | }); 35 | 36 | describe('encodeNumber', (): void => { 37 | it('encodes 1 correctly', (): void => { 38 | expect( 39 | encodeNumber(1) 40 | ).toEqual(new Uint8Array([0, 1])); 41 | }); 42 | 43 | it('encodes 257 correctly', (): void => { 44 | expect( 45 | encodeNumber(257) 46 | ).toEqual(new Uint8Array([1, 1])); 47 | }); 48 | }); 49 | 50 | describe('createAddressPayload', (): void => { 51 | it('encodes an address properly', (): void => { 52 | expect( 53 | u8aToString( 54 | createAddressPayload('5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq', '0x') 55 | ) 56 | ).toEqual( 57 | 'substrate:' + 58 | '5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq:' + 59 | '0x' 60 | ); 61 | }); 62 | }); 63 | 64 | describe('createSignPayload', (): void => { 65 | it('encodes a payload properly', (): void => { 66 | expect( 67 | u8aToHex( 68 | createSignPayload('5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq', 3, 'THIS IS SPARTA!', '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe') 69 | ) 70 | ).toEqual( 71 | '0x' + // prefix 72 | '53' + // substrate 73 | '01' + // sr25519 74 | '03' + // sign tx 75 | 'f4cd755672a8f9542ca9da4fbf2182e79135d94304002e6a09ffc96fef6e6c4c' + // publickey 76 | '544849532049532053504152544121' + // THIS IS SPARTA! 77 | 'b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe' // genesisHash 78 | ); 79 | }); 80 | }); 81 | 82 | describe('createFrames', (): void => { 83 | it('encodes frames properly', (): void => { 84 | expect( 85 | createFrames( 86 | createSignPayload('5HbgaJEuVN5qGbkhgtuDQANivSWwHXWsC2erP1SQUXgciTVq', 0, '0x12345678', '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe') 87 | ).map((u8a): string => u8aToHex(u8a)) 88 | ).toEqual([ 89 | '0x' + 90 | '00' + // multipart 91 | '0001' + // length 92 | '0000' + // index 93 | '530100' + // payload info, substrate + sr25519 + signtx 94 | 'f4cd755672a8f9542ca9da4fbf2182e79135d94304002e6a09ffc96fef6e6c4c' + // publicKey 95 | '12345678' + // data 96 | 'b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe' // genesisHash 97 | ]); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /packages/react-qr/src/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/react-qr authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { isString, u8aConcat, u8aToU8a } from '@polkadot/util'; 5 | import { decodeAddress } from '@polkadot/util-crypto'; 6 | 7 | import { ADDRESS_PREFIX, CRYPTO_SR25519, FRAME_SIZE, SUBSTRATE_ID } from './constants.js'; 8 | 9 | const MULTIPART = new Uint8Array([0]); 10 | 11 | export function encodeNumber (value: number): Uint8Array { 12 | return new Uint8Array([value >> 8, value & 0xff]); 13 | } 14 | 15 | export function encodeString (value: string): Uint8Array { 16 | const count = value.length; 17 | const u8a = new Uint8Array(count); 18 | 19 | for (let i = 0; i < count; i++) { 20 | u8a[i] = value.charCodeAt(i); 21 | } 22 | 23 | return u8a; 24 | } 25 | 26 | export function decodeString (value: Uint8Array): string { 27 | return value.reduce((str, code): string => { 28 | return str + String.fromCharCode(code); 29 | }, ''); 30 | } 31 | 32 | export function createAddressPayload (address: string, genesisHash: string): Uint8Array { 33 | return encodeString(`${ADDRESS_PREFIX}:${address}:${genesisHash}`); 34 | } 35 | 36 | export function createSignPayload (address: string, cmd: number, payload: string | Uint8Array, genesisHash: string | Uint8Array): Uint8Array { 37 | return u8aConcat( 38 | SUBSTRATE_ID, 39 | CRYPTO_SR25519, 40 | new Uint8Array([cmd]), 41 | decodeAddress(address), 42 | u8aToU8a(payload), 43 | u8aToU8a(genesisHash) 44 | ); 45 | } 46 | 47 | export function createFrames (input: Uint8Array): Uint8Array[] { 48 | const frames = []; 49 | let idx = 0; 50 | 51 | while (idx < input.length) { 52 | frames.push(input.subarray(idx, idx + FRAME_SIZE)); 53 | 54 | idx += FRAME_SIZE; 55 | } 56 | 57 | return frames.map((frame, index: number): Uint8Array => 58 | u8aConcat( 59 | MULTIPART, 60 | encodeNumber(frames.length), 61 | encodeNumber(index), 62 | frame 63 | ) 64 | ); 65 | } 66 | 67 | export function createImgSize (size?: string | number): Record { 68 | if (!size) { 69 | return { 70 | height: 'auto', 71 | width: '100%' 72 | }; 73 | } 74 | 75 | const height = isString(size) 76 | ? size 77 | : `${size}px`; 78 | 79 | return { 80 | height, 81 | width: height 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /packages/react-qr/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/*.spec.ts" 10 | ], 11 | "references": [ 12 | { "path": "../ui-settings/tsconfig.build.json" } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/react-qr/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "**/*.spec.ts" 12 | ], 13 | "references": [ 14 | { "path": "../react-qr/tsconfig.build.json" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/reactnative-identicon 2 | 3 | A generic identity icon that can render icons based on an address. 4 | 5 | ## Usage Examples 6 | 7 | To install the component, do `yarn add @polkadot/reactnative-identicon` 8 | 9 | Inside a React component, you can now render any account with the associated icon - 10 | 11 | ```javascript 12 | import Identicon from '@polkadot/reactnative-identicon'; 13 | 14 | ... 15 | render () { 16 | // address is an ss58-encoded address or publicKey (hex string or Uint8Array) 17 | const { address } = this.props; 18 | // size (optional) is a number, indicating the size (in pixels, 64 as default) 19 | const size = 32; 20 | 21 | // standard className & style props are also available 22 | return ( 23 | 27 | ); 28 | } 29 | ... 30 | ``` 31 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Renders an SVG picture representing an address", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/reactnative-identicon#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/reactnative-identicon", 11 | "repository": { 12 | "directory": "packages/reactnative-identicon", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "@polkadot/ui-shared": "3.14.1", 25 | "@polkadot/util": "^13.5.1", 26 | "@polkadot/util-crypto": "^13.5.1", 27 | "react-native-svg": "^14.1.0", 28 | "tslib": "^2.8.1" 29 | }, 30 | "devDependencies": { 31 | "@types/react-native": "^0.72.8" 32 | }, 33 | "peerDependencies": { 34 | "@polkadot/util": "*", 35 | "@polkadot/util-crypto": "*", 36 | "react": "*", 37 | "react-native": "*" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/Identicon.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Prefix } from '@polkadot/util-crypto/address/types'; 5 | import type { Props as ComponentProps } from './types.js'; 6 | 7 | import React from 'react'; 8 | 9 | import { isHex, isU8a, u8aToHex } from '@polkadot/util'; 10 | import { decodeAddress, encodeAddress } from '@polkadot/util-crypto'; 11 | 12 | import { Empty, Polkadot } from './icons/index.js'; 13 | 14 | const Fallback = Polkadot; 15 | 16 | interface Props { 17 | prefix?: Prefix; 18 | size?: number; 19 | theme?: 'polkadot'; 20 | value?: string | Uint8Array | null; 21 | } 22 | 23 | interface State { 24 | address: string; 25 | publicKey: string; 26 | } 27 | 28 | const DEFAULT_SIZE = 64; 29 | const DEFAULT_THEME = 'polkadot'; 30 | 31 | const Components: Record> = { 32 | polkadot: Polkadot 33 | }; 34 | 35 | export default class IdentityIcon extends React.PureComponent { 36 | public override state: State = { 37 | address: '', 38 | publicKey: '0x' 39 | }; 40 | 41 | private static prefix?: Prefix = undefined; 42 | 43 | public static setDefaultPrefix (prefix: Prefix): void { 44 | IdentityIcon.prefix = prefix; 45 | } 46 | 47 | public static getDerivedStateFromProps ({ prefix = IdentityIcon.prefix, value }: Props, prevState: State): State | null { 48 | try { 49 | const address = isU8a(value) || isHex(value) 50 | ? encodeAddress(value, prefix) 51 | : (value || ''); 52 | const publicKey = u8aToHex(decodeAddress(address, true, prefix)); 53 | 54 | return address === prevState.address 55 | ? null 56 | : { address, publicKey }; 57 | } catch { 58 | return { 59 | address: '', 60 | publicKey: '0x' 61 | }; 62 | } 63 | } 64 | 65 | public override render (): React.ReactNode { 66 | const { size = DEFAULT_SIZE, theme = DEFAULT_THEME } = this.props; 67 | const { address, publicKey } = this.state; 68 | 69 | const Component = !address 70 | ? Empty 71 | : Components[theme] || Fallback; 72 | 73 | return ( 74 | 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './icons/index.js'; 5 | export { packageInfo } from './packageInfo.js'; 6 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/icons/Empty.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Props } from '../types.js'; 5 | 6 | import React from 'react'; 7 | import { View } from 'react-native'; 8 | import { Circle, Svg } from 'react-native-svg'; 9 | 10 | export default function Empty ({ size }: Props): React.ReactElement { 11 | return ( 12 | 13 | 18 | 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/icons/Polkadot.tsx: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Circle as CircleType } from '@polkadot/ui-shared/icons/types'; 5 | import type { Props } from '../types.js'; 6 | 7 | import React, { useMemo } from 'react'; 8 | import { View } from 'react-native'; 9 | import { Circle as SvgCircle, Svg } from 'react-native-svg'; 10 | 11 | import { polkadotIcon } from '@polkadot/ui-shared'; 12 | 13 | function renderCircle ({ cx, cy, fill, r }: CircleType, key: number): React.ReactNode { 14 | return ( 15 | 22 | ); 23 | } 24 | 25 | export default function Identicon ({ address, isAlternative = false, size }: Props): React.ReactElement { 26 | const circles = useMemo( 27 | () => polkadotIcon(address, { isAlternative }), 28 | [address, isAlternative] 29 | ); 30 | 31 | return ( 32 | 33 | 39 | {circles.map(renderCircle)} 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { default as Empty } from './Empty.js'; 5 | export { default as Polkadot } from './Polkadot.js'; 6 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | import Identicon from './Identicon.js'; 7 | 8 | export * from './bundle.js'; 9 | 10 | export default Identicon; 11 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { packageInfo as sharedInfo } from '@polkadot/ui-shared/packageInfo'; 8 | import { detectPackage } from '@polkadot/util'; 9 | 10 | import { packageInfo } from './packageInfo.js'; 11 | 12 | detectPackage(packageInfo, null, [sharedInfo]); 13 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/reactnative-identicon', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export interface Props { 5 | address: string; 6 | isAlternative?: boolean; 7 | publicKey: string; 8 | size: number; 9 | } 10 | -------------------------------------------------------------------------------- /packages/reactnative-identicon/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "references": [ 9 | { "path": "../ui-shared/tsconfig.build.json" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui-keyring/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/ui-keyring 2 | 3 | A wrapper extending the base @polkadot/keyring interface for usage in the browser: 4 | Key management of user accounts including generation and retrieval of keyring pairs from a variety of input combinations. 5 | 6 | ## Usage Examples 7 | 8 | All module methods are exposed through a single default export. 9 | 10 | ### Regular 11 | 12 | ```js 13 | import keyring from @polkadot/ui-keyring 14 | 15 | render () { 16 | // encode publicKey to ss58 address 17 | const address = keyring.encodeAddress(publicKey); 18 | 19 | // get keyring pair from ss58 address 20 | const pair = keyring.getPair(address); 21 | 22 | // ask questions about that particular keyring pair 23 | const isLocked = pair.isLocked; 24 | const meta = pair.meta; 25 | 26 | // save account from pair 27 | keyring.saveAccount(pair, password); 28 | 29 | // save address without unlocking it 30 | keyring.saveAddress(address, { ...meta }); 31 | } 32 | ``` 33 | 34 | ## Observables 35 | 36 | Option 1: Declarative subscribe/unsubscribe w/ react-with-observable (recommended 'React' way) 37 | 38 | ```js 39 | import accountObservable from '@polkadot/ui-keyring/observable/accounts'; 40 | import { SingleAddress, SubjectInfo } from '@polkadot/ui-keyring/observable/types'; 41 | import React from 'react'; 42 | import { Subscribe } from 'react-with-observable'; 43 | import { map } from 'rxjs'; 44 | 45 | class MyReactComponent extends React.PureComponent { 46 | render () { 47 | 48 | {accountObservable.subject.pipe( 49 | map((allAccounts: SubjectInfo) => 50 | !allAccounts 51 | ? this.renderEmpty() 52 | : Object.values(allAccounts).map((account: SingleAddress) => 53 | // Your component goes here 54 | console.log(account.json.address) 55 | ) 56 | ))} 57 | 58 | } 59 | 60 | renderEmpty () { 61 | return ( 62 |
no accounts to display ...
63 | ); 64 | } 65 | } 66 | 67 | ``` 68 | 69 | Option 2: Imperative subscribe/unsubscribe 70 | 71 | ```js 72 | import accountObservable from '@polkadot/ui-keyring/observable/accounts'; 73 | import { SingleAddress, SubjectInfo } from '@polkadot/ui-keyring/observable/types'; 74 | import React from 'react'; 75 | import { Subscription } from 'rxjs'; 76 | 77 | type State = { 78 | allAccounts?: SubjectInfo, 79 | subscriptions?: [Subscription] 80 | } 81 | 82 | class MyReactComponent extends React.PureComponent { 83 | componentDidMount () { 84 | const accountSubscription = accountObservable.subject.subscribe((observedAccounts) => { 85 | this.setState({ 86 | accounts: observedAccounts 87 | }); 88 | }) 89 | 90 | this.setState({ 91 | subscriptions: [accountSubscription] 92 | }); 93 | } 94 | 95 | componentWillUnmount () { 96 | const { subscriptions } = this.state; 97 | 98 | for (s in subscriptions) { 99 | s.subject.unsubscribe(); 100 | } 101 | } 102 | 103 | render () { 104 | const { accounts } = this.state; 105 | 106 | return ( 107 |

All Accounts

108 | { 109 | Object.keys(accounts).map((address: SingleAddress) => { 110 | return

{address}

; 111 | }) 112 | } 113 | ) 114 | } 115 | } 116 | ``` 117 | 118 | ## FAQ 119 | 120 | - Difference between Keyring Accounts and Addresses? 121 | - From the perspective of the keyring, it saves a particular user's unlocked identities as an account, a la keyring.saveAccount(pair, password). So with these accounts you are able to send and sign transactions. 122 | - To save addresses without unlocking them (i.e. because a user might want to have easy access to addresses they frequently transact with), use keyring.saveAddress(address, meta) 123 | - What are 'external' accounts, i.e. when to set the `isExternal` meta key to true? 124 | - An external account is one where the keys are not managed by keyring, e.g. in Parity Signer or Ledger Nano. 125 | - SS58 Encode / Decode? 126 | - SS58 is a simple address format designed for Substrate based chains. You can read about its specification in more detail in the [Substrate Documentation](https://docs.substrate.io/reference/address-formats/). 127 | 128 | **If you have any unanswered/undocumented questions, please raise an issue [here](https://github.com/polkadot-js/ui/issues).** 129 | 130 | 131 | ## Users 132 | 133 | Keyring is core to many polkadot/substrate apps. 134 | 135 | * [polkadot-js/apps](https://github.com/polkadot-js/apps) 136 | * [paritytech/substrate-light-ui](https://github.com/paritytech/substrate-light-ui) 137 | -------------------------------------------------------------------------------- /packages/ui-keyring/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "engines": { 5 | "node": ">=18" 6 | }, 7 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-keyring#readme", 8 | "license": "Apache-2.0", 9 | "name": "@polkadot/ui-keyring", 10 | "repository": { 11 | "directory": "packages/ui-keyring", 12 | "type": "git", 13 | "url": "https://github.com/polkadot-js/ui.git" 14 | }, 15 | "sideEffects": [ 16 | "./packageDetect.js", 17 | "./packageDetect.cjs" 18 | ], 19 | "type": "module", 20 | "version": "3.14.1", 21 | "main": "index.js", 22 | "dependencies": { 23 | "@polkadot/keyring": "^13.5.1", 24 | "@polkadot/ui-settings": "3.14.1", 25 | "@polkadot/util": "^13.5.1", 26 | "@polkadot/util-crypto": "^13.5.1", 27 | "mkdirp": "^3.0.1", 28 | "rxjs": "^7.8.1", 29 | "store": "^2.0.12", 30 | "tslib": "^2.8.1" 31 | }, 32 | "devDependencies": { 33 | "@types/mkdirp": "^2.0.0", 34 | "@types/store": "^2.0.5" 35 | }, 36 | "peerDependencies": { 37 | "@polkadot/keyring": "*", 38 | "@polkadot/ui-settings": "*", 39 | "@polkadot/util": "*" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/Base.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringInstance, KeyringPair } from '@polkadot/keyring/types'; 5 | import type { HexString } from '@polkadot/util/types'; 6 | import type { Prefix } from '@polkadot/util-crypto/address/types'; 7 | import type { AddressSubject } from './observable/types.js'; 8 | import type { KeyringOptions, KeyringStore } from './types.js'; 9 | 10 | import { createTestKeyring } from '@polkadot/keyring'; 11 | import { isBoolean, isNumber, isString } from '@polkadot/util'; 12 | 13 | import { accounts } from './observable/accounts.js'; 14 | import { addresses } from './observable/addresses.js'; 15 | import { contracts } from './observable/contracts.js'; 16 | import { env } from './observable/env.js'; 17 | import { BrowserStore } from './stores/Browser.js'; // direct import (skip index with all) 18 | 19 | export class Base { 20 | #accounts: AddressSubject; 21 | 22 | #addresses: AddressSubject; 23 | 24 | #contracts: AddressSubject; 25 | 26 | #isEthereum: boolean; 27 | 28 | #keyring?: KeyringInstance; 29 | 30 | protected _store: KeyringStore; 31 | 32 | protected _genesisHash?: HexString | undefined; 33 | 34 | protected _genesisHashAdd: HexString[] = []; 35 | 36 | constructor () { 37 | this.#accounts = accounts; 38 | this.#addresses = addresses; 39 | this.#contracts = contracts; 40 | this.#isEthereum = false; 41 | this._store = new BrowserStore(); 42 | } 43 | 44 | public get accounts (): AddressSubject { 45 | return this.#accounts; 46 | } 47 | 48 | public get addresses (): AddressSubject { 49 | return this.#addresses; 50 | } 51 | 52 | public get contracts (): AddressSubject { 53 | return this.#contracts; 54 | } 55 | 56 | public get isEthereum (): boolean { 57 | return this.#isEthereum; 58 | } 59 | 60 | public get keyring (): KeyringInstance { 61 | if (this.#keyring) { 62 | return this.#keyring; 63 | } 64 | 65 | throw new Error('Keyring should be initialised via \'loadAll\' before use'); 66 | } 67 | 68 | public get genesisHash (): HexString | undefined { 69 | return this._genesisHash; 70 | } 71 | 72 | public get genesisHashes (): HexString[] { 73 | return this._genesisHash 74 | ? [this._genesisHash, ...this._genesisHashAdd] 75 | : [...this._genesisHashAdd]; 76 | } 77 | 78 | public decodeAddress = (key: string | Uint8Array, ignoreChecksum?: boolean, ss58Format?: Prefix): Uint8Array => { 79 | return this.keyring.decodeAddress(key, ignoreChecksum, ss58Format); 80 | }; 81 | 82 | public encodeAddress = (key: string | Uint8Array, ss58Format?: Prefix): string => { 83 | return this.keyring.encodeAddress(key, ss58Format); 84 | }; 85 | 86 | public getPair (address: string | Uint8Array): KeyringPair { 87 | return this.keyring.getPair(address); 88 | } 89 | 90 | public getPairs (): KeyringPair[] { 91 | return this.keyring.getPairs().filter((pair: KeyringPair): boolean => 92 | env.isDevelopment() || pair.meta.isTesting !== true 93 | ); 94 | } 95 | 96 | public isAvailable (_address: Uint8Array | string): boolean { 97 | const accountsValue = this.accounts.subject.getValue(); 98 | const addressesValue = this.addresses.subject.getValue(); 99 | const contractsValue = this.contracts.subject.getValue(); 100 | const address = isString(_address) 101 | ? _address 102 | : this.encodeAddress(_address); 103 | 104 | return !accountsValue[address] && !addressesValue[address] && !contractsValue[address]; 105 | } 106 | 107 | public isPassValid (password: string): boolean { 108 | return password.length > 0; 109 | } 110 | 111 | public setSS58Format (ss58Format?: Prefix): void { 112 | if (this.#keyring && isNumber(ss58Format)) { 113 | this.#keyring.setSS58Format(ss58Format); 114 | } 115 | } 116 | 117 | public setDevMode (isDevelopment: boolean): void { 118 | env.set(isDevelopment); 119 | } 120 | 121 | protected initKeyring (options: KeyringOptions): void { 122 | const keyring = createTestKeyring(options, true); 123 | 124 | if (isBoolean(options.isDevelopment)) { 125 | this.setDevMode(options.isDevelopment); 126 | } 127 | 128 | // set Ethereum state 129 | this.#isEthereum = keyring.type === 'ethereum'; 130 | 131 | this.#keyring = keyring; 132 | this._genesisHash = options.genesisHash && ( 133 | isString(options.genesisHash) 134 | ? options.genesisHash.toString() as HexString 135 | : options.genesisHash.toHex() 136 | ); 137 | this._genesisHashAdd = options.genesisHashAdd || []; 138 | this._store = options.store || this._store; 139 | 140 | this.addAccountPairs(); 141 | } 142 | 143 | protected addAccountPairs (): void { 144 | this.keyring 145 | .getPairs() 146 | .forEach(({ address, meta }: KeyringPair): void => { 147 | this.accounts.add(this._store, address, { address, meta }); 148 | }); 149 | } 150 | 151 | protected addTimestamp (pair: KeyringPair): void { 152 | if (!pair.meta.whenCreated) { 153 | pair.setMeta({ whenCreated: Date.now() }); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/Keyring.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@polkadot/keyring/types'; 5 | import type { BN } from '@polkadot/util'; 6 | import type { EncryptedJson } from '@polkadot/util-crypto/json/types'; 7 | import type { KeypairType } from '@polkadot/util-crypto/types'; 8 | import type { AddressSubject, SingleAddress } from './observable/types.js'; 9 | import type { CreateResult, KeyringAddress, KeyringAddressType, KeyringItemType, KeyringJson, KeyringJson$Meta, KeyringOptions, KeyringPairs$Json, KeyringStruct } from './types.js'; 10 | 11 | import { createPair } from '@polkadot/keyring'; 12 | import { chains } from '@polkadot/ui-settings'; 13 | import { bnToBn, hexToU8a, isFunction, isHex, isString, objectSpread, stringify, stringToU8a, u8aSorted, u8aToString } from '@polkadot/util'; 14 | import { base64Decode, createKeyMulti, jsonDecrypt, jsonEncrypt } from '@polkadot/util-crypto'; 15 | 16 | import { env } from './observable/env.js'; 17 | import { KeyringOption } from './options/index.js'; 18 | import { Base } from './Base.js'; 19 | import { accountKey, accountRegex, addressKey, addressRegex, contractKey, contractRegex } from './defaults.js'; 20 | 21 | const RECENT_EXPIRY = 24 * 60 * 60; 22 | 23 | // No accounts (or test accounts) should be loaded until after the chain determination. 24 | // Chain determination occurs outside of Keyring. Loading `keyring.loadAll({ type: 'ed25519' | 'sr25519' })` is triggered 25 | // from the API after the chain is received 26 | export class Keyring extends Base implements KeyringStruct { 27 | public readonly keyringOption = new KeyringOption(); 28 | 29 | #stores = { 30 | account: (): AddressSubject => this.accounts, 31 | address: (): AddressSubject => this.addresses, 32 | contract: (): AddressSubject => this.contracts 33 | }; 34 | 35 | public addExternal (address: string | Uint8Array, meta: KeyringPair$Meta = {}): CreateResult { 36 | const pair = this.keyring.addFromAddress(address, objectSpread({}, meta, { isExternal: true }), null, meta?.type); 37 | 38 | return { 39 | json: this.saveAccount(pair), 40 | pair 41 | }; 42 | } 43 | 44 | public addHardware (address: string | Uint8Array, hardwareType: string, meta: KeyringPair$Meta = {}): CreateResult { 45 | return this.addExternal(address, objectSpread({}, meta, { hardwareType, isHardware: true })); 46 | } 47 | 48 | public addMultisig (addresses: (string | Uint8Array)[], threshold: bigint | BN | number, meta: KeyringPair$Meta = {}): CreateResult { 49 | let address = createKeyMulti(addresses, threshold); 50 | 51 | // For Ethereum chains, the first 20 bytes of the hash indicates the actual address 52 | // Testcases via creation and on-chain events: 53 | // - input: 0x7a1671a0224c8927b08f978027d586ab6868de0d31bb5bc956b625ced2ab18c4 54 | // - output: 0x7a1671a0224c8927b08f978027d586ab6868de0d 55 | if (this.isEthereum) { 56 | address = address.slice(0, 20); 57 | } 58 | 59 | // we could use `sortAddresses`, but rather use internal encode/decode so we are 100% 60 | const who = u8aSorted( 61 | addresses.map((who) => this.decodeAddress(who)) 62 | ).map((who) => this.encodeAddress(who)); 63 | 64 | return this.addExternal(address, objectSpread({}, meta, { isMultisig: true, threshold: bnToBn(threshold).toNumber(), who })); 65 | } 66 | 67 | public addPair (pair: KeyringPair, password: string): CreateResult { 68 | this.keyring.addPair(pair); 69 | 70 | return { 71 | json: this.saveAccount(pair, password), 72 | pair 73 | }; 74 | } 75 | 76 | public addUri (suri: string, password?: string, meta: KeyringPair$Meta = {}, type?: KeypairType): CreateResult { 77 | const pair = this.keyring.addFromUri(suri, meta, type); 78 | 79 | return { 80 | json: this.saveAccount(pair, password), 81 | pair 82 | }; 83 | } 84 | 85 | public backupAccount (pair: KeyringPair, password: string): KeyringPair$Json { 86 | if (!pair.isLocked) { 87 | pair.lock(); 88 | } 89 | 90 | pair.decodePkcs8(password); 91 | 92 | return pair.toJson(password); 93 | } 94 | 95 | public async backupAccounts (addresses: string[], password: string): Promise { 96 | const accountPromises = addresses.map((address) => { 97 | return new Promise((resolve) => { 98 | this._store.get(accountKey(address), resolve); 99 | }); 100 | }); 101 | 102 | const accounts = await Promise.all(accountPromises); 103 | 104 | return objectSpread({}, jsonEncrypt(stringToU8a(JSON.stringify(accounts)), ['batch-pkcs8'], password), { 105 | accounts: accounts.map((account) => ({ 106 | address: account.address, 107 | meta: account.meta 108 | })) 109 | }); 110 | } 111 | 112 | public createFromJson (json: KeyringPair$Json, meta: KeyringPair$Meta = {}): KeyringPair { 113 | return this.keyring.createFromJson( 114 | objectSpread({}, json, { 115 | meta: objectSpread({}, json.meta, meta) 116 | }) 117 | ); 118 | } 119 | 120 | public createFromUri (suri: string, meta: KeyringPair$Meta = {}, type?: KeypairType): KeyringPair { 121 | return this.keyring.createFromUri(suri, meta, type); 122 | } 123 | 124 | public encryptAccount (pair: KeyringPair, password: string): void { 125 | const json = pair.toJson(password); 126 | 127 | json.meta.whenEdited = Date.now(); 128 | 129 | this.keyring.addFromJson(json); 130 | this.accounts.add(this._store, pair.address, json, pair.type); 131 | } 132 | 133 | public forgetAccount (address: string): void { 134 | this.keyring.removePair(address); 135 | this.accounts.remove(this._store, address); 136 | } 137 | 138 | public forgetAddress (address: string): void { 139 | this.addresses.remove(this._store, address); 140 | } 141 | 142 | public forgetContract (address: string): void { 143 | this.contracts.remove(this._store, address); 144 | } 145 | 146 | public getAccount (address: string | Uint8Array): KeyringAddress | undefined { 147 | return this.getAddress(address, 'account'); 148 | } 149 | 150 | public getAccounts (): KeyringAddress[] { 151 | const available = this.accounts.subject.getValue(); 152 | 153 | return Object 154 | .keys(available) 155 | .map((address) => this.getAddress(address, 'account')) 156 | .filter((account): account is KeyringAddress => 157 | !!account && 158 | ( 159 | env.isDevelopment() || 160 | account.meta.isTesting !== true 161 | ) 162 | ); 163 | } 164 | 165 | public getAddress (_address: string | Uint8Array, type: KeyringItemType | null = null): KeyringAddress | undefined { 166 | const address = isString(_address) 167 | ? _address 168 | : this.encodeAddress(_address); 169 | const publicKey = this.decodeAddress(address); 170 | const stores = type 171 | ? [this.#stores[type]] 172 | : Object.values(this.#stores); 173 | 174 | const info = stores.reduce((lastInfo, store): SingleAddress | undefined => 175 | (store().subject.getValue()[address] || lastInfo), undefined); 176 | 177 | return info && { 178 | address, 179 | meta: info.json.meta, 180 | publicKey 181 | }; 182 | } 183 | 184 | public getAddresses (): KeyringAddress[] { 185 | const available = this.addresses.subject.getValue(); 186 | 187 | return Object 188 | .keys(available) 189 | .map((address) => this.getAddress(address)) 190 | .filter((account): account is KeyringAddress => !!account); 191 | } 192 | 193 | public getContract (address: string | Uint8Array): KeyringAddress | undefined { 194 | return this.getAddress(address, 'contract'); 195 | } 196 | 197 | public getContracts (): KeyringAddress[] { 198 | const available = this.contracts.subject.getValue(); 199 | 200 | return Object 201 | .entries(available) 202 | .filter(([, { json: { meta: { contract } } }]): boolean => 203 | !!contract && contract.genesisHash === this.genesisHash 204 | ) 205 | .map(([address]) => this.getContract(address)) 206 | .filter((account): account is KeyringAddress => !!account); 207 | } 208 | 209 | private rewriteKey (json: KeyringJson, key: string, hexAddr: string, creator: (addr: string) => string): void { 210 | if (hexAddr.startsWith('0x')) { 211 | return; 212 | } 213 | 214 | this._store.remove(key); 215 | this._store.set(creator(hexAddr), json); 216 | } 217 | 218 | private loadAccount (json: KeyringJson, key: string): void { 219 | if (!json.meta.isTesting && (json as KeyringPair$Json).encoded) { 220 | const pair = this.keyring.addFromJson(json as KeyringPair$Json, true); 221 | 222 | this.accounts.add(this._store, pair.address, json, pair.type); 223 | } 224 | 225 | const [, hexAddr] = key.split(':'); 226 | 227 | this.rewriteKey(json, key, hexAddr.trim(), accountKey); 228 | } 229 | 230 | private loadAddress (json: KeyringJson, key: string): void { 231 | const { isRecent, whenCreated = 0 } = json.meta; 232 | 233 | if (isRecent && (Date.now() - whenCreated) > RECENT_EXPIRY) { 234 | this._store.remove(key); 235 | 236 | return; 237 | } 238 | 239 | // We assume anything hex that is not 32bytes (64 + 2 bytes hex) is an Ethereum-like address 240 | // (this caters for both H160 addresses as well as full or compressed publicKeys) - in the case 241 | // of both ecdsa and ethereum, we keep it as-is 242 | const address = isHex(json.address) && json.address.length !== 66 243 | ? json.address 244 | : this.encodeAddress( 245 | isHex(json.address) 246 | ? hexToU8a(json.address) 247 | // FIXME Just for the transition period (ignoreChecksum) 248 | : this.decodeAddress(json.address, true) 249 | ); 250 | const [, hexAddr] = key.split(':'); 251 | 252 | this.addresses.add(this._store, address, json); 253 | this.rewriteKey(json, key, hexAddr, addressKey); 254 | } 255 | 256 | private loadContract (json: KeyringJson, key: string): void { 257 | const address = this.encodeAddress( 258 | this.decodeAddress(json.address) 259 | ); 260 | const [, hexAddr] = key.split(':'); 261 | 262 | // move genesisHash to top-level (TODO Remove from contracts section?) 263 | json.meta.genesisHash = json.meta.genesisHash || (json.meta.contract?.genesisHash); 264 | 265 | this.contracts.add(this._store, address, json); 266 | this.rewriteKey(json, key, hexAddr, contractKey); 267 | } 268 | 269 | private loadInjected (address: string, meta: KeyringJson$Meta, type?: KeypairType): void { 270 | const json = { 271 | address, 272 | meta: objectSpread({}, meta, { isInjected: true }) 273 | }; 274 | const pair = this.keyring.addFromAddress(address, json.meta, null, type); 275 | 276 | this.accounts.add(this._store, pair.address, json, pair.type); 277 | } 278 | 279 | private allowGenesis (json?: KeyringJson | { meta: KeyringJson$Meta } | null): boolean { 280 | if (json?.meta && this.genesisHash) { 281 | const hashes: (string | null | undefined)[] = Object.values(chains).find((hashes): boolean => 282 | hashes.includes(this.genesisHash || '') 283 | ) || [this.genesisHash]; 284 | 285 | if (json.meta.genesisHash) { 286 | return hashes.includes(json.meta.genesisHash) || this.genesisHashes.includes(json.meta.genesisHash); 287 | } else if (json.meta.contract) { 288 | return hashes.includes(json.meta.contract.genesisHash); 289 | } 290 | } 291 | 292 | return true; 293 | } 294 | 295 | public loadAll (options: KeyringOptions, injected: { address: string; meta: KeyringJson$Meta, type?: KeypairType }[] = []): void { 296 | super.initKeyring(options); 297 | 298 | this._store.all((key: string, json: KeyringJson): void => { 299 | if (!isFunction(options.filter) || options.filter(json)) { 300 | try { 301 | if (this.allowGenesis(json)) { 302 | if (accountRegex.test(key)) { 303 | this.loadAccount(json, key); 304 | } else if (addressRegex.test(key)) { 305 | this.loadAddress(json, key); 306 | } else if (contractRegex.test(key)) { 307 | this.loadContract(json, key); 308 | } 309 | } 310 | } catch { 311 | console.warn(`Keyring: Unable to load ${key}:${stringify(json)}`); 312 | } 313 | } 314 | }); 315 | 316 | injected.forEach((account): void => { 317 | if (this.allowGenesis(account)) { 318 | try { 319 | this.loadInjected(account.address, account.meta, account.type); 320 | } catch { 321 | console.warn(`Keyring: Unable to inject ${stringify(account)}`); 322 | } 323 | } 324 | }); 325 | 326 | this.keyringOption.init(this); 327 | } 328 | 329 | public restoreAccount (json: KeyringPair$Json, password: string): KeyringPair { 330 | const cryptoType = Array.isArray(json.encoding.content) ? json.encoding.content[1] : 'ed25519'; 331 | const encType = Array.isArray(json.encoding.type) ? json.encoding.type : [json.encoding.type]; 332 | const pair = createPair( 333 | { toSS58: this.encodeAddress, type: cryptoType as KeypairType }, 334 | { publicKey: this.decodeAddress(json.address, true) }, 335 | json.meta, 336 | isHex(json.encoded) ? hexToU8a(json.encoded) : base64Decode(json.encoded), 337 | encType 338 | ); 339 | 340 | // unlock, save account and then lock (locking cleans secretKey, so needs to be last) 341 | pair.decodePkcs8(password); 342 | this.addPair(pair, password); 343 | pair.lock(); 344 | 345 | return pair; 346 | } 347 | 348 | public restoreAccounts (json: EncryptedJson, password: string): void { 349 | const accounts: KeyringJson[] = JSON.parse(u8aToString(jsonDecrypt(json, password))) as KeyringJson[]; 350 | 351 | accounts.forEach((account) => { 352 | this.loadAccount(account, accountKey(account.address)); 353 | }); 354 | } 355 | 356 | public saveAccount (pair: KeyringPair, password?: string): KeyringPair$Json { 357 | this.addTimestamp(pair); 358 | 359 | const json = pair.toJson(password); 360 | 361 | this.keyring.addFromJson(json); 362 | this.accounts.add(this._store, pair.address, json, pair.type); 363 | 364 | return json; 365 | } 366 | 367 | public saveAccountMeta (pair: KeyringPair, meta: KeyringPair$Meta): void { 368 | const address = pair.address; 369 | 370 | this._store.get(accountKey(address), (json: KeyringJson): void => { 371 | pair.setMeta(meta); 372 | json.meta = pair.meta; 373 | 374 | this.accounts.add(this._store, address, json, pair.type); 375 | }); 376 | } 377 | 378 | public saveAddress (address: string, meta: KeyringPair$Meta, type: KeyringAddressType = 'address'): KeyringPair$Json { 379 | const available = this.addresses.subject.getValue(); 380 | 381 | const json = available[address]?.json || { 382 | address, 383 | meta: { 384 | isRecent: undefined, 385 | whenCreated: Date.now() 386 | } 387 | }; 388 | 389 | Object.keys(meta).forEach((key): void => { 390 | json.meta[key] = meta[key]; 391 | }); 392 | 393 | delete json.meta.isRecent; 394 | 395 | this.#stores[type]().add(this._store, address, json); 396 | 397 | return json as KeyringPair$Json; 398 | } 399 | 400 | public saveContract (address: string, meta: KeyringPair$Meta): KeyringPair$Json { 401 | return this.saveAddress(address, meta, 'contract'); 402 | } 403 | 404 | public saveRecent (address: string): SingleAddress { 405 | const available = this.addresses.subject.getValue(); 406 | 407 | if (!available[address]) { 408 | this.addresses.add(this._store, address, { 409 | address, 410 | meta: { 411 | genesisHash: this.genesisHash, 412 | isRecent: true, 413 | whenCreated: Date.now() 414 | } 415 | }); 416 | } 417 | 418 | return this.addresses.subject.getValue()[address]; 419 | } 420 | } 421 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Keyring } from './Keyring.js'; 5 | 6 | export { packageInfo } from './packageInfo.js'; 7 | 8 | export const keyring = new Keyring(); 9 | 10 | export { Keyring }; 11 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/defaults.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { decodeAddress } from '@polkadot/keyring'; 5 | import { u8aToHex } from '@polkadot/util'; 6 | 7 | const ACCOUNT_PREFIX = 'account:'; 8 | const ADDRESS_PREFIX = 'address:'; 9 | const CONTRACT_PREFIX = 'contract:'; 10 | 11 | function toHex (address: string): string { 12 | return u8aToHex( 13 | // When saving pre-checksum changes, ensure that we can decode 14 | decodeAddress(address, true) 15 | ); 16 | } 17 | 18 | export function accountKey (address: string): string { 19 | return `${ACCOUNT_PREFIX}${toHex(address)}`; 20 | } 21 | 22 | export function addressKey (address: string): string { 23 | return `${ADDRESS_PREFIX}${toHex(address)}`; 24 | } 25 | 26 | export function contractKey (address: string): string { 27 | return `${CONTRACT_PREFIX}${toHex(address)}`; 28 | } 29 | 30 | export const accountRegex = new RegExp(`^${ACCOUNT_PREFIX}0x[0-9a-f]*`, ''); 31 | 32 | export const addressRegex = new RegExp(`^${ADDRESS_PREFIX}0x[0-9a-f]*`, ''); 33 | 34 | export const contractRegex = new RegExp(`^${CONTRACT_PREFIX}0x[0-9a-f]*`, ''); 35 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | import { keyring } from './bundle.js'; 7 | 8 | export * from './bundle.js'; 9 | 10 | export default keyring; 11 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/json.d.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Be able to import json in TS 5 | // https://stackoverflow.com/questions/49996456/importing-json-file-in-typescript 6 | declare module '*.json' { 7 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 8 | const value: any; 9 | 10 | export default value; 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/accounts.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { accountKey } from '../defaults.js'; 5 | import { genericSubject } from './genericSubject.js'; 6 | 7 | export const accounts = /*#__PURE__*/ genericSubject(accountKey, true); 8 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/addresses.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { addressKey } from '../defaults.js'; 5 | import { genericSubject } from './genericSubject.js'; 6 | 7 | export const addresses = /*#__PURE__*/ genericSubject(addressKey); 8 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/contracts.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { contractKey } from '../defaults.js'; 5 | import { genericSubject } from './genericSubject.js'; 6 | 7 | export const contracts = /*#__PURE__*/ genericSubject(contractKey); 8 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/env.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { BehaviorSubject } from 'rxjs'; 5 | 6 | const subject = new BehaviorSubject(false); 7 | 8 | export const env = { 9 | isDevelopment: (): boolean => 10 | subject.getValue(), 11 | set: (isDevelopment: boolean): void => { 12 | subject.next(isDevelopment); 13 | }, 14 | subject 15 | }; 16 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/genericSubject.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeypairType } from '@polkadot/util-crypto/types'; 5 | import type { KeyringJson, KeyringStore } from '../types.js'; 6 | import type { AddressSubject, SingleAddress, SubjectInfo } from './types.js'; 7 | 8 | import { BehaviorSubject } from 'rxjs'; 9 | 10 | import { objectCopy, objectSpread } from '@polkadot/util'; 11 | 12 | import { createOptionItem } from '../options/item.js'; 13 | import { env } from './env.js'; 14 | 15 | function callNext (current: SubjectInfo, subject: BehaviorSubject, withTest: boolean): void { 16 | const isDevMode = env.isDevelopment(); 17 | const filtered: SubjectInfo = {}; 18 | 19 | Object.keys(current).forEach((key): void => { 20 | const { json: { meta: { isTesting = false } = {} } = {} } = current[key]; 21 | 22 | if (!withTest || isDevMode || isTesting !== true) { 23 | filtered[key] = current[key]; 24 | } 25 | }); 26 | 27 | subject.next(filtered); 28 | } 29 | 30 | export function genericSubject (keyCreator: (address: string) => string, withTest = false): AddressSubject { 31 | let current: SubjectInfo = {}; 32 | const subject = new BehaviorSubject({}); 33 | const next = (): void => callNext(current, subject, withTest); 34 | 35 | env.subject.subscribe(next); 36 | 37 | return { 38 | add: (store: KeyringStore, address: string, json: KeyringJson, type?: KeypairType): SingleAddress => { 39 | current = objectCopy(current); 40 | 41 | current[address] = { 42 | json: objectSpread({}, json, { address }), 43 | option: createOptionItem(address, json.meta.name), 44 | type 45 | }; 46 | 47 | // we do not store dev or injected accounts (external/transient) 48 | if (!json.meta.isInjected && (!json.meta.isTesting || env.isDevelopment())) { 49 | store.set(keyCreator(address), json); 50 | } 51 | 52 | next(); 53 | 54 | return current[address]; 55 | }, 56 | remove: (store: KeyringStore, address: string): void => { 57 | current = objectCopy(current); 58 | 59 | delete current[address]; 60 | 61 | store.remove(keyCreator(address)); 62 | next(); 63 | }, 64 | subject 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { SubjectInfo } from './types.js'; 5 | 6 | import { combineLatest, map } from 'rxjs'; 7 | 8 | import { accounts } from './accounts.js'; 9 | import { addresses } from './addresses.js'; 10 | import { contracts } from './contracts.js'; 11 | 12 | interface Result { 13 | accounts: SubjectInfo; 14 | addresses: SubjectInfo; 15 | contracts: SubjectInfo; 16 | } 17 | 18 | export const obervableAll = /*#__PURE__*/ combineLatest([ 19 | accounts.subject, 20 | addresses.subject, 21 | contracts.subject 22 | ]).pipe( 23 | map(([accounts, addresses, contracts]): Result => ({ 24 | accounts, 25 | addresses, 26 | contracts 27 | })) 28 | ); 29 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/observable/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { BehaviorSubject } from 'rxjs'; 5 | import type { KeypairType } from '@polkadot/util-crypto/types'; 6 | import type { KeyringSectionOption } from '../options/types.js'; 7 | import type { KeyringJson, KeyringStore } from '../types.js'; 8 | 9 | export interface SingleAddress { 10 | json: KeyringJson; 11 | option: KeyringSectionOption; 12 | type?: KeypairType | undefined; 13 | } 14 | 15 | export type SubjectInfo = Record; 16 | 17 | export interface AddressSubject { 18 | add: (store: KeyringStore, address: string, json: KeyringJson, type?: KeypairType) => SingleAddress; 19 | remove: (store: KeyringStore, address: string) => void; 20 | subject: BehaviorSubject; 21 | } 22 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/options/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Subscription } from 'rxjs'; 5 | import type { SingleAddress } from '../observable/types.js'; 6 | import type { KeyringStruct } from '../types.js'; 7 | import type { KeyringOptionInstance, KeyringOptions, KeyringSectionOption, KeyringSectionOptions } from './types.js'; 8 | 9 | import { BehaviorSubject } from 'rxjs'; 10 | 11 | import { obervableAll } from '../observable/index.js'; 12 | 13 | let hasCalledInitOptions = false; 14 | 15 | const sortByName = (a: SingleAddress, b: SingleAddress): number => { 16 | const valueA = a.option.name; 17 | const valueB = b.option.name; 18 | 19 | return valueA.localeCompare(valueB); 20 | }; 21 | 22 | const sortByCreated = (a: SingleAddress, b: SingleAddress): number => { 23 | const valueA = a.json.meta.whenCreated || 0; 24 | const valueB = b.json.meta.whenCreated || 0; 25 | 26 | if (valueA < valueB) { 27 | return 1; 28 | } 29 | 30 | if (valueA > valueB) { 31 | return -1; 32 | } 33 | 34 | return 0; 35 | }; 36 | 37 | export class KeyringOption implements KeyringOptionInstance { 38 | #allSub: Subscription | null = null; 39 | 40 | public readonly optionsSubject = new BehaviorSubject(this.emptyOptions()); 41 | 42 | public createOptionHeader (name: string): KeyringSectionOption { 43 | return { 44 | key: `header-${name.toLowerCase()}`, 45 | name, 46 | value: null 47 | }; 48 | } 49 | 50 | public init (keyring: KeyringStruct): void { 51 | if (hasCalledInitOptions) { 52 | throw new Error('Unable to initialise options more than once'); 53 | } 54 | 55 | this.#allSub = obervableAll.subscribe((): void => { 56 | const opts = this.emptyOptions(); 57 | 58 | this.addAccounts(keyring, opts); 59 | this.addAddresses(keyring, opts); 60 | this.addContracts(keyring, opts); 61 | 62 | opts.address = this.linkItems({ Addresses: opts.address, Recent: opts.recent }); 63 | opts.account = this.linkItems({ Accounts: opts.account, Development: opts.testing }); 64 | opts.contract = this.linkItems({ Contracts: opts.contract }); 65 | opts.all = ([] as KeyringSectionOptions).concat(opts.account, opts.address); 66 | opts.allPlus = ([] as KeyringSectionOptions).concat(opts.account, opts.address, opts.contract); 67 | 68 | this.optionsSubject.next(opts); 69 | }); 70 | 71 | hasCalledInitOptions = true; 72 | } 73 | 74 | public clear (): void { 75 | if (this.#allSub) { 76 | this.#allSub.unsubscribe(); 77 | } 78 | } 79 | 80 | private linkItems (items: Record): KeyringSectionOptions { 81 | return Object.keys(items).reduce((result, header): KeyringSectionOptions => { 82 | const options = items[header]; 83 | 84 | return result.concat( 85 | options.length 86 | ? [this.createOptionHeader(header)] 87 | : [], 88 | options 89 | ); 90 | }, [] as KeyringSectionOptions); 91 | } 92 | 93 | private addAccounts (keyring: KeyringStruct, options: KeyringOptions): void { 94 | const available = keyring.accounts.subject.getValue(); 95 | 96 | Object 97 | .values(available) 98 | .sort(sortByName) 99 | .forEach(({ json: { meta: { isTesting = false } }, option }: SingleAddress): void => { 100 | if (!isTesting) { 101 | options.account.push(option); 102 | } else { 103 | options.testing.push(option); 104 | } 105 | }); 106 | } 107 | 108 | private addAddresses (keyring: KeyringStruct, options: KeyringOptions): void { 109 | const available = keyring.addresses.subject.getValue(); 110 | 111 | Object 112 | .values(available) 113 | .filter(({ json }: SingleAddress): boolean => !!json.meta.isRecent) 114 | .sort(sortByCreated) 115 | .forEach(({ option }: SingleAddress): void => { 116 | options.recent.push(option); 117 | }); 118 | 119 | Object 120 | .values(available) 121 | .filter(({ json }: SingleAddress): boolean => !json.meta.isRecent) 122 | .sort(sortByName) 123 | .forEach(({ option }: SingleAddress): void => { 124 | options.address.push(option); 125 | }); 126 | } 127 | 128 | private addContracts (keyring: KeyringStruct, options: KeyringOptions): void { 129 | const available = keyring.contracts.subject.getValue(); 130 | 131 | Object 132 | .values(available) 133 | .sort(sortByName) 134 | .forEach(({ option }: SingleAddress): void => { 135 | options.contract.push(option); 136 | }); 137 | } 138 | 139 | private emptyOptions (): KeyringOptions { 140 | return { 141 | account: [], 142 | address: [], 143 | all: [], 144 | allPlus: [], 145 | contract: [], 146 | recent: [], 147 | testing: [] 148 | }; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/options/item.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringSectionOption } from './types.js'; 5 | 6 | import { isUndefined } from '@polkadot/util'; 7 | 8 | export function createOptionItem (address: string, _name?: string): KeyringSectionOption { 9 | const name = isUndefined(_name) 10 | ? ( 11 | (address.length > 15) 12 | ? `${address.slice(0, 6)}…${address.slice(-6)}` 13 | : address 14 | ) 15 | : _name; 16 | 17 | return { 18 | key: address, 19 | name, 20 | value: address 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/options/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringItemType, KeyringStruct } from '../types.js'; 5 | 6 | export interface KeyringSectionOption { 7 | key: string | null; 8 | name: string; 9 | value: string | null; 10 | } 11 | 12 | export type KeyringSectionOptions = KeyringSectionOption[]; 13 | 14 | export type KeyringOptions = { 15 | [type in KeyringItemType | 'all' | 'allPlus' | 'recent' | 'testing']: KeyringSectionOptions 16 | }; 17 | 18 | export type KeyringOption$Type = keyof KeyringOptions; 19 | 20 | export interface KeyringOptionInstance { 21 | createOptionHeader: (name: string) => KeyringSectionOption; 22 | init: (keyring: KeyringStruct) => void; 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { packageInfo as settingsInfo } from '@polkadot/ui-settings/packageInfo'; 8 | import { detectPackage } from '@polkadot/util'; 9 | 10 | import { packageInfo } from './packageInfo.js'; 11 | 12 | detectPackage(packageInfo, null, [settingsInfo]); 13 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/ui-keyring', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/stores/Browser.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringJson, KeyringStore } from '../types.js'; 5 | 6 | import store from 'store'; 7 | 8 | export class BrowserStore implements KeyringStore { 9 | public all (fn: (key: string, value: KeyringJson) => void): void { 10 | store.each((value: KeyringJson, key: string): void => { 11 | fn(key, value); 12 | }); 13 | } 14 | 15 | public get (key: string, fn: (value: KeyringJson) => void): void { 16 | fn(store.get(key) as KeyringJson); 17 | } 18 | 19 | public remove (key: string, fn?: () => void): void { 20 | store.remove(key); 21 | fn && fn(); 22 | } 23 | 24 | public set (key: string, value: KeyringJson, fn?: () => void): void { 25 | store.set(key, value); 26 | fn && fn(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/stores/File.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringJson, KeyringStore } from '../types.js'; 5 | 6 | import { mkdirpSync } from 'mkdirp'; 7 | import fs from 'node:fs'; 8 | import path from 'node:path'; 9 | 10 | // NOTE untested and unused by any known apps, probably broken in various mysterious ways 11 | export class FileStore implements KeyringStore { 12 | #path: string; 13 | 14 | constructor (path: string) { 15 | if (!fs.existsSync(path)) { 16 | mkdirpSync(path); 17 | } 18 | 19 | this.#path = path; 20 | } 21 | 22 | private validateKey (key: string): boolean { 23 | // Make sure the key has a .json extension 24 | if (!key.endsWith('.json')) { 25 | console.error('Non-JSON file requested: ', key); 26 | 27 | return false; 28 | } 29 | 30 | // Remove '.json' 31 | const keyWithoutExtension = key.slice(0, -5); 32 | // Only allow alphanumeric characters, hyphens, and underscores in the base filename 33 | const safeKeyRegex = /^[a-zA-Z0-9_-]+$/; 34 | 35 | if (!safeKeyRegex.test(keyWithoutExtension)) { 36 | console.error('Invalid key format detected: ', key); 37 | 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | public all (fn: (key: string, value: KeyringJson) => void): void { 45 | fs 46 | .readdirSync(this.#path) 47 | .filter((key): boolean => !['.', '..', '.DS_Store'].includes(key)) 48 | .forEach((key): void => { 49 | const value = this._readKey(key); 50 | 51 | value?.address && fn(key, value); 52 | }); 53 | } 54 | 55 | public get (key: string, fn: (value: KeyringJson) => void): void { 56 | const value = this._readKey(key); 57 | 58 | if (!value?.address) { 59 | throw new Error(`Invalid JSON found for ${key}`); 60 | } 61 | 62 | fn(value); 63 | } 64 | 65 | public remove (key: string, fn?: () => void): void { 66 | fs.unlinkSync(this._getPath(key)); 67 | fn && fn(); 68 | } 69 | 70 | public set (key: string, value: KeyringJson, fn?: () => void): void { 71 | fs.writeFileSync(this._getPath(key), Buffer.from(JSON.stringify(value), 'utf-8')); 72 | fn && fn(); 73 | } 74 | 75 | private _getPath (key: string): string { 76 | if (!this.validateKey(key)) { 77 | throw new Error('Invalid key format'); 78 | } 79 | 80 | return path.join(this.#path, key); 81 | } 82 | 83 | private _readKey (key: string): KeyringJson | undefined { 84 | try { 85 | return JSON.parse( 86 | fs.readFileSync(this._getPath(key)).toString('utf-8') 87 | ) as KeyringJson; 88 | } catch (error) { 89 | console.error(error); 90 | } 91 | 92 | return undefined; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/stores/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { BrowserStore } from './Browser.js'; 5 | export { FileStore } from './File.js'; 6 | -------------------------------------------------------------------------------- /packages/ui-keyring/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-keyring authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { KeyringInstance as BaseKeyringInstance, KeyringOptions as KeyringOptionsBase, KeyringPair, KeyringPair$Json, KeyringPair$Meta } from '@polkadot/keyring/types'; 5 | import type { HexString } from '@polkadot/util/types'; 6 | import type { EncryptedJson } from '@polkadot/util-crypto/json/types'; 7 | import type { KeypairType } from '@polkadot/util-crypto/types'; 8 | import type { AddressSubject, SingleAddress } from './observable/types.js'; 9 | 10 | export type ContractMeta = NonNullable; 11 | 12 | export type KeyringJson$Meta = KeyringPair$Meta; 13 | 14 | export interface KeyringJson { 15 | address: string; 16 | meta: KeyringJson$Meta; 17 | } 18 | 19 | export interface KeyringPairs$Json extends EncryptedJson { 20 | accounts: KeyringJson[]; 21 | } 22 | 23 | export interface KeyringStore { 24 | all: (cb: (key: string, value: KeyringJson) => void) => void; 25 | get: (key: string, cb: (value: KeyringJson) => void) => void; 26 | remove: (key: string, cb?: () => void) => void; 27 | set: (key: string, value: KeyringJson, cb?: () => void) => void; 28 | } 29 | 30 | export interface KeyringOptions extends KeyringOptionsBase { 31 | filter?: (json: KeyringJson) => boolean; 32 | genesisHash?: HexString | { toHex: () => HexString }; 33 | genesisHashAdd?: HexString[]; 34 | isDevelopment?: boolean; 35 | store?: KeyringStore; 36 | } 37 | 38 | export interface KeyringAddress { 39 | readonly address: string; 40 | readonly meta: KeyringJson$Meta; 41 | readonly publicKey: Uint8Array; 42 | } 43 | 44 | export type KeyringAddressType = 'address' | 'contract'; 45 | 46 | export type KeyringItemType = 'account' | KeyringAddressType; 47 | 48 | export interface CreateResult { 49 | json: KeyringPair$Json; 50 | pair: KeyringPair; 51 | } 52 | 53 | export interface KeyringStruct { 54 | readonly accounts: AddressSubject; 55 | readonly addresses: AddressSubject; 56 | readonly contracts: AddressSubject; 57 | readonly keyring: BaseKeyringInstance | undefined; 58 | readonly genesisHash?: string | undefined; 59 | 60 | addExternal: (publicKey: Uint8Array, meta?: KeyringPair$Meta) => CreateResult; 61 | addPair: (pair: KeyringPair, password: string) => CreateResult; 62 | addUri: (suri: string, password?: string, meta?: KeyringPair$Meta, type?: KeypairType) => CreateResult; 63 | backupAccount: (pair: KeyringPair, password: string) => KeyringPair$Json; 64 | backupAccounts: (addresses: string[], password: string) => Promise 65 | createFromUri (suri: string, meta?: KeyringPair$Meta, type?: KeypairType): KeyringPair; 66 | decodeAddress: (key: string | Uint8Array) => Uint8Array; 67 | encodeAddress: (key: string | Uint8Array) => string; 68 | encryptAccount: (pair: KeyringPair, password: string) => void; 69 | forgetAccount: (address: string) => void; 70 | forgetAddress: (address: string) => void; 71 | forgetContract: (address: string) => void; 72 | getAccount: (address: string | Uint8Array) => KeyringAddress | undefined; 73 | getAccounts: () => KeyringAddress[]; 74 | getAddress: (address: string | Uint8Array, type: KeyringItemType | null) => KeyringAddress | undefined; 75 | getAddresses: () => KeyringAddress[]; 76 | getContract: (address: string | Uint8Array) => KeyringAddress | undefined; 77 | getContracts: (genesisHash?: string) => KeyringAddress[]; 78 | getPair: (address: string | Uint8Array) => KeyringPair; 79 | getPairs: () => KeyringPair[]; 80 | isAvailable: (address: string | Uint8Array) => boolean; 81 | isPassValid: (password: string) => boolean; 82 | loadAll: (options: KeyringOptions) => void; 83 | restoreAccount: (json: KeyringPair$Json, password: string) => KeyringPair; 84 | restoreAccounts: (json: EncryptedJson, password: string) => void; 85 | saveAccount: (pair: KeyringPair, password?: string) => KeyringPair$Json; 86 | saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta) => void; 87 | saveAddress: (address: string, meta: KeyringPair$Meta) => KeyringPair$Json; 88 | saveContract: (address: string, meta: KeyringPair$Meta) => KeyringPair$Json; 89 | saveRecent: (address: string) => SingleAddress; 90 | setDevMode: (isDevelopment: boolean) => void; 91 | } 92 | -------------------------------------------------------------------------------- /packages/ui-keyring/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/*.spec.ts" 10 | ], 11 | "references": [ 12 | { "path": "../ui-settings/tsconfig.build.json" } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui-keyring/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "**/*.spec.ts" 12 | ], 13 | "references": [ 14 | { "path": "../ui-keyring/tsconfig.build.json" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui-settings/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/ui-settings 2 | 3 | Manages app settings including endpoints, themes and prefixes 4 | 5 | ## Usage Examples 6 | 7 | User preferences are set as a settings object in the browser's local storage. 8 | 9 | ```js 10 | import settings from '@polkadot/ui-settings'; 11 | 12 | render () { 13 | // get api endpoint for the selected chain 14 | const WS_URL = settings.apiUrl(); 15 | 16 | // get the selected il8n language 17 | const language = settings.il8nLang(); 18 | 19 | // get all available il8n languages 20 | const languages = settings.availableLanguages(); 21 | 22 | // update settings 23 | const updatedSettings = { 24 | ...settings, 25 | i18nLang: 'Arabic' 26 | } 27 | settings.set(updatedSettings); 28 | 29 | // NOTE: API currently does not handle hot reconnecting properly, 30 | so you need to manually reload the page after updating settings. 31 | window.location.reload(); 32 | } 33 | ``` 34 | 35 | ## Used by 36 | 37 | Apps that currently use the settings package 38 | 39 | * [polkadot-js/apps](https://www.github.com/polkadot-js/apps) 40 | * [paritytech/substrate-light-ui](https://github.com/paritytech/substrate-light-ui) 41 | -------------------------------------------------------------------------------- /packages/ui-settings/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Manages app settings", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-settings#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/ui-settings", 11 | "repository": { 12 | "directory": "packages/ui-settings", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "@polkadot/networks": "^13.5.1", 25 | "@polkadot/util": "^13.5.1", 26 | "eventemitter3": "^5.0.1", 27 | "store": "^2.0.12", 28 | "tslib": "^2.8.1" 29 | }, 30 | "devDependencies": { 31 | "@types/store": "^2.0.5" 32 | }, 33 | "peerDependencies": { 34 | "@polkadot/networks": "*", 35 | "@polkadot/util": "*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/ui-settings/src/Settings.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Endpoint, EndpointType, Option, SettingsStruct } from './types.js'; 5 | 6 | import { EventEmitter } from 'eventemitter3'; 7 | import store from 'store'; 8 | 9 | import { hasProcess, isUndefined } from '@polkadot/util'; 10 | 11 | import { CAMERA, CAMERA_DEFAULT, CRYPTOS, CRYPTOS_ETH, CRYPTOS_LEDGER, ENDPOINT_DEFAULT, ENDPOINTS, ICON_DEFAULT, ICONS, LANGUAGE_DEFAULT, LEDGER_APP, LEDGER_APP_DEFAULT, LEDGER_CONN, LEDGER_CONN_DEFAULT, LOCKING, LOCKING_DEFAULT, METADATA_UP, METADATA_UP_DEFAULT, NOTIFICATION_DEFAULT, PREFIX_DEFAULT, PREFIXES, STORAGE, STORAGE_DEFAULT, UIMODE_DEFAULT, UIMODES, UITHEME_DEFAULT, UITHEMES } from './defaults/index.js'; 12 | 13 | type ChangeCallback = (settings: SettingsStruct) => void; 14 | type OnTypes = 'change'; 15 | 16 | function withDefault (options: Option[], option: string | undefined, fallback: string): string { 17 | const _option = option || fallback; 18 | 19 | return options.some(({ value }) => value === _option) 20 | ? _option 21 | : fallback; 22 | } 23 | 24 | export class Settings implements SettingsStruct { 25 | readonly #emitter: EventEmitter; 26 | 27 | #apiType: Endpoint; 28 | 29 | // will become deprecated for supporting substrate connect light clients. apiType structure should be used instead 30 | #apiUrl: string; 31 | 32 | #camera: string; 33 | 34 | #i18nLang: string; 35 | 36 | #icon: string; 37 | 38 | #ledgerApp: string; 39 | 40 | #ledgerConn: string; 41 | 42 | #locking: string; 43 | 44 | #metadataUp: string; 45 | 46 | #prefix: number; 47 | 48 | #storage: string; 49 | 50 | #uiMode: string; 51 | 52 | #uiTheme: string; 53 | 54 | #notification: string; 55 | 56 | constructor () { 57 | const settings = (store.get('settings') as SettingsStruct) || {}; 58 | 59 | this.#emitter = new EventEmitter(); 60 | 61 | // will become deprecated for supporting substrate connect light clients. apiType structure should be used instead 62 | this.#apiUrl = (typeof settings.apiUrl === 'string' && settings.apiUrl) || (hasProcess && process.env?.['WS_URL']) || (ENDPOINT_DEFAULT.value as string); 63 | this.#apiType = { param: this.#apiUrl, type: 'json-rpc' as EndpointType }; 64 | this.#camera = withDefault(CAMERA, settings.camera, CAMERA_DEFAULT); 65 | this.#ledgerApp = withDefault(LEDGER_APP, settings.ledgerApp, LEDGER_APP_DEFAULT); 66 | this.#ledgerConn = withDefault(LEDGER_CONN, settings.ledgerConn, LEDGER_CONN_DEFAULT); 67 | this.#i18nLang = settings.i18nLang || LANGUAGE_DEFAULT; 68 | this.#icon = settings.icon || ICON_DEFAULT; 69 | this.#locking = settings.locking || LOCKING_DEFAULT; 70 | this.#metadataUp = withDefault(METADATA_UP, settings.storage, METADATA_UP_DEFAULT); 71 | this.#notification = settings.notification || NOTIFICATION_DEFAULT; 72 | this.#prefix = isUndefined(settings.prefix) ? PREFIX_DEFAULT : settings.prefix; 73 | this.#storage = withDefault(STORAGE, settings.storage, STORAGE_DEFAULT); 74 | this.#uiMode = settings.uiMode || UIMODE_DEFAULT; 75 | this.#uiTheme = settings.uiTheme || UITHEME_DEFAULT; 76 | } 77 | 78 | public get camera (): string { 79 | return this.#camera; 80 | } 81 | 82 | public get apiType (): Endpoint { 83 | return this.#apiType; 84 | } 85 | 86 | public get apiUrl (): string { 87 | return this.#apiUrl; 88 | } 89 | 90 | public get i18nLang (): string { 91 | return this.#i18nLang; 92 | } 93 | 94 | public get icon (): string { 95 | return this.#icon; 96 | } 97 | 98 | public get notification (): string { 99 | return this.#notification; 100 | } 101 | 102 | public get ledgerApp (): string { 103 | return this.#ledgerApp; 104 | } 105 | 106 | public get ledgerConn (): string { 107 | return this.#ledgerConn; 108 | } 109 | 110 | public get locking (): string { 111 | return this.#locking; 112 | } 113 | 114 | public get metadataUp (): string { 115 | return this.#metadataUp; 116 | } 117 | 118 | public get prefix (): number { 119 | return this.#prefix; 120 | } 121 | 122 | public get storage (): string { 123 | return this.#storage; 124 | } 125 | 126 | public get uiMode (): string { 127 | return this.#uiMode; 128 | } 129 | 130 | public get uiTheme (): string { 131 | return this.#uiTheme; 132 | } 133 | 134 | public get availableCamera (): Option[] { 135 | return CAMERA; 136 | } 137 | 138 | public get availableCryptos (): Option[] { 139 | return CRYPTOS; 140 | } 141 | 142 | public get availableCryptosEth (): Option[] { 143 | return CRYPTOS_ETH; 144 | } 145 | 146 | public get availableCryptosLedger (): Option[] { 147 | return CRYPTOS_LEDGER; 148 | } 149 | 150 | public get availableIcons (): Option[] { 151 | return ICONS; 152 | } 153 | 154 | public get availableLedgerApp (): Option[] { 155 | return LEDGER_APP; 156 | } 157 | 158 | public get availableLedgerConn (): Option[] { 159 | return LEDGER_CONN; 160 | } 161 | 162 | public get availableLocking (): Option[] { 163 | return LOCKING; 164 | } 165 | 166 | public get availableMetadataUp (): Option[] { 167 | return METADATA_UP; 168 | } 169 | 170 | public get availableNodes (): Option[] { 171 | return ENDPOINTS; 172 | } 173 | 174 | public get availablePrefixes (): Option[] { 175 | return PREFIXES; 176 | } 177 | 178 | public get availableStorage (): Option[] { 179 | return STORAGE; 180 | } 181 | 182 | public get availableUIModes (): Option[] { 183 | return UIMODES; 184 | } 185 | 186 | public get availableUIThemes (): Option[] { 187 | return UITHEMES; 188 | } 189 | 190 | public get (): SettingsStruct { 191 | return { 192 | apiType: this.#apiType, 193 | apiUrl: this.#apiUrl, 194 | camera: this.#camera, 195 | i18nLang: this.#i18nLang, 196 | icon: this.#icon, 197 | ledgerApp: this.#ledgerApp, 198 | ledgerConn: this.#ledgerConn, 199 | locking: this.#locking, 200 | metadataUp: this.#metadataUp, 201 | notification: this.#notification, 202 | prefix: this.#prefix, 203 | storage: this.#storage, 204 | uiMode: this.#uiMode, 205 | uiTheme: this.#uiTheme 206 | }; 207 | } 208 | 209 | public set (settings: Partial): void { 210 | this.#apiType = settings.apiType || this.#apiType; 211 | this.#apiUrl = settings.apiUrl || this.#apiUrl; 212 | this.#camera = settings.camera || this.#camera; 213 | this.#ledgerConn = settings.ledgerConn || this.#ledgerConn; 214 | this.#ledgerApp = settings.ledgerApp || this.#ledgerApp; 215 | this.#i18nLang = settings.i18nLang || this.#i18nLang; 216 | this.#icon = settings.icon || this.#icon; 217 | this.#locking = settings.locking || this.#locking; 218 | this.#metadataUp = settings.metadataUp || this.#metadataUp; 219 | this.#notification = settings.notification || this.#notification; 220 | this.#prefix = isUndefined(settings.prefix) ? this.#prefix : settings.prefix; 221 | this.#storage = settings.storage || this.#storage; 222 | this.#uiMode = settings.uiMode || this.#uiMode; 223 | this.#uiTheme = settings.uiTheme || this.#uiTheme; 224 | 225 | const newValues = this.get(); 226 | 227 | store.set('settings', newValues); 228 | this.#emitter.emit('change', newValues); 229 | } 230 | 231 | public on (type: OnTypes, cb: ChangeCallback): void { 232 | this.#emitter.on(type, cb); 233 | } 234 | } 235 | 236 | export const settings = new Settings(); 237 | -------------------------------------------------------------------------------- /packages/ui-settings/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { Settings, settings } from './Settings.js'; 5 | 6 | export { chains } from './defaults/chains.js'; 7 | export { ENDPOINT_DEFAULT, ICON_DEFAULT, ICON_DEFAULT_HOST, LANGUAGE_DEFAULT, LOCKING_DEFAULT, PREFIX_DEFAULT, UIMODE_DEFAULT, UITHEME_DEFAULT } from './defaults/index.js'; 8 | export { packageInfo } from './packageInfo.js'; 9 | 10 | export { Settings, settings }; 11 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/chains.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { selectableNetworks } from '@polkadot/networks'; 5 | import { objectSpread } from '@polkadot/util'; 6 | 7 | type ChainDef = string[]; 8 | 9 | export const chains: Record = /*#__PURE__*/ selectableNetworks 10 | .filter((n) => n.genesisHash.length) 11 | .reduce((chains, { genesisHash, network }) => objectSpread(chains, { [network]: genesisHash }), {}); 12 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/crypto.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | export const CRYPTOS: Option[] = [ 7 | { 8 | info: 'sr25519', 9 | text: 'Schnorrkel (sr25519, recommended)', 10 | value: 'sr25519' 11 | }, 12 | { 13 | info: 'ed25519', 14 | text: 'Edwards (ed25519, alternative)', 15 | value: 'ed25519' 16 | }, 17 | { 18 | info: 'ecdsa', 19 | text: 'ECDSA (Non BTC/ETH compatible)', 20 | value: 'ecdsa' 21 | } 22 | ]; 23 | 24 | export const CRYPTOS_ETH: Option[] = [ 25 | { 26 | info: 'ethereum', 27 | text: 'ECDSA (ETH compatible)', 28 | value: 'ethereum' 29 | } 30 | ]; 31 | 32 | export const CRYPTOS_LEDGER: Option[] = [ 33 | ...CRYPTOS, 34 | { 35 | info: 'ed25519-ledger', 36 | text: 'Ledger (ed25519, BIP32 derivation)', 37 | value: 'ed25519-ledger' 38 | } 39 | ]; 40 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/endpoints.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | export const ENDPOINTS: Option[] = [ 7 | { 8 | info: 'local', 9 | text: 'Local Node (Own, 127.0.0.1:9944)', 10 | value: 'ws://127.0.0.1:9944/' 11 | } 12 | ]; 13 | 14 | export const ENDPOINT_DEFAULT = ENDPOINTS[0]; 15 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | export { CRYPTOS, CRYPTOS_ETH, CRYPTOS_LEDGER } from './crypto.js'; 7 | export { ENDPOINT_DEFAULT, ENDPOINTS } from './endpoints.js'; 8 | export { LEDGER_APP, LEDGER_APP_DEFAULT, LEDGER_CONN, LEDGER_CONN_DEFAULT } from './ledger.js'; 9 | export { PREFIX_DEFAULT, PREFIXES } from './ss58.js'; 10 | export { ICON_DEFAULT, ICON_DEFAULT_HOST, ICONS, NOTIFICATION_DEFAULT, UIMODE_DEFAULT, UIMODES, UITHEME_DEFAULT, UITHEMES } from './ui.js'; 11 | 12 | export const CAMERA_DEFAULT = 'off'; 13 | 14 | export const CAMERA: Option[] = [ 15 | { 16 | info: 'on', 17 | text: 'Allow camera access', 18 | value: 'on' 19 | }, 20 | { 21 | info: 'off', 22 | text: 'Do not allow camera access', 23 | value: 'off' 24 | } 25 | ]; 26 | 27 | export const LANGUAGE_DEFAULT = 'default'; 28 | 29 | export const LOCKING_DEFAULT = 'session'; 30 | 31 | export const LOCKING: Option[] = [ 32 | { 33 | info: 'session', 34 | text: 'Once per session', 35 | value: 'session' 36 | }, 37 | { 38 | info: 'tx', 39 | text: 'On each transaction', 40 | value: 'tx' 41 | } 42 | ]; 43 | 44 | export const METADATA_UP_DEFAULT = 'off'; 45 | 46 | export const METADATA_UP: Option[] = [ 47 | { 48 | info: 'off', 49 | text: 'Do not auto-update extension metadata', 50 | value: 'off' 51 | }, 52 | { 53 | info: 'on', 54 | text: 'Auto-update extension metadata', 55 | value: 'on' 56 | } 57 | ]; 58 | 59 | export const STORAGE_DEFAULT = 'off'; 60 | 61 | export const STORAGE: Option[] = [ 62 | { 63 | info: 'on', 64 | text: 'Allow local in-browser account storage', 65 | value: 'on' 66 | }, 67 | { 68 | info: 'off', 69 | text: 'Do not allow local in-browser account storage', 70 | value: 'off' 71 | } 72 | ]; 73 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/ledger.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | export const LEDGER_CONN_DEFAULT = 'none'; 7 | 8 | export const LEDGER_CONN: Option[] = [ 9 | { 10 | info: 'none', 11 | text: 'Do not attach Ledger devices', 12 | value: 'none' 13 | }, 14 | // Deprecated 15 | // { 16 | // info: 'u2f', 17 | // text: 'Attach Ledger via U2F', 18 | // value: 'u2f' 19 | // }, 20 | { 21 | info: 'webusb', 22 | text: 'Attach Ledger via WebUSB (Chrome, recommended)', 23 | value: 'webusb' 24 | }, 25 | { 26 | info: 'hid', 27 | text: 'Attach Ledger via WebHID (Chrome, experimental)', 28 | value: 'hid' 29 | } 30 | ]; 31 | 32 | export const LEDGER_APP_DEFAULT = 'generic'; 33 | 34 | export const LEDGER_APP: Option[] = [ 35 | { 36 | info: 'generic', 37 | text: 'Use the Ledger Polkadot Generic App', 38 | value: 'generic' 39 | }, 40 | { 41 | info: 'migration', 42 | text: 'Use the Ledger Migration App', 43 | value: 'migration' 44 | }, 45 | { 46 | info: 'chainSpecific', 47 | text: 'Use the Chain Specific Ledger App', 48 | value: 'chainSpecific' 49 | } 50 | ]; 51 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/ss58.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | import { availableNetworks } from '@polkadot/networks'; 7 | 8 | export const PREFIX_DEFAULT = -1; 9 | 10 | const defaultNetwork: Option = { 11 | info: 'default', 12 | text: 'Default for the connected node', 13 | value: -1 14 | }; 15 | 16 | const networks = availableNetworks.map(({ displayName, network, prefix }) => ({ 17 | info: network, 18 | text: displayName, 19 | value: prefix 20 | })); 21 | 22 | export const PREFIXES: Option[] = [defaultNetwork, ...networks]; 23 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/type.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // matches https://polkadot.js.org & https://*.polkadot.io 5 | export const isPolkadot = typeof window !== 'undefined' && window.location.host.includes('polkadot'); 6 | -------------------------------------------------------------------------------- /packages/ui-settings/src/defaults/ui.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Option } from '../types.js'; 5 | 6 | import { isPolkadot } from './type.js'; 7 | 8 | export const LANGUAGE_DEFAULT = 'default'; 9 | 10 | export const UIMODE_DEFAULT = !isPolkadot && typeof window !== 'undefined' && window.location.host.includes('ui-light') 11 | ? 'light' 12 | : 'full'; 13 | 14 | export const UIMODES: Option[] = [ 15 | { 16 | info: 'full', 17 | text: 'Fully featured', 18 | value: 'full' 19 | }, 20 | { 21 | info: 'light', 22 | text: 'Basic features only', 23 | value: 'light' 24 | } 25 | ]; 26 | 27 | export const UITHEME_DEFAULT = isPolkadot 28 | ? 'polkadot' 29 | : 'substrate'; 30 | 31 | export const UITHEMES: Option[] = [ 32 | { 33 | info: 'polkadot', 34 | text: 'Polkadot', 35 | value: 'polkadot' 36 | }, 37 | { 38 | info: 'substrate', 39 | text: 'Substrate', 40 | value: 'substrate' 41 | } 42 | ]; 43 | 44 | export const ICON_DEFAULT = 'default'; 45 | 46 | export const ICON_DEFAULT_HOST = isPolkadot 47 | ? 'polkadot' 48 | : 'substrate'; 49 | 50 | export const ICONS: Option[] = [ 51 | { 52 | info: 'default', 53 | text: 'Default for the connected node', 54 | value: 'default' 55 | }, 56 | { 57 | info: 'polkadot', 58 | text: 'Polkadot', 59 | value: 'polkadot' 60 | }, 61 | { 62 | info: 'substrate', 63 | text: 'Substrate', 64 | value: 'substrate' 65 | }, 66 | { 67 | info: 'beachball', 68 | text: 'Beachball', 69 | value: 'beachball' 70 | } 71 | ]; 72 | 73 | export const NOTIFICATION_DEFAULT = 'popup'; 74 | -------------------------------------------------------------------------------- /packages/ui-settings/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { settings } from './bundle.js'; 5 | 6 | export * from './bundle.js'; 7 | 8 | export default settings; 9 | -------------------------------------------------------------------------------- /packages/ui-settings/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { detectPackage } from '@polkadot/util'; 8 | 9 | import { packageInfo } from './packageInfo.js'; 10 | 11 | detectPackage(packageInfo, null, []); 12 | -------------------------------------------------------------------------------- /packages/ui-settings/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/ui-settings', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/ui-settings/src/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-settings authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export interface Option { 5 | disabled?: boolean; 6 | info: string; 7 | text: string; 8 | value: string | number; 9 | } 10 | 11 | export interface SettingsStruct { 12 | apiType: Endpoint; 13 | apiUrl: string; 14 | camera: string; 15 | i18nLang: string; 16 | icon: string; 17 | ledgerApp: string; 18 | ledgerConn: string; 19 | locking: string; 20 | metadataUp: string; 21 | notification: string; 22 | prefix: number; 23 | storage: string; 24 | uiMode: string; 25 | uiTheme: string; 26 | } 27 | 28 | export interface NetworkSpecsStruct { 29 | color: string; 30 | decimals: number; 31 | genesisHash: string; 32 | prefix: number; 33 | title: string; 34 | unit: string; 35 | } 36 | 37 | export interface Endpoint { 38 | type: EndpointType; 39 | param: string; 40 | } 41 | 42 | export type EndpointType = 'json-rpc' | 'substrate-connect'; 43 | -------------------------------------------------------------------------------- /packages/ui-settings/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "references": [] 9 | } 10 | -------------------------------------------------------------------------------- /packages/ui-shared/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/ui-shared 2 | 3 | Shared logic for various logic. For identicon generation used inside the `@polkadot/{react, reactnative, vue}-identicon` icons. 4 | -------------------------------------------------------------------------------- /packages/ui-shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Shared logic that is usable accross all frameworks-specific areas", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-shared#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/ui-shared", 11 | "repository": { 12 | "directory": "packages/ui-shared", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "colord": "^2.9.3", 25 | "tslib": "^2.8.1" 26 | }, 27 | "devDependencies": { 28 | "@polkadot/util": "^13.5.1", 29 | "@polkadot/util-crypto": "^13.5.1", 30 | "@types/xmlserializer": "^0.6.6", 31 | "xmlserializer": "^0.6.1" 32 | }, 33 | "peerDependencies": { 34 | "@polkadot/util": "*", 35 | "@polkadot/util-crypto": "*" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/ui-shared/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './icons/index.js'; 5 | export { packageInfo } from './packageInfo.js'; 6 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/LICENSE: -------------------------------------------------------------------------------- 1 | Apache-2.0 License (Apache-2.0) 2 | 3 | Copyright 2016 Dan Finlay 4 | Copyright 2017-2025 @polkadot/ui-shared authors & contributors 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DApache-2.0LAIMS ALL WARRANTIES WITH 11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 12 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 14 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 15 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 | PERFORMANCE OF THIS SOFTWARE. 17 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/ui-shared/beachball 2 | 3 | Adapted from [Jazzicon](https://github.com/danfinlay/jazzicon) by Dan Finlay with the following changes - 4 | 5 | - Random values now is read from the Uint8Array supplied (as opposed to having the seed as a number). This allows us to give an publicKey/address as an input and use those values in the pattern generation. 6 | - Upgrade to the underlying [color](https://github.com/Qix-/color) library 7 | - Generate circles as shapes (instead of rectangles) 8 | - Interface updated to take in optional className & style 9 | - Update everywhere to use ES6 10 | - Split source into self-contained functions (TODO: future testing) 11 | - Everything has been updated to use flow 12 | - Test the library functions 13 | - Copyright headers added (original also under Apache-2.0) 14 | 15 | ## Usage 16 | 17 | Also see [src/demo.js](src/demo.js) for a randomly generated example. 18 | 19 | ![demo](https://raw.githubusercontent.com/polkadot-js/ui/master/packages/ui-shared/demo.png) 20 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/colors.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import type { ColorGen } from './types.js'; 7 | 8 | import { colors as newColors } from './colors.js'; 9 | import { seeder as newSeeder } from './seeder.js'; 10 | 11 | describe('colors', (): void => { 12 | let colors: ColorGen; 13 | 14 | beforeEach((): void => { 15 | colors = newColors(newSeeder()); 16 | }); 17 | 18 | it('generates using default alpha', (): void => { 19 | expect( 20 | colors() 21 | ).toEqual( 22 | // with original color ... 23 | // 'hsl(37.19999999999999, 100%, 54.9%)' 24 | 'hsl(37, 100%, 55%)' 25 | ); 26 | }); 27 | 28 | it('applies specified alpha', (): void => { 29 | expect( 30 | colors(0.5) 31 | ).toEqual( 32 | // with original color ... 33 | // 'hsla(37.19999999999999, 100%, 54.9%, 0.5)' 34 | 'hsla(37, 100%, 55%, 0.5)' 35 | ); 36 | }); 37 | 38 | it('rolates colors', (): void => { 39 | const orig = colors(); 40 | 41 | expect( 42 | orig 43 | ).toEqual('hsl(37, 100%, 55%)'); 44 | 45 | expect( 46 | colors() 47 | ).not.toEqual(orig); 48 | }); 49 | 50 | it('works in edge conditions (0xff)', (): void => { 51 | const u8a = new Uint8Array(32); 52 | 53 | u8a.fill(255); 54 | 55 | expect( 56 | colors = newColors(newSeeder(u8a)) 57 | ).not.toThrow(); 58 | 59 | expect( 60 | colors() 61 | ).toEqual( 62 | // with original color ... 63 | // 'hsl(15, 0%, 100%)' 64 | 'hsl(0, 0%, 100%)' 65 | ); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/colors.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import type { Colord } from 'colord'; 7 | import type { ColorGen, Seeder } from './types.js'; 8 | 9 | import { colord } from 'colord'; 10 | 11 | import { COLORS } from './defaults.js'; 12 | 13 | const WOBBLE = 30; 14 | 15 | export function colors (seeder: Seeder): ColorGen { 16 | const amount = (seeder() * WOBBLE) - (WOBBLE / 2); 17 | const all = COLORS.map((hex): Colord => 18 | colord(hex).rotate(amount) 19 | ); 20 | 21 | return (alpha = 1): string => { 22 | const index = Math.floor(all.length * seeder()); 23 | 24 | return all.splice(index, 1)[0] 25 | .alpha(alpha) 26 | .toHslString(); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/container.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import { container } from './container.js'; 7 | 8 | describe('container', (): void => { 9 | it('applies default styles', (): void => { 10 | expect( 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access 12 | (container(100).style as any)._values 13 | ).toMatchObject({ 14 | background: 'white', 15 | 'border-radius': '50px', 16 | display: 'inline-block', 17 | height: '100px', 18 | margin: '0px', 19 | overflow: 'hidden', 20 | padding: '0px', 21 | width: '100px' 22 | }); 23 | }); 24 | 25 | it('overrides with supplied styles', (): void => { 26 | expect( 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-member-access 28 | (container(50, 'black', '', { display: 'block' }).style as any)._values 29 | ).toMatchObject({ 30 | background: 'black', 31 | 'border-radius': '25px', 32 | display: 'block', 33 | height: '50px', 34 | margin: '0px', 35 | overflow: 'hidden', 36 | padding: '0px', 37 | width: '50px' 38 | }); 39 | }); 40 | 41 | it('applies the specified className', (): void => { 42 | expect( 43 | container(100, 'blue', 'testClass').className 44 | ).toEqual('testClass'); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/container.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | export function container (diameter: number, background = 'white', className = '', _style: Record = {}): HTMLElement { 7 | const element = document.createElement('div'); 8 | const style = Object.assign({ 9 | background, 10 | borderRadius: `${diameter / 2}px`, 11 | display: 'inline-block', 12 | height: `${diameter}px`, 13 | margin: '0px', 14 | overflow: 'hidden', 15 | padding: '0px', 16 | width: `${diameter}px` 17 | }, _style); 18 | 19 | element.className = className; 20 | element.style.background = background; 21 | 22 | Object.keys(style).forEach((key: unknown): void => { 23 | element.style[key as number] = style[key as number]; 24 | }); 25 | 26 | return element; 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/defaults.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | export const COLORS: readonly string[] = [ 7 | // https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/ 8 | '#ffe119', '#4363d8', '#f58231', '#fabebe', '#e6beff', '#800000', '#000075', '#a9a9a9', '#ffffff', '#000000' 9 | ] as const; 10 | 11 | export const SHAPE_COUNT = 5 as const; 12 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/demo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import { isNull } from '@polkadot/util'; 7 | import { encodeAddress, randomAsU8a } from '@polkadot/util-crypto'; 8 | 9 | import { beachballIcon } from './index.js'; 10 | 11 | const element = document.getElementById('demo'); 12 | 13 | function generateIcon (seed: string = encodeAddress(randomAsU8a(32))): void { 14 | const start = Date.now(); 15 | 16 | if (isNull(element)) { 17 | throw new Error('Unable to find #demo element'); 18 | } 19 | 20 | element.appendChild( 21 | beachballIcon(seed, { isAlternative: false, size: 100 }, 'padded') 22 | ); 23 | 24 | console.log(`Icon generated in ${(Date.now() - start)}ms`); 25 | } 26 | 27 | function generateIcons (count = 512): void { 28 | generateIcon(encodeAddress(new Uint8Array(32))); 29 | 30 | for (let index = 1; index < count; index++) { 31 | generateIcon(); 32 | } 33 | } 34 | 35 | generateIcons(); 36 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/index.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xmlserializer from 'xmlserializer'; 7 | 8 | import { beachballIcon } from './index.js'; 9 | 10 | describe('identicon', (): void => { 11 | it('generates a basic [0,..,0] identicon', (): void => { 12 | expect( 13 | xmlserializer.serializeToString( 14 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 15 | beachballIcon(new Uint8Array(32), { isAlternative: false, size: 256 }) as any 16 | ) 17 | ).toEqual( 18 | // with original color ... 19 | // '
' 20 | '
' 21 | ); 22 | }); 23 | 24 | it('allows overrides', (): void => { 25 | expect( 26 | xmlserializer.serializeToString( 27 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 28 | beachballIcon(new Uint8Array(32), { isAlternative: false, size: 100 }, 'testClass', { display: 'block' }) as any 29 | ) 30 | ).toEqual( 31 | // with original color ... 32 | // '
' 33 | '
' 34 | ); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import type { Options } from '../types.js'; 7 | 8 | import { circle } from './shape/circle.js'; 9 | import { element } from './svg/element.js'; 10 | import { colors } from './colors.js'; 11 | import { container as newContainer } from './container.js'; 12 | import { SHAPE_COUNT } from './defaults.js'; 13 | import { seeder as newSeeder } from './seeder.js'; 14 | 15 | export function beachballIcon (seed: string | Uint8Array, { size = 256 }: Options, className = '', style?: Record): HTMLElement { 16 | const seeder = newSeeder(seed); 17 | const colorGen = colors(seeder); 18 | const outer = newContainer(size, 'white', className, style); 19 | const container = newContainer(size, colorGen()); 20 | const svg = element(size); 21 | 22 | outer.appendChild(container); 23 | container.appendChild(svg); 24 | 25 | for (let count = 0; count < SHAPE_COUNT; count++) { 26 | const fill = colorGen(); 27 | const shape = circle(seeder, fill, size, count); 28 | 29 | svg.appendChild(shape); 30 | } 31 | 32 | return outer; 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/seeder.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import type { Seeder } from './types.js'; 7 | 8 | import { seeder as newSeeder } from './seeder.js'; 9 | 10 | describe('seeder', (): void => { 11 | let seeder: Seeder; 12 | 13 | beforeEach((): void => { 14 | seeder = newSeeder(new Uint8Array([1, 2, 3, 4])); 15 | }); 16 | 17 | it('generates numbers using 2 spaces', (): void => { 18 | expect( 19 | seeder() 20 | ).toEqual(0.0156402587890625); 21 | }); 22 | 23 | it('generates numbers using 2 spaces (incremented)', (): void => { 24 | seeder(); 25 | 26 | expect( 27 | seeder() 28 | ).toEqual(0.0078582763671875); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/seeder.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { Seeder } from './types.js'; 5 | 6 | import { isU8a, stringToU8a } from '@polkadot/util'; 7 | 8 | const DIVISOR = 256 * 256; 9 | 10 | export function seeder (_seed: string | Uint8Array = new Uint8Array(32)): Seeder { 11 | const seed: Uint8Array = isU8a(_seed) 12 | ? _seed 13 | : stringToU8a(_seed); 14 | 15 | let index = (seed[Math.floor(seed.length / 2)] % seed.length) - 1; 16 | 17 | const next = (): number => { 18 | index += 1; 19 | 20 | if (index === seed.length) { 21 | index = 0; 22 | } 23 | 24 | return seed[index]; 25 | }; 26 | 27 | return (): number => { 28 | return ((next() * 256) + next()) / DIVISOR; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/shape/circle.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xmlserializer from 'xmlserializer'; 7 | 8 | import { seeder } from '../seeder.js'; 9 | import { circle } from './circle.js'; 10 | 11 | describe('circle', (): void => { 12 | it('creates a circle shape', (): void => { 13 | expect( 14 | xmlserializer.serializeToString( 15 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 16 | circle(seeder(), 'blue', 50, 2) as any 17 | ) 18 | ).toEqual(''); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/shape/circle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import type { Seeder } from '../types.js'; 7 | 8 | import { SHAPE_COUNT } from '../defaults.js'; 9 | import { circle as newCircle } from '../svg/circle.js'; 10 | 11 | export function circle (seeder: Seeder, fill: string, diameter: number, count: number): Element { 12 | const center = diameter / 2; 13 | const angle = seeder() * 360; 14 | const radius = (((SHAPE_COUNT - count) / SHAPE_COUNT) * (diameter / 2)) + ((diameter / 8) * seeder()); 15 | const offset = (diameter / 4) * (seeder() + ((count + 1) / SHAPE_COUNT)); 16 | const cx = (offset * Math.sin(angle)) + center; 17 | const cy = (offset * Math.cos(angle)) + center; 18 | const svg = newCircle(radius, cx, cy); 19 | 20 | svg.setAttributeNS('', 'fill', fill); 21 | 22 | return svg; 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/shape/square.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import type { Seeder } from '../types.js'; 7 | 8 | import { SHAPE_COUNT } from '../defaults.js'; 9 | import { rect as newRect } from '../svg/rect.js'; 10 | 11 | export function square (seeder: Seeder, fill: string, diameter: number, count: number): Element { 12 | const center = diameter / 2; 13 | const svg = newRect(diameter); 14 | const firstRot = seeder(); 15 | const angle = Math.PI * 2 * firstRot; 16 | const scale = count / SHAPE_COUNT; 17 | const velocity = ((diameter / SHAPE_COUNT) * seeder()) + (scale * diameter); 18 | const tx = (Math.cos(angle) * velocity).toFixed(3); 19 | const ty = (Math.sin(angle) * velocity).toFixed(3); 20 | const rot = ((firstRot * 360) + (seeder() * 180)).toFixed(1); 21 | 22 | svg.setAttributeNS('', 'transform', `translate(${tx} ${ty}) rotate(${rot} ${center} ${center})`); 23 | svg.setAttributeNS('', 'fill', fill); 24 | 25 | return svg; 26 | } 27 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/circle.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xs from 'xmlserializer'; 7 | 8 | import { circle } from './circle.js'; 9 | 10 | describe('circle', (): void => { 11 | it('creates a basic SVG circle element', (): void => { 12 | expect( 13 | xs.serializeToString( 14 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 15 | circle(123, 12, 34) as any 16 | ) 17 | ).toEqual(''); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/circle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { svg } from './svg.js'; 5 | 6 | export function circle (r: number, cx: number, cy: number): Element { 7 | const elem = svg('circle'); 8 | 9 | elem.setAttributeNS('', 'cx', `${cx}`); 10 | elem.setAttributeNS('', 'cy', `${cy}`); 11 | elem.setAttributeNS('', 'r', `${r}`); 12 | 13 | return elem; 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/element.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xs from 'xmlserializer'; 7 | 8 | import { element } from './element.js'; 9 | 10 | describe('element', (): void => { 11 | it('creates a basic SVG element', (): void => { 12 | expect( 13 | xs.serializeToString( 14 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 15 | element(123) as any 16 | ) 17 | ).toEqual(''); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/element.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | import { svg } from './svg.js'; 7 | 8 | export function element (size: number, type = 'svg', x = 0, y = 0): Element { 9 | const elem = svg(type); 10 | 11 | elem.setAttributeNS('', 'x', `${x}`); 12 | elem.setAttributeNS('', 'y', `${y}`); 13 | elem.setAttributeNS('', 'width', `${size}`); 14 | elem.setAttributeNS('', 'height', `${size}`); 15 | 16 | return elem; 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/rect.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xs from 'xmlserializer'; 7 | 8 | import { rect } from './rect.js'; 9 | 10 | describe('rect', (): void => { 11 | it('creates a basic SVG rect element', (): void => { 12 | expect( 13 | xs.serializeToString( 14 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 15 | rect(123) as any 16 | ) 17 | ).toEqual(''); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/rect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { element } from './element.js'; 5 | 6 | export function rect (size: number): Element { 7 | const elem = element(size, 'rect'); 8 | 9 | elem.setAttributeNS('', 'rx', `${size / 16}`); 10 | elem.setAttributeNS('', 'ry', `${size / 16}`); 11 | 12 | return elem; 13 | } 14 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/svg.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import xs from 'xmlserializer'; 7 | 8 | import { svg } from './svg.js'; 9 | 10 | describe('svg', (): void => { 11 | it('creates a basic SVG element', (): void => { 12 | expect( 13 | xs.serializeToString( 14 | // eslint-disable-next-line @typescript-eslint/no-unsafe-argument,@typescript-eslint/no-explicit-any 15 | svg('rect') as any 16 | ) 17 | ).toEqual(''); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/svg/svg.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2016 Dan Finlay 5 | 6 | const SVG_NS = 'http://www.w3.org/2000/svg'; 7 | 8 | export function svg (type: string): Element { 9 | return document.createElementNS(SVG_NS, type); 10 | } 11 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/beachball/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export type Seeder = () => number; 5 | 6 | export type ColorGen = (alpha?: number) => string; 7 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { beachballIcon } from './beachball/index.js'; 5 | export { polkadotIcon } from './polkadot.js'; 6 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/polkadot.spec.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | /// 5 | 6 | import { polkadotIcon } from './polkadot.js'; 7 | 8 | describe('polkadotIcon', (): void => { 9 | it('generates the correct points from known', (): void => { 10 | expect( 11 | polkadotIcon('5Dqvi1p4C7EhPPFKCixpF3QiaJEaDwWrR9gfWR5eUsfC39TX', { isAlternative: false }) 12 | ).toEqual([ 13 | { cx: 32, cy: 32, fill: '#eee', r: 32 }, 14 | { cx: 32, cy: 8, fill: 'hsl(196, 65%, 53%)', r: 5 }, 15 | { cx: 32, cy: 20, fill: 'hsl(320, 65%, 53%)', r: 5 }, 16 | { cx: 21.607695154586736, cy: 14, fill: 'transparent', r: 5 }, 17 | { cx: 11.215390309173472, cy: 20, fill: 'hsl(112, 65%, 15%)', r: 5 }, 18 | { cx: 21.607695154586736, cy: 26, fill: 'hsl(22, 65%, 15%)', r: 5 }, 19 | { cx: 11.215390309173472, cy: 32, fill: 'hsl(213, 65%, 15%)', r: 5 }, 20 | { cx: 11.215390309173472, cy: 44, fill: 'hsl(163, 65%, 53%)', r: 5 }, 21 | { cx: 21.607695154586736, cy: 38, fill: 'hsl(213, 65%, 53%)', r: 5 }, 22 | { cx: 21.607695154586736, cy: 50, fill: 'hsl(185, 65%, 75%)', r: 5 }, 23 | { cx: 32, cy: 56, fill: 'hsl(163, 65%, 53%)', r: 5 }, 24 | { cx: 32, cy: 44, fill: 'hsl(213, 65%, 53%)', r: 5 }, 25 | { cx: 42.392304845413264, cy: 50, fill: 'hsl(213, 65%, 15%)', r: 5 }, 26 | { cx: 52.78460969082653, cy: 44, fill: 'hsl(112, 65%, 15%)', r: 5 }, 27 | { cx: 42.392304845413264, cy: 38, fill: 'hsl(22, 65%, 15%)', r: 5 }, 28 | { cx: 52.78460969082653, cy: 32, fill: 'transparent', r: 5 }, 29 | { cx: 52.78460969082653, cy: 20, fill: 'hsl(196, 65%, 53%)', r: 5 }, 30 | { cx: 42.392304845413264, cy: 26, fill: 'hsl(320, 65%, 53%)', r: 5 }, 31 | { cx: 42.392304845413264, cy: 14, fill: 'hsl(11, 65%, 35%)', r: 5 }, 32 | { cx: 32, cy: 32, fill: 'hsl(309, 65%, 53%)', r: 5 } 33 | ]); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/polkadot.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Copyright 2018 Paritytech via paritytech/oo7/polkadot-identicon 5 | 6 | // This has been converted from the original version that can be found at 7 | // 8 | // https://github.com/paritytech/oo7/blob/251ba2b7c45503b68eab4320c270b5afa9bccb60/packages/polkadot-identicon/src/index.jsx 9 | 10 | import type { Circle, Options } from './types.js'; 11 | 12 | import { blake2AsU8a, decodeAddress } from '@polkadot/util-crypto'; 13 | 14 | interface Scheme { 15 | freq: number; 16 | colors: readonly number[]; 17 | } 18 | 19 | const S = 64; 20 | const C = S / 2; 21 | const Z = S / 64 * 5; 22 | 23 | const SCHEMES: readonly Scheme[] = [ 24 | /* target */ { colors: [0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 1], freq: 1 }, 25 | /* cube */ { colors: [0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 5], freq: 20 }, 26 | /* quazar */ { colors: [1, 2, 3, 1, 2, 4, 5, 5, 4, 1, 2, 3, 1, 2, 4, 5, 5, 4, 0], freq: 16 }, 27 | /* flower */ { colors: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 3], freq: 32 }, 28 | /* cyclic */ { colors: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6], freq: 32 }, 29 | /* vmirror */ { colors: [0, 1, 2, 3, 4, 5, 3, 4, 2, 0, 1, 6, 7, 8, 9, 7, 8, 6, 10], freq: 128 }, 30 | /* hmirror */ { colors: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 6, 7, 5, 3, 4, 2, 11], freq: 128 } 31 | ]; 32 | 33 | const SCHEMES_TOTAL = SCHEMES 34 | .map((s): number => s.freq) 35 | .reduce((a, b): number => a + b); 36 | 37 | const OUTER_CIRCLE: Circle = { 38 | cx: C, 39 | cy: C, 40 | fill: '#eee', 41 | r: C 42 | }; 43 | 44 | let zeroHash: Uint8Array = new Uint8Array(); 45 | 46 | function getRotation (isSixPoint: boolean): { r: number; ro2: number; r3o4: number; ro4: number; rroot3o2: number; rroot3o4: number } { 47 | const r = isSixPoint 48 | ? (C / 8 * 5) 49 | : (C / 4 * 3); 50 | const rroot3o2 = r * Math.sqrt(3) / 2; 51 | const ro2 = r / 2; 52 | const rroot3o4 = r * Math.sqrt(3) / 4; 53 | const ro4 = r / 4; 54 | const r3o4 = r * 3 / 4; 55 | 56 | return { r, r3o4, ro2, ro4, rroot3o2, rroot3o4 }; 57 | } 58 | 59 | function getCircleXY (isSixPoint = false): [number, number][] { 60 | const { r, r3o4, ro2, ro4, rroot3o2, rroot3o4 } = getRotation(isSixPoint); 61 | 62 | return [ 63 | [C, C - r], 64 | [C, C - ro2], 65 | [C - rroot3o4, C - r3o4], 66 | [C - rroot3o2, C - ro2], 67 | [C - rroot3o4, C - ro4], 68 | [C - rroot3o2, C], 69 | [C - rroot3o2, C + ro2], 70 | [C - rroot3o4, C + ro4], 71 | [C - rroot3o4, C + r3o4], 72 | [C, C + r], 73 | [C, C + ro2], 74 | [C + rroot3o4, C + r3o4], 75 | [C + rroot3o2, C + ro2], 76 | [C + rroot3o4, C + ro4], 77 | [C + rroot3o2, C], 78 | [C + rroot3o2, C - ro2], 79 | [C + rroot3o4, C - ro4], 80 | [C + rroot3o4, C - r3o4], 81 | [C, C] 82 | ]; 83 | } 84 | 85 | function findScheme (d: number): Scheme { 86 | let cum = 0; 87 | const schema = SCHEMES.find((schema): boolean => { 88 | cum += schema.freq; 89 | 90 | return d < cum; 91 | }); 92 | 93 | if (!schema) { 94 | throw new Error('Unable to find schema'); 95 | } 96 | 97 | return schema; 98 | } 99 | 100 | function addressToId (address: string): Uint8Array { 101 | if (!zeroHash.length) { 102 | zeroHash = blake2AsU8a(new Uint8Array(32), 512); 103 | } 104 | 105 | return blake2AsU8a(decodeAddress(address), 512).map((x, i) => (x + 256 - zeroHash[i]) % 256); 106 | } 107 | 108 | function getColors (address: string): string[] { 109 | const id = addressToId(address); 110 | const d = Math.floor((id[30] + id[31] * 256) % SCHEMES_TOTAL); 111 | const rot = (id[28] % 6) * 3; 112 | const sat = (Math.floor(id[29] * 70 / 256 + 26) % 80) + 30; 113 | const scheme = findScheme(d); 114 | const palette = Array.from(id).map((x, i): string => { 115 | const b = (x + i % 28 * 58) % 256; 116 | 117 | if (b === 0) { 118 | return '#444'; 119 | } else if (b === 255) { 120 | return 'transparent'; 121 | } 122 | 123 | const h = Math.floor(b % 64 * 360 / 64); 124 | const l = [53, 15, 35, 75][Math.floor(b / 64)]; 125 | 126 | return `hsl(${h}, ${sat}%, ${l}%)`; 127 | }); 128 | 129 | return scheme.colors.map((_, i): string => 130 | palette[scheme.colors[i < 18 ? (i + rot) % 18 : 18]] 131 | ); 132 | } 133 | 134 | /** 135 | * @description Generates an array of the circles that make up an identicon 136 | */ 137 | export function polkadotIcon (address: string, { isAlternative }: Options): Circle[] { 138 | const xy = getCircleXY(isAlternative); 139 | let colors: string[]; 140 | 141 | try { 142 | // in some cases, e.g. RN where crypto may not be initialized, chaos can 143 | // happen when hashing, in these cases we just fill with a placeholder 144 | colors = getColors(address); 145 | } catch { 146 | colors = new Array(xy.length).fill('#ddd'); 147 | } 148 | 149 | return [OUTER_CIRCLE].concat( 150 | xy.map(([cx, cy], index): Circle => ({ 151 | cx, cy, fill: colors[index], r: Z 152 | })) 153 | ); 154 | } 155 | -------------------------------------------------------------------------------- /packages/ui-shared/src/icons/types.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export interface Circle { 5 | cx: number; 6 | cy: number; 7 | fill: string; 8 | r: number; 9 | } 10 | 11 | export interface Options { 12 | isAlternative?: boolean; 13 | size?: number; 14 | } 15 | -------------------------------------------------------------------------------- /packages/ui-shared/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2018-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | export * from './bundle.js'; 7 | -------------------------------------------------------------------------------- /packages/ui-shared/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { detectPackage } from '@polkadot/util'; 8 | 9 | import { packageInfo } from './packageInfo.js'; 10 | 11 | detectPackage(packageInfo, null, []); 12 | -------------------------------------------------------------------------------- /packages/ui-shared/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/ui-shared', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/ui-shared/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "exclude": [ 9 | "**/*.spec.ts" 10 | ], 11 | "references": [] 12 | } 13 | -------------------------------------------------------------------------------- /packages/ui-shared/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src", 7 | "emitDeclarationOnly": false, 8 | "noEmit": true 9 | }, 10 | "include": [ 11 | "**/*.spec.ts" 12 | ], 13 | "references": [ 14 | { "path": "../ui-shared/tsconfig.build.json" } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/vue-identicon/README.md: -------------------------------------------------------------------------------- 1 | # @polkadot/vue-identicon 2 | 3 | A generic identity icon that can render icons based on an address. 4 | 5 | ## Usage Examples 6 | 7 | To install the component, do `yarn add @polkadot/vue-identicon` and then use it with `import Identicon from '@polkadot/vue-identicon';` 8 | 9 | Inside a Vue component, you can now render any account with the associated icon, with associated props - 10 | 11 | - `value` - the address you wish to display 12 | - `size` (optional, defaults to `64`) - the size in pixels 13 | - `theme` (optional, defaults to `substrate`) - the theme to use, one of 14 | - `polkadot` or 15 | - `substrate` (equivalent to `jdenticon`) or 16 | - `beachball` or 17 | - `empty` (displays nothing) 18 | 19 | ``` 20 | 27 | 28 | 37 | ``` 38 | -------------------------------------------------------------------------------- /packages/vue-identicon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/vue-identicon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Jaco Greeff ", 3 | "bugs": "https://github.com/polkadot-js/ui/issues", 4 | "description": "Renders an SVG picture representing an address", 5 | "engines": { 6 | "node": ">=18" 7 | }, 8 | "homepage": "https://github.com/polkadot-js/ui/tree/master/packages/vue-identicon#readme", 9 | "license": "Apache-2.0", 10 | "name": "@polkadot/vue-identicon", 11 | "repository": { 12 | "directory": "packages/vue-identicon", 13 | "type": "git", 14 | "url": "https://github.com/polkadot-js/ui.git" 15 | }, 16 | "sideEffects": [ 17 | "./packageDetect.js", 18 | "./packageDetect.cjs" 19 | ], 20 | "type": "module", 21 | "version": "3.14.1", 22 | "main": "index.js", 23 | "dependencies": { 24 | "@polkadot/ui-shared": "3.14.1", 25 | "@polkadot/util": "^13.5.1", 26 | "@polkadot/util-crypto": "^13.5.1", 27 | "jdenticon": "3.2.0", 28 | "tslib": "^2.8.1" 29 | }, 30 | "devDependencies": { 31 | "vue": "^2.7.16" 32 | }, 33 | "peerDependencies": { 34 | "@polkadot/util": "*", 35 | "@polkadot/util-crypto": "*", 36 | "vue": ">= 2.7" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/Identicon.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { VNode } from 'vue'; 5 | import type { Prefix } from '@polkadot/util-crypto/address/types'; 6 | 7 | import { defineComponent, h } from 'vue'; 8 | 9 | import { isHex, isU8a, u8aToHex } from '@polkadot/util'; 10 | import { decodeAddress, encodeAddress, isEthereumAddress } from '@polkadot/util-crypto'; 11 | 12 | import { Beachball, Empty, Jdenticon, Polkadot } from './icons/index.js'; 13 | import { adaptVNodeAttrs } from './util.js'; 14 | 15 | interface Account { 16 | address: string; 17 | publicKey: string; 18 | } 19 | 20 | interface Data { 21 | address: string; 22 | iconSize: number; 23 | isAlternativeIcon: boolean; 24 | publicKey: string; 25 | type: 'beachball' | 'empty' | 'jdenticon' | 'polkadot' | 'substrate'; 26 | } 27 | 28 | const DEFAULT_SIZE = 64; 29 | 30 | function resolvePublicKey (value: string | Uint8Array, prefix?: Prefix): string { 31 | if (isHex(value) && isEthereumAddress(value)) { 32 | return value.padEnd(66, '0'); 33 | } 34 | 35 | return isU8a(value) || isHex(value) 36 | ? encodeAddress(value as string, prefix) 37 | : value; 38 | } 39 | 40 | export function encodeAccount (value: string | Uint8Array, prefix?: Prefix): Account { 41 | try { 42 | const address = resolvePublicKey(value, prefix); 43 | const publicKey = u8aToHex(decodeAddress(address, false, prefix)); 44 | 45 | return { address, publicKey }; 46 | } catch { 47 | return { address: '', publicKey: '0x' }; 48 | } 49 | } 50 | 51 | /** 52 | * @name Identicon 53 | * @description The main Identicon component, taking a number of properties 54 | * @example 55 | * ```html 56 | * 57 | * ``` 58 | */ 59 | export const Identicon = defineComponent({ 60 | components: { 61 | Beachball, 62 | Empty, 63 | Jdenticon, 64 | Polkadot 65 | }, 66 | created: function (): void { 67 | this.createData(); 68 | }, 69 | data: function (): Data { 70 | return { 71 | address: '', 72 | iconSize: DEFAULT_SIZE, 73 | isAlternativeIcon: false, 74 | publicKey: '0x', 75 | type: 'empty' 76 | }; 77 | }, 78 | methods: { 79 | createData: function (): void { 80 | this.iconSize = this.size as number || DEFAULT_SIZE; 81 | this.type = this.theme as 'empty'; 82 | this.isAlternativeIcon = this.isAlternative as boolean || false; 83 | this.recodeAddress(); 84 | }, 85 | recodeAddress: function (): void { 86 | const { address, publicKey } = encodeAccount(this.value as string); 87 | 88 | this.address = address; 89 | this.publicKey = publicKey; 90 | } 91 | }, 92 | props: ['prefix', 'isAlternative', 'size', 'theme', 'value'], 93 | render (): VNode { 94 | const { address, iconSize, isAlternativeIcon, publicKey, type } = this.$data; 95 | 96 | if (type === 'empty') { 97 | return h(Empty, { 98 | ...adaptVNodeAttrs({ 99 | key: address, 100 | size: iconSize 101 | }) 102 | }, []); 103 | } else if (type === 'jdenticon') { 104 | return h(Jdenticon, { 105 | ...adaptVNodeAttrs( 106 | { 107 | key: address, 108 | publicKey, 109 | size: iconSize 110 | } 111 | ) 112 | }, []); 113 | } else if (type === 'substrate') { 114 | throw new Error('substrate type is not supported'); 115 | } 116 | 117 | const cmp = type.charAt(0).toUpperCase() + type.slice(1); 118 | 119 | if (['Beachball', 'Polkadot'].includes(cmp)) { 120 | const component = cmp === 'Beachball' 121 | ? Beachball 122 | : Polkadot; 123 | 124 | return h(component, { 125 | ...adaptVNodeAttrs({ 126 | address, 127 | isAlternative: isAlternativeIcon, 128 | key: address, 129 | size: iconSize 130 | }) 131 | }, []); 132 | } else { 133 | return h(cmp, {}, []); 134 | } 135 | }, 136 | watch: { 137 | value: function (): void { 138 | this.recodeAddress(); 139 | } 140 | } 141 | }); 142 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/bundle.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export { Identicon } from './Identicon.js'; 5 | export { packageInfo } from './packageInfo.js'; 6 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/icons/Beachball.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { VNode } from 'vue'; 5 | 6 | import { defineComponent, h } from 'vue'; 7 | 8 | import { beachballIcon } from '@polkadot/ui-shared'; 9 | 10 | interface PropsType { 11 | address: string; 12 | size: number; 13 | isAlternative: boolean; 14 | } 15 | 16 | /** 17 | * @name Beachball 18 | * @description The Beachball identicon 19 | */ 20 | export const Beachball = defineComponent({ 21 | props: ['address', 'size', 'isAlternative'], 22 | render (): VNode { 23 | const { address, isAlternative, size } = this.$props as PropsType; 24 | 25 | return h({ 26 | template: beachballIcon(address, { 27 | isAlternative, 28 | size 29 | }).outerHTML 30 | }); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/icons/Empty.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { defineComponent } from 'vue'; 5 | 6 | /** 7 | * @name Empty 8 | * @description An empty identicon 9 | */ 10 | export const Empty = defineComponent({ 11 | props: ['size'], 12 | template: ` 13 | 14 | 15 | 16 | ` 17 | }); 18 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/icons/Jdenticon.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { VNode } from 'vue'; 5 | 6 | import * as jdenticon from 'jdenticon'; 7 | import { defineComponent, h } from 'vue'; 8 | 9 | interface PropsType { 10 | publicKey: string, 11 | size: number 12 | } 13 | 14 | /** 15 | * @name Jdenticon 16 | * @description The substrate default via Jdenticon 17 | */ 18 | export const Jdenticon = defineComponent({ 19 | props: ['publicKey', 'size'], 20 | render (): VNode { 21 | const { publicKey, size } = this.$props as PropsType; 22 | 23 | return h({ 24 | template: jdenticon.toSvg(publicKey.substring(2), size) 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/icons/Polkadot.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import type { VNode } from 'vue'; 5 | 6 | import { defineComponent, h } from 'vue'; 7 | 8 | import { polkadotIcon } from '@polkadot/ui-shared'; 9 | 10 | import { adaptVNodeAttrs } from '../util.js'; 11 | 12 | interface propsType { 13 | address: string; 14 | isAlternative: boolean; 15 | size: number; 16 | } 17 | 18 | /** 19 | * @name Polkadot 20 | * @description The Polkadot default identicon 21 | */ 22 | export const Polkadot = defineComponent({ 23 | props: ['address', 'isAlternative', 'size'], 24 | render (): VNode { 25 | const { address, isAlternative, size } = this.$props as propsType; 26 | const circles = polkadotIcon(address, { isAlternative }).map(({ cx, cy, fill, r }) => 27 | h('circle', { ...adaptVNodeAttrs({ cx, cy, fill, r }) }, []) 28 | ); 29 | 30 | return h('svg', { 31 | ...adaptVNodeAttrs({ 32 | height: size, 33 | viewBox: '0 0 64 64', 34 | width: size 35 | }) 36 | }, circles); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/reactnative-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | export * from './Beachball.js'; 5 | export * from './Empty.js'; 6 | export * from './Jdenticon.js'; 7 | export * from './Polkadot.js'; 8 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import './packageDetect.js'; 5 | 6 | import { Identicon } from './bundle.js'; 7 | 8 | export * from './bundle.js'; 9 | 10 | export default Identicon; 11 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/packageDetect.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | // (packageInfo imports will be kept as-is, user-editable) 6 | 7 | import { packageInfo as sharedInfo } from '@polkadot/ui-shared/packageInfo'; 8 | import { detectPackage } from '@polkadot/util'; 9 | 10 | import { packageInfo } from './packageInfo.js'; 11 | 12 | detectPackage(packageInfo, null, [sharedInfo]); 13 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/packageInfo.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | // Do not edit, auto-generated by @polkadot/dev 5 | 6 | export const packageInfo = { name: '@polkadot/vue-identicon', path: 'auto', type: 'auto', version: '3.14.1' }; 7 | -------------------------------------------------------------------------------- /packages/vue-identicon/src/util.ts: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/vue-identicon authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import { version } from 'vue'; 5 | 6 | const isV3 = version.startsWith('3.'); 7 | 8 | /** 9 | * @internal 10 | * 11 | * Adjust attributes to be usable in both Vue 2 and Vue 3 components. 12 | */ 13 | export function adaptVNodeAttrs (data: Record): Record { 14 | return isV3 15 | ? data 16 | : { attrs: data }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/vue-identicon/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "..", 5 | "outDir": "./build", 6 | "rootDir": "./src" 7 | }, 8 | "references": [ 9 | { "path": "../ui-shared/tsconfig.build.json" } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // Copyright 2017-2025 @polkadot/ui-shared authors & contributors 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | import path from 'path'; 5 | 6 | import { createBundle } from '@polkadot/dev/config/rollup'; 7 | 8 | const pkgs = [ 9 | '@polkadot/react-identicon', 10 | '@polkadot/react-qr', 11 | '@polkadot/ui-keyring', 12 | '@polkadot/ui-settings', 13 | '@polkadot/vue-identicon' 14 | ]; 15 | 16 | const external = [ 17 | ...pkgs, 18 | '@polkadot/hw-ledger', 19 | '@polkadot/keyring', 20 | '@polkadot/util', 21 | '@polkadot/util-crypto', 22 | 'react', 23 | 'react-dom', 24 | 'vue', 25 | 'vue-router' 26 | ]; 27 | 28 | const globals = { 29 | react: 'React', 30 | 'react-dom': 'ReactDOM', 31 | vue: 'Vue', 32 | 'vue-router': 'VueRouter' 33 | }; 34 | 35 | const entries = ['ui-shared'].reduce((all, p) => ({ 36 | ...all, 37 | [`@polkadot/${p}`]: path.resolve(process.cwd(), `packages/${p}/build`) 38 | }), { 39 | // re-exported in @polkadot/util-crypto, map directly 40 | '@polkadot/networks': '@polkadot/util-crypto' 41 | }); 42 | 43 | const overrides = {}; 44 | 45 | export default pkgs.map((pkg) => { 46 | const override = (overrides[pkg] || {}); 47 | 48 | return createBundle({ 49 | external, 50 | globals, 51 | pkg, 52 | ...override, 53 | entries: { 54 | ...entries, 55 | ...(override.entries || {}) 56 | } 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@polkadot/dev/config/tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "paths": { 6 | "@polkadot/react-identicon": ["react-identicon/src/index.ts"], 7 | "@polkadot/react-qr": ["react-qr/src/index.ts"], 8 | "@polkadot/reactnative-identicon": ["reactnative-identicon/src/index.ts"], 9 | "@polkadot/ui-keyring": ["ui-keyring/src/index.ts"], 10 | "@polkadot/ui-settings": ["ui-settings/src/index.ts"], 11 | "@polkadot/ui-settings/packageInfo": ["ui-settings/src/packageInfo.ts"], 12 | "@polkadot/ui-settings/types": ["ui-settings/src/types.ts"], 13 | "@polkadot/ui-shared": ["ui-shared/src/index.ts"], 14 | "@polkadot/ui-shared/icons/*": ["ui-shared/src/icons/*.ts"], 15 | "@polkadot/ui-shared/packageInfo": ["ui-shared/src/packageInfo.ts"], 16 | "@polkadot/vue-identicon": ["vue-identicon/src/index.ts"], 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true 4 | }, 5 | "files": [], 6 | "references": [ 7 | { "path": "./packages/react-identicon/tsconfig.build.json" }, 8 | { "path": "./packages/react-qr/tsconfig.build.json" }, 9 | { "path": "./packages/react-qr/tsconfig.spec.json" }, 10 | { "path": "./packages/reactnative-identicon/tsconfig.build.json" }, 11 | { "path": "./packages/ui-keyring/tsconfig.build.json" }, 12 | { "path": "./packages/ui-keyring/tsconfig.spec.json" }, 13 | { "path": "./packages/ui-settings/tsconfig.build.json" }, 14 | { "path": "./packages/ui-shared/tsconfig.build.json" }, 15 | { "path": "./packages/ui-shared/tsconfig.spec.json" }, 16 | { "path": "./packages/vue-identicon/tsconfig.build.json" } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages", 5 | "composite": false 6 | }, 7 | "include": [ 8 | "packages/**/src/**/*", 9 | ".prettierrc.cjs", 10 | "eslint.config.js", 11 | "rollup.config.js" 12 | ], 13 | "exclude": [ 14 | "**/node_modules/**/*" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "./packages", 5 | "composite": false 6 | }, 7 | "include": [ 8 | "packages/**/src/**/*" 9 | ], 10 | "exclude": [ 11 | "**/node_modules/**/*" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------