index.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // Copyright 2017-2019 @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 { I18nProps } from '@polkadot/react-components/types';
  5. import { KeypairType } from '@polkadot/util-crypto/types';
  6. import { GeneratorMatches, GeneratorMatch, GeneratorResult } from '../vanitygen/types';
  7. import { ComponentProps } from '../types';
  8. import React from 'react';
  9. import styled from 'styled-components';
  10. import { Button, Dropdown, Input, TxComponent } from '@polkadot/react-components';
  11. import uiSettings from '@polkadot/ui-settings';
  12. import CreateModal from '../modals/Create';
  13. import generator from '../vanitygen';
  14. import matchRegex from '../vanitygen/regex';
  15. import generatorSort from '../vanitygen/sort';
  16. import Match from './Match';
  17. import translate from './translate';
  18. interface Props extends ComponentProps, I18nProps {}
  19. interface State {
  20. createSeed: string | null;
  21. elapsed: number;
  22. isMatchValid: boolean;
  23. isRunning: boolean;
  24. keyCount: 0;
  25. keyTime: 0;
  26. match: string;
  27. matches: GeneratorMatches;
  28. startAt: number;
  29. type: KeypairType;
  30. withCase: boolean;
  31. withHex: boolean;
  32. }
  33. const DEFAULT_MATCH = 'Some';
  34. const BOOL_OPTIONS = [
  35. { text: 'No', value: false },
  36. { text: 'Yes', value: true }
  37. ];
  38. class VanityApp extends TxComponent<Props, State> {
  39. private results: GeneratorResult[] = [];
  40. public state: State = {
  41. createSeed: null,
  42. elapsed: 0,
  43. isMatchValid: true,
  44. isRunning: false,
  45. keyCount: 0,
  46. keyTime: 0,
  47. match: DEFAULT_MATCH,
  48. matches: [],
  49. startAt: 0,
  50. type: 'ed25519',
  51. withCase: true,
  52. withHex: true
  53. };
  54. private _isActive = false;
  55. public componentWillUnmount (): void {
  56. this._isActive = false;
  57. }
  58. public render (): React.ReactNode {
  59. const { className, onStatusChange } = this.props;
  60. const { createSeed, type } = this.state;
  61. return (
  62. <div className={className}>
  63. {this.renderOptions()}
  64. {this.renderButtons()}
  65. {this.renderStats()}
  66. {this.renderMatches()}
  67. {createSeed && (
  68. <CreateModal
  69. onClose={this.closeCreate}
  70. onStatusChange={onStatusChange}
  71. seed={createSeed}
  72. type={type}
  73. />
  74. )}
  75. </div>
  76. );
  77. }
  78. private renderButtons (): React.ReactNode {
  79. const { t } = this.props;
  80. const { isMatchValid, isRunning } = this.state;
  81. return (
  82. <Button.Group>
  83. <Button
  84. icon={
  85. isRunning
  86. ? 'stop'
  87. : 'sign-in'
  88. }
  89. isDisabled={!isMatchValid}
  90. isPrimary={!isRunning}
  91. onClick={this.toggleStart}
  92. label={
  93. isRunning
  94. ? t('Stop generation')
  95. : t('Start generation')
  96. }
  97. ref={this.button}
  98. />
  99. </Button.Group>
  100. );
  101. }
  102. private renderMatches (): React.ReactNode {
  103. const { matches } = this.state;
  104. return (
  105. <div className='vanity--App-matches'>
  106. {matches.map((match): React.ReactNode => (
  107. <Match
  108. {...match}
  109. key={match.address}
  110. onCreateToggle={this.onCreateToggle}
  111. onRemove={this.onRemove}
  112. />
  113. ))}
  114. </div>
  115. );
  116. }
  117. private renderOptions (): React.ReactNode {
  118. const { t } = this.props;
  119. const { isMatchValid, isRunning, match, type, withCase } = this.state;
  120. return (
  121. <>
  122. <div className='ui--row'>
  123. <Input
  124. autoFocus
  125. className='medium'
  126. help={t('Type here what you would like your address to contain. This tool will generate the keys and show the associated addresses that best match your search. You can use "?" as a wildcard for a character.')}
  127. isDisabled={isRunning}
  128. isError={!isMatchValid}
  129. label={t('Search for')}
  130. onChange={this.onChangeMatch}
  131. onEnter={this.submit}
  132. value={match}
  133. />
  134. <Dropdown
  135. className='medium'
  136. help={t('Should the search be case sensitive, e.g if you select "no" your search for "Some" may return addresses containing "somE" or "sOme"...')}
  137. isDisabled={isRunning}
  138. label={t('case sensitive')}
  139. options={BOOL_OPTIONS}
  140. onChange={this.onChangeCase}
  141. value={withCase}
  142. />
  143. </div>
  144. <div className='ui--row'>
  145. <Dropdown
  146. className='medium'
  147. defaultValue={type}
  148. help={t('Determines what cryptography will be used to create this account. Note that to validate on Polkadot, the session account must use "ed25519".')}
  149. label={t('keypair crypto type')}
  150. onChange={this.onChangeType}
  151. options={uiSettings.availableCryptos}
  152. />
  153. </div>
  154. </>
  155. );
  156. }
  157. private renderStats (): React.ReactNode {
  158. const { t } = this.props;
  159. const { elapsed, keyCount } = this.state;
  160. if (!keyCount) {
  161. return null;
  162. }
  163. const secs = elapsed / 1000;
  164. return (
  165. <div className='vanity--App-stats'>
  166. {t('Evaluated {{count}} keys in {{elapsed}}s ({{avg}} keys/s)', {
  167. replace: {
  168. avg: (keyCount / secs).toFixed(3),
  169. count: keyCount,
  170. elapsed: secs.toFixed(2)
  171. }
  172. })}
  173. </div>
  174. );
  175. }
  176. private checkMatches (): void {
  177. const results = this.results;
  178. this.results = [];
  179. if (results.length === 0 || !this._isActive) {
  180. return;
  181. }
  182. this.setState(
  183. ({ keyCount, keyTime, matches, startAt }: State): Pick<State, never> => {
  184. let newKeyCount = keyCount;
  185. let newKeyTime = keyTime;
  186. const newMatches = results
  187. .reduce((result, { elapsed, found }): GeneratorMatch[] => {
  188. newKeyCount += found.length;
  189. newKeyTime += elapsed;
  190. return result.concat(found);
  191. }, matches)
  192. .sort(generatorSort)
  193. .slice(0, 25);
  194. const elapsed = Date.now() - startAt;
  195. return {
  196. elapsed,
  197. matches: newMatches,
  198. keyCount: newKeyCount,
  199. keyTime: newKeyTime
  200. };
  201. }
  202. );
  203. }
  204. private executeGeneration = (): void => {
  205. if (!this.state.isRunning) {
  206. this.checkMatches();
  207. return;
  208. }
  209. setTimeout((): void => {
  210. if (this._isActive) {
  211. if (this.results.length === 25) {
  212. this.checkMatches();
  213. }
  214. const { match, type, withCase, withHex } = this.state;
  215. this.results.push(
  216. generator({
  217. match,
  218. runs: 10,
  219. type,
  220. withCase,
  221. withHex
  222. })
  223. );
  224. this.executeGeneration();
  225. }
  226. }, 0);
  227. }
  228. private onCreateToggle = (createSeed: string): void => {
  229. this.setState({ createSeed });
  230. }
  231. private onChangeCase = (withCase: boolean): void => {
  232. this.setState({ withCase });
  233. }
  234. private onChangeMatch = (match: string): void => {
  235. this.setState({
  236. isMatchValid:
  237. matchRegex.test(match) &&
  238. (match.length !== 0) &&
  239. (match.length < 31),
  240. match
  241. });
  242. }
  243. private onChangeType = (type: KeypairType): void => {
  244. this.setState({ type });
  245. }
  246. private onRemove = (address: string): void => {
  247. this.setState(
  248. ({ matches }: State): Pick<State, never> => ({
  249. matches: matches.filter((item): boolean =>
  250. item.address !== address
  251. )
  252. })
  253. );
  254. }
  255. private toggleStart = (): void => {
  256. this.setState(
  257. ({ isRunning, keyCount, keyTime, startAt }: State): Pick<State, never> => {
  258. this._isActive = !isRunning;
  259. return {
  260. isRunning: this._isActive,
  261. keyCount: this._isActive ? 0 : keyCount,
  262. keyTime: this._isActive ? 0 : keyTime,
  263. startAt: this._isActive ? Date.now() : startAt
  264. };
  265. },
  266. this.executeGeneration
  267. );
  268. }
  269. private closeCreate = (): void => {
  270. this.setState({ createSeed: null });
  271. }
  272. }
  273. export default translate(
  274. styled(VanityApp)`
  275. .vanity--App-matches {
  276. padding: 1em 0;
  277. }
  278. .vanity--App-stats {
  279. padding: 1em 0 0 0;
  280. opacity: 0.45;
  281. text-align: center;
  282. }
  283. `
  284. );