Compare commits
No commits in common. "master" and "production" have entirely different histories.
master
...
production
|
@ -1,3 +0,0 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
14
.eslintrc.js
|
@ -1,14 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: ['plugin:vue/essential', 'eslint:recommended'],
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint',
|
||||
},
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
},
|
||||
};
|
51
.github/workflows/deploy.yml
vendored
|
@ -1,51 +0,0 @@
|
|||
name: Build and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm test
|
||||
|
||||
build-and-deploy:
|
||||
name: Build and deploy
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- run: npm ci
|
||||
- run: npm run build:deploy
|
||||
- run: echo "vanity-eth.tk" > dist/CNAME
|
||||
- run: npx serve dist &
|
||||
- run: sudo snap install monolith
|
||||
- run: mkdir monolith
|
||||
- run: monolith http://localhost:3000 -o monolith/vanity-eth.html
|
||||
- uses: JamesIves/github-pages-deploy-action@v4.3.3
|
||||
with:
|
||||
branch: production
|
||||
folder: dist
|
||||
clean: true
|
||||
single-commit: true
|
||||
- uses: JamesIves/github-pages-deploy-action@v4.3.3
|
||||
with:
|
||||
branch: offline
|
||||
folder: monolith
|
||||
clean: true
|
||||
single-commit: true
|
4
.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
node_modules
|
||||
dist
|
||||
stats.json
|
||||
.idea
|
|
@ -1,4 +0,0 @@
|
|||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx pretty-quick --staged
|
1
.nvmrc
|
@ -1 +0,0 @@
|
|||
v16.20.1
|
1
CNAME
Normal file
|
@ -0,0 +1 @@
|
|||
vanity-eth.tk
|
21
LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Boris K
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
90
README.md
|
@ -1,90 +0,0 @@
|
|||
# Vanity-ETH
|
||||
|
||||
[](https://github.com/bokub/vanity-eth/actions/workflows/deploy.yml?query=branch%3Amaster)
|
||||
[](https://raw.githubusercontent.com/bokub/vanity-eth/master/LICENSE)
|
||||
[](https://github.com/bokub/prettier-config)
|
||||
[](https://codeclimate.com/github/bokub/vanity-eth/maintainability)
|
||||
|
||||
Browser-based ETH vanity address generator
|
||||
|
||||
Just type [`vanity-eth.tk`](https://vanity-eth.tk) to use it ⚡️
|
||||
|
||||
[](https://vanity-eth.tk)
|
||||
|
||||
## What's a vanity address?
|
||||
|
||||
A vanity address is an address in which you can choose a part of it to make it appear less random.
|
||||
|
||||
Examples:
|
||||
|
||||
- `0xc0ffee254729296a45a3885639AC7E10F9d54979`
|
||||
- `0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E`
|
||||
|
||||
## Usage
|
||||
|
||||
First of all, visit [`vanity-eth.tk`](https://vanity-eth.tk)
|
||||
|
||||
Enter a short prefix and/or suffix of your choice and click _Generate_ to start. Your browser will
|
||||
generate lots of random addresses until it finds one that matches your input.
|
||||
|
||||
Once an address is found, you can choose to reveal the private key or click the _Save_ button to download a password-encrypted keystore file.
|
||||
|
||||
Adjusting the number of working threads can increase or decrease the speed, depending on your computer's capabilities.
|
||||
|
||||
## Security
|
||||
|
||||
As mentioned earlier, all computations occur solely within your browser. Nothing ever leaves your machine, or even your browser tab.
|
||||
There is no database, no server-side code. Everything vanishes when you close your browser tab.
|
||||
|
||||
**Vanity-ETH cannot and will never store your private key.** If you have concerns about its trustworthiness, you have three options to ensure the privacy of your key:
|
||||
|
||||
- After loading the web page, you can disconnect from the internet and continue using it seamlessly
|
||||
- Alternatively, you can download the latest build of Vanity-ETH [here](https://git.io/veth-dl)
|
||||
and use it on an offline computer
|
||||
- The code is 100% open source and available on GitHub, allowing you to review it thoroughly before usage.
|
||||
|
||||
Vanity-ETH uses a cryptographically secure pseudorandom number generator (CSPRNG) to generate Ethereum addresses.
|
||||
|
||||
The keystore file is encrypted with an AES-128-CTR cipher using the PBKDF2-SHA256 derivation function with 65536 hashing rounds.
|
||||
|
||||
## Other browser-based tools
|
||||
|
||||
Be aware that due to its popularity and open-source nature, Vanity-ETH has been widely copied, leading to the existence of websites claiming to provide the same functionality. Sometimes, they are perfect clones hosted on very similar domains.
|
||||
|
||||
Most of them do not credit the original code, are not open-source, and may contain malicious code.
|
||||
|
||||
Vanity-ETH has always been the **first** browser-based ETH vanity address generator, and remains the most popular and trusted one.
|
||||
|
||||
To be sure you're on the real Vanity-ETH website, search for [Vanity-ETH on GitHub](https://github.com/search?o=desc&q=Vanity-ETH&s=stars), find the repository with the most stars (> 600), and click the link in the description. Double check by searching [Vanity-ETH on Google](https://www.google.com/search?q=Vanity-ETH).
|
||||
|
||||
## Performance
|
||||
|
||||
Vanity-ETH's performance may vary significantly across different browsers. Currently, Chrome provides the best results.
|
||||
|
||||
While you can use Vanity-ETH on your phone or tablet, it is unlikely to match the speed of a traditional computer.
|
||||
|
||||
**N.B:** Vanity-ETH is designed to be a user-friendly tool that runs directly in your browser, providing easy accessibility without the need to download or install additional software.
|
||||
However, browser-based tools have inherent limitations that may affect their performance and efficiency. Some dedicated command-line tools are more difficult to use, but may offer better performance.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Any address generated with Vanity-ETH is ERC-20 compatible, which means you can use it for an ICO, an airdrop, or just
|
||||
to withdraw your funds from an exchange.
|
||||
|
||||
The keystore file is 100% compatible with MyEtherWallet, MetaMask, Mist, and geth.
|
||||
|
||||
## Build Vanity-ETH from source
|
||||
|
||||
A GitHub Action is in charge of building and deploying Vanity-ETH to GitHub pages automatically 🤖, but you can make
|
||||
your own build from source if you want (you will need Node.js 16)
|
||||
|
||||
```sh
|
||||
git clone https://github.com/bokub/vanity-eth
|
||||
cd vanity-eth
|
||||
npm i
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
You can support this project by sending tips to `0xAceBabe64807cb045505b268ef253D8fC2FeF5Bc` 💛
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
presets: ['@vue/cli-plugin-babel/preset'],
|
||||
};
|
1
css/app.05396ab5.css
Normal file
1
css/chunk-vendors.502acf74.css
Normal file
|
@ -0,0 +1 @@
|
|||
html.remodal-is-locked{overflow:hidden;touch-action:none}.remodal,[data-remodal-id]{display:none}.remodal-overlay{position:fixed;z-index:9999;top:-5000px;right:-5000px;bottom:-5000px;left:-5000px;display:none}.remodal-wrapper{position:fixed;z-index:10000;top:0;right:0;bottom:0;left:0;display:none;overflow:auto;text-align:center;-webkit-overflow-scrolling:touch}.remodal-wrapper:after{display:inline-block;height:100%;margin-left:-.05em;content:""}.remodal-overlay,.remodal-wrapper{backface-visibility:hidden}.remodal{position:relative;outline:none;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}.remodal-is-initialized{display:inline-block}.remodal-bg.remodal-is-opened,.remodal-bg.remodal-is-opening{filter:blur(3px)}.remodal-overlay{background:rgba(43,46,56,.9)}.remodal-overlay.remodal-is-closing,.remodal-overlay.remodal-is-opening{animation-duration:.3s;animation-fill-mode:forwards}.remodal-overlay.remodal-is-opening{animation-name:remodal-overlay-opening-keyframes}.remodal-overlay.remodal-is-closing{animation-name:remodal-overlay-closing-keyframes}.remodal-wrapper{padding:10px 10px 0}.remodal{box-sizing:border-box;width:100%;margin-bottom:10px;padding:35px;transform:translateZ(0);color:#2b2e38;background:#fff}.remodal.remodal-is-closing,.remodal.remodal-is-opening{animation-duration:.3s;animation-fill-mode:forwards}.remodal.remodal-is-opening{animation-name:remodal-opening-keyframes}.remodal.remodal-is-closing{animation-name:remodal-closing-keyframes}.remodal,.remodal-wrapper:after{vertical-align:middle}.remodal-close{position:absolute;top:0;left:0;display:block;overflow:visible;width:35px;height:35px;margin:0;padding:0;cursor:pointer;transition:color .2s;text-decoration:none;color:#95979c;border:0;outline:0;background:transparent}.remodal-close:focus,.remodal-close:hover{color:#2b2e38}.remodal-close:before{font-family:Arial,Helvetica CY,Nimbus Sans L,sans-serif!important;font-size:25px;line-height:35px;position:absolute;top:0;left:0;display:block;width:35px;content:"\00d7";text-align:center}.remodal-cancel,.remodal-confirm{font:inherit;display:inline-block;overflow:visible;min-width:110px;margin:0;padding:12px 0;cursor:pointer;transition:background .2s;text-align:center;vertical-align:middle;text-decoration:none;border:0;outline:0}.remodal-confirm{color:#fff;background:#81c784}.remodal-confirm:focus,.remodal-confirm:hover{background:#66bb6a}.remodal-cancel{color:#fff;background:#e57373}.remodal-cancel:focus,.remodal-cancel:hover{background:#ef5350}.remodal-cancel::-moz-focus-inner,.remodal-close::-moz-focus-inner,.remodal-confirm::-moz-focus-inner{padding:0;border:0}@keyframes remodal-opening-keyframes{0%{transform:scale(1.05);opacity:0}to{transform:none;opacity:1;filter:blur(0)}}@keyframes remodal-closing-keyframes{0%{transform:scale(1);opacity:1}to{transform:scale(.95);opacity:0;filter:blur(0)}}@keyframes remodal-overlay-opening-keyframes{0%{opacity:0}to{opacity:1}}@keyframes remodal-overlay-closing-keyframes{0%{opacity:1}to{opacity:0}}@media only screen and (min-width:641px){.remodal{max-width:700px}}.lt-ie9 .remodal-overlay{background:#2b2e38}.lt-ie9 .remodal{width:700px}
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
379
index.html
Normal file
|
@ -0,0 +1,379 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en_US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Vanity-ETH | Ethereum vanity address generator</title>
|
||||
<meta property="og:title" content="Vanity-ETH" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Vanity-ETH is an open source generator using your web browser to generate Ethereum
|
||||
vanity addresses. You can get a custom ETH vanity address right now without the need to install any software.
|
||||
Vanity-ETH provides an encrypted keystore compatible with MyEtherWallet, MetaMask, Mist, and geth."
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Vanity-ETH is an open source generator using your web browser to generate Ethereum
|
||||
vanity addresses. You can get a custom ETH vanity address right now without the need to install any software.
|
||||
Vanity-ETH provides an encrypted keystore compatible with MyEtherWallet, MetaMask, Mist, and geth."
|
||||
/>
|
||||
<link rel="canonical" href="https://vanity-eth.tk/" />
|
||||
<meta property="og:url" content="https://vanity-eth.tk/" />
|
||||
<meta property="og:site_name" content="Vanity-ETH" />
|
||||
<meta name="google-site-verification" content="DFWJVWz9IRrh-wjBxn0Y8ith5FTqMeJTSUtuJ595BEs" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<!--[if IE]><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /><![endif]-->
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<script
|
||||
async=""
|
||||
defer=""
|
||||
data-website-id="9086c519-8c4a-4f8e-9dfe-daee3739238a"
|
||||
src="https://metrics.vanity-eth.tk/umami-script.js"
|
||||
></script>
|
||||
<link href="css/app.05396ab5.css" rel="preload" as="style" />
|
||||
<link href="css/chunk-vendors.502acf74.css" rel="preload" as="style" />
|
||||
<link href="js/app.cfab15d4.js" rel="preload" as="script" />
|
||||
<link href="js/chunk-vendors.6533d65f.js" rel="preload" as="script" />
|
||||
<link href="css/chunk-vendors.502acf74.css" rel="stylesheet" />
|
||||
<link href="css/app.05396ab5.css" rel="stylesheet" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="img/icons/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="img/icons/favicon-16x16.png" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
<meta name="theme-color" content="#4DBA87" />
|
||||
<meta name="apple-mobile-web-app-capable" content="no" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
<meta name="apple-mobile-web-app-title" content="vanity-eth" />
|
||||
<link rel="apple-touch-icon" href="img/icons/apple-touch-icon-152x152.png" />
|
||||
<link rel="mask-icon" href="img/icons/safari-pinned-tab.svg" color="#4DBA87" />
|
||||
<meta name="msapplication-TileImage" content="img/icons/msapplication-icon-144x144.png" />
|
||||
<meta name="msapplication-TileColor" content="#000000" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="remodal-bg prerender remodal-is-closed">
|
||||
<div id="content" class="container">
|
||||
<div data-v-105bd278>
|
||||
<h1 data-v-105bd278>VANITY-ETH</h1>
|
||||
<p data-v-105bd278>ETH vanity address generator</p>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div data-v-2885a99a class="panel">
|
||||
<p data-v-2885a99a>
|
||||
Vanity-ETH is an open-source tool that uses your web browser to generate Ethereum vanity addresses.<br
|
||||
data-v-2885a99a
|
||||
/>
|
||||
Enter a short prefix and/or suffix of your choice and click <i data-v-2885a99a>Generate</i> to start.
|
||||
</p>
|
||||
<div data-v-2885a99a class="shortcut">
|
||||
<button data-v-2885a99a type="button" class="button-large">Start now</button>
|
||||
</div>
|
||||
<h2 data-v-2885a99a>What's a vanity address?</h2>
|
||||
<div data-v-2885a99a class="p">
|
||||
A vanity address is an address in which you can choose a part of it to make it appear less random.<br
|
||||
data-v-2885a99a
|
||||
/>
|
||||
Examples:
|
||||
<ul data-v-2885a99a>
|
||||
<li data-v-2885a99a>
|
||||
<span data-v-2885a99a class="monospace">0xc0ffee254729296a45a3885639AC7E10F9d54979</span>
|
||||
</li>
|
||||
<li data-v-2885a99a>
|
||||
<span data-v-2885a99a class="monospace">0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h2 data-v-2885a99a>How it works</h2>
|
||||
<p data-v-2885a99a>
|
||||
Enter a short prefix and/or suffix of your choice and click <i data-v-2885a99a>Generate</i> to start.
|
||||
Your browser will generate lots of random addresses until it finds one that matches your input.<br
|
||||
data-v-2885a99a
|
||||
/>
|
||||
Once an address is found, you can choose to reveal the private key or click the
|
||||
<i data-v-2885a99a>Save</i> button to download a password-encrypted keystore file.<br
|
||||
data-v-2885a99a
|
||||
/><br data-v-2885a99a />
|
||||
Adjusting the number of working threads can increase or decrease the speed, depending on your computer's
|
||||
capabilities.<br data-v-2885a99a />
|
||||
</p>
|
||||
<h2 data-v-2885a99a>Security</h2>
|
||||
<p data-v-2885a99a>
|
||||
As mentioned earlier, all computations occur solely within your browser. Nothing ever leaves your
|
||||
machine, or even your browser tab. There is no database, no server-side code. Everything vanishes when
|
||||
you close your browser tab.<br data-v-2885a99a /><br data-v-2885a99a /><b data-v-2885a99a
|
||||
>Vanity-ETH cannot and will never store your private key.</b
|
||||
>
|
||||
If you have concerns about its trustworthiness, you have three options to ensure the privacy of your
|
||||
key:<br data-v-2885a99a />
|
||||
- After loading the web page, you can disconnect from the internet and continue using it seamlessly<br
|
||||
data-v-2885a99a
|
||||
/>
|
||||
- Alternatively, you can download the latest build of Vanity-ETH
|
||||
<a data-v-2885a99a href="https://git.io/veth-dl" target="_blank">here</a> and use it on an offline
|
||||
computer<br data-v-2885a99a />
|
||||
- The code is 100% open source and available on
|
||||
<a data-v-2885a99a href="https://github.com/bokub/vanity-eth" target="_blank">GitHub</a>, allowing
|
||||
you to review it thoroughly before usage<br data-v-2885a99a /><br data-v-2885a99a />
|
||||
Vanity-ETH uses a cryptographically secure pseudorandom number generator (CSPRNG) to generate Ethereum
|
||||
addresses.<br data-v-2885a99a />
|
||||
The keystore file is encrypted with an AES-128-CTR cipher using the PBKDF2-SHA256 derivation function
|
||||
with 65536 hashing rounds.
|
||||
</p>
|
||||
<h2 data-v-2885a99a>Other browser-based tools</h2>
|
||||
<p data-v-2885a99a>
|
||||
Be aware that due to its popularity and open-source nature, Vanity-ETH has been widely copied, leading
|
||||
to the existence of websites claiming to provide the same functionality. Sometimes, they are perfect
|
||||
clones hosted on very similar domains.<br data-v-2885a99a />
|
||||
Most of them do not credit the original code, are not open-source, and may contain malicious code.<br
|
||||
data-v-2885a99a
|
||||
/><br data-v-2885a99a />
|
||||
Vanity-ETH has always been the <b data-v-2885a99a>first</b> browser-based ETH vanity address
|
||||
generator, and remains the most popular and trusted one.<br data-v-2885a99a /><br
|
||||
data-v-2885a99a
|
||||
/>
|
||||
To be sure you're on the real Vanity-ETH website, search for
|
||||
<a
|
||||
data-v-2885a99a
|
||||
href="https://github.com/search?o=desc&q=Vanity-ETH&s=stars"
|
||||
target="_blank"
|
||||
>Vanity-ETH on GitHub</a
|
||||
>, find the repository with the most stars (> 600), and click the link in the description. Double
|
||||
check by searching
|
||||
<a data-v-2885a99a href="https://www.google.com/search?q=Vanity-ETH" target="_blank"
|
||||
>Vanity-ETH on Google</a
|
||||
>.
|
||||
</p>
|
||||
<h2 data-v-2885a99a>Performance</h2>
|
||||
<p data-v-2885a99a>
|
||||
Vanity-ETH's performance may vary significantly across different browsers. Currently, Chrome provides
|
||||
the best results.<br data-v-2885a99a />
|
||||
While you can use Vanity-ETH on your phone or tablet, it is unlikely to match the speed of a traditional
|
||||
computer.<br data-v-2885a99a /><br data-v-2885a99a /><b data-v-2885a99a>N.B:</b> Vanity-ETH is
|
||||
designed to be a user-friendly tool that runs directly in your browser, providing easy accessibility
|
||||
without the need to download or install additional software.<br data-v-2885a99a />
|
||||
However, browser-based tools have inherent limitations that may affect their performance and efficiency.
|
||||
Some dedicated command-line tools are more difficult to use, but may offer better performance.
|
||||
</p>
|
||||
<h2 data-v-2885a99a>Compatibility</h2>
|
||||
<p data-v-2885a99a>
|
||||
Any address generated with Vanity-ETH is ERC-20 compatible, which means you can use it for an ICO, an
|
||||
airdrop, or just to withdraw your funds from an exchange.<br data-v-2885a99a />
|
||||
The keystore file is 100% compatible with MyEtherWallet, MetaMask, Mist, and geth.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div data-v-0d134f6a id="input-panel" class="panel">
|
||||
<form data-v-0d134f6a>
|
||||
<!---->
|
||||
<div data-v-0d134f6a class="row">
|
||||
<div data-v-0d134f6a class="col-12 col-sm-6 col-md-12 col-lg-6">
|
||||
<input data-v-0d134f6a type="text" id="input" placeholder="Prefix" class="text-input-large" />
|
||||
</div>
|
||||
<div data-v-0d134f6a class="col-12 col-sm-6 col-md-12 col-lg-6">
|
||||
<input data-v-0d134f6a type="text" id="input" placeholder="Suffix" class="text-input-large" />
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-0d134f6a class="row justify-content-center hide-render">
|
||||
<div data-v-0d134f6a class="spinner">
|
||||
<div data-v-0d134f6a></div>
|
||||
<div data-v-0d134f6a></div>
|
||||
<div data-v-0d134f6a></div>
|
||||
<div data-v-0d134f6a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-0d134f6a class="example hide-prerender">
|
||||
E.g.
|
||||
<span data-v-0d134f6a class="monospace">
|
||||
0x<!----><span data-v-0d134f6a>593Da0221dC30D723D211176a55317F22c49c1a1</span
|
||||
><!----></span
|
||||
>
|
||||
</div>
|
||||
<div data-v-0d134f6a class="controls hide-prerender">
|
||||
<label data-v-0d134f6a class="checkbox"
|
||||
><input data-v-0d134f6a type="checkbox" name="checkbox" checked="checked" /><i
|
||||
data-v-0d134f6a
|
||||
class="left"
|
||||
></i>
|
||||
Case-sensitive
|
||||
</label>
|
||||
</div>
|
||||
<div data-v-0d134f6a class="threads hide-prerender">
|
||||
<input data-v-0d134f6a type="button" value="-" class="square-btn button-large" /><input
|
||||
data-v-0d134f6a
|
||||
type="button"
|
||||
value="+"
|
||||
class="square-btn arrow button-large"
|
||||
/>
|
||||
<h4 data-v-0d134f6a>2</h4>
|
||||
<span data-v-0d134f6a> threads</span><span data-v-0d134f6a> (recommended)</span>
|
||||
</div>
|
||||
<div data-v-0d134f6a class="row">
|
||||
<div data-v-0d134f6a class="col-lg-6 col-sm-12">
|
||||
<input
|
||||
data-v-0d134f6a
|
||||
type="button"
|
||||
value="Generate"
|
||||
disabled="disabled"
|
||||
class="button-large hide-render"
|
||||
/><input data-v-0d134f6a type="button" value="Generate" class="button-large hide-prerender" />
|
||||
</div>
|
||||
<div data-v-0d134f6a class="col-lg-6 col-sm-12">
|
||||
<input data-v-0d134f6a type="button" value="Stop" disabled="disabled" class="button-large" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div data-v-664556a1 class="panel">
|
||||
<div data-v-664556a1>Difficulty: <span data-v-664556a1 class="output">1</span></div>
|
||||
<div data-v-664556a1>Generated: <span data-v-664556a1 class="output">0 addresses</span></div>
|
||||
<div data-v-664556a1>50% probability: <span data-v-664556a1 class="output">0 addresses</span></div>
|
||||
<div data-v-664556a1>Speed: <span data-v-664556a1 class="output">0 addr/s</span></div>
|
||||
<div data-v-664556a1>Status: <span data-v-664556a1 class="output">Waiting</span></div>
|
||||
<div data-v-664556a1 class="probability">
|
||||
<div data-v-664556a1 class="probability-bar" style="width: 0%"></div>
|
||||
</div>
|
||||
<div data-v-664556a1 class="percentage">
|
||||
<h4 data-v-664556a1>0%</h4>
|
||||
<div data-v-664556a1>Probability</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div data-v-05c9bba6 class="panel result">
|
||||
<div data-v-05c9bba6 class="row">
|
||||
<div data-v-05c9bba6 id="identicon" class="float-left"></div>
|
||||
<div data-v-05c9bba6 class="col">
|
||||
<div data-v-05c9bba6>Address: <span data-v-05c9bba6 class="output"></span></div>
|
||||
<div data-v-05c9bba6>
|
||||
Private key:
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div data-v-05c9bba6 class="col-lg-2 col-12">
|
||||
<button data-v-05c9bba6 data-remodal-target="modal" disabled="disabled" class="save button-large">
|
||||
<i data-v-05c9bba6 class="icon-lock"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer data-v-167a421c>
|
||||
<div data-v-167a421c class="container">
|
||||
<div data-v-167a421c class="row">
|
||||
<div data-v-167a421c class="col-12 col-lg-6 address">
|
||||
Tips:
|
||||
<a
|
||||
data-v-167a421c
|
||||
href="https://etherscan.io/address/0xAceBabe64807cb045505b268ef253D8fC2FeF5Bc#tokentxns"
|
||||
target="_blank"
|
||||
>0xAceBabe64807cb045505b268ef253D8fC2FeF5Bc</a
|
||||
>
|
||||
</div>
|
||||
<div data-v-167a421c class="col-12 col-lg-6 links">
|
||||
<a
|
||||
data-v-167a421c
|
||||
href="https://etherscan.io/address/0xAceBabe64807cb045505b268ef253D8fC2FeF5Bc"
|
||||
target="_blank"
|
||||
><i data-v-167a421c class="icon-ethereum"></i> Donate </a
|
||||
><a data-v-167a421c href="https://github.com/bokub/vanity-eth" target="_blank"
|
||||
><i data-v-167a421c class="icon-star"></i> Star me </a
|
||||
><a data-v-167a421c href="https://github.com/bokub/vanity-eth/wiki/download-Vanity-ETH" target="_blank"
|
||||
><i data-v-167a421c class="icon-download"></i> Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<a
|
||||
data-v-887f6dee
|
||||
href="https://github.com/bokub/vanity-eth"
|
||||
target="_blank"
|
||||
aria-label="View source on Github"
|
||||
><svg
|
||||
data-v-887f6dee
|
||||
width="80"
|
||||
height="80"
|
||||
viewBox="0 0 250 250"
|
||||
aria-hidden="true"
|
||||
style="
|
||||
fill: rgb(21, 21, 19);
|
||||
color: rgb(255, 255, 255);
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
border: 0px;
|
||||
right: 0px;
|
||||
"
|
||||
>
|
||||
<defs data-v-887f6dee>
|
||||
<mask data-v-887f6dee id="octomask">
|
||||
<path data-v-887f6dee fill="white" d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path
|
||||
data-v-887f6dee
|
||||
fill="black"
|
||||
d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
|
||||
class="octo-arm"
|
||||
style="transform-origin: 130px 106px"
|
||||
></path>
|
||||
<path
|
||||
data-v-887f6dee
|
||||
d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
|
||||
fill="black"
|
||||
class="octo-body"
|
||||
></path>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect
|
||||
data-v-887f6dee
|
||||
fill="white"
|
||||
width="100%"
|
||||
height="100%"
|
||||
mask="url(#octomask)"
|
||||
class="filler"
|
||||
></rect></svg
|
||||
></a>
|
||||
</div>
|
||||
<script src="js/chunk-vendors.6533d65f.js"></script>
|
||||
<script src="js/app.cfab15d4.js"></script>
|
||||
<div class="remodal-overlay remodal-is-closed" style="display: none"></div>
|
||||
<div class="remodal-wrapper remodal-is-closed" style="display: none">
|
||||
<div
|
||||
data-remodal-id="modal"
|
||||
data-remodal-options="hashTracking: false"
|
||||
class="remodal remodal-is-initialized remodal-is-closed"
|
||||
tabindex="-1"
|
||||
>
|
||||
<button data-remodal-action="close" class="remodal-close"></button>
|
||||
<h3 class="title">Create encrypted keystore file (UTC / JSON)</h3>
|
||||
<form>
|
||||
<div>
|
||||
<input type="text" autocomplete="username" class="hidden" /><input
|
||||
autocomplete="new-password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
class="text-input-large"
|
||||
/><button type="button" class="show-password"><i class="icon-eye-on"></i></button>
|
||||
</div>
|
||||
<div><button type="button" disabled="disabled" class="button-large">Download</button></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
2
js/app.cfab15d4.js
Normal file
1
js/app.cfab15d4.js.map
Normal file
58
js/chunk-vendors.6533d65f.js
Normal file
1
js/chunk-vendors.6533d65f.js.map
Normal file
1
manifest.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"vanity-eth","short_name":"vanity-eth","theme_color":"#4DBA87","icons":[{"src":"./img/icons/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"./img/icons/android-chrome-512x512.png","sizes":"512x512","type":"image/png"},{"src":"./img/icons/android-chrome-maskable-192x192.png","sizes":"192x192","type":"image/png","purpose":"maskable"},{"src":"./img/icons/android-chrome-maskable-512x512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}],"start_url":".","display":"standalone","background_color":"#000000"}
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
29801
package-lock.json
generated
49
package.json
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"name": "vanity-eth",
|
||||
"description": "Browser-based ETH vanity address generator ",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build:deploy": "npm i --no-save prerender-spa-plugin && cross-env DEPLOY=true npm run build",
|
||||
"test": "vue-cli-service lint --nofix",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"prettier": "@bokub/prettier-config",
|
||||
"dependencies": {
|
||||
"blockies": "^0.0.2",
|
||||
"bootstrap": "^4.6.0",
|
||||
"core-js": "^3.6.5",
|
||||
"crypto-js": "^3.3.0",
|
||||
"downloadjs": "^1.4.7",
|
||||
"humanize-duration": "^3.27.0",
|
||||
"keccak": "^3.0.3",
|
||||
"randombytes": "^2.1.0",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"remodal": "^1.1.1",
|
||||
"secp256k1": "^5.0.0",
|
||||
"vue": "^2.6.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@bokub/prettier-config": "^1.1.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"husky": "^7.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.11",
|
||||
"worker-loader": "^3.0.8"
|
||||
}
|
||||
}
|
82
precache-manifest.349eb0642e88af1e95fe55c8d011e303.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
self.__precacheManifest = (self.__precacheManifest || []).concat([
|
||||
{
|
||||
"revision": "2153922c033bf3fdb4705ebf7be31010",
|
||||
"url": "android-chrome-192x192.png"
|
||||
},
|
||||
{
|
||||
"revision": "d4cdd378ffe7495aab66850f9a3d275c",
|
||||
"url": "android-chrome-512x512.png"
|
||||
},
|
||||
{
|
||||
"revision": "364a7f6c755e2b23b29a7603d737f298",
|
||||
"url": "apple-touch-icon.png"
|
||||
},
|
||||
{
|
||||
"revision": "a493ba0aa0b8ec8068d786d7248bb92c",
|
||||
"url": "browserconfig.xml"
|
||||
},
|
||||
{
|
||||
"revision": "502659629cb03dadf338",
|
||||
"url": "css/app.05396ab5.css"
|
||||
},
|
||||
{
|
||||
"revision": "42ba3d241752489317b8",
|
||||
"url": "css/chunk-vendors.502acf74.css"
|
||||
},
|
||||
{
|
||||
"revision": "4cb60baf7ec071b1f2edc5855c4aed58",
|
||||
"url": "favicon-16x16.png"
|
||||
},
|
||||
{
|
||||
"revision": "42cbe675b4e2a81d4496527031e63c5c",
|
||||
"url": "favicon-32x32.png"
|
||||
},
|
||||
{
|
||||
"revision": "f1a4a058fbba1e35a406188ae7eddaf8",
|
||||
"url": "fonts/lato-regular.f1a4a058.woff2"
|
||||
},
|
||||
{
|
||||
"revision": "79982cd1f74c6fa7451bf9b37ead09ff",
|
||||
"url": "fonts/montserrat-bold.79982cd1.woff2"
|
||||
},
|
||||
{
|
||||
"revision": "501ce09c42716a2f6e1503a25eb174c9",
|
||||
"url": "fonts/montserrat.501ce09c.woff2"
|
||||
},
|
||||
{
|
||||
"revision": "e92cc0fb9e1a7debc138224fd02a462a",
|
||||
"url": "fonts/roboto-mono.e92cc0fb.woff2"
|
||||
},
|
||||
{
|
||||
"revision": "4c36a510ecb00d065d837ba56471ebe4",
|
||||
"url": "img/tick-mark.4c36a510.png"
|
||||
},
|
||||
{
|
||||
"revision": "8f7fdb15977d751bd0703916077905e4",
|
||||
"url": "index.html"
|
||||
},
|
||||
{
|
||||
"revision": "502659629cb03dadf338",
|
||||
"url": "js/app.cfab15d4.js"
|
||||
},
|
||||
{
|
||||
"revision": "42ba3d241752489317b8",
|
||||
"url": "js/chunk-vendors.6533d65f.js"
|
||||
},
|
||||
{
|
||||
"revision": "576255d425b4b27cc64764b896d506cb",
|
||||
"url": "manifest.json"
|
||||
},
|
||||
{
|
||||
"revision": "87d3392219ef931e2dad2b75b242a69c",
|
||||
"url": "mstile-150x150.png"
|
||||
},
|
||||
{
|
||||
"revision": "2ad4442bab02bd70d0294df0338a882a",
|
||||
"url": "safari-pinned-tab.svg"
|
||||
},
|
||||
{
|
||||
"revision": "b9aa277fcfc34c31db6c7a7ea3469b8c",
|
||||
"url": "site.webmanifest"
|
||||
}
|
||||
]);
|
|
@ -1,46 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en_US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<title>Vanity-ETH | Ethereum vanity address generator</title>
|
||||
<meta property="og:title" content="Vanity-ETH" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Vanity-ETH is an open source generator using your web browser to generate Ethereum
|
||||
vanity addresses. You can get a custom ETH vanity address right now without the need to install any software.
|
||||
Vanity-ETH provides an encrypted keystore compatible with MyEtherWallet, MetaMask, Mist, and geth."
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Vanity-ETH is an open source generator using your web browser to generate Ethereum
|
||||
vanity addresses. You can get a custom ETH vanity address right now without the need to install any software.
|
||||
Vanity-ETH provides an encrypted keystore compatible with MyEtherWallet, MetaMask, Mist, and geth."
|
||||
/>
|
||||
<link rel="canonical" href="https://vanity-eth.tk/" />
|
||||
<meta property="og:url" content="https://vanity-eth.tk/" />
|
||||
<meta property="og:site_name" content="Vanity-ETH" />
|
||||
<meta name="google-site-verification" content="DFWJVWz9IRrh-wjBxn0Y8ith5FTqMeJTSUtuJ595BEs" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="theme-color" content="#ffffff" />
|
||||
<script
|
||||
async
|
||||
defer
|
||||
data-website-id="9086c519-8c4a-4f8e-9dfe-daee3739238a"
|
||||
src="https://metrics.vanity-eth.tk/umami-script.js"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
34
service-worker.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Welcome to your Workbox-powered service worker!
|
||||
*
|
||||
* You'll need to register this file in your web app and you should
|
||||
* disable HTTP caching for this file too.
|
||||
* See https://goo.gl/nhQhGp
|
||||
*
|
||||
* The rest of the code is auto-generated. Please don't update this file
|
||||
* directly; instead, make changes to your Workbox build configuration
|
||||
* and re-run your build process.
|
||||
* See https://goo.gl/2aRDsh
|
||||
*/
|
||||
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"precache-manifest.349eb0642e88af1e95fe55c8d011e303.js"
|
||||
);
|
||||
|
||||
workbox.core.setCacheNameDetails({prefix: "vanity-eth"});
|
||||
|
||||
self.addEventListener('message', (event) => {
|
||||
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||
self.skipWaiting();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The workboxSW.precacheAndRoute() method efficiently caches and responds to
|
||||
* requests for URLs in the manifest.
|
||||
* See https://goo.gl/S9QRab
|
||||
*/
|
||||
self.__precacheManifest = [].concat(self.__precacheManifest || []);
|
||||
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
|
376
src/App.vue
|
@ -1,376 +0,0 @@
|
|||
<template>
|
||||
<div id="app" class="remodal-bg render">
|
||||
<div class="container" id="content">
|
||||
<!--Headline-->
|
||||
<headline></headline>
|
||||
|
||||
<!--Description-->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<description></description>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Error-->
|
||||
<div v-if="error" class="row">
|
||||
<div class="col-md-12">
|
||||
<err :error="error"></err>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!--User input-->
|
||||
<div class="col-md-6">
|
||||
<userInput
|
||||
:running="running"
|
||||
:cores="cores"
|
||||
@start="startGen"
|
||||
@stop="stopGen"
|
||||
@input-change="setInput"
|
||||
></userInput>
|
||||
</div>
|
||||
|
||||
<!--Statistics-->
|
||||
<div class="col-md-6">
|
||||
<statistics
|
||||
:prefix="input.prefix"
|
||||
:suffix="input.suffix"
|
||||
:checksum="input.checksum"
|
||||
:status="status"
|
||||
:first-tick="firstTick"
|
||||
></statistics>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Result-->
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<result :address="result.address" :private-key="result.privateKey"></result>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Save modal-->
|
||||
<save :address="result.address.toLowerCase()" :private-key="result.privateKey"></save>
|
||||
|
||||
<!--Footer-->
|
||||
<foot></foot>
|
||||
|
||||
<!--Github corner-->
|
||||
<corner></corner>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Worker from './js/vanity.js';
|
||||
|
||||
import Headline from './vue/Headline';
|
||||
import Description from './vue/Description';
|
||||
import Err from './vue/Error';
|
||||
import UserInput from './vue/Input';
|
||||
import Statistics from './vue/Statistics';
|
||||
import Result from './vue/Result';
|
||||
import Save from './vue/Save.vue';
|
||||
import Corner from './vue/Corner';
|
||||
import Foot from './vue/Footer';
|
||||
|
||||
export default {
|
||||
components: { Headline, Description, Err, UserInput, Statistics, Result, Save, Corner, Foot },
|
||||
data: function () {
|
||||
return {
|
||||
running: false,
|
||||
status: 'Waiting',
|
||||
workers: [],
|
||||
threads: 4,
|
||||
cores: 0,
|
||||
result: { address: '', privateKey: '' },
|
||||
input: { prefix: '', suffix: '', checksum: true },
|
||||
firstTick: null,
|
||||
error: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
threads: function () {
|
||||
if (!this.running) {
|
||||
this.initWorkers();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setInput: function (inputType, value) {
|
||||
// eslint-disable-next-line default-case
|
||||
switch (inputType) {
|
||||
case 'prefix':
|
||||
this.input.prefix = value;
|
||||
break;
|
||||
case 'suffix':
|
||||
this.input.suffix = value;
|
||||
break;
|
||||
case 'checksum':
|
||||
this.input.checksum = value;
|
||||
break;
|
||||
case 'threads':
|
||||
this.threads = value;
|
||||
}
|
||||
},
|
||||
|
||||
displayResult: function (result) {
|
||||
this.$emit('increment-counter', result.attempts);
|
||||
this.result.address = result.address;
|
||||
this.result.privateKey = result.privKey;
|
||||
this.status = 'Address found';
|
||||
},
|
||||
|
||||
clearResult: function () {
|
||||
this.result.address = '';
|
||||
this.result.privateKey = '';
|
||||
this.$emit('increment-counter', -1);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create missing workers, remove the unwanted ones.
|
||||
*/
|
||||
initWorkers: function () {
|
||||
const self = this;
|
||||
if (this.workers.length === this.threads) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove unwanted workers
|
||||
if (this.workers.length > this.threads) {
|
||||
for (let w = this.threads; w < this.workers.length; w++) {
|
||||
this.workers[w].terminate();
|
||||
}
|
||||
this.workers = this.workers.slice(0, this.threads);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create workers
|
||||
for (let w = this.workers.length; w < this.threads; w++) {
|
||||
try {
|
||||
this.workers[w] = new Worker();
|
||||
this.workers[w].onmessage = (event) => self.parseWorkerMessage(event.data);
|
||||
} catch (err) {
|
||||
this.error = err;
|
||||
this.status = 'Error';
|
||||
console.error(this.error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
parseWorkerMessage: function (wallet) {
|
||||
if (wallet.error) {
|
||||
this.stopGen();
|
||||
this.error = wallet.error;
|
||||
this.status = 'Error';
|
||||
console.error(this.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wallet.address) {
|
||||
this.stopGen();
|
||||
return this.displayResult(wallet);
|
||||
}
|
||||
this.$emit('increment-counter', wallet.attempts);
|
||||
},
|
||||
|
||||
startGen: function () {
|
||||
if (!window.Worker) {
|
||||
this.error = 'workers_unsupported';
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearResult();
|
||||
this.running = true;
|
||||
|
||||
for (let w = 0; w < this.workers.length; w++) {
|
||||
this.workers[w].postMessage(this.input);
|
||||
}
|
||||
|
||||
this.status = 'Running';
|
||||
this.firstTick = performance.now();
|
||||
},
|
||||
|
||||
stopGen: function () {
|
||||
this.running = false;
|
||||
this.status = 'Stopped';
|
||||
for (let i = 0; i < this.workers.length; i++) {
|
||||
this.workers[i].terminate();
|
||||
}
|
||||
this.workers = [];
|
||||
this.initWorkers();
|
||||
},
|
||||
|
||||
countCores: function () {
|
||||
// Estimate number of cores on machine
|
||||
let cores = 0;
|
||||
try {
|
||||
cores = parseInt(navigator.hardwareConcurrency, 10);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (cores) {
|
||||
this.cores = cores;
|
||||
this.threads = this.cores;
|
||||
}
|
||||
},
|
||||
checkLocation() {
|
||||
try {
|
||||
this.error = window.self !== window.top ? 'insecure_location' : this.error;
|
||||
} catch (e) {
|
||||
this.error = 'insecure_location';
|
||||
}
|
||||
const hostname = window.location.hostname;
|
||||
if (hostname && ['localhost', '127.0.0.1', 'vanity-eth.tk'].indexOf(hostname) === -1) {
|
||||
this.error = 'insecure_location';
|
||||
}
|
||||
},
|
||||
benchmark(max) {
|
||||
max = max || 10000;
|
||||
const step = 500;
|
||||
const worker = new Worker();
|
||||
let attempts = 0;
|
||||
const times = [];
|
||||
const durations = [];
|
||||
const timeTaken = (a, d) => Math.round((1000 * a) / d);
|
||||
worker.onmessage = () => {
|
||||
times.push(performance.now());
|
||||
if (times.length === 1) {
|
||||
return;
|
||||
}
|
||||
durations.push(times[times.length - 1] - times[times.length - 2]);
|
||||
attempts += step;
|
||||
console.info(
|
||||
attempts + '/' + max + '...' + timeTaken(step, durations[durations.length - 1]) + ' addr/s'
|
||||
);
|
||||
if (attempts >= max) {
|
||||
console.info(
|
||||
'\nSpeed range: ' +
|
||||
timeTaken(step, Math.max(...durations)) +
|
||||
' - ' +
|
||||
timeTaken(step, Math.min(...durations)) +
|
||||
' addr/s'
|
||||
);
|
||||
console.info('Average: ' + timeTaken(attempts, times[times.length - 1] - times[0]) + ' addr/s');
|
||||
worker.terminate();
|
||||
}
|
||||
};
|
||||
const input = { checksum: true, prefix: 'f'.repeat(5), suffix: '' };
|
||||
console.info('Starting benchmark with 1 core...');
|
||||
worker.postMessage(input);
|
||||
},
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.checkLocation();
|
||||
this.countCores();
|
||||
this.initWorkers();
|
||||
window['benchmark'] = this.benchmark;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
// Bootstrap - Required
|
||||
@import "~bootstrap/scss/functions"
|
||||
@import "~bootstrap/scss/variables"
|
||||
@import "~bootstrap/scss/mixins"
|
||||
|
||||
// Bootstrap - Optional
|
||||
@import "~bootstrap/scss/reboot"
|
||||
@import "~bootstrap/scss/grid"
|
||||
|
||||
@import "css/variables"
|
||||
@import "css/fonts"
|
||||
|
||||
body
|
||||
padding: 0
|
||||
font-family: 'Lato', sans-serif
|
||||
background: $bg-fallback
|
||||
background: linear-gradient(140deg, $bg-2 0%, $bg-1 100%)
|
||||
background-attachment: fixed
|
||||
font-size: 16px
|
||||
|
||||
h1, h2, h3, h4, h5, h6, p, label
|
||||
margin: 0
|
||||
font-weight: normal
|
||||
|
||||
a, a:visited, a:hover
|
||||
color: $text-alt
|
||||
text-decoration: underline
|
||||
|
||||
a:hover
|
||||
color: $text
|
||||
|
||||
.panel
|
||||
padding: 1.5em 3em
|
||||
background-color: $panel-background
|
||||
margin-top: 2em
|
||||
color: $text
|
||||
font-weight: 400
|
||||
box-shadow: $shadow
|
||||
transition: box-shadow 0.2s ease-in-out
|
||||
&:hover
|
||||
box-shadow: $shadow-big
|
||||
|
||||
#content
|
||||
margin-top: 8em
|
||||
margin-bottom: 6em
|
||||
|
||||
.text-input-large
|
||||
width: 100%
|
||||
color: $text
|
||||
background: $panel-background-alt
|
||||
outline: none
|
||||
font-size: 1.3em
|
||||
padding: 0.5em
|
||||
border: none
|
||||
margin-bottom: 10px
|
||||
-webkit-appearance: none
|
||||
&::placeholder
|
||||
color: $placeholder
|
||||
|
||||
.button-large
|
||||
border: none
|
||||
outline: none
|
||||
color: $text-opposite
|
||||
padding: 8px
|
||||
font-size: 19px
|
||||
font-weight: 500
|
||||
margin: 1.3em 0 0 0
|
||||
cursor: pointer
|
||||
-webkit-appearance: none
|
||||
background: $primary
|
||||
width: 100%
|
||||
&:hover
|
||||
background: $secondary
|
||||
&:disabled
|
||||
background: $disabled
|
||||
cursor: auto
|
||||
|
||||
/*-- Pre-render-specific --
|
||||
|
||||
#app.render .hide-render
|
||||
display: none
|
||||
|
||||
#app.prerender .hide-prerender
|
||||
display: none
|
||||
|
||||
/*-- Responsive design --
|
||||
|
||||
@media screen and (max-width: 1024px)
|
||||
#content
|
||||
margin-top: 7em
|
||||
margin-bottom: 5em
|
||||
|
||||
@media screen and (max-width: 640px)
|
||||
#content
|
||||
margin-top: 5em
|
||||
margin-bottom: 4em
|
||||
|
||||
@media screen and (max-width: 480px)
|
||||
.panel
|
||||
padding: 1em
|
||||
</style>
|
|
@ -1,65 +0,0 @@
|
|||
@font-face
|
||||
font-family: 'Lato'
|
||||
font-style: normal
|
||||
font-weight: 400
|
||||
src: local('Lato Regular'), local('Lato-Regular'), url(./assets/fonts/lato-regular.woff2) format('woff2')
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215
|
||||
|
||||
@font-face
|
||||
font-family: 'Montserrat'
|
||||
font-style: normal
|
||||
font-weight: 400
|
||||
src: local('Montserrat Regular'), local('Montserrat-Regular'), url(./assets/fonts/montserrat.woff2) format('woff2')
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215
|
||||
|
||||
@font-face
|
||||
font-family: 'Montserrat'
|
||||
font-style: normal
|
||||
font-weight: 700
|
||||
src: local('Montserrat Bold'), local('Montserrat-Bold'), url(./assets/fonts/montserrat-bold.woff2) format('woff2')
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215
|
||||
|
||||
@font-face
|
||||
font-family: 'Roboto Mono'
|
||||
font-style: normal
|
||||
font-weight: 400
|
||||
src: local('Roboto Mono'), local('RobotoMono-Regular'), url(./assets/fonts/roboto-mono.woff2) format('woff2')
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD
|
||||
|
||||
@font-face
|
||||
font-family: 'icomoon'
|
||||
src: url(./assets/fonts/icomoon.woff) format('woff')
|
||||
font-weight: normal
|
||||
font-style: normal
|
||||
|
||||
[class^="icon-"], [class*=" icon-"]
|
||||
font-family: 'icomoon' !important
|
||||
speak: never
|
||||
font-style: normal
|
||||
font-weight: normal
|
||||
font-variant: normal
|
||||
text-transform: none
|
||||
line-height: 1
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased
|
||||
-moz-osx-font-smoothing: grayscale
|
||||
|
||||
|
||||
.icon-lock:before
|
||||
content: "\e900"
|
||||
|
||||
.icon-ethereum:before
|
||||
content: "\e901"
|
||||
|
||||
.icon-eye-off:before
|
||||
content: "\e902"
|
||||
|
||||
.icon-eye-on:before
|
||||
content: "\e903"
|
||||
|
||||
.icon-star:before
|
||||
content: "\e904"
|
||||
|
||||
.icon-download:before
|
||||
content: "\e905"
|
|
@ -1,23 +0,0 @@
|
|||
$text: #353535
|
||||
$text-alt: #5d5d5d
|
||||
$text-opposite: #fff
|
||||
$logo: #fff
|
||||
$border-grey: #97a2a8
|
||||
|
||||
$panel-background: #fff
|
||||
$panel-background-alt: #e0e0e0
|
||||
$disabled: #c3c3c3
|
||||
$placeholder: #b2b2b2
|
||||
|
||||
$bg-1: #036ED9
|
||||
$bg-2: #0FF0B3
|
||||
$bg-fallback: #09c4c5
|
||||
|
||||
$primary: #0cd3bc
|
||||
$secondary: #46decc
|
||||
$error: #f55959
|
||||
|
||||
$shadow: 1px 5px 10px rgba(0, 0, 0, 0.15)
|
||||
$shadow-big: 2px 10px 15px rgba(0, 0, 0, 0.15)
|
||||
|
||||
$monospace-font: "Roboto Mono", "Courier New", "Courier", monospace
|
115
src/js/vanity.js
|
@ -1,115 +0,0 @@
|
|||
/* eslint-env worker */
|
||||
const secp256k1 = require('secp256k1');
|
||||
const keccak = require('keccak');
|
||||
const randomBytes = require('randombytes');
|
||||
|
||||
const step = 500;
|
||||
|
||||
/**
|
||||
* Transform a private key into an address
|
||||
*/
|
||||
const privateToAddress = (privateKey) => {
|
||||
const pub = secp256k1.publicKeyCreate(privateKey, false).slice(1);
|
||||
return keccak('keccak256').update(Buffer.from(pub)).digest().slice(-20).toString('hex');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a wallet from a random private key
|
||||
* @returns {{address: string, privKey: string}}
|
||||
*/
|
||||
const getRandomWallet = () => {
|
||||
const randbytes = randomBytes(32);
|
||||
return {
|
||||
address: privateToAddress(randbytes).toString('hex'),
|
||||
privKey: randbytes.toString('hex'),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if a wallet respects the input constraints
|
||||
* @param address - Wallet address
|
||||
* @param prefix - Prefix chosen by the user
|
||||
* @param suffix - Suffix chosen by the user
|
||||
* @param isChecksum - Is the input case-sensitive
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const isValidVanityAddress = (address, prefix, suffix, isChecksum) => {
|
||||
const addressPrefix = address.substring(0, prefix.length);
|
||||
const addressSuffix = address.substring(40 - suffix.length);
|
||||
|
||||
if (!isChecksum) {
|
||||
return prefix === addressPrefix && suffix === addressSuffix;
|
||||
}
|
||||
if (prefix.toLowerCase() !== addressPrefix || suffix.toLowerCase() !== addressSuffix) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValidChecksum(address, prefix, suffix);
|
||||
};
|
||||
|
||||
const isValidChecksum = (address, prefix, suffix) => {
|
||||
const hash = keccak('keccak256').update(address).digest().toString('hex');
|
||||
|
||||
for (let i = 0; i < prefix.length; i++) {
|
||||
if (prefix[i] !== (parseInt(hash[i], 16) >= 8 ? address[i].toUpperCase() : address[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < suffix.length; i++) {
|
||||
const j = i + 40 - suffix.length;
|
||||
if (suffix[i] !== (parseInt(hash[j], 16) >= 8 ? address[j].toUpperCase() : address[j])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const toChecksumAddress = (address) => {
|
||||
const hash = keccak('keccak256').update(address).digest().toString('hex');
|
||||
let ret = '';
|
||||
for (let i = 0; i < address.length; i++) {
|
||||
ret += parseInt(hash[i], 16) >= 8 ? address[i].toUpperCase() : address[i];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate a lot of wallets until one satisfies the input constraints
|
||||
* @param prefix - Prefix chosen by the user
|
||||
* @param suffix - Suffix chosen by the user
|
||||
* @param isChecksum - Is the input case-sensitive
|
||||
* @param cb - Callback called after x attempts, or when an address if found
|
||||
* @returns
|
||||
*/
|
||||
const getVanityWallet = (prefix, suffix, isChecksum, cb) => {
|
||||
let wallet = getRandomWallet();
|
||||
let attempts = 1;
|
||||
|
||||
const pre = isChecksum ? prefix : prefix.toLowerCase();
|
||||
const suf = isChecksum ? suffix : suffix.toLowerCase();
|
||||
|
||||
while (!isValidVanityAddress(wallet.address, pre, suf, isChecksum)) {
|
||||
if (attempts >= step) {
|
||||
cb({ attempts });
|
||||
attempts = 0;
|
||||
}
|
||||
wallet = getRandomWallet();
|
||||
attempts++;
|
||||
}
|
||||
cb({ address: '0x' + toChecksumAddress(wallet.address), privKey: wallet.privKey, attempts });
|
||||
};
|
||||
|
||||
onmessage = function (event) {
|
||||
const input = event.data;
|
||||
try {
|
||||
getVanityWallet(input.prefix, input.suffix, input.checksum, (message) => postMessage(message));
|
||||
} catch (err) {
|
||||
self.postMessage({ error: err.toString() });
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
onmessage,
|
||||
};
|
|
@ -1,9 +0,0 @@
|
|||
import Vue from 'vue';
|
||||
import App from './App.vue';
|
||||
import './registerServiceWorker';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
new Vue({
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app');
|
|
@ -1,32 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
import { register } from 'register-service-worker';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register(`${process.env.BASE_URL}service-worker.js`, {
|
||||
ready() {
|
||||
console.log(
|
||||
'App is being served from cache by a service worker.\n' +
|
||||
'For more details, visit https://goo.gl/AFskqB'
|
||||
);
|
||||
},
|
||||
registered() {
|
||||
console.log('Service worker has been registered.');
|
||||
},
|
||||
cached() {
|
||||
console.log('Content has been cached for offline use.');
|
||||
},
|
||||
updatefound() {
|
||||
console.log('New content is downloading.');
|
||||
},
|
||||
updated() {
|
||||
console.log('New content is available; please refresh.');
|
||||
},
|
||||
offline() {
|
||||
console.log('No internet connection found. App is running in offline mode.');
|
||||
},
|
||||
error(error) {
|
||||
console.error('Error during service worker registration:', error);
|
||||
},
|
||||
});
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
<template>
|
||||
<a href="https://github.com/bokub/vanity-eth" target="_blank" aria-label="View source on Github">
|
||||
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
|
||||
<defs>
|
||||
<mask id="octomask">
|
||||
<path fill="white" d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
|
||||
<path fill="black" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" style="transform-origin: 130px 106px;" class="octo-arm"></path>
|
||||
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="black" class="octo-body"></path>
|
||||
</mask>
|
||||
</defs>
|
||||
<rect class="filler" fill="white" width="100%" height="100%" mask="url(#octomask)"></rect>
|
||||
</svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
|
||||
a
|
||||
&:hover .octo-arm
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
svg, img
|
||||
position: absolute
|
||||
top: 0
|
||||
border: 0
|
||||
right: 0
|
||||
|
||||
@keyframes octocat-wave
|
||||
0%, 100%
|
||||
transform: rotate(0)
|
||||
20%, 60%
|
||||
transform: rotate(-25deg)
|
||||
40%, 80%
|
||||
transform: rotate(10deg)
|
||||
|
||||
@media (max-width: 500px)
|
||||
a:hover .octo-arm
|
||||
animation: none
|
||||
|
||||
a .octo-arm
|
||||
animation: octocat-wave 560ms ease-in-out
|
||||
</style>
|
|
@ -1,123 +0,0 @@
|
|||
<template>
|
||||
<div class="panel">
|
||||
<p>
|
||||
Vanity-ETH is an open-source tool that uses your web browser to generate Ethereum vanity addresses.<br />
|
||||
Enter a short prefix and/or suffix of your choice and click <i>Generate</i> to start.
|
||||
</p>
|
||||
<div class="shortcut">
|
||||
<button type="button" class="button-large" @click="scrollDown">Start now</button>
|
||||
</div>
|
||||
|
||||
<h2>What's a vanity address?</h2>
|
||||
<div class="p">
|
||||
A vanity address is an address in which you can choose a part of it to make it appear less random.<br />
|
||||
Examples:
|
||||
<ul>
|
||||
<li><span class="monospace">0xc0ffee254729296a45a3885639AC7E10F9d54979</span></li>
|
||||
<li><span class="monospace">0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2>How it works</h2>
|
||||
<p>
|
||||
Enter a short prefix and/or suffix of your choice and click <i>Generate</i> to start. Your browser will
|
||||
generate lots of random addresses until it finds one that matches your input.<br />
|
||||
Once an address is found, you can choose to reveal the private key or click the <i>Save</i> button to
|
||||
download a password-encrypted keystore file.<br /><br />
|
||||
Adjusting the number of working threads can increase or decrease the speed, depending on your computer's
|
||||
capabilities.<br />
|
||||
</p>
|
||||
<h2>Security</h2>
|
||||
<p>
|
||||
As mentioned earlier, all computations occur solely within your browser. Nothing ever leaves your machine,
|
||||
or even your browser tab. There is no database, no server-side code. Everything vanishes when you close your
|
||||
browser tab.<br /><br />
|
||||
<b>Vanity-ETH cannot and will never store your private key.</b> If you have concerns about its
|
||||
trustworthiness, you have three options to ensure the privacy of your key:<br />
|
||||
- After loading the web page, you can disconnect from the internet and continue using it
|
||||
seamlessly<br />
|
||||
- Alternatively, you can download the latest build of Vanity-ETH
|
||||
<a href="https://git.io/veth-dl" target="_blank">here</a> and use it on an offline computer<br />
|
||||
- The code is 100% open source and available on
|
||||
<a href="https://github.com/bokub/vanity-eth" target="_blank">GitHub</a>, allowing you to review it
|
||||
thoroughly before usage<br />
|
||||
<br />
|
||||
Vanity-ETH uses a cryptographically secure pseudorandom number generator (CSPRNG) to generate Ethereum
|
||||
addresses.<br />
|
||||
The keystore file is encrypted with an AES-128-CTR cipher using the PBKDF2-SHA256 derivation function with
|
||||
65536 hashing rounds.
|
||||
</p>
|
||||
<h2>Other browser-based tools</h2>
|
||||
<p>
|
||||
Be aware that due to its popularity and open-source nature, Vanity-ETH has been widely copied, leading to
|
||||
the existence of websites claiming to provide the same functionality. Sometimes, they are perfect clones
|
||||
hosted on very similar domains.<br />
|
||||
Most of them do not credit the original code, are not open-source, and may contain malicious code.<br /><br />
|
||||
Vanity-ETH has always been the <b>first</b> browser-based ETH vanity address generator, and remains the most
|
||||
popular and trusted one.<br /><br />
|
||||
To be sure you're on the real Vanity-ETH website, search for
|
||||
<a href="https://github.com/search?o=desc&q=Vanity-ETH&s=stars" target="_blank">Vanity-ETH on GitHub</a>,
|
||||
find the repository with the most stars (> 600), and click the link in the description. Double check by
|
||||
searching <a href="https://www.google.com/search?q=Vanity-ETH" target="_blank">Vanity-ETH on Google</a>.
|
||||
</p>
|
||||
<h2>Performance</h2>
|
||||
<p>
|
||||
Vanity-ETH's performance may vary significantly across different browsers. Currently, Chrome provides the
|
||||
best results.<br />
|
||||
While you can use Vanity-ETH on your phone or tablet, it is unlikely to match the speed of a traditional
|
||||
computer.<br /><br />
|
||||
<b>N.B:</b> Vanity-ETH is designed to be a user-friendly tool that runs directly in your browser, providing
|
||||
easy accessibility without the need to download or install additional software.<br />
|
||||
However, browser-based tools have inherent limitations that may affect their performance and efficiency.
|
||||
Some dedicated command-line tools are more difficult to use, but may offer better performance.
|
||||
</p>
|
||||
<h2>Compatibility</h2>
|
||||
<p>
|
||||
Any address generated with Vanity-ETH is ERC-20 compatible, which means you can use it for an ICO, an
|
||||
airdrop, or just to withdraw your funds from an exchange.<br />
|
||||
The keystore file is 100% compatible with MyEtherWallet, MetaMask, Mist, and geth.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
scrollTimeOut: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
scrollDown() {
|
||||
this.scrollTo(document.getElementById('input-panel'), -1);
|
||||
},
|
||||
scrollTo(element, lastValue) {
|
||||
let currentValue = window.scrollY;
|
||||
let diff = element.getBoundingClientRect().top / 6;
|
||||
if (Math.abs(diff) > 1 && currentValue > lastValue) {
|
||||
window.scrollTo(0, window.scrollY + diff);
|
||||
this.scrollTimeOut = setTimeout(this.scrollTo, 30, element, currentValue);
|
||||
} else if (currentValue >= lastValue) {
|
||||
document.getElementById('input').focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
p, .p
|
||||
margin: 15px 0 20px
|
||||
color: $text-alt
|
||||
overflow-x: hidden
|
||||
text-overflow: ellipsis
|
||||
.monospace
|
||||
font-family: $monospace-font
|
||||
font-size: 0.85em
|
||||
.shortcut
|
||||
text-align: center
|
||||
.button-large
|
||||
width: 150px
|
||||
margin: 15px 0 35px
|
||||
</style>
|
|
@ -1,38 +0,0 @@
|
|||
<template>
|
||||
<div class="panel">
|
||||
<p v-if="error === 'workers_unsupported'">
|
||||
Your browser does not support multi-thread computation.<br>
|
||||
Please use a different browser.
|
||||
</p>
|
||||
|
||||
<div v-if="error === 'insecure_location'">
|
||||
<h3>Security alert</h3>
|
||||
|
||||
You are using Vanity-ETH from an unknown website, which could steal your private keys.<br>
|
||||
To stay safe, use Vanity-ETH on <a href="https://vanity-eth.tk" target="_blank">vanity-eth.tk</a>, or
|
||||
download the latest build <a href="https://git.io/veth-dl" target="_blank">here</a> to use offline.
|
||||
</div>
|
||||
<p v-else v-html="error.replace('\n', '<br>')"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
error: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
.panel
|
||||
background-color: $error
|
||||
color: $text-opposite
|
||||
a, a:visited, a:hover
|
||||
text-decoration: underline
|
||||
color: $text-opposite
|
||||
</style>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 address">
|
||||
Tips:
|
||||
<a
|
||||
:href="`https://etherscan.io/address/${tipsAddress}#tokentxns`"
|
||||
target="_blank"
|
||||
v-text="tipsAddress"
|
||||
></a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 links">
|
||||
<a :href="`https://etherscan.io/address/${tipsAddress}`" target="_blank">
|
||||
<i class="icon-ethereum"></i> Donate
|
||||
</a>
|
||||
<a href="https://github.com/bokub/vanity-eth" target="_blank">
|
||||
<i class="icon-star"></i> Star me
|
||||
</a>
|
||||
<a href="https://github.com/bokub/vanity-eth/wiki/download-Vanity-ETH" target="_blank">
|
||||
<i class="icon-download"></i> Download
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
tipsAddress: '0xAceBabe64807cb045505b268ef253D8fC2FeF5Bc',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
footer
|
||||
padding: 1rem 0 0.5rem
|
||||
background-color: $panel-background
|
||||
color: $text-alt
|
||||
a
|
||||
text-decoration: none
|
||||
.address
|
||||
margin-bottom: 20px
|
||||
color: $text
|
||||
a
|
||||
font-family: $monospace-font
|
||||
margin-left: 15px
|
||||
word-break: break-all
|
||||
.links
|
||||
text-align: right
|
||||
a
|
||||
margin-right: 30px
|
||||
padding-bottom: 2px
|
||||
i
|
||||
font-size: 1.2em
|
||||
|
||||
@media screen and (max-width: 480px)
|
||||
footer
|
||||
padding-bottom: 1em
|
||||
</style>
|
|
@ -1,72 +0,0 @@
|
|||
<template>
|
||||
<!--Github corner-->
|
||||
<div>
|
||||
<h1>VANITY-ETH</h1>
|
||||
<p>ETH vanity address generator</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
|
||||
div
|
||||
margin-bottom: 8em
|
||||
color: $logo
|
||||
font-family: 'Montserrat', sans-serif
|
||||
text-align: center
|
||||
|
||||
h1
|
||||
font-size: 3em
|
||||
font-weight: 700
|
||||
border: 4px solid $logo
|
||||
width: 7.8em
|
||||
margin: 0 auto
|
||||
|
||||
p
|
||||
font-size: 1.5em
|
||||
letter-spacing: 2px
|
||||
font-weight: 400
|
||||
margin-top: 1em
|
||||
|
||||
/*-- Responsive design --
|
||||
|
||||
@media screen and (max-width: 1280px)
|
||||
h1
|
||||
font-size: 2.8em
|
||||
|
||||
@media screen and (max-width: 1024px)
|
||||
div
|
||||
margin-bottom: 4em
|
||||
h1
|
||||
font-size: 2.5em
|
||||
border-width: 3px
|
||||
p
|
||||
font-size: 1.4em
|
||||
margin-top: 0.8em
|
||||
|
||||
@media screen and (max-width: 640px)
|
||||
div
|
||||
margin-bottom: 4em
|
||||
h1
|
||||
font-size: 2.2em
|
||||
p
|
||||
font-size: 1.3em
|
||||
margin-top: 0.7em
|
||||
|
||||
@media screen and (max-width: 480px)
|
||||
h1
|
||||
font-size: 2em
|
||||
border-width: 2px
|
||||
p
|
||||
font-size: 1.2em
|
||||
|
||||
@media screen and (max-width: 320px)
|
||||
h1
|
||||
font-size: 1.6em
|
||||
p
|
||||
font-size: 1em
|
||||
</style>
|
|
@ -1,314 +0,0 @@
|
|||
<template>
|
||||
<div class="panel" id="input-panel">
|
||||
<form @submit.prevent="startGen">
|
||||
<div class="error-text" v-if="inputError">Numbers and letters from A to F only</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-6 col-md-12 col-lg-6">
|
||||
<input
|
||||
:class="{ error: prefixError }"
|
||||
type="text"
|
||||
class="text-input-large"
|
||||
id="input"
|
||||
placeholder="Prefix"
|
||||
v-model="prefix"
|
||||
:disabled="running"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-12 col-lg-6">
|
||||
<input
|
||||
:class="{ error: suffixError }"
|
||||
type="text"
|
||||
class="text-input-large"
|
||||
id="input"
|
||||
placeholder="Suffix"
|
||||
v-model="suffix"
|
||||
:disabled="running"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center hide-render">
|
||||
<div class="spinner">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="example hide-prerender">
|
||||
E.g.
|
||||
<span v-if="inputError" class="monospace">N/A</span>
|
||||
<span v-else class="monospace">
|
||||
0x<!--
|
||||
--><b v-if="example.prefix" v-text="example.prefix"></b
|
||||
><!--
|
||||
--><span v-text="example.random"></span
|
||||
><!--
|
||||
--><b v-if="example.suffix" v-text="example.suffix"></b>
|
||||
</span>
|
||||
</div>
|
||||
<div class="controls hide-prerender">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="checkbox" checked="" v-model="checksum" :disabled="running" />
|
||||
<i class="left"> </i>
|
||||
Case-sensitive
|
||||
</label>
|
||||
</div>
|
||||
<div class="threads hide-prerender">
|
||||
<input
|
||||
type="button"
|
||||
class="square-btn button-large"
|
||||
value="-"
|
||||
@click="threads--"
|
||||
:disabled="running || threads <= 1"
|
||||
/>
|
||||
<input
|
||||
type="button"
|
||||
class="square-btn arrow button-large"
|
||||
value="+"
|
||||
@click="threads++"
|
||||
:disabled="running"
|
||||
/>
|
||||
<h4 v-text="threads"></h4>
|
||||
<span> threads</span>
|
||||
<span v-if="threads === cores"> (recommended)</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-sm-12">
|
||||
<input type="button" value="Generate" class="button-large hide-render" disabled />
|
||||
<input
|
||||
type="button"
|
||||
value="Generate"
|
||||
class="button-large hide-prerender"
|
||||
@click="startGen"
|
||||
:disabled="running || inputError || error"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-lg-6 col-sm-12">
|
||||
<input type="button" value="Stop" class="button-large" @click="stopGen" :disabled="!running" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const isValidHex = function (hex) {
|
||||
return hex.length ? /^[0-9A-F]+$/g.test(hex.toUpperCase()) : true;
|
||||
};
|
||||
|
||||
function mixCase(str) {
|
||||
let ret = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const l = str.substr(i, 1);
|
||||
ret += Math.random() < 0.5 ? l.toUpperCase() : l.toLowerCase();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
export default {
|
||||
props: {
|
||||
running: Boolean,
|
||||
cores: Number,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
threads: this.$props.cores || 4,
|
||||
prefix: '',
|
||||
suffix: '',
|
||||
checksum: true,
|
||||
error: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
prefixError: function () {
|
||||
return !isValidHex(this.prefix);
|
||||
},
|
||||
suffixError: function () {
|
||||
return !isValidHex(this.suffix);
|
||||
},
|
||||
inputError: function () {
|
||||
return this.prefixError || this.suffixError;
|
||||
},
|
||||
example: function () {
|
||||
if (this.inputError) {
|
||||
return null;
|
||||
}
|
||||
const prefix = this.checksum ? this.prefix : mixCase(this.prefix);
|
||||
const suffix = this.checksum ? this.suffix : mixCase(this.suffix);
|
||||
let random = '';
|
||||
for (let i = 0; i < 40 - this.prefix.length - this.suffix.length; i++) {
|
||||
random += mixCase(Math.floor(Math.random() * 16).toString(16));
|
||||
}
|
||||
return { random, prefix, suffix };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
startGen: function () {
|
||||
if (!this.running && !this.inputError && !this.error) {
|
||||
this.$emit('start');
|
||||
}
|
||||
},
|
||||
stopGen: function () {
|
||||
this.$emit('stop');
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
prefix: function () {
|
||||
this.$emit('input-change', 'prefix', this.prefix);
|
||||
},
|
||||
suffix: function () {
|
||||
this.$emit('input-change', 'suffix', this.suffix);
|
||||
},
|
||||
checksum: function () {
|
||||
this.$emit('input-change', 'checksum', this.checksum);
|
||||
},
|
||||
threads: function () {
|
||||
this.$emit('input-change', 'threads', this.threads);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
.panel
|
||||
min-height: 280px
|
||||
|
||||
.error-text
|
||||
font-size: 14px
|
||||
color: $error
|
||||
|
||||
input.error
|
||||
border: 1px solid $error
|
||||
|
||||
.example
|
||||
font-size: 14px
|
||||
word-break: break-all
|
||||
color: $text-alt
|
||||
b
|
||||
color: $text
|
||||
.monospace
|
||||
font-family: $monospace-font
|
||||
.controls
|
||||
margin: 12px 0
|
||||
> div
|
||||
padding: 5px 0
|
||||
|
||||
.checkbox
|
||||
margin-bottom: 4px
|
||||
padding-left: 30px
|
||||
line-height: 27px
|
||||
cursor: pointer
|
||||
position: relative
|
||||
color: $text
|
||||
font-weight: 400
|
||||
&:last-child
|
||||
margin-bottom: 0
|
||||
i
|
||||
position: absolute
|
||||
bottom: 4px
|
||||
left: 17.5em
|
||||
display: block
|
||||
width: 19px
|
||||
height: 19px
|
||||
outline: none
|
||||
border: 1px solid $border-grey
|
||||
&.left
|
||||
position: absolute
|
||||
bottom: 4px
|
||||
left: 0
|
||||
display: block
|
||||
width: 19px
|
||||
height: 19px
|
||||
outline: none
|
||||
border: 1px solid $border-grey
|
||||
input
|
||||
+ i:after
|
||||
content: ''
|
||||
background: url("../assets/images/tick-mark.png") no-repeat
|
||||
top: 4px
|
||||
left: 3px
|
||||
width: 15px
|
||||
height: 15px
|
||||
position: absolute
|
||||
opacity: 0
|
||||
position: absolute
|
||||
left: -9999px
|
||||
&:checked + i:after
|
||||
opacity: 1
|
||||
|
||||
.switch
|
||||
position: relative
|
||||
width: 40px
|
||||
height: 24px
|
||||
margin: 0 5px
|
||||
input
|
||||
visibility: hidden
|
||||
|
||||
.slider
|
||||
position: absolute
|
||||
cursor: pointer
|
||||
top: 0
|
||||
left: 0
|
||||
right: 0
|
||||
bottom: 0
|
||||
background-color: $primary
|
||||
transition: .2s
|
||||
&:before
|
||||
position: absolute
|
||||
content: ""
|
||||
height: 16px
|
||||
width: 16px
|
||||
left: 4px
|
||||
bottom: 4px
|
||||
background-color: white
|
||||
transition: .2s
|
||||
|
||||
input
|
||||
&:checked + .slider
|
||||
background-color: $primary
|
||||
&:focus + .slider
|
||||
box-shadow: 0 0 1px $primary
|
||||
&:checked + .slider:before
|
||||
transform: translateX(16px)
|
||||
|
||||
.threads
|
||||
h4
|
||||
display: inline
|
||||
input[type=button].square-btn
|
||||
display: inline-block
|
||||
width: 24px
|
||||
height: 24px
|
||||
margin: 0 5px 2px 0
|
||||
padding: 0
|
||||
line-height: 1em
|
||||
|
||||
.justify-content-center
|
||||
justify-content: center
|
||||
|
||||
.spinner
|
||||
width: 64px
|
||||
height: 64px
|
||||
margin: 18px
|
||||
& > div
|
||||
position: absolute
|
||||
width: 51px
|
||||
height: 51px
|
||||
margin: 6px
|
||||
border: 6px solid $primary
|
||||
border-radius: 50%
|
||||
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite
|
||||
border-color: $primary transparent transparent transparent
|
||||
&:nth-child(1)
|
||||
animation-delay: -0.45s
|
||||
&:nth-child(2)
|
||||
animation-delay: -0.3s
|
||||
&:nth-child(3)
|
||||
animation-delay: -0.15s
|
||||
|
||||
@keyframes lds-ring
|
||||
0%
|
||||
transform: rotate(0deg)
|
||||
100%
|
||||
transform: rotate(360deg)
|
||||
</style>
|
|
@ -1,85 +0,0 @@
|
|||
<template>
|
||||
<div class="panel result">
|
||||
<div class="row">
|
||||
<div class="float-left" id="identicon"></div>
|
||||
<div class="col">
|
||||
<div>Address: <span class="output" v-text="address"></span></div>
|
||||
<div>
|
||||
Private key:
|
||||
<span
|
||||
class="output"
|
||||
v-if="privateKey"
|
||||
v-text="reveal ? privateKey : 'Click to reveal'"
|
||||
@click="revealKey()"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-2 col-12">
|
||||
<button data-remodal-target="modal" class="save button-large" :disabled="!privateKey">
|
||||
<i class="icon-lock"></i>Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as blockies from 'blockies';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
address: String,
|
||||
privateKey: String,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
reveal: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
address(addr) {
|
||||
this.reveal = false;
|
||||
const id = document.getElementById('identicon');
|
||||
id.innerHTML = '';
|
||||
if (addr) {
|
||||
id.appendChild(blockies({ seed: addr.toLocaleLowerCase(), scale: 6 }));
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
revealKey() {
|
||||
this.reveal = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
#identicon
|
||||
width: 48px
|
||||
height: 48px
|
||||
margin: 0 15px
|
||||
background-color: $panel-background-alt
|
||||
|
||||
.output
|
||||
font-family: $monospace-font
|
||||
color: $text-alt
|
||||
margin-left: 15px
|
||||
word-break: break-all
|
||||
font-size: 15px
|
||||
|
||||
.panel > div:not(:last-child)
|
||||
margin-bottom: 15px
|
||||
|
||||
.save
|
||||
margin-top: 30px
|
||||
i
|
||||
margin-right: 8px
|
||||
top: 2px
|
||||
position: relative
|
||||
|
||||
@media screen and (min-width: 992px)
|
||||
.save
|
||||
margin-top: 0
|
||||
</style>
|
154
src/vue/Save.vue
|
@ -1,154 +0,0 @@
|
|||
<template>
|
||||
<div class="remodal" data-remodal-id="modal" data-remodal-options="hashTracking: false">
|
||||
<button data-remodal-action="close" class="remodal-close"></button>
|
||||
<h3 class="title">Create encrypted keystore file (UTC / JSON)</h3>
|
||||
<form @submit.prevent="save">
|
||||
<div>
|
||||
<input class="hidden" type="text" autocomplete="username" />
|
||||
<input
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
autocomplete="new-password"
|
||||
class="text-input-large"
|
||||
v-model="password"
|
||||
placeholder="Password"
|
||||
/>
|
||||
<button type="button" class="show-password" @click="showPassword = !showPassword">
|
||||
<i :class="showPassword ? 'icon-eye-off' : 'icon-eye-on'"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="button-large"
|
||||
@click="save"
|
||||
:disabled="!password || !privateKey || loading"
|
||||
v-text="loading ? 'Generating...' : 'Download'"
|
||||
></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'remodal/src/remodal';
|
||||
import 'randombytes';
|
||||
import * as download from 'downloadjs';
|
||||
|
||||
import { v4 } from 'uuid';
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
privateKey: String,
|
||||
address: String,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
showPassword: false,
|
||||
password: '',
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
privateKey: function () {
|
||||
this.password = ''; // Reset password when new address is generated
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
if (this.password) {
|
||||
this.loading = true;
|
||||
setTimeout(() => {
|
||||
const wallet = this.generateWallet(this.privateKey, this.password);
|
||||
const fileName = 'UTC--' + new Date().toISOString().replace(/:/g, '-') + '--' + this.address;
|
||||
download(JSON.stringify(wallet), fileName, 'application/json');
|
||||
this.loading = false;
|
||||
}, 20);
|
||||
}
|
||||
},
|
||||
|
||||
// Generate a JSON wallet from a private key and a password
|
||||
generateWallet(privateKey, password) {
|
||||
privateKey = Buffer.from(privateKey, 'hex');
|
||||
return {
|
||||
address: this.address,
|
||||
crypto: this.encryptPrivateKey(privateKey, password),
|
||||
id: v4(),
|
||||
version: 3,
|
||||
};
|
||||
},
|
||||
|
||||
sliceWordArray(wordArray, start, end) {
|
||||
const newArray = wordArray.clone();
|
||||
newArray.words = newArray.words.slice(start, end);
|
||||
newArray.sigBytes = (end - start) * 4;
|
||||
return newArray;
|
||||
},
|
||||
|
||||
encryptPrivateKey(privateKey, password) {
|
||||
const iv = CryptoJS.lib.WordArray.random(16);
|
||||
const salt = CryptoJS.lib.WordArray.random(32);
|
||||
const key = CryptoJS.PBKDF2(password, salt, {
|
||||
keySize: 8,
|
||||
hasher: CryptoJS.algo.SHA256,
|
||||
iterations: 262144,
|
||||
});
|
||||
const cipher = CryptoJS.AES.encrypt(
|
||||
CryptoJS.enc.Hex.parse(privateKey.toString('hex')),
|
||||
this.sliceWordArray(key, 0, 4),
|
||||
{
|
||||
iv: iv,
|
||||
mode: CryptoJS.mode.CTR,
|
||||
padding: CryptoJS.pad.NoPadding,
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line new-cap
|
||||
const mac = CryptoJS.SHA3(this.sliceWordArray(key, 4, 8).concat(cipher.ciphertext), {
|
||||
outputLength: 256,
|
||||
});
|
||||
|
||||
return {
|
||||
kdf: 'pbkdf2',
|
||||
kdfparams: { c: 262144, dklen: 32, prf: 'hmac-sha256', salt: salt.toString() },
|
||||
cipher: 'aes-128-ctr',
|
||||
ciphertext: cipher.ciphertext.toString(),
|
||||
cipherparams: { iv: iv.toString() },
|
||||
mac: mac.toString(),
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass">
|
||||
@import "~remodal/src/remodal.css"
|
||||
@import "~remodal/src/remodal-default-theme.css"
|
||||
@import "../css/variables"
|
||||
.remodal-overlay
|
||||
background: rgba(0, 0, 0, 0.85)
|
||||
|
||||
.remodal
|
||||
background-color: $panel-background
|
||||
color: $text
|
||||
.title
|
||||
margin-bottom: 45px
|
||||
.remodal-close
|
||||
outline: none
|
||||
margin: 8px
|
||||
&:before
|
||||
font-size: 2em
|
||||
&:hover
|
||||
color: $text
|
||||
.hidden
|
||||
display: none
|
||||
.show-password
|
||||
position: absolute
|
||||
border: none
|
||||
font-size: 24px
|
||||
background: rgba(0,0,0,0)
|
||||
color: $text
|
||||
transform: translate(-50px, 12px)
|
||||
outline: none !important
|
||||
box-shadow: none !important
|
||||
-webkit-appearance: none
|
||||
</style>
|
|
@ -1,156 +0,0 @@
|
|||
<template>
|
||||
<div class="panel">
|
||||
<div>Difficulty: <span class="output" v-text="formatNum(difficulty)">1</span></div>
|
||||
<div>
|
||||
Generated:
|
||||
<span class="output" v-text="formatNum(count) + (count === 1 ? ' address' : ' addresses')"
|
||||
>0 addresses</span
|
||||
>
|
||||
</div>
|
||||
<div>50% probability: <span class="output" v-text="speed ? time50 : adresses50">0 addresses</span></div>
|
||||
<div>Speed: <span class="output" v-text="speed + ' addr/s'">0 addr/s</span></div>
|
||||
<div>Status: <span class="output" v-text="status">Waiting</span></div>
|
||||
|
||||
<!--Probability-->
|
||||
<div class="probability">
|
||||
<div class="probability-bar" :style="'width:' + probability + '%'"></div>
|
||||
</div>
|
||||
<div class="percentage">
|
||||
<h4 v-text="probability + '%'">0%</h4>
|
||||
<div>Probability</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import humanizeDuration from 'humanize-duration';
|
||||
|
||||
const computeDifficulty = function (prefix, suffix, isChecksum) {
|
||||
const pattern = prefix + suffix;
|
||||
const ret = Math.pow(16, pattern.length);
|
||||
return isChecksum ? ret * Math.pow(2, pattern.replace(/[^a-f]/gi, '').length) : ret;
|
||||
};
|
||||
|
||||
const computeProbability = function (difficulty, attempts) {
|
||||
return 1 - Math.pow(1 - 1 / difficulty, attempts);
|
||||
};
|
||||
|
||||
const isValidHex = function (hex) {
|
||||
return hex.length ? /^[0-9A-F]+$/g.test(hex.toUpperCase()) : true;
|
||||
};
|
||||
|
||||
export default {
|
||||
data: function () {
|
||||
return {
|
||||
speed: 0,
|
||||
count: 0,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
prefix: String,
|
||||
suffix: String,
|
||||
checksum: Boolean,
|
||||
status: String,
|
||||
firstTick: {},
|
||||
},
|
||||
watch: {
|
||||
prefix() {
|
||||
this.count = 0;
|
||||
},
|
||||
suffix() {
|
||||
this.count = 0;
|
||||
},
|
||||
checksum() {
|
||||
this.count = 0;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
inputError: function () {
|
||||
return !isValidHex(this.prefix) || !isValidHex(this.suffix);
|
||||
},
|
||||
difficulty: function () {
|
||||
return this.inputError ? 'N/A' : computeDifficulty(this.prefix, this.suffix, this.checksum);
|
||||
},
|
||||
probability50() {
|
||||
return this.inputError ? 0 : Math.floor(Math.log(0.5) / Math.log(1 - 1 / this.difficulty));
|
||||
},
|
||||
adresses50: function () {
|
||||
if (this.probability50 === -Infinity) {
|
||||
return 'Nearly impossible';
|
||||
}
|
||||
return this.inputError ? 'N/A' : this.formatNum(this.probability50) + ' addresses';
|
||||
},
|
||||
time50: function () {
|
||||
const seconds = this.probability50 / this.speed;
|
||||
if (seconds > 200 * 365.25 * 24 * 3600 || seconds === -Infinity) {
|
||||
return 'Thousands of years';
|
||||
}
|
||||
return this.inputError ? 'N/A' : humanizeDuration(Math.round(seconds) * 1000, { largest: 2 });
|
||||
},
|
||||
probability: function () {
|
||||
return Math.round(10000 * computeProbability(this.difficulty, this.count)) / 100;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
formatNum: function (num) {
|
||||
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
|
||||
},
|
||||
},
|
||||
created: function () {
|
||||
this.$parent.$on('increment-counter', (incr) => {
|
||||
this.count += incr > 0 ? incr : -this.count;
|
||||
this.speed = incr > 0 ? Math.floor((1000 * this.count) / (performance.now() - this.firstTick)) : 0;
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="sass" scoped>
|
||||
@import "../css/variables"
|
||||
.panel > div:not(:last-child)
|
||||
margin-bottom: 17px
|
||||
|
||||
.panel
|
||||
min-height: 280px
|
||||
padding-bottom: 3.2em
|
||||
> div:not(.percentage)
|
||||
clear: both
|
||||
|
||||
.probability
|
||||
width: 85%
|
||||
margin: 5px 0
|
||||
height: 18px
|
||||
background: $panel-background-alt
|
||||
float: left
|
||||
|
||||
.probability-bar
|
||||
height: 100%
|
||||
width: 0
|
||||
display: block
|
||||
background-color: $primary
|
||||
|
||||
.percentage
|
||||
float: right
|
||||
width: 15%
|
||||
text-align: center
|
||||
position: relative
|
||||
top: -10px
|
||||
left: 15px
|
||||
div
|
||||
font-size: 12px
|
||||
h5
|
||||
color: $text
|
||||
font-weight: 500
|
||||
|
||||
.output
|
||||
font-family: $monospace-font
|
||||
color: $text-alt
|
||||
margin-left: 15px
|
||||
word-break: break-all
|
||||
|
||||
@media screen and (max-width: 480px)
|
||||
.percentage
|
||||
left: -5px
|
||||
.probability
|
||||
width: 80%
|
||||
</style>
|
|
@ -1,36 +0,0 @@
|
|||
const path = require('path');
|
||||
const prettier = require('prettier');
|
||||
|
||||
module.exports = {
|
||||
publicPath: '',
|
||||
chainWebpack: (config) => {
|
||||
// Worker Loader
|
||||
config.module
|
||||
.rule('worker')
|
||||
.test(/vanity\.js$/)
|
||||
.use('worker-loader')
|
||||
.loader('worker-loader')
|
||||
.options({
|
||||
inline: 'no-fallback',
|
||||
filename: 'vanity.js',
|
||||
})
|
||||
.end();
|
||||
},
|
||||
configureWebpack: {
|
||||
plugins: process.env.DEPLOY
|
||||
? [
|
||||
new (require('prerender-spa-plugin'))({
|
||||
staticDir: path.join(__dirname, 'dist'),
|
||||
routes: ['/'],
|
||||
postProcess(renderedRoute) {
|
||||
renderedRoute.html = prettier
|
||||
.format(renderedRoute.html, { filepath: 'index.html', printWidth: 120 })
|
||||
.replace('render', 'prerender')
|
||||
.replace(/(data-v-[0-9a-f]+)=""/gm, '$1');
|
||||
return renderedRoute;
|
||||
},
|
||||
}),
|
||||
]
|
||||
: [],
|
||||
},
|
||||
};
|
|
@ -1,110 +0,0 @@
|
|||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const pretty = require('pretty');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const SriPlugin = require('webpack-subresource-integrity');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
index: './src/main.js'
|
||||
},
|
||||
output: {
|
||||
crossOriginLoading: 'anonymous',
|
||||
path: path.resolve(__dirname, './dist'),
|
||||
publicPath: '/dist/',
|
||||
filename: '[name].js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.vue$/,
|
||||
loader: 'vue-loader',
|
||||
options: {extractCSS: process.env.NODE_ENV === 'production'}
|
||||
},
|
||||
{
|
||||
test: /vanity\.js$/,
|
||||
loader: 'worker-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
inline: true,
|
||||
name: 'vanity.js'
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'babel-loader',
|
||||
exclude: /node_modules(?!\/keccak)/
|
||||
},
|
||||
{
|
||||
test: /\.(png|woff|woff2)/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'url-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
vue$: 'vue/dist/vue.esm.js'
|
||||
},
|
||||
extensions: ['*', '.js', '.vue', '.json']
|
||||
},
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
noInfo: true,
|
||||
overlay: true
|
||||
},
|
||||
performance: {
|
||||
hints: false
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
|
||||
TID: JSON.stringify(process.env.TID)
|
||||
}
|
||||
}),
|
||||
new CopyWebpackPlugin([{
|
||||
from: 'src/assets/images/favicon.ico',
|
||||
to: '.',
|
||||
toType: 'dir'
|
||||
}])
|
||||
]
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports.plugins = module.exports.plugins.concat([
|
||||
new ExtractTextPlugin('style.css'),
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
sourceMap: false,
|
||||
compress: {
|
||||
warnings: false
|
||||
}
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'index.html',
|
||||
filename: '../index.html',
|
||||
inject: false
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
if (process.env.DEPLOY) {
|
||||
const SpaPlugin = require('prerender-spa-plugin');
|
||||
module.exports.plugins = module.exports.plugins.concat([
|
||||
new SriPlugin({
|
||||
hashFuncNames: ['sha256', 'sha384']
|
||||
}),
|
||||
new SpaPlugin({
|
||||
staticDir: path.join(__dirname),
|
||||
routes: ['/'],
|
||||
postProcess(renderedRoute) {
|
||||
renderedRoute.html = pretty(renderedRoute.html, {ocd: true})
|
||||
.replace('render', 'prerender')
|
||||
.replace(/(data-v-[0-9a-f]+)=""/gm, '$1');
|
||||
return renderedRoute;
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|