index.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // Copyright 2017-2020 @polkadot/app-accounts authors & contributors
  2. // This software may be modified and distributed under the terms
  3. // of the Apache-2.0 license. See the LICENSE file for details.
  4. import { ActionStatus } from '@polkadot/react-components/Status/types';
  5. import { KeypairType } from '@polkadot/util-crypto/types';
  6. import { GeneratorMatches, GeneratorMatch, GeneratorResult } from '@polkadot/vanitygen/types';
  7. import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
  8. import styled from 'styled-components';
  9. import { Button, Dropdown, Input, Table } from '@polkadot/react-components';
  10. import { useIsMountedRef } from '@polkadot/react-hooks';
  11. import uiSettings from '@polkadot/ui-settings';
  12. import generator from '@polkadot/vanitygen/generator';
  13. import matchRegex from '@polkadot/vanitygen/regex';
  14. import generatorSort from '@polkadot/vanitygen/sort';
  15. import CreateModal from '../Accounts/modals/Create';
  16. import { useTranslation } from '../translate';
  17. import Match from './Match';
  18. interface Props {
  19. className?: string;
  20. onStatusChange: (status: ActionStatus) => void;
  21. }
  22. interface Match {
  23. isMatchValid: boolean;
  24. match: string;
  25. }
  26. interface Results {
  27. elapsed: number;
  28. isRunning: boolean;
  29. keyCount: number;
  30. keyTime: number;
  31. matches: GeneratorMatches;
  32. startAt: number;
  33. }
  34. const DEFAULT_MATCH = 'Some';
  35. const BOOL_OPTIONS = [
  36. { text: 'No', value: false },
  37. { text: 'Yes', value: true }
  38. ];
  39. function VanityApp ({ className = '', onStatusChange }: Props): React.ReactElement<Props> {
  40. const { t } = useTranslation();
  41. const results = useRef<GeneratorResult[]>([]);
  42. const runningRef = useRef(false);
  43. const mountedRef = useIsMountedRef();
  44. const [createSeed, setCreateSeed] = useState<string | null>(null);
  45. const [{ elapsed, isRunning, keyCount, matches }, setResults] = useState<Results>({
  46. elapsed: 0,
  47. isRunning: false,
  48. keyCount: -1,
  49. keyTime: 0,
  50. matches: [],
  51. startAt: 0
  52. });
  53. const [{ isMatchValid, match }, setMatch] = useState<Match>({ isMatchValid: true, match: DEFAULT_MATCH });
  54. const [type, setType] = useState<KeypairType>('ed25519');
  55. const [withCase, setWithCase] = useState(true);
  56. const _clearSeed = useCallback(
  57. () => setCreateSeed(null),
  58. []
  59. );
  60. const _checkMatches = useCallback(
  61. (): void => {
  62. const checks = results.current;
  63. results.current = [];
  64. if (checks.length === 0 || !mountedRef.current) {
  65. return;
  66. }
  67. setResults(
  68. ({ isRunning, keyCount, keyTime, matches, startAt }: Results): Results => {
  69. let newKeyCount = keyCount;
  70. let newKeyTime = keyTime;
  71. const newMatches = checks.reduce((result, { elapsed, found }): GeneratorMatch[] => {
  72. newKeyCount += found.length;
  73. newKeyTime += elapsed;
  74. return result.concat(found);
  75. }, matches);
  76. return {
  77. elapsed: Date.now() - startAt,
  78. isRunning,
  79. keyCount: newKeyCount,
  80. keyTime: newKeyTime,
  81. matches: newMatches.sort(generatorSort).slice(0, 25),
  82. startAt
  83. };
  84. }
  85. );
  86. },
  87. [mountedRef]
  88. );
  89. const _executeGeneration = useCallback(
  90. (): void => {
  91. if (!runningRef.current) {
  92. return _checkMatches();
  93. }
  94. setTimeout((): void => {
  95. if (mountedRef.current) {
  96. if (results.current.length === 25) {
  97. _checkMatches();
  98. }
  99. results.current.push(
  100. generator({ match, runs: 10, type, withCase, withHex: true })
  101. );
  102. _executeGeneration();
  103. }
  104. }, 0);
  105. },
  106. [_checkMatches, match, mountedRef, runningRef, type, withCase]
  107. );
  108. const _onChangeMatch = useCallback(
  109. (match: string): void => setMatch({
  110. isMatchValid:
  111. matchRegex.test(match) &&
  112. (match.length !== 0) &&
  113. (match.length < 31),
  114. match
  115. }),
  116. []
  117. );
  118. const _onRemove = useCallback(
  119. (address: string): void => setResults(
  120. (results: Results): Results => ({
  121. ...results,
  122. matches: results.matches.filter((item) => item.address !== address)
  123. })
  124. ),
  125. []
  126. );
  127. const _toggleStart = useCallback(
  128. (): void => setResults(
  129. ({ elapsed, isRunning, keyCount, keyTime, matches, startAt }: Results): Results => ({
  130. elapsed,
  131. isRunning: !isRunning,
  132. keyCount: isRunning ? keyCount : 0,
  133. keyTime: isRunning ? keyTime : 0,
  134. matches,
  135. startAt: isRunning ? startAt : Date.now()
  136. })
  137. ),
  138. []
  139. );
  140. useEffect((): void => {
  141. runningRef.current = isRunning;
  142. if (isRunning) {
  143. _executeGeneration();
  144. }
  145. }, [_executeGeneration, isRunning]);
  146. const header = useMemo(() => [
  147. [t('matches'), 'start', 2],
  148. [t('Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)', {
  149. replace: {
  150. avg: (keyCount / (elapsed / 1000)).toFixed(3),
  151. count: keyCount,
  152. elapsed: (elapsed / 1000).toFixed(2)
  153. }
  154. }), 'start'],
  155. [t('secret'), 'start'],
  156. []
  157. ], [elapsed, keyCount, t]);
  158. return (
  159. <div className={className}>
  160. <div className='ui--row'>
  161. <Input
  162. autoFocus
  163. className='medium'
  164. help={t<string>('Type here what you would like your address to contain. This tool will generate the keys and show the associated addresses that best match your search. You can use "?" as a wildcard for a character.')}
  165. isDisabled={isRunning}
  166. isError={!isMatchValid}
  167. label={t<string>('Search for')}
  168. onChange={_onChangeMatch}
  169. onEnter={_toggleStart}
  170. value={match}
  171. />
  172. <Dropdown
  173. className='medium'
  174. help={t<string>('Should the search be case sensitive, e.g if you select "no" your search for "Some" may return addresses containing "somE" or "sOme"...')}
  175. isDisabled={isRunning}
  176. label={t<string>('case sensitive')}
  177. onChange={setWithCase}
  178. options={BOOL_OPTIONS}
  179. value={withCase}
  180. />
  181. </div>
  182. <div className='ui--row'>
  183. <Dropdown
  184. className='medium'
  185. defaultValue={type}
  186. help={t<string>('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
  187. label={t<string>('keypair crypto type')}
  188. onChange={setType}
  189. options={uiSettings.availableCryptos}
  190. />
  191. </div>
  192. <Button.Group>
  193. <Button
  194. icon={
  195. isRunning
  196. ? 'stop'
  197. : 'sign-in-alt'
  198. }
  199. isDisabled={!isMatchValid}
  200. label={
  201. isRunning
  202. ? t<string>('Stop generation')
  203. : t<string>('Start generation')
  204. }
  205. onClick={_toggleStart}
  206. />
  207. </Button.Group>
  208. {matches.length !== 0 && (
  209. <Table
  210. className='vanity--App-matches'
  211. empty={t<string>('No matches found')}
  212. header={header}
  213. >
  214. {matches.map((match): React.ReactNode => (
  215. <Match
  216. {...match}
  217. key={match.address}
  218. onCreateToggle={setCreateSeed}
  219. onRemove={_onRemove}
  220. />
  221. ))}
  222. </Table>
  223. )}
  224. {createSeed && (
  225. <CreateModal
  226. onClose={_clearSeed}
  227. onStatusChange={onStatusChange}
  228. seed={createSeed}
  229. type={type}
  230. />
  231. )}
  232. </div>
  233. );
  234. }
  235. export default React.memo(styled(VanityApp)`
  236. .vanity--App-matches {
  237. overflow-x: auto;
  238. padding: 1em 0;
  239. }
  240. .vanity--App-stats {
  241. padding: 1em 0 0 0;
  242. opacity: 0.45;
  243. text-align: center;
  244. }
  245. `);