This commit is contained in:
poma 2020-12-15 22:11:36 +03:00
commit d0a3a51b68
No known key found for this signature in database
GPG Key ID: BA20CB01FE165657
99 changed files with 15728 additions and 0 deletions

13
.editorconfig Normal file
View File

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

20
.eslintrc.js Normal file
View File

@ -0,0 +1,20 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
parserOptions: {
parser: 'babel-eslint',
},
extends: [
'@nuxtjs',
'prettier',
'prettier/vue',
'plugin:prettier/recommended',
'plugin:nuxt/recommended',
],
plugins: ['prettier'],
// add your custom rules here
rules: {},
}

32
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,32 @@
version: 2
updates:
# Fetch and update latest `npm` packages
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
time: '00:00'
open-pull-requests-limit: 10
reviewers:
- rstormsf
assignees:
- rstormsf
commit-message:
prefix: fix
prefix-development: chore
include: scope
# Fetch and update latest `github-actions` pkgs
- package-ecosystem: github-actions
directory: '/'
schedule:
interval: daily
time: '00:00'
open-pull-requests-limit: 10
reviewers:
- rstormsf
assignees:
- rstormsf
commit-message:
prefix: fix
prefix-development: chore
include: scope

49
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,49 @@
name: ci
on:
push:
branches:
- main
- master
pull_request:
branches:
- main
- master
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
steps:
- name: Checkout 🛎
uses: actions/checkout@master
- name: Setup node env 🏗
uses: actions/setup-node@v2.1.2
with:
node-version: ${{ matrix.node }}
- name: Get yarn cache directory path 🛠
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache node_modules 📦
uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies 👨🏻‍💻
run: yarn
- name: Run linter 👀
run: yarn lint

90
.gitignore vendored Normal file
View File

@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp

4
.prettierrc Normal file
View File

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

20
README.md Normal file
View File

@ -0,0 +1,20 @@
# governance-dep-ui
## Build Setup
```bash
# install dependencies
$ yarn install
# serve with hot reload at localhost:3000
$ yarn dev
# build for production and launch server
$ yarn build
$ yarn start
# generate static project
$ yarn generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).

39
abi/deployer.abi.json Normal file
View File

@ -0,0 +1,39 @@
[
{
"inputs": [
{
"internalType": "bytes",
"name": "_initCode",
"type": "bytes"
},
{
"internalType": "bytes32",
"name": "_salt",
"type": "bytes32"
}
],
"name": "deploy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "addr",
"type": "address"
}
],
"name": "Deployed",
"type": "event"
}
]

95
abi/ens.mock.abi.json Normal file
View File

@ -0,0 +1,95 @@
[
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "registry",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "resolver",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_node",
"type": "bytes32"
}
],
"name": "addr",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_node",
"type": "bytes32"
},
{
"internalType": "address",
"name": "_addr",
"type": "address"
}
],
"name": "setAddr",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "_nodes",
"type": "bytes32[]"
},
{
"internalType": "address[]",
"name": "_addresses",
"type": "address[]"
}
],
"name": "setAddrBulk",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="256px" height="417px" viewBox="0 0 256 417" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
<g>
<polygon fill="#343434" points="127.9611 0 125.1661 9.5 125.1661 285.168 127.9611 287.958 255.9231 212.32"/>
<polygon fill="#8C8C8C" points="127.962 0 0 212.32 127.962 287.959 127.962 154.158"/>
<polygon fill="#3C3C3B" points="127.9611 312.1866 126.3861 314.1066 126.3861 412.3056 127.9611 416.9066 255.9991 236.5866"/>
<polygon fill="#8C8C8C" points="127.962 416.9052 127.962 312.1852 0 236.5852"/>
<polygon fill="#141414" points="127.9611 287.9577 255.9211 212.3207 127.9611 154.1587"/>
<polygon fill="#393939" points="0.0009 212.3208 127.9609 287.9578 127.9609 154.1588"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 841 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#d80027"/><path fill="#ffda44" d="M140.1 155.8l22.1 68h71.5l-57.8 42.1 22.1 68-57.9-42-57.9 42 22.2-68-57.9-42.1H118zm163.4 240.7l-16.9-20.8-25 9.7 14.5-22.5-16.9-20.9 25.9 6.9 14.6-22.5 1.4 26.8 26 6.9-25.1 9.6zm33.6-61l8-25.6-21.9-15.5 26.8-.4 7.9-25.6 8.7 25.4 26.8-.3-21.5 16 8.6 25.4-21.9-15.5zm45.3-147.6L370.6 212l19.2 18.7-26.5-3.8-11.8 24-4.6-26.4-26.6-3.8 23.8-12.5-4.6-26.5 19.2 18.7zm-78.2-73l-2 26.7 24.9 10.1-26.1 6.4-1.9 26.8-14.1-22.8-26.1 6.4 17.3-20.5-14.2-22.7 24.9 10.1z"/></svg>

After

Width:  |  Height:  |  Size: 601 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#eee"/><path fill="#0052b4" d="M53 100.1a255 255 0 0 0-44.2 89.1H142l-89-89zm450.2 89.1a255 255 0 0 0-44.1-89l-89 89h133zM8.8 322.8a255 255 0 0 0 44.1 89l89-89H9zm403-269.9a255 255 0 0 0-89-44V142l89-89zM100.2 459.1a255 255 0 0 0 89.1 44V370l-89 89zm89-450.3a255 255 0 0 0-89 44.1l89 89.1V8.8zm133.6 494.4a255 255 0 0 0 89-44.1l-89-89v133zM370 322.8l89 89a255 255 0 0 0 44.2-89H370z"/><g fill="#d80027"><path d="M509.8 222.6H289.4V2.2A258.6 258.6 0 0 0 256 0c-11.3 0-22.5.7-33.4 2.2v220.4H2.2A258.6 258.6 0 0 0 0 256c0 11.3.7 22.5 2.2 33.4h220.4v220.4a258.4 258.4 0 0 0 66.8 0V289.4h220.4A258.5 258.5 0 0 0 512 256c0-11.3-.7-22.5-2.2-33.4z"/><path d="M322.8 322.8L437 437a256.6 256.6 0 0 0 15-16.4l-97.7-97.8h-31.5zm-133.6 0L75 437a256.6 256.6 0 0 0 16.4 15l97.8-97.7v-31.5zm0-133.6L75 75a256.6 256.6 0 0 0-15 16.4l97.7 97.8h31.5zm133.6 0L437 75a256.3 256.3 0 0 0-16.4-15l-97.8 97.7v31.5z"/></g></svg>

After

Width:  |  Height:  |  Size: 1003 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill="#eee"/><path fill="#0052b4" d="M496 345a255.4 255.4 0 0 0 0-178H16a255.5 255.5 0 0 0 0 178l240 22.3L496 345z"/><path fill="#d80027" d="M256 512a256 256 0 0 0 240-167H16a256 256 0 0 0 240 167z"/></svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@ -0,0 +1,3 @@
<svg width="15" height="11" viewBox="0 0 15 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.292889 4.53553C-0.0976512 4.92607 -0.097608 5.55925 0.292889 5.94974L4.53553 10.1924C4.92603 10.5829 5.5592 10.5829 5.94974 10.1924L14.435 1.7071C14.8256 1.31656 14.8255 0.683386 14.435 0.292889C14.0445 -0.097608 13.4114 -0.0976512 13.0208 0.292889L5.24264 8.07106L1.7071 4.53553C1.31661 4.14503 0.683429 4.14499 0.292889 4.53553Z" fill="#44F1A6"/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

View File

@ -0,0 +1,3 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.2886 0.146442C14.0933 -0.048806 13.7767 -0.0488222 13.5815 0.146442L7.21751 6.5104L0.853547 0.146442C0.658299 -0.048806 0.341705 -0.0488222 0.14644 0.146442C-0.0488189 0.341702 -0.0488081 0.658301 0.14644 0.853549L6.5104 7.21751L0.14644 13.5815C-0.0488189 13.7767 -0.0488081 14.0933 0.14644 14.2886C0.341689 14.4838 0.658288 14.4838 0.853547 14.2886L7.21751 7.92462L13.5815 14.2886C13.7767 14.4838 14.0933 14.4838 14.2886 14.2886C14.4838 14.0933 14.4838 13.7767 14.2886 13.5815L7.92461 7.21751L14.2886 0.853549C14.4838 0.658285 14.4838 0.341691 14.2886 0.146442Z" fill="#6B6B6B"/>
</svg>

After

Width:  |  Height:  |  Size: 697 B

View File

@ -0,0 +1,3 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.67711 0.322889C9.24659 -0.107627 8.5486 -0.107633 8.11807 0.322889L5 3.44096L1.88193 0.322889C1.45141 -0.107627 0.753411 -0.107633 0.322889 0.322889C-0.107621 0.753399 -0.107627 1.45141 0.322889 1.88193L3.44096 5L0.322889 8.11807C-0.107633 8.5486 -0.107627 9.24659 0.322889 9.67711C0.753405 10.1076 1.4514 10.1076 1.88193 9.67711L5 6.55904L8.11807 9.67711C8.54859 10.1076 9.24659 10.1076 9.67711 9.67711C10.1076 9.24659 10.1076 8.54859 9.67711 8.11807L6.55904 5L9.67711 1.88193C10.1076 1.4514 10.1076 0.753405 9.67711 0.322889Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 659 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="12">
<path fill-rule="evenodd" d="M9 12H1a1 1 0 0 1 0-2h3V6H1a1 1 0 0 1 0-2h4a1 1 0 0 1 1 1v5h3a1 1 0 0 1 0 2zM5 2H4a1 1 0 0 1 0-2h1a1 1 0 0 1 0 2z"/>
</svg>

After

Width:  |  Height:  |  Size: 221 B

View File

@ -0,0 +1,3 @@
<svg width="10" height="7" viewBox="0 0 10 7" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.279335 2.85355C-0.0931092 3.21057 -0.0931144 3.78942 0.279335 4.14645L2.97686 6.73224C3.34931 7.08926 3.95317 7.08925 4.32562 6.73224L9.72066 1.56066C10.0931 1.20364 10.0931 0.624788 9.72066 0.267765C9.34822 -0.0892575 8.74435 -0.0892525 8.3719 0.267765L3.65124 4.79289L1.6281 2.85355C1.25565 2.49653 0.65178 2.49654 0.279335 2.85355Z" fill="#0E0E0E"/>
</svg>

After

Width:  |  Height:  |  Size: 466 B

View File

@ -0,0 +1,3 @@
<svg width="2" height="12" viewBox="0 0 2 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447723 0 0 0.447716 0 1V7C0 7.55229 0.447723 8 1 8C1.55228 8 2 7.55229 2 7V1C2 0.447716 1.55228 0 1 0ZM1 12C1.55228 12 2 11.5523 2 11C2 10.4477 1.55228 10 1 10C0.447723 10 0 10.4477 0 11C0 11.5523 0.447723 12 1 12Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 388 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><path d="M17.58,3.75a2.39,2.39,0,0,0-1.75-1.47l-.09,0s0-.06,0-.09A2.4,2.4,0,0,0,14.25.42,6,6,0,0,0,12.05,0a5.93,5.93,0,0,0-5.8,4.68,5.56,5.56,0,0,0-.12,1.63l-5,5a3.94,3.94,0,0,0,5.57,5.57l5-5a6.34,6.34,0,0,0,1.63-.13,5.94,5.94,0,0,0,4.26-8Zm-2.74,5A3.85,3.85,0,0,1,12.9,9.8,4.06,4.06,0,0,1,11,9.74L5.3,15.43a1.92,1.92,0,0,1-2.73,0,1.92,1.92,0,0,1,0-2.73L8.26,7A4.06,4.06,0,0,1,8.2,5.1,3.85,3.85,0,0,1,9.27,3.16a3.93,3.93,0,0,1,4.24-.88.38.38,0,0,1,.25.3.41.41,0,0,1-.11.38L11.52,5.09l.25,1.14,1.14.25L15,4.35a.41.41,0,0,1,.38-.11.38.38,0,0,1,.3.25A3.93,3.93,0,0,1,14.84,8.73Z"/></svg>

After

Width:  |  Height:  |  Size: 645 B

1
assets/images/logo.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 39.9 39.9"><path d="M40,19.4A17.3,17.3,0,0,0,22.8,2.8,17.1,17.1,0,0,0,8.6,10.4,11.3,11.3,0,0,1,19.3,0,17.1,17.1,0,0,0,2.8,17.1a16.8,16.8,0,0,0,7.7,14.2A11.3,11.3,0,0,1,0,20.6a17.1,17.1,0,0,0,31.3,9A11.4,11.4,0,0,1,20.6,39.9,17.1,17.1,0,0,0,37.1,22.8,17.3,17.3,0,0,0,29.5,8.6,11.8,11.8,0,0,1,40,19.4ZM20,27.2a7.4,7.4,0,0,1-5.2-2.1,7.1,7.1,0,0,1-2-5.1A7.2,7.2,0,1,1,20,27.2Z" fill="#44f1a6"/></svg>

After

Width:  |  Height:  |  Size: 450 B

1
assets/images/metamask-fox.svg Executable file
View File

@ -0,0 +1 @@
<svg viewBox="0 0 323.6 302" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round" transform="translate(1.84 .5)"><path d="m305.19 0-125.88 94.09 23.28-55.51z" fill="#e2761b" stroke="#e2761b"/><g fill="#e4761b" stroke="#e4761b"><path d="m14.6 0 124.86 95-22.14-56.42z"/><path d="m259.9 218.11-33.53 51.69 71.74 19.87 20.62-70.41z"/><path d="m1.31 219.26 20.5 70.41 71.73-19.87-33.54-51.69z"/><path d="m89.49 130.76-20 30.44 71.23 3.18-2.53-77z"/><path d="m230.3 130.76-49.3-44.31-1.65 77.93 71.1-3.18z"/><path d="m93.54 269.8 42.76-21-36.94-29z"/><path d="m183.49 248.8 42.88 21-5.94-50z"/></g><path d="m226.37 269.8-42.88-21 3.41 28.14-.38 11.84z" fill="#d7c1b3" stroke="#d7c1b3"/><path d="m93.54 269.8 39.85 19-.25-11.84 3.16-28.14z" fill="#d7c1b3" stroke="#d7c1b3"/><path d="m134 201.18-35.65-10.57 25.17-11.61z" fill="#233447" stroke="#233447"/><path d="m185.76 201.18 10.51-22.18 25.3 11.59z" fill="#233447" stroke="#233447"/><g fill="#cd6116" stroke="#cd6116"><path d="m93.54 269.8 6.07-51.69-39.61 1.15z"/><path d="m220.3 218.11 6.07 51.69 33.53-50.54z"/><path d="m250.41 161.2-71.1 3.18 6.58 36.8 10.5-22.18 25.3 11.59z"/><path d="m98.35 190.61 25.3-11.61 10.35 22.18 6.71-36.8-71.21-3.18z"/></g><path d="m69.5 161.2 29.86 58.57-1-29.16z" fill="#e4751f" stroke="#e4751f"/><path d="m221.69 190.61-1.26 29.16 30-58.57z" fill="#e4751f" stroke="#e4751f"/><path d="m140.73 164.38-6.73 36.8 8.35 43.41 1.9-57.17z" fill="#e4751f" stroke="#e4751f"/><path d="m179.31 164.38-3.41 22.92 1.52 57.29 8.47-43.41z" fill="#e4751f" stroke="#e4751f"/><path d="m185.89 201.18-8.47 43.41 6.07 4.21 36.94-29 1.26-29.16z" fill="#f6851b" stroke="#f6851b"/><path d="m98.35 190.61 1 29.16 36.94 29 6.07-4.21-8.36-43.38z" fill="#f6851b" stroke="#f6851b"/><path d="m186.52 288.78.38-11.84-3.16-2.81h-47.69l-2.91 2.81.25 11.84-39.85-19 13.92 11.46 28.21 19.76h48.45l28.34-19.74 13.91-11.46z" fill="#c0ad9e" stroke="#c0ad9e"/><path d="m183.49 248.8-6.07-4.21h-35l-6.07 4.21-3.16 28.14 2.91-2.81h47.69l3.16 2.81z" fill="#161616" stroke="#161616"/><path d="m310.5 100.21 10.76-51.95-16.07-48.26-121.7 90.91 46.81 39.85 66.16 19.49 14.68-17.19-6.33-4.59 10.12-9.29-7.84-6.11 10.12-7.77z" fill="#763d16" stroke="#763d16"/><path d="m-1.34 48.26 10.75 52-6.83 5.04 10.12 7.77-7.7 6.11 10.12 9.29-6.32 4.59 14.55 17.19 66.16-19.49 46.79-39.85-121.7-90.91z" fill="#763d16" stroke="#763d16"/><path d="m296.46 150.25-66.16-19.49 20.11 30.44-30 58.57 39.47-.51h58.83z" fill="#f6851b" stroke="#f6851b"/><path d="m89.49 130.76-66.16 19.49-22 69h58.67l39.35.51-29.85-58.56z" fill="#f6851b" stroke="#f6851b"/><path d="m179.31 164.38 4.18-73.47 19.23-52.33h-85.4l19 52.33 4.43 73.47 1.52 23.17.12 57h35l.25-57z" fill="#f6851b" stroke="#f6851b"/></g></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
<svg viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg"><path d="m1.29 6.48c-1.29 2.52-1.29 5.87-1.29 12.52s0 10 1.29 12.52a11.94 11.94 0 0 0 5.19 5.19c2.52 1.29 5.87 1.29 12.52 1.29s10 0 12.52-1.29a11.94 11.94 0 0 0 5.19-5.19c1.29-2.52 1.29-5.87 1.29-12.52s0-10-1.29-12.52a11.94 11.94 0 0 0 -5.19-5.19c-2.52-1.29-5.87-1.29-12.52-1.29s-10 0-12.52 1.29a11.94 11.94 0 0 0 -5.19 5.19zm27.19 3.77a1.22 1.22 0 0 1 .88.37 1.25 1.25 0 0 1 .36.88c-.06 3.54-.2 6.25-.45 8.39a17.62 17.62 0 0 1 -1.19 5 7.93 7.93 0 0 1 -1.4 2.18 11 11 0 0 1 -2.52 1.93c-.4.24-.81.47-1.24.71-.92.53-1.93 1.1-3.08 1.87a1.22 1.22 0 0 1 -1.37 0c-1.17-.78-2.2-1.36-3.13-1.89l-.6-.34a12.93 12.93 0 0 1 -2.92-2.1 7.36 7.36 0 0 1 -1.46-2.09 14.51 14.51 0 0 1 -1.16-4.16 71 71 0 0 1 -.6-9.52 1.2 1.2 0 0 1 .4-.86 1.22 1.22 0 0 1 .88-.37h.51a13 13 0 0 0 8.05-2.49 1.26 1.26 0 0 1 1.52 0 13 13 0 0 0 8.04 2.49zm-2.76 13.87a12 12 0 0 0 1-3.47 64.57 64.57 0 0 0 .55-7.94 15.19 15.19 0 0 1 -8.07-2.46 15.23 15.23 0 0 1 -8.06 2.46c.07 2.88.2 5.13.4 6.89a15.45 15.45 0 0 0 1 4.32 5.31 5.31 0 0 0 1 1.5 8.35 8.35 0 0 0 2 1.49c.36.22.74.44 1.17.68.75.42 1.61.91 2.57 1.52 1-.6 1.8-1.08 2.54-1.51l.65-.36a11.4 11.4 0 0 0 2.39-1.68 5.23 5.23 0 0 0 .86-1.44z" fill="#fff" fill-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

68
assets/styles/app.scss Normal file
View File

@ -0,0 +1,68 @@
.wrapper {
display: flex;
flex-direction: column;
min-height: 100vh;
background: linear-gradient(180deg, #181818 0%, #000000 100%);
background-attachment: fixed;
> .main-content {
flex: 1;
}
}
@import 'components/font';
@import 'components/base';
@import 'components/button';
@import 'components/icons';
@import 'components/step';
@import 'components/header';
@import 'components/wallets';
@import 'components/modal';
@import 'components/flag';
@import 'components/dropdown';
@import 'components/loading';
@import 'components/notice';
@import 'components/table';
.title {
span {
color: $primary;
}
&.is-14px {
font-size: 0.875rem;
}
}
.tornado-discoverer {
margin-top: -3.5rem;
background-image: url('../images/discoverer.svg');
background-size: contain;
background-repeat: no-repeat;
@include mobile {
margin-top: -1.5rem;
}
}
.delete {
border-radius: 0;
height: 1rem;
max-height: 1rem;
max-width: 1rem;
min-height: 1rem;
min-width: 1rem;
width: 1rem;
background-color: #6B6B6B;
mask-size: contain;
mask-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.2131 1.07106C15.0179 0.875816 14.7013 0.875799 14.506 1.07106L8.14207 7.43502L1.77811 1.07106C1.58286 0.875816 1.26627 0.875799 1.071 1.07106C0.875742 1.26632 0.875752 1.58292 1.071 1.77817L7.43496 8.14213L1.071 14.5061C0.875742 14.7014 0.875752 15.018 1.071 15.2132C1.26625 15.4084 1.58285 15.4085 1.77811 15.2132L8.14207 8.84924L14.506 15.2132C14.7013 15.4084 15.0179 15.4085 15.2131 15.2132C15.4084 15.0179 15.4084 14.7013 15.2131 14.5061L8.84918 8.14213L15.2131 1.77817C15.4084 1.58291 15.4084 1.26631 15.2131 1.07106Z' fill='%23C0D4F3'/%3E%3C/svg%3E%0A");
&::before, &::after {
content: none;
}
&:hover, &:focus {
background-color: #fff;
}
}

View File

@ -0,0 +1,97 @@
$control-height: 2.438rem;
$control-border-width: 1px;
$control-padding-vertical: calc(0.375rem - #{$control-border-width});
$control-padding-horizontal: calc(1.063rem - #{$control-border-width});
$primary: #44f1a6;
$primary-invert: #000000;
$warning: #FF8F50;
$danger: #F44C6A;
$size-normal: 0.875rem;
@import '~bulma/sass/utilities/_all';
$widescreen-enabled: false;
$fullhd-enabled: false;
$body-family: 'PT Mono', monospace;
$body-background-color: $primary-invert;
$body-color: $white;
$body-min-width: 320px;
$title-size: 2.25rem;
$title-color: $white;
$title-weight: 600;
$title-line-height: 2.5rem;
$subtitle-color: $white;
$subtitle-size: 0.813rem;
$subtitle-line-height: 1.5;
$subtitle-negative-margin: -1rem;
$link: $primary;
$link-hover: darken($primary, 15%);
$navbar-background-color: transparent;
$navbar-item-color: $white;
$navbar-item-hover-color: $primary;
$navbar-item-hover-background-color: transparent;
$navbar-item-active-color: $primary;
$navbar-breakpoint: $tablet;
$modal-background-background-color: rgba(#000, 0.95);
$modal-card-title-color: $primary;
$modal-card-head-background-color: $primary-invert;
$modal-card-head-border-bottom: none;
$modal-card-foot-border-top: none;
$modal-card-body-background-color: $primary-invert;
$modal-card-foot-radius: 0;
$modal-card-head-radius: 0;
$modal-card-body-padding: 0 1.5rem;
$modal-card-head-padding: 1.5rem;
$box-background-color: #1F1F1F;
$box-shadow: none;
$box-color: #fefefe;
$box-padding: 1.5rem;
$footer-background-color: transparent;
$footer-padding: 2rem;
$dropdown-item-color: $white;
$dropdown-content-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px #6B6B6B;
$dropdown-content-background-color: $primary-invert;
$dropdown-item-hover-background-color: #2B2B2B;
$dropdown-item-hover-color: $white;
$dropdown-item-active-background-color: #313131;
$dropdown-item-active-color: $white;
$dropdown-content-padding-bottom: 0;
$dropdown-content-padding-top: 0;
$table-background-color: #151515;
$table-color: $white;
$table-head-cell-color: $white;
$table-head-cell-border-width: 0 0 4px;
$table-cell-border: 1px solid $primary;
$table-striped-row-even-background-color: #1F1F1F;
$table-row-hover-background-color: rgba($primary, .154);
$table-striped-row-even-hover-background-color: rgba($primary, .154);
$table-cell-padding: 1.5em 1.25rem;
$loading-background-legacy: #000;
$loading-background: rgba(0,0,0,0.95);
$notification-background-color: #1F1F1F;
$notification-radius: 6px;
@import '~bulma/sass/base/_all';
@import '~bulma/sass/helpers/_all';
@import '~bulma/sass/elements/_all';
@import '~bulma/sass/components/_all';
@import '~bulma/sass/form/_all';
@import '~bulma/sass/grid/_all';
@import '~bulma/sass/layout/_all';
@import '~buefy/src/scss/buefy';

View File

@ -0,0 +1,97 @@
.button {
font-weight: 700;
&--active {
&::after {
content: '';
margin-left: 0.15rem;
margin-top: -0.55rem;
width: 4px;
height: 4px;
background-color: $primary;
border-radius: 100%;
box-shadow: 0 0 1px 1px $primary;
}
}
&.is-icon {
width: 1.5rem;
height: 1.5rem;
padding: 0;
background-color: transparent;
border: none;
.icon {
height: 1.5rem;
width: 1.5rem;
}
.trnd {
width: 100%;
height: 100%;
background-color: #6b6b6b;
}
&:focus:not(:active),
&.is-focused:not(:active) {
box-shadow: none;
}
}
&.is-primary {
&.is-outlined {
background-color: #182922;
}
.trnd {
background-color: $primary;
}
&:hover,
&:focus {
.trnd {
background-color: $primary-invert;
}
}
&[disabled] {
.trnd {
background-color: $primary;
}
}
}
.icon {
&:first-child:not(:last-child) {
margin-left: -0.15em;
margin-right: 0.5em;
}
&:last-child:not(:first-child) {
margin-left: 0.5em;
margin-right: -0.15em;
}
&:first-child:last-child {
margin-left: 0;
margin-right: 0;
}
}
&:not(.is-small) {
span:not(.icon):not(.b-tooltip):first-child:last-child {
padding: 0 0.5rem;
}
}
}
.buttons {
.button.is-icon:not(:last-child):not(.is-fullwidth) {
margin-right: 1.25rem;
}
.break {
margin: 0 1.25rem 0.5rem 0;
width: 1px;
align-self: stretch;
background-color: #2a2a2a;
}
}

View File

@ -0,0 +1,53 @@
.dropdown.is-mobile-modal {
.dropdown-content {
overflow: hidden;
}
.dropdown-item {
&:focus {
outline: none;
}
}
&.is-expanded-modal {
.dropdown-menu {
@include desktop {
min-width: 400px;
}
}
}
}
.dropdown-langs {
margin-bottom: .5rem;
.dropdown-menu {
min-width: auto;
padding-top: 0;
padding-bottom: .5rem;
}
&.is-mobile-modal {
.dropdown-menu {
max-width: 100px;
}
}
a.dropdown-item {
padding-right: 1rem;
line-height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 400;
.flag-icon {
margin-right: .5rem;
}
}
.button {
margin-bottom: 0;
}
}

View File

@ -0,0 +1,24 @@
.flag-icon {
background-size: contain;
background-position: 50%;
background-repeat: no-repeat;
position: relative;
display: inline-block;
height: 24px;
width: 24px;
&:before {
content: '\00a0';
}
}
// https://github.com/HatScripts/circle-flags/tree/master/flags
@mixin flag-icon($country) {
.flag-icon-#{$country} {
background-image: url('../images/flags/#{$country}.svg');
}
}
$countries: gb ru cn;
@each $country in $countries {
@include flag-icon($country);
}

View File

@ -0,0 +1,14 @@
@font-face {
font-family: 'PT Mono';
src: local('PT Mono'), local('PTMono-Regular'), url('../fonts/PTMono-Regular.woff2') format('woff2');
font-display: swap;
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'PT Mono';
src: local('PT Mono Bold'), local('PTMono-Bold'), url('../fonts/PTMono-Bold.woff2') format('woff2');
font-display: swap;
font-weight: 700;
font-style: normal;
}

View File

@ -0,0 +1,30 @@
.header {
padding-top: 20px;
padding-left: .75rem;
padding-right: .75rem;
@include tablet {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
@include desktop {
> .container {
.navbar-brand {
margin-left: 0;
.navbar-item {
padding-left: 0;
}
}
}
}
.logo {
height: 40px;
}
.navbar-item {
font-size: 0.875rem
}
}

View File

@ -0,0 +1,109 @@
.trnd {
height: 1rem;
width: 1rem;
background-color: $primary;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
.trnd-48px {
height: 1.875rem;
width: 1.875rem;
}
&-tool {
mask-image: url('../images/icons/tool.svg');
}
&-check {
mask-image: url('../images/icons/check.svg');
}
&-wallet {
mask-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 18 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M11 10C10.4477 10 10 10.4477 10 11C10 11.5523 10.4477 12 11 12H13C13.5523 12 14 11.5523 14 11C14 10.4477 13.5523 10 13 10H11Z' fill='%230E1633'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M4 0C1.79083 0 0 1.79086 0 4V14C0 16.2091 1.79083 18 4 18H14C16.2092 18 18 16.2091 18 14V8C18 5.79086 16.2092 4 14 4H12C12 1.79086 10.2092 0 8 0H4ZM2 6V14C2 15.1046 2.89545 16 4 16H14C15.1046 16 16 15.1046 16 14V8C16 6.89543 15.1046 6 14 6H2ZM2 4C2 2.89543 2.89545 2 4 2H8C9.10455 2 10 2.89543 10 4H2Z' fill='%230E1633'/%3E%3C/svg%3E");
}
&-logout {
mask-image: url("data:image/svg+xml,%3Csvg width='19' height='18' viewBox='0 0 19 18' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M4 0C1.79083 0 0 1.79086 0 4V14C0 16.2091 1.79083 18 4 18H10C10.5523 18 11 17.5523 11 17C11 16.4477 10.5523 16 10 16H4C2.89545 16 2 15.1046 2 14V4C2 2.89543 2.89545 2 4 2H10C10.5523 2 11 1.55228 11 1C11 0.447716 10.5523 0 10 0H4Z' fill='%230E1633'/%3E%3Cpath d='M15.1213 5.70708C14.7308 5.31655 14.0976 5.31655 13.7071 5.70708C13.3165 6.0976 13.3165 6.73077 13.7071 7.12129L14.5858 7.99997H6C5.44769 7.99997 5 8.44769 5 8.99997C5 9.55225 5.44769 9.99997 6 9.99997H14.6406L13.707 10.9336C13.3165 11.3241 13.3165 11.9571 13.707 12.3476C14.0975 12.7381 14.7306 12.7381 15.1211 12.3476L17.7148 9.75388C17.9575 9.51125 18.0494 9.17508 17.9905 8.86153C17.9597 8.63882 17.8555 8.43955 17.7031 8.28889L15.1213 5.70708Z' fill='%230E1633'/%3E%3C/svg%3E%0A");
}
&-stats {
mask-image: url("data:image/svg+xml,%3Csvg width='22' height='24' viewBox='0 0 22 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M13 0C12.4477 0 12 0.447693 12 1V19C12 19.5523 12.4477 20 13 20C13.5523 20 14 19.5523 14 19V1C14 0.447693 13.5523 0 13 0Z' fill='%23838BAD'/%3E%3Cpath d='M8 6C8 5.44769 8.44769 5 9 5C9.55231 5 10 5.44769 10 6V19C10 19.5523 9.55231 20 9 20C8.44769 20 8 19.5523 8 19V6Z' fill='%23838BAD'/%3E%3Cpath d='M5 8C4.44769 8 4 8.44769 4 9V19C4 19.5523 4.44769 20 5 20C5.55231 20 6 19.5523 6 19V9C6 8.44769 5.55231 8 5 8Z' fill='%23838BAD'/%3E%3Cpath d='M0 13C0 12.4477 0.447693 12 1 12C1.55231 12 2 12.4477 2 13V19C2 19.5523 1.55231 20 1 20C0.447693 20 0 19.5523 0 19V13Z' fill='%23838BAD'/%3E%3Cpath d='M0 23C0 22.4477 0.447693 22 1 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H1C0.447693 24 0 23.5523 0 23Z' fill='%23838BAD'/%3E%3Cpath d='M16 9C16 8.44769 16.4477 8 17 8C17.5523 8 18 8.44769 18 9V19C18 19.5523 17.5523 20 17 20C16.4477 20 16 19.5523 16 19V9Z' fill='%23838BAD'/%3E%3Cpath d='M21 5C20.4477 5 20 5.44769 20 6V19C20 19.5523 20.4477 20 21 20C21.5523 20 22 19.5523 22 19V6C22 5.44769 21.5523 5 21 5Z' fill='%23838BAD'/%3E%3C/svg%3E%0A");
}
&-twitter {
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='20' viewBox='0 0 24 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 2.3037C23.1075 2.69439 22.1565 2.95335 21.165 3.07909C22.185 2.47135 22.9635 1.51634 23.3295 0.365239C22.3785 0.93106 21.3285 1.33073 20.2095 1.55376C19.3065 0.594262 18.0195 0 16.6155 0C13.8915 0 11.6985 2.2064 11.6985 4.91127C11.6985 5.30046 11.7315 5.67468 11.8125 6.03094C7.722 5.83185 4.1025 3.87543 1.671 0.895135C1.2465 1.6301 0.9975 2.47135 0.9975 3.37696C0.9975 5.07742 1.875 6.58478 3.183 7.45746C2.3925 7.44249 1.617 7.21347 0.96 6.85272C0.96 6.86769 0.96 6.88715 0.96 6.90661C0.96 9.29264 2.6655 11.2745 4.902 11.7311C4.5015 11.8403 4.065 11.8927 3.612 11.8927C3.297 11.8927 2.979 11.8748 2.6805 11.8089C3.318 13.7533 5.127 15.1829 7.278 15.2293C5.604 16.536 3.4785 17.3234 1.1775 17.3234C0.774 17.3234 0.387 17.3054 0 17.2561C2.1795 18.6586 4.7625 19.4595 7.548 19.4595C16.602 19.4595 21.552 11.9751 21.552 5.48757C21.552 5.27052 21.5445 5.06096 21.534 4.85289C22.5105 4.16133 23.331 3.29763 24 2.3037Z' fill='%23838BAD'/%3E%3C/svg%3E%0A");
}
&-telegram {
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='20' viewBox='0 0 24 20' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M9.4174 13.1813L9.0204 18.7653C9.58841 18.7653 9.83441 18.5213 10.1294 18.2283L12.7925 15.6833L18.3106 19.7243C19.3226 20.2883 20.0357 19.9913 20.3087 18.7933L23.9308 1.8214L23.9318 1.8204C24.2528 0.324404 23.3907 -0.260593 22.4047 0.106405L1.1142 8.25736C-0.33883 8.82136 -0.31683 9.63136 0.867199 9.99835L6.31033 11.6913L18.9536 3.78039C19.5486 3.38639 20.0897 3.60439 19.6446 3.99839L9.4174 13.1813Z' fill='%23838BAD'/%3E%3C/svg%3E%0A");
}
&-github {
mask-image: url("data:image/svg+xml,%3Csvg width='24' height='23' viewBox='0 0 24 23' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 0C5.37 0 0 5.28 0 11.792C0 17.003 3.438 21.422 8.205 22.98C8.805 23.091 9.025 22.726 9.025 22.413C9.025 22.133 9.015 21.391 9.01 20.408C5.672 21.119 4.968 18.826 4.968 18.826C4.422 17.465 3.633 17.101 3.633 17.101C2.546 16.37 3.717 16.385 3.717 16.385C4.922 16.467 5.555 17.6 5.555 17.6C6.625 19.403 8.364 18.882 9.05 18.581C9.158 17.818 9.467 17.299 9.81 17.004C7.145 16.709 4.344 15.695 4.344 11.177C4.344 9.89 4.809 8.838 5.579 8.013C5.444 7.715 5.039 6.516 5.684 4.892C5.684 4.892 6.689 4.576 8.984 6.101C9.944 5.839 10.964 5.709 11.984 5.703C13.004 5.709 14.024 5.839 14.984 6.101C17.264 4.576 18.269 4.892 18.269 4.892C18.914 6.516 18.509 7.715 18.389 8.013C19.154 8.838 19.619 9.89 19.619 11.177C19.619 15.707 16.814 16.704 14.144 16.994C14.564 17.348 14.954 18.071 14.954 19.176C14.954 20.754 14.939 22.022 14.939 22.405C14.939 22.714 15.149 23.083 15.764 22.965C20.565 21.417 24 16.995 24 11.792C24 5.28 18.627 0 12 0Z' fill='%23838BAD'/%3E%3C/svg%3E%0A");
}
&-loading {
height: 1.875rem;
width: 1.875rem;
background: conic-gradient(from 180deg at 50% 50%, $primary 0deg, rgba(255, 255, 255, 0) 360deg);
animation: spin 2s linear infinite;
position: relative;
border-radius: 100%;
&:after {
content: '';
position: absolute;
height: 22px;
width: 22px;
background: $notification-background-color;
left: calc(50% - 11px);
top: calc(50% - 11px);
border-radius: 100%;
}
}
&-info {
height: 1.875rem;
width: 1.875rem;
background-image: url('../images/icons/notice/info.svg');
background-position: center;
background-repeat: no-repeat;
border-radius: 100%;
}
&-success {
height: 1.875rem;
width: 1.875rem;
background-image: url('../images/icons/notice/success.svg');
background-position: center;
background-repeat: no-repeat;
border-radius: 100%;
}
&-warning {
height: 1.875rem;
width: 1.875rem;
background-image: url('../images/icons/notice/warning.svg');
background-position: center;
background-repeat: no-repeat;
border-radius: 100%;
background-color: $warning;
}
&-danger {
height: 1.875rem;
width: 1.875rem;
background-image: url('../images/icons/notice/danger.svg');
background-position: center;
background-repeat: no-repeat;
border-radius: 100%;
background-color: $danger;
}
}
@keyframes spin{
0% {
transform:rotate(0deg)
}
to {
transform:rotate(-1turn)
}
}

View File

@ -0,0 +1,37 @@
.loading-overlay {
.loading-container {
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.loading-tornado {
&:after {
content: '';
display: block;
height: 60px;
width: 60px;
background-image: url('../images/logo.svg');
animation: spinAroundReverse 2000ms infinite linear;
}
}
.loading-message {
padding-top: .5rem;
color: $primary;
text-align: center;
+ .button {
margin-top: .5rem;
}
}
}
}
@keyframes spinAroundReverse {
from {
transform: rotate(359deg); }
to {
transform: rotate(0deg); }
}

View File

@ -0,0 +1,25 @@
.box-modal {
max-width: 440px;
overflow-y: auto;
&-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
&.is-spaced {
margin-bottom: .475rem;
}
}
&-title {
font-size: 1rem;
font-weight: bold;
}
.note {
font-size: 0.875rem;
margin-bottom: 1rem;
}
}

View File

@ -0,0 +1,29 @@
.notices {
.notification {
width: 290px;
pointer-events: auto;
padding: 1.071rem 3.214rem 1.071rem 1.071em;
font-size: 0.813rem;
a:not(.button):not(.dropdown-item) {
color: $primary;
display: block;
margin-top: .15rem;
text-decoration: none;
font-size: 0.750rem;
&:hover {
text-decoration: underline;
}
}
.media {
align-items: center;
}
.delete {
right: 1rem;
top: calc(50% - .5rem);
}
}
}

View File

@ -0,0 +1,130 @@
.steps {
padding-top: 1.25rem;
}
.step {
margin: 1.25rem 0;
background-color: #151515;
border-radius: 6px;
.step-container {
display: flex;
align-items: center;
border-radius: 6px;
background: #1F1F1F;
@include mobile {
flex-wrap: wrap;
}
}
&:first-child {
margin-top: 0;
}
.diamond {
margin: 1.25rem 0 1.25rem 1.25rem;
@include mobile {
flex: none;
width: 100%;
margin-left: 0;
margin-right: 0;
margin-bottom: 0;
}
}
&-body {
padding: 1.25rem;
flex-grow: 1;
@include mobile {
flex: none;
width: 100%;
text-align: center;
padding-bottom: 0;
}
h4 {
font-weight: 700;
@include until(576px) {
font-size: 0.9rem;
}
+ .deployed {
margin-top: .25rem;
}
}
.deployed {
font-size: 0.813rem;
color: #6B6B6B;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
}
&-more-button {
padding: 1.25rem;
@include mobile {
flex: none;
width: 100%;
text-align: center;
padding: .75rem 1.25rem;
}
.button {
padding: 0;
background-color: transparent;
border: 0;
color: $primary;
font-size: 0.875rem;
&:focus:not(:active), &.is-focused:not(:active) {
box-shadow: none;
}
}
}
&-tail {
background-color: #191919;
align-self: stretch;
display: flex;
justify-content: center;
align-items: center;
padding: 1.25rem;
width: 148px;
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
@include mobile {
flex: none;
width: 100%;
border-top-right-radius: 0;
border-bottom-left-radius: 6px;
}
.completed {
display: flex;
align-items: center;
font-size: $size-normal;
color: $primary;
font-weight: 700;
.icon {
margin-right: 0.5em;
}
}
}
&-more {
font-size: 0.813rem;
p {
padding: 1.25rem;
}
}
}

View File

@ -0,0 +1,61 @@
.b-table {
.table-wrapper {
border-radius: 6px;
}
.table {
a {
color: $primary;
text-decoration: underline;
}
th {
font-weight: $weight-normal;
}
td {
border-color: rgba($primary, .5)
}
td, th {
vertical-align: middle;
}
tbody {
tr {
&:last-child {
td {
border-bottom-width: 1px;
}
}
&:not(.is-selected) {
&:nth-child(odd) {
background-color: $table-striped-row-even-background-color;
}
&:hover {
background-color: $table-row-hover-background-color;
&:nth-child(odd) {
background-color: $table-striped-row-even-hover-background-color;
}
}
}
&:not(.is-selected).is-empty {
background-color: transparent;
&:hover {
background-color: transparent;
}
}
}
}
}
.dropdown.is-expanded {
min-width: 75px;
}
}

View File

@ -0,0 +1,130 @@
.wallets {
&.field.is-grouped.is-grouped-multiline {
margin: -0.5rem;
> .control {
margin: 0.5rem;
&:not(.control-with-select) {
.button {
height: 100%;
}
}
}
}
.button {
width: 120px;
flex-direction: column;
height: auto;
&.is-black {
color: $primary-invert;
background-color: #559774;
&:hover {
background-color: $primary;
}
&[disabled] {
background-color: #559774;
}
}
&:before {
content: '';
height: 64px;
width: 64px;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
margin: 0.25rem 0;
}
&.is-metamask:before {
background-image: url('../images/metamask-fox.svg');
}
&.is-generic:before {
background-image: url('../images/ethereum.svg');
}
&.is-trustwallet:before {
background-image: url('../images/trustwallet.svg');
}
}
.control-with-select {
display: flex;
flex-direction: column;
.button {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
border-bottom-width: 0;
&:before {
height: 38px;
}
}
.select {
&.is-empty {
select {
color: rgba(255, 255, 255, 0.7);
}
}
select {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-top-width: 0;
border-color: #559774;
background-color: #559774;
height: 1.929rem;
color: $primary-invert;
&:hover {
background-color: $primary;
border-color: $primary;
}
&:focus {
border-color: #559774;
box-shadow: none;
}
}
&:not(.is-multiple) {
height: 1.929rem;
&:not(.is-loading) {
&::after {
border-color: $primary-invert;
}
}
}
}
}
.network-select {
width: 120px;
overflow: hidden;
}
/*> .control:hover {
.button {
border-color: #cdcdcd;
}
&.control-with-select {
.button {
border-bottom-color: $black;
}
.select select {
border-color: #cdcdcd;
}
}*
}*/
}

50
components/Diamond.vue Normal file
View File

@ -0,0 +1,50 @@
<template>
<svg
width="60"
height="60"
viewBox="0 0 60 60"
fill="none"
class="diamond"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="30"
cy="30"
r="29.5"
fill="#111111"
:stroke="waiting ? '#FFB950' : active ? '#44F1A6' : '#3F3F3F'"
/>
<circle
cx="30"
cy="30"
r="23.5"
:fill="waiting ? '#674B21' : active ? '#276C4E' : '#191919'"
:stroke="waiting ? '#FFB950' : active ? '#44F1A6' : '#3F3F3F'"
/>
<path
d="M34.5801 15.893C34.6485 16.9078 34.4722 17.4907 34.1736 17.886C33.8637 18.2962 33.3621 18.5882 32.5885 18.8921C32.2511 19.0247 31.8702 19.1558 31.4515 19.3C29.9916 19.8026 28.0728 20.4631 25.94 21.8938C23.8639 23.2864 22.4643 25.0309 21.3363 26.4369C20.9682 26.8958 20.6291 27.3186 20.3047 27.6813C19.6245 28.4421 19.0358 28.9193 18.3942 29.0845C17.777 29.2435 17.0037 29.1381 15.8951 28.4718C13.8305 27.2309 13.4688 24.7439 14.399 21.9281C15.3227 19.1318 17.4754 16.2282 20.0662 14.4904C22.6546 12.7541 26.2741 11.849 29.2741 12.0493C30.7723 12.1494 32.0721 12.5223 33.0055 13.1619C33.9217 13.7898 34.4985 14.6808 34.5801 15.893Z"
:fill="waiting ? '#CC9440' : active ? '#3AC589' : '#2B2B2B'"
:stroke="waiting ? '#FFB950' : active ? '#44F1A6' : '#3F3F3F'"
/>
<path
d="M44.5653 38.8122C44.7443 38.8672 44.8454 38.9473 44.907 39.0292C44.9708 39.1138 45.019 39.2361 45.0317 39.415C45.0583 39.7898 44.922 40.3278 44.6007 40.9715C43.9648 42.2456 42.7203 43.7047 41.3387 44.6648C39.946 45.6324 38.2245 46.3213 36.8574 46.4887C36.1691 46.573 35.6284 46.5179 35.2712 46.3555C34.951 46.21 34.7622 45.9763 34.7271 45.575C34.6742 44.9706 34.7575 44.6626 34.8723 44.4788C34.987 44.2951 35.1852 44.15 35.5682 43.9855C35.7449 43.9096 35.9558 43.8332 36.195 43.7466C36.9544 43.4715 37.999 43.0931 39.141 42.2995C40.2209 41.5492 40.9829 40.7142 41.5823 40.0575C41.809 39.8091 42.0124 39.5862 42.2009 39.403C42.5596 39.0546 42.8671 38.8363 43.2057 38.7332C43.5362 38.6326 43.9552 38.6245 44.5653 38.8122Z"
:fill="waiting ? '#CC9440' : active ? '#3AC589' : '#2B2B2B'"
:stroke="waiting ? '#FFB950' : active ? '#44F1A6' : '#3F3F3F'"
/>
</svg>
</template>
<script>
export default {
props: {
active: {
type: Boolean,
default: false,
},
waiting: {
type: Boolean,
default: false,
},
},
}
</script>

105
components/Footer.vue Normal file
View File

@ -0,0 +1,105 @@
<template>
<footer class="footer">
<div class="container">
<div class="level">
<div class="level-left"></div>
<div class="level-right">
<div class="level-item is-column">
<div class="level-subitem">
<div class="buttons">
<b-button
tag="a"
type="is-icon"
href="https://explore.duneanalytics.com/public/dashboards/UEU02CHiGtNw9crfeD6OJ7bKPnvFtNjOgZ7Vc6uj"
target="_blank"
icon-right="stats"
></b-button>
<b-button
tag="a"
type="is-icon"
href="https://twitter.com/TornadoCash"
target="_blank"
icon-right="twitter"
></b-button>
<b-button
tag="a"
type="is-icon"
href="https://t.me/TornadoCashOfficial"
target="_blank"
icon-right="telegram"
></b-button>
<b-button
tag="a"
type="is-icon"
href="https://github.com/tornadocash"
target="_blank"
icon-right="github"
></b-button>
<div class="break"></div>
<b-dropdown
v-model="$i18n.locale"
class="dropdown-langs"
position="is-top-left"
aria-role="list"
@change="langChange"
>
<b-button slot="trigger" type="is-icon">
<FlagIcon
:code="$i18n.locale"
:class="'is-active-locale-' + $i18n.locale"
/>
</b-button>
<b-dropdown-item
v-for="locale in locales"
:key="locale"
:value="locale"
aria-role="listitem"
>
<FlagIcon :code="locale" />
{{ printLang(locale) }}
</b-dropdown-item>
</b-dropdown>
</div>
</div>
</div>
</div>
</div>
</div>
</footer>
</template>
<script>
import { FlagIcon } from '@/components/icons'
export default {
components: {
FlagIcon,
},
computed: {
locales() {
return this.$i18n.availableLocales
},
},
methods: {
langChange(lang) {
localStorage.setItem('lang', lang)
if (lang === 'zh') {
lang += '-cn'
}
// this.$moment.locale(lang)
},
printLang(lang) {
let code = lang
switch (code) {
case 'zh':
code = 'cn'
break
}
return code.toUpperCase()
},
},
}
</script>

18
components/Loading.vue Normal file
View File

@ -0,0 +1,18 @@
<template>
<b-loading :active.sync="enabled">
<div class="loading-container">
<div class="loading-tornado"></div>
<div class="loading-message">{{ message }}...</div>
</div>
</b-loading>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState('loading', ['enabled', 'message', 'txHash']),
...mapState('getNetwork', ['getProviderName']),
},
}
</script>

9
components/Logo.vue Normal file
View File

@ -0,0 +1,9 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" class="logo" viewBox="0 0 155 40">
<path
fill="#44F1A6"
fill-rule="evenodd"
d="M29.4,8.6A11.5,11.5,0,0,1,39.9,19.4,17.1,17.1,0,0,0,8.5,10.5,11.5,11.5,0,0,1,19.3,0a17.2,17.2,0,0,0-8.9,31.4A11.5,11.5,0,0,1,0,20.6a17.1,17.1,0,0,0,31.3,9.1A11.4,11.4,0,0,1,20.6,40,17.1,17.1,0,0,0,29.4,8.6ZM19.9,27.2a6.9,6.9,0,0,1-5-2.1,7.1,7.1,0,1,1,5,2.1Zm36.6-9.7v7.4c0,1.3.5,1.6,1.8,1.6a4.8,4.8,0,0,0,1.8-.3v2.9a6.4,6.4,0,0,1-2.4.5c-3,0-4.8-1.9-4.8-5V17.5H50.4V14.4h2.5V9.9h3.6v4.5h3.6v3.1ZM77,21.9c0,5-2.5,7.7-7,7.7s-7.1-2.7-7.1-7.7,2.5-7.7,7.1-7.7S77,16.9,77,21.9Zm-3.6,0c0-4-1.3-4.8-3.4-4.8s-3.5.8-3.5,4.8,1.3,4.7,3.5,4.7S73.4,25.8,73.4,21.9ZM88,14.1v3.2a11.5,11.5,0,0,0-3.8.5V29.4H80.6V15.7A20.4,20.4,0,0,1,88,14.1Zm16.9,6.3v9h-3.6V20.1c0-1.4-.2-2.8-3.5-2.8a12,12,0,0,0-2.8.3V29.4H91.4V15.8a15,15,0,0,1,6.9-1.7C102.4,14.1,104.9,16.5,104.9,20.4Zm16.2.1v4.1c0,2.4-1.6,5-6.3,5s-6.4-2.6-6.4-5.1.5-4.7,6.5-4.7a11,11,0,0,1,2.6.2c0-1.7-.8-2.9-3.2-2.9a11.2,11.2,0,0,0-4.4.8V15a12.5,12.5,0,0,1,4.6-.9C118.8,14.1,121.1,16.4,121.1,20.5Zm-3.6,1.7h-2.1c-2.5,0-3.4.7-3.4,1.9s.9,2.4,2.8,2.4,2.7-.9,2.7-2.3Zm13.9-8.1,2.5.3V8.7h3.6V24.6c0,2.4-1.7,5-6.4,5-2,0-6.7-.8-6.7-7.9C124.4,16.8,126.9,14.1,131.4,14.1Zm-.3,12.4c1.6,0,2.8-.4,2.8-1.6V17.2l-2.4-.2c-2.1,0-3.5,1-3.5,4.5S128.8,26.5,131.1,26.5ZM155,21.9c0,5-2.5,7.7-7.1,7.7s-7.1-2.7-7.1-7.7,2.5-7.7,7.1-7.7S155,16.9,155,21.9Zm-3.6,0c0-4-1.4-4.8-3.5-4.8s-3.5.8-3.5,4.8,1.4,4.7,3.5,4.7S151.4,25.8,151.4,21.9Z"
/>
</svg>
</template>

72
components/Navbar.vue Normal file
View File

@ -0,0 +1,72 @@
<template>
<b-navbar wrapper-class="container" class="header">
<template slot="brand">
<b-navbar-item tag="router-link" to="/" active-class="">
<Logo />
</b-navbar-item>
</template>
<template slot="start">
<b-navbar-item
href="https://medium.com/@tornado.cash/tornado-cash-governance-proposal-a55c5c7d0703"
target="_blank"
class="decorate"
>
{{ $t('info') }}
</b-navbar-item>
</template>
<template slot="end">
<b-navbar-item tag="div">
<div class="buttons">
<b-button
v-if="isLoggedIn"
type="is-primary"
outlined
icon-left="logout"
@click="onLogOut"
>{{ $t('logout') }}</b-button
>
<b-button
v-else
type="is-primary"
outlined
icon-left="wallet"
@click="onLogIn"
>{{ $t('connect') }}</b-button
>
</div>
</b-navbar-item>
</template>
</b-navbar>
</template>
<script>
import Logo from '@/components/Logo'
import Web3Connect from '@/components/Web3Connect'
import { mapGetters, mapActions } from 'vuex'
export default {
components: {
Logo,
},
computed: {
...mapGetters('provider', ['getProviderName']),
isLoggedIn() {
return !!this.getProviderName
},
},
methods: {
...mapActions('provider', ['clearState']),
onLogIn() {
this.$buefy.modal.open({
parent: this,
component: Web3Connect,
hasModalCard: true,
width: 440,
})
},
onLogOut() {
this.clearState()
},
},
}
</script>

53
components/Notices.vue Normal file
View File

@ -0,0 +1,53 @@
<template>
<div class="notices is-top">
<b-notification
v-for="notice in notices"
:key="notice.id"
class="is-top-right"
has-icon
:icon="notice.type"
:aria-close-label="$t('closeNotification')"
role="alert"
@close="close(notice.id)"
>
<i18n :path="notice.title.path || notice.title" tag="span">
<template v-slot:value>
<b>{{ notice.title.value }}</b>
</template>
</i18n>
<a
v-if="typeof notice.callback === 'function'"
@click="callbackWithClose(notice.id, notice.callback)"
>
Scroll to voucher
</a>
<a
v-if="notice.txHash"
:href="txExplorerUrl(notice.txHash)"
target="_blank"
>
{{ $t('viewOnEtherscan') }}
</a>
</b-notification>
</div>
</template>
<script>
import { mapState, mapActions, mapGetters } from 'vuex'
export default {
computed: {
...mapState('notice', ['notices']),
...mapGetters('txStorage', ['txExplorerUrl']),
},
methods: {
...mapActions('notice', ['deleteNotice']),
callbackWithClose(id, callback) {
callback()
this.deleteNotice({ id })
},
close(id) {
this.deleteNotice({ id })
},
},
}
</script>

135
components/Step.vue Normal file
View File

@ -0,0 +1,135 @@
<template>
<div :id="data.isActive ? 'current' : ''" class="step">
<div class="step-container">
<diamond
:active="!!data.deployerAddress"
:waiting="!canDeploy(data.domain)"
/>
<div class="step-body">
<h4>{{ data.title }}</h4>
<h5 v-if="data.domain" class="deployed">
ENS:
<a :href="domainUrl(data.expectedAddress)" target="_blank">{{
data.domain
}}</a>
</h5>
<i18n
v-if="data.deployerAddress"
class="deployed"
tag="h6"
path="deployedBy"
>
<template v-slot:link>
<a :href="txExplorerUrl(data.deployTransaction)" target="_blank">{{
data.deployerAddress
}}</a>
</template>
</i18n>
</div>
<div class="step-more-button">
<b-button
size="is-small"
:class="data.isActive ? 'button--active' : ''"
@click="isExpanded = !isExpanded"
>{{ isExpanded ? 'Less' : 'More' }}</b-button
>
</div>
<div class="step-tail">
<div v-if="data.deployerAddress" class="completed">
<b-icon icon="check" />
<span>{{ $t('completed') }}</span>
</div>
<b-tooltip
v-else
:label="
isNotLoggedIn
? $t('pleaseConnectWallet')
: !canDeploy(data.domain)
? $t('dependsOnEns', { ens: data.dependsOn.join(', ') })
: ''
"
position="is-top"
multilined
:size="isNotLoggedIn ? 'is-small' : 'is-large'"
:active="isNotLoggedIn || !canDeploy(data.domain)"
>
<b-button
type="is-primary"
outlined
icon-left="tool"
:disabled="
isNotLoggedIn || !canDeploy(data.domain) || data.isPending
"
@mousedown="(e) => e.preventDefault()"
@click="onDeploy"
>
{{ $t('deploy') }}
</b-button>
</b-tooltip>
</div>
</div>
<transition-expand>
<div v-show="isExpanded" class="step-more">
<p v-show="!data.airdrops">
{{ data.description }}
</p>
<b-table
v-show="data.airdrops"
:data="data.airdrops"
:sticky-header="true"
:row-class="(row) => row.address === getAccount && 'is-selected'"
>
<b-table-column v-slot="props" field="address" label="Address">
<a :href="domainUrl(props.row.address)" target="_blank">{{
props.row.address
}}</a>
</b-table-column>
<b-table-column v-slot="props" field="value" label="Value">
{{ Number(props.row.value).toFixed(4) }} vTORN
</b-table-column>
</b-table>
</div>
</transition-expand>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
import Diamond from '@/components/Diamond'
import TransitionExpand from '@/components/TransitionExpand'
export default {
components: {
Diamond,
TransitionExpand,
},
props: {
data: {
type: Object,
required: true,
},
},
data() {
return {
isExpanded: false,
}
},
computed: {
...mapGetters('provider', ['getProviderName', 'getAccount']),
...mapGetters('steps', ['canDeploy']),
...mapGetters('txStorage', ['txExplorerUrl']),
isNotLoggedIn() {
return !this.getProviderName
},
},
methods: {
...mapActions('deploy', ['deployContract']),
onDeploy() {
this.deployContract({ action: this.data, index: this.$vnode.key })
},
domainUrl(address) {
return `https://etherscan.io/address/${address}`
},
},
}
</script>

38
components/Steps.vue Normal file
View File

@ -0,0 +1,38 @@
<template>
<div class="steps">
<step v-for="(step, index) in getData" :key="index" :data="step" />
</div>
</template>
<script>
import Step from '@/components/Step'
import { mapState } from 'vuex'
export default {
components: {
Step,
},
computed: {
...mapState('steps', ['steps']),
...mapState('airdrop', ['airdrops', 'notificationIndex']),
getData() {
if (Array.isArray(this.airdrops)) {
return this.steps.map((step, index) => {
if (step.contract === 'Airdrop.sol') {
const dropIndex = index + this.airdrops.length - this.steps.length
return {
...step,
airdrops: this.airdrops[dropIndex],
isActive: this.notificationIndex === dropIndex,
}
}
return step
})
}
return this.steps
},
},
}
</script>

View File

@ -0,0 +1,71 @@
<script>
export default {
name: 'TransitionExpand',
functional: true,
render(createElement, context) {
const data = {
props: {
name: 'expand',
},
on: {
afterEnter(element) {
element.style.height = 'auto'
},
enter(element) {
const { width } = getComputedStyle(element)
element.style.width = width
element.style.position = 'absolute'
element.style.visibility = 'hidden'
element.style.height = 'auto'
const { height } = getComputedStyle(element)
element.style.width = null
element.style.position = null
element.style.visibility = null
element.style.height = 0
// Force repaint to make sure the
// animation is triggered correctly.
// eslint-disable-next-line no-unused-expressions
getComputedStyle(element).height
requestAnimationFrame(() => {
element.style.height = height
})
},
leave(element) {
const { height } = getComputedStyle(element)
element.style.height = height
// Force repaint to make sure the
// animation is triggered correctly.
// eslint-disable-next-line no-unused-expressions
getComputedStyle(element).height
requestAnimationFrame(() => {
element.style.height = 0
})
},
},
}
return createElement('transition', data, context.children)
},
}
</script>
<style scoped>
* {
will-change: height;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000px;
}
</style>
<style>
.expand-enter-active,
.expand-leave-active {
transition: height 0.2s ease-in-out, opacity 0.2s;
overflow: hidden;
}
.expand-enter,
.expand-leave-to {
height: 0;
opacity: 0;
}
</style>

View File

@ -0,0 +1,83 @@
<template>
<div class="modal-card box box-modal">
<header class="box-modal-header is-spaced">
<div class="box-modal-title">{{ $t('yourWallet') }}</div>
<button type="button" class="delete" @click="$emit('close')" />
</header>
<div class="note">
{{ $t('pleaseSelectYourWeb3Wallet') }}
</div>
<div
class="field is-grouped is-grouped-centered is-grouped-multiline wallets"
>
<!-- <div v-show="isGeneric" class="control">
<button
class="button is-small is-black is-generic"
@click="_web3Connect('generic')"
>
{{ $t('otherWallet') }}
</button>
</div> -->
<div class="control">
<b-tooltip
:label="$t('pleaseInstallMetamask')"
position="is-top"
:active="!isMetamask"
multilined
>
<button
:disabled="!isMetamask"
class="button is-small is-black is-metamask"
@click="_web3Connect('metamask')"
>
Metamask
</button>
</b-tooltip>
</div>
<div v-show="isTrust" class="control">
<button
class="button is-small is-black is-trustwallet"
@click="_web3Connect('trustwallet')"
>
Trust Wallet
</button>
</div>
</div>
</div>
</template>
<script>
/* eslint-disable no-console */
export default {
computed: {
isMetamask() {
return window.web3 && window.web3.currentProvider.isMetaMask
},
// isGeneric() {
// return (
// !this.isMetamask && !this.isTrust && (window.web3 || window.ethereum)
// )
// },
isTrust() {
return window.web3 && window.web3.currentProvider.isTrust
},
},
methods: {
async _web3Connect(name, network) {
this.$store.dispatch('loading/enable', {})
try {
await this.$store.dispatch(
'provider/initProvider',
{ name, network },
{ root: true }
)
this.$store.dispatch('steps/fetchDeploymentStatus', {})
} catch (e) {
console.error(e)
}
this.$store.dispatch('loading/disable')
this.$parent.close()
},
},
}
</script>

View File

@ -0,0 +1,26 @@
<template>
<i v-if="code" class="flag-icon" :class="flagIconClass"></i>
</template>
<script>
export default {
name: 'FlagIcon',
props: {
code: { type: String, default: null },
},
computed: {
flagIconClass() {
let code = this.code
switch (code) {
case 'zh':
code = 'cn'
break
case 'en':
code = 'gb'
break
}
return 'flag-icon-' + code
},
},
}
</script>

View File

@ -0,0 +1 @@
export { default as FlagIcon } from './FlagIcon'

12
jsconfig.json Normal file
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"~~/*": ["./*"],
"@@/*": ["./*"]
}
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

29
langs/en.json Normal file
View File

@ -0,0 +1,29 @@
{
"closeNotification": "Close notification",
"yourWallet": "Your Wallet",
"pleaseSelectYourWeb3Wallet": "Please select your Web3 compatible wallet:",
"otherWallet": "Other Wallet",
"currentNetworkIsNotSupported": "Current network is not supported. Try Kovan or Mainnet",
"logout": "Logout",
"connect": "Connect",
"close": "Close",
"preparingTransactionData": "Preparing Transaction Data",
"loading": "Loading",
"pleaseConfirmTransactionInWallet": "Please confirm transaction in {wallet}",
"info": "Info",
"pleaseInstallMetamask": "Please install Metamask. We recommend using this wallet.",
"completed": "Completed",
"deploy": "Deploy",
"deployedBy": "Deployed by: {link}",
"startNow": "Start now",
"completedTasks": "Completed Tasks: {progress}",
"pageSubtitle": "Follow these simple steps to become part of deployment of Tornado.Cash Governance protocol.",
"alreadyDeployed": "Already deployed",
"contractDeployed": "Contract successfully deployed",
"transactionFailed": "Transaction was failed",
"cannotBeExecuted": "This action is not deployable at the moment",
"sendingTransaction": "Sending transaction",
"viewOnEtherscan": "View on Etherscan",
"pleaseConnectWallet": "Please connect your wallet first",
"dependsOnEns": "This action depends on {ens}"
}

8
langs/index.js Normal file
View File

@ -0,0 +1,8 @@
import en from './en.json'
import ru from './ru.json'
import zh from './zh.json'
export default {
en,
ru,
zh,
}

16
langs/ru.json Normal file
View File

@ -0,0 +1,16 @@
{
"closeNotification": "Закрыть",
"yourWallet": "Подключение кошелька",
"pleaseSelectYourWeb3Wallet": "Пожалуйста, выберите Web3-совместимый кошелек:",
"otherWallet": "Другой кошелек",
"currentNetworkIsNotSupported": "Текущая сеть не поддерживается. Попробуйте Kovan или Mainnet",
"logout": "Выйти",
"connect": "Подключить",
"close": "Закрыть",
"preparingTransactionData": "Подготовка данных транзакции",
"viewInExplorer": "Посмотреть транзакцию",
"loading": "Загрузка",
"pleaseConfirmTransactionInWallet": "Пожалуйста, подтвердите транзакцию в {wallet}",
"info": "Информация",
"pleaseInstallMetamask": "Установите Metamask. Мы рекомендуем использовать этот кошелек."
}

15
langs/zh.json Normal file
View File

@ -0,0 +1,15 @@
{
"closeNotification": "关闭通知",
"yourWallet": "你的钱包",
"pleaseSelectYourWeb3Wallet": "请选择你的 Web3 钱包:",
"otherWallet": "其它钱包",
"currentNetworkIsNotSupported": "暂不支持这个网络,请尝试 Kovan 或者主网",
"logout": "退出",
"connect": "连接",
"close": "关闭",
"preparingTransactionData": "正在准备交易数据",
"viewInExplorer": "在浏览器中查看",
"loading": "加载中",
"pleaseConfirmTransactionInWallet": "请在 {wallet} 中确认交易",
"info": "信息"
}

7
layouts/README.md Normal file
View File

@ -0,0 +1,7 @@
# LAYOUTS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Application Layouts.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/views#layouts).

50
layouts/default.vue Normal file
View File

@ -0,0 +1,50 @@
<template>
<div class="wrapper">
<Navbar />
<section class="main-content section">
<div class="container">
<nuxt />
</div>
</section>
<Footer />
<Loading />
<Notices />
</div>
</template>
<script>
import Navbar from '@/components/Navbar'
import Footer from '@/components/Footer'
import Loading from '@/components/Loading'
import Notices from '@/components/Notices'
import { mapActions } from 'vuex'
import { localStorage } from '@/utillites'
export default {
components: {
Navbar,
Footer,
Loading,
Notices,
},
async mounted() {
this.setAirdropAddresses()
const result = localStorage.getItem('provider')
if (result && result.name) {
await this.initProvider({
name: result.name,
network: result.network,
})
}
this.fetchDeploymentStatus()
this.statusPooling()
this.fetchGasPrice()
},
methods: {
...mapActions('provider', ['initProvider']),
...mapActions('airdrop', ['setAirdropAddresses']),
...mapActions('steps', ['statusPooling', 'fetchDeploymentStatus']),
...mapActions('gasPrice', ['fetchGasPrice']),
},
}
</script>

8
middleware/README.md Normal file
View File

@ -0,0 +1,8 @@
# MIDDLEWARE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).

39
networkConfig.js Normal file
View File

@ -0,0 +1,39 @@
const networkConfig = {
netId1: {
rpcCallRetryAttempt: 15,
gasPrices: { instant: 80, fast: 50, standard: 25, low: 8 },
currencyName: 'ETH',
explorerUrl: {
tx: 'https://etherscan.io/tx/',
address: 'https://etherscan.io/address/',
},
networkName: 'Mainnet',
rpcUrls: {
Infura: {
name: 'Infura',
url: 'https://mainnet.infura.io/v3/2884a3281c1d4ae8952e25c84d76bced',
},
MyCrypto: { name: 'MyCrypto', url: 'https://api.mycryptoapi.com/eth' },
},
pollInterval: 60,
},
netId5: {
rpcCallRetryAttempt: 15,
gasPrices: { instant: 80, fast: 50, standard: 25, low: 8 },
currencyName: 'gETH',
explorerUrl: {
tx: 'https://goerli.etherscan.io/tx/',
address: 'https://goerli.etherscan.io/address/',
},
networkName: 'goerli',
rpcUrls: {
Infura: {
name: 'Infura',
url: 'https://goerli.infura.io/v3/2884a3281c1d4ae8952e25c84d76bced',
},
},
pollInterval: 200,
},
}
export default networkConfig

104
nuxt.config.js Normal file
View File

@ -0,0 +1,104 @@
export default {
// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
ssr: false,
// Target (https://go.nuxtjs.dev/config-target)
target: 'static',
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
title: 'Tornado.cash',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
],
link: [
{ rel: 'manifest', href: '/manifest.json' },
{
rel: 'shortcut icon',
type: 'image/x-icon',
href: '/favicon/favicon.ico',
},
{ rel: 'apple-touch-icon', href: '/favicon/apple-touch-icon.png' },
],
},
// Customize the progress-bar color
loading: { color: '#44F1A6', height: '5px', duration: 5000 },
loadingIndicator: {
name: 'circle',
color: '#44F1A6',
background: '#000',
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: ['@/assets/styles/app.scss'],
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: ['~/plugins/i18n.js'],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [
// https://go.nuxtjs.dev/eslint
'@nuxtjs/eslint-module',
],
// Modules (https://go.nuxtjs.dev/config-modules)
modules: [
// https://go.nuxtjs.dev/buefy
[
'nuxt-buefy',
{
css: false,
materialDesignIcons: false,
defaultIconPack: 'trnd',
customIconPacks: {
trnd: {
sizes: {
default: 'trnd-24px',
'is-small': null,
'is-medium': 'trnd-36px',
'is-large': 'trnd-48px',
},
iconPrefix: 'trnd-',
},
},
},
],
'nuxt-web3-provider',
],
provider: {
rpcUrl: 'https://mainnet.infura.io/v3/da564f81919d40c9a3bcaee4ff44438d',
},
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {
/*
** You can extend webpack config here
*/
extend(config, ctx) {
config.output.publicPath = './_nuxt/'
},
// splitChunks: {
// commons: false
// }
},
router: {
linkActiveClass: '',
linkExactActiveClass: 'is-active',
extendRoutes(routes, resolve) {
routes.push({
name: 'ipfs-root',
path: '*',
component: resolve(__dirname, 'pages/index.vue'),
})
},
},
}

36
package.json Normal file
View File

@ -0,0 +1,36 @@
{
"name": "governance-dep-ui",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"lint:js": "eslint --ext .js,.vue --ignore-path .gitignore .",
"lint": "yarn lint:js",
"deploy-prod": "npm run generate && push-dir --allow-unclean --dir=dist --branch=gh-pages --cleanup --remote=temp"
},
"dependencies": {
"nuxt-web3-provider": "^0.1.1",
"core-js": "^3.6.5",
"gas-price-oracle": "^0.2.2",
"node-sass": "^4.14.1",
"nuxt": "^2.14.6",
"nuxt-buefy": "^0.4.4",
"push-dir": "^0.4.1",
"sass-loader": "^10.0.3",
"vue-i18n": "^8.22.1",
"web3": "1.2.6"
},
"devDependencies": {
"@nuxtjs/eslint-config": "^3.1.0",
"@nuxtjs/eslint-module": "^3.0.1",
"babel-eslint": "^10.1.0",
"eslint": "^7.10.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-nuxt": "^1.0.0",
"eslint-plugin-prettier": "^3.1.4",
"prettier": "^2.1.2"
}
}

6
pages/README.md Normal file
View File

@ -0,0 +1,6 @@
# PAGES
This directory contains your Application Views and Routes.
The framework reads all the `*.vue` files inside this directory and creates the router of your application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing).

55
pages/index.vue Normal file
View File

@ -0,0 +1,55 @@
<template>
<div>
<h1 class="title has-text-centered">
Tornado.cash <span>Governance</span> Initiation
</h1>
<h2 class="subtitle has-text-centered">{{ $t('pageSubtitle') }}</h2>
<div class="buttons is-centered">
<b-button
type="is-primary"
outlined
icon-left="tool"
@mousedown="(e) => e.preventDefault()"
@click="onStart"
>{{ $t('startNow') }}</b-button
>
</div>
<i18n tag="h3" class="title is-14px mt-6" path="completedTasks">
<template v-slot:progress>
<span>{{ deployedCount }}</span>
</template>
</i18n>
<div class="tornado-discoverer image is-16by9"></div>
<steps ref="steps" />
</div>
</template>
<script>
import Steps from '@/components/Steps'
import { mapGetters } from 'vuex'
export default {
components: {
Steps,
},
computed: {
...mapGetters('steps', ['deployedCount']),
},
methods: {
scrollTo(element) {
window.scrollTo({
behavior: 'smooth',
left: 0,
top: element.getBoundingClientRect().top,
})
},
onStart() {
this.scrollTo(this.$refs.steps.$el)
},
},
}
</script>

7
plugins/README.md Normal file
View File

@ -0,0 +1,7 @@
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).

124
plugins/i18n.js Normal file
View File

@ -0,0 +1,124 @@
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '../langs/index'
/**
* @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
* @param choicesLength {number} an overall amount of available choices
* @returns a final choice index to select plural word by
**/
VueI18n.prototype.getChoiceIndex = function (choice, choicesLength) {
// this === VueI18n instance, so the locale property also exists here
// add the word "only" if the value is greater than 1 and less than 5
if (this.locale !== 'ru') {
if (choice === 0 || choice === 1) {
return choice
}
if (choice > 1 && choice < 5) {
return 2
}
return 3
}
// comply with the rules of the Russian language
if (choice === 0) {
return 0
}
const teen = choice > 10 && choice < 20
const endsWithOne = choice % 10 === 1
if (!teen && endsWithOne) {
return 1
}
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
return 2
}
return choicesLength < 4 ? 2 : 3
}
Vue.use(VueI18n)
let lang = 'en'
if (process.browser) {
const locale =
localStorage.getItem('lang') ||
navigator.language.substr(0, 2).toLowerCase()
lang = !messages[locale] ? 'en' : locale
}
const dateTimeFormats = {
en: {
long: {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: 'numeric',
minute: 'numeric',
hour12: true,
},
},
ru: {
long: {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
},
},
zh: {
long: {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long',
hour: 'numeric',
minute: 'numeric',
hour12: true,
},
},
}
const numberFormats = {
en: {
compact: {
notation: 'compact',
},
},
ru: {
compact: {
notation: 'compact',
},
},
zh: {
compact: {
notation: 'compact',
},
},
}
// Create VueI18n instance with options
export default ({ app, route, store }) => {
app.i18n = new VueI18n({
locale: lang,
fallbackLocale: 'en',
messages,
silentFallbackWarn: true,
dateTimeFormats,
numberFormats,
})
if (lang === 'zh') {
lang += '-cn'
}
// app.$moment.locale(lang)
}

1
static/CNAME Normal file
View File

@ -0,0 +1 @@
initiation.tornado.cash

11
static/README.md Normal file
View File

@ -0,0 +1,11 @@
# STATIC
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your static files.
Each file inside this directory is mapped to `/`.
Thus you'd want to delete this README.md before deploying to production.
Example: `/static/robots.txt` is mapped as `/robots.txt`.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#static).

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/favicon/mstile-150x150.png"/>
<TileColor>#000403</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
static/favicon/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

19
static/manifest.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "Tornado Cash",
"short_name": "Tornado Cash",
"icons": [
{
"src": "/favicon/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/favicon/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#000",
"background_color": "#000",
"display": "standalone"
}

2
static/robots.txt Normal file
View File

@ -0,0 +1,2 @@
User-agent: *
Disallow: /

10
store/README.md Normal file
View File

@ -0,0 +1,10 @@
# STORE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your Vuex Store files.
Vuex Store option is implemented in the Nuxt.js framework.
Creating a file in this directory automatically activates the option in the framework.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/vuex-store).

52
store/airdrop/actions.js Normal file
View File

@ -0,0 +1,52 @@
import { localStorage } from '@/utillites'
import { SET_AIRDROP, SET_NOTIFICATION } from './constant'
export default {
setAirdropAddresses({ getters, commit }) {
const savedActions = localStorage.getItem('actions')
if (savedActions) {
commit(SET_AIRDROP, savedActions)
return
}
const actions = getters.selectAirdropData
localStorage.setItem('actions', actions)
commit(SET_AIRDROP, actions)
},
checkAddress({ rootGetters, commit, dispatch, state }) {
const address = rootGetters['provider/getAccount']
const callback = () => {
window.scrollTo({
behavior: 'smooth',
left: 0,
top: document.getElementById('current').getBoundingClientRect().top,
})
}
state.airdrops.forEach((item, index) => {
item.forEach((i) => {
if (i.address.toLowerCase() === address.toLowerCase()) {
commit(SET_NOTIFICATION, index)
dispatch(
'notice/addNotice',
{
notice: {
type: 'success',
title: `Your address was included into the airdrop #${
index + 1
}`,
callback,
},
},
{ root: true }
)
}
})
})
},
}

View File

@ -0,0 +1,2 @@
export const SET_AIRDROP = 'airdrop/SET_AIRDROP'
export const SET_NOTIFICATION = 'airdrop/SET_NOTIFICATION'

31
store/airdrop/getters.js Normal file
View File

@ -0,0 +1,31 @@
import { fromWei } from 'web3-utils'
import deploymentActions from '~/static/deploymentActions.json'
export default {
getAirdrops: (state) => {
return state.airdrops
},
selectAirdropsActions: () => {
return deploymentActions.actions.filter(
(action) => action.contract === 'Airdrop.sol'
)
},
selectAirdropData: (state, getters) => {
const actions = getters.selectAirdropsActions
return actions.map((action) => {
const data = action.bytecode.split(state.secretCode)[1]
// eslint-disable-next-line no-unused-vars
const [_, ...addressInfo] = data.match(/.{128}/g)
return addressInfo.map((info) => {
return {
address: `0x${info.slice(24, 64)}`,
value: fromWei(info.slice(64, 128)),
}
})
})
},
}

View File

@ -0,0 +1,10 @@
import { SET_AIRDROP, SET_NOTIFICATION } from './constant'
export default {
[SET_AIRDROP](state, airdrops) {
this._vm.$set(state, 'airdrops', airdrops)
},
[SET_NOTIFICATION](state, index) {
this._vm.$set(state, 'notificationIndex', index)
},
}

6
store/airdrop/state.js Normal file
View File

@ -0,0 +1,6 @@
export default () => ({
airdrops: [],
notificationIndex: -1,
secretCode:
'd6d5ad7ec98c44fe89ef66c3277ef0ec7b1acbd7e0134bc1291fd952d7ff6030',
})

186
store/deploy.js Normal file
View File

@ -0,0 +1,186 @@
/* eslint-disable no-console */
import { hexToNumber, numberToHex } from 'web3-utils'
import deployerABI from '../abi/deployer.abi.json'
import deploymentActions from '../static/deploymentActions.json'
const state = () => {
return {}
}
const getters = {
deployerContract: (state, getters, rootState, rootGetters) => (isProxy) => {
const web3 = rootGetters['provider/getWeb3']
return new web3.eth.Contract(
deployerABI,
isProxy
? deploymentActions.deployer
: deploymentActions.actions[0].expectedAddress
)
},
}
const mutations = {}
const actions = {
async deployContract(
{ state, dispatch, getters, rootGetters, commit, rootState },
{ action, index }
) {
try {
dispatch('loading/enable', {}, { root: true })
const isProxy = action.domain === 'deployer.contract.tornadocash.eth'
const ethAccount = rootGetters['provider/getAccount']
const web3 = rootGetters['provider/getWeb3']
const code = await web3.eth.getCode(action.expectedAddress)
console.log('code', code)
if (code !== '0x') {
dispatch(
'notice/addNoticeWithInterval',
{
notice: {
title: 'alreadyDeployed',
type: 'danger',
},
},
{ root: true }
)
throw new Error('Already deployed')
}
const gasPrice = rootGetters['gasPrice/fastGasPrice']
const data = getters
.deployerContract(isProxy)
.methods.deploy(action.bytecode, deploymentActions.salt)
.encodeABI()
const callParamsEstimate = {
method: 'eth_estimateGas',
params: [
{
from: ethAccount,
to: getters.deployerContract(isProxy)._address,
// gas: numberToHex(6e6),
gasPrice,
value: `0x0`,
data,
},
],
from: ethAccount,
}
const gasEstimate =
action.domain === 'deployer.contract.tornadocash.eth'
? numberToHex(1e6)
: await dispatch('provider/sendRequest', callParamsEstimate, {
root: true,
})
const gasWithBuffer = Math.ceil(hexToNumber(gasEstimate) * 1.1)
const callParams = {
method: 'eth_sendTransaction',
params: [
{
from: ethAccount,
to: getters.deployerContract(isProxy)._address,
gas: numberToHex(gasWithBuffer),
gasPrice,
value: 0,
data,
},
],
from: ethAccount,
}
dispatch(
'loading/changeText',
{
message: this.app.i18n.t('pleaseConfirmTransactionInWallet', {
wallet: rootGetters['provider/getProviderName'],
}),
},
{ root: true }
)
const txHash = await dispatch('provider/sendRequest', callParams, {
root: true,
})
console.log('txHash', txHash)
dispatch('loading/disable', {}, { root: true })
dispatch(
'steps/setPendingState',
{ status: true, stepIndex: index },
{ root: true }
)
const noticeId = await dispatch(
'notice/addNotice',
{
notice: {
title: 'sendingTransaction',
txHash,
type: 'loading',
},
},
{ root: true }
)
const success = await dispatch(
'txStorage/runTxWatcher',
{ txHash },
{ root: true }
)
if (success) {
dispatch(
'notice/updateNotice',
{
id: noticeId,
notice: {
title: 'contractDeployed',
type: 'success',
},
interval: 20000,
},
{ root: true }
)
dispatch('steps/fetchDeploymentStatus', {}, { root: true })
} else {
dispatch(
'notice/updateNotice',
{
id: noticeId,
notice: {
title: 'transactionFailed',
type: 'danger',
},
interval: 20000,
},
{ root: true }
)
}
} catch (e) {
console.error('deployContract', e.message)
await dispatch(
'notice/addNotice',
{
notice: {
title: 'cannotBeExecuted',
type: 'danger',
},
},
{ root: true }
)
} finally {
dispatch('loading/disable', {}, { root: true })
dispatch(
'steps/setPendingState',
{ status: false, stepIndex: index },
{ root: true }
)
}
},
}
export default {
namespaced: true,
state,
getters,
mutations,
actions,
}

55
store/gasPrice.js Normal file
View File

@ -0,0 +1,55 @@
/* eslint-disable no-console */
import { GasPriceOracle } from 'gas-price-oracle'
import networkConfig from '@/networkConfig'
const { toHex, toWei } = require('web3-utils')
export const state = () => {
return {
instant: networkConfig.netId1.gasPrices.instant,
fast: networkConfig.netId1.gasPrices.fast,
standard: networkConfig.netId1.gasPrices.standard,
low: networkConfig.netId1.gasPrices.low,
custom: null,
}
}
export const getters = {
oracle: (state, getters, rootState, rootGetters) => {
const currentRpc = rootGetters['provider/getNetwork'].rpcUrls.Infura.url
console.log('currentRpc', currentRpc)
return new GasPriceOracle({ defaultRpc: currentRpc })
},
fastGasPrice: (state) => {
return toHex(toWei(state.fast.toString(), 'gwei'))
},
lowGasPrice: (state) => {
return toHex(toWei(state.standard.toString(), 'gwei'))
},
}
export const mutations = {
SAVE_GAS_PRICES(state, { instant, fast, standard, low }) {
this._vm.$set(state, 'instant', instant)
this._vm.$set(state, 'fast', fast)
this._vm.$set(state, 'standard', standard)
this._vm.$set(state, 'low', low)
},
SAVE_CUSTOM_GAS_PRICE(state, { custom }) {
this._vm.$set(state, 'custom', custom)
},
}
export const actions = {
async fetchGasPrice({ getters, commit, dispatch, rootGetters, state }) {
const { pollInterval } = rootGetters['provider/getNetwork']
try {
const gas = await getters.oracle.gasPrices(state)
commit('SAVE_GAS_PRICES', gas)
console.log(`Got fast gas price ${state.fast}`)
setTimeout(() => dispatch('fetchGasPrice'), 1000 * pollInterval)
} catch (e) {
console.error('fetchGasPrice', e)
setTimeout(() => dispatch('fetchGasPrice'), 1000 * pollInterval)
}
},
}

34
store/loading.js Normal file
View File

@ -0,0 +1,34 @@
export const state = () => {
return {
message: '',
enabled: false,
txHash: null,
}
}
export const getters = {}
export const mutations = {
ENABLE(state, { message, txHash }) {
state.message = message
state.txHash = txHash
state.enabled = true
},
DISABLE(state) {
state.message = ''
state.txHash = null
state.enabled = false
},
}
export const actions = {
enable({ commit }, { message = this.app.i18n.t('loading'), txHash }) {
commit('ENABLE', { message, txHash })
},
changeText({ commit }, { message }) {
commit('ENABLE', { message })
},
disable({ commit }) {
commit('DISABLE')
},
}

80
store/notice.js Normal file
View File

@ -0,0 +1,80 @@
const NOTICE_INTERVAL = 10000
export const state = () => {
return {
notices: [],
timers: {},
}
}
export const mutations = {
ADD_NOTICE(state, notice) {
state.notices.push(notice)
},
UPDATE_NOTICE(state, { index, notice }) {
this._vm.$set(state.notices, index, notice)
},
DELETE_NOTICE(state, index) {
this._vm.$delete(state.notices, index)
},
ADD_NOTICE_TIMER(state, { id, timerId }) {
this._vm.$set(state.timers, id, { timerId })
},
DELETE_NOTICE_TIMER(state, id) {
this._vm.$delete(state.timers, id)
},
}
export const actions = {
addNotice({ commit }, { notice }) {
return new Promise((resolve) => {
const id = `f${(+new Date()).toString(16)}`
commit('ADD_NOTICE', { ...notice, id })
resolve(id)
})
},
addNoticeTimer({ commit, dispatch }, { id, interval = NOTICE_INTERVAL }) {
const timerId = setTimeout(() => {
dispatch('deleteNotice', { id })
}, interval)
commit('ADD_NOTICE_TIMER', { id, timerId })
},
deleteNoticeTimer({ state, commit }, { id }) {
if (state.timers[id]) {
clearTimeout(state.timers[id].timerId)
commit('DELETE_NOTICE_TIMER', id)
}
},
addNoticeWithInterval({ dispatch }, { notice, interval }) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve) => {
const id = await dispatch('addNotice', { notice })
dispatch('addNoticeTimer', { id, interval })
resolve(id)
})
},
deleteNotice({ state, commit, dispatch }, { id }) {
const index = state.notices.findIndex((i) => {
return i.id === id
})
commit('DELETE_NOTICE', index)
dispatch('deleteNoticeTimer', { id })
},
updateNotice({ state, commit, dispatch }, { id, notice, interval }) {
const { notices } = state
const index = notices.findIndex((i) => {
return i.id === id
})
commit('UPDATE_NOTICE', {
index,
notice: {
...notices[index],
...notice,
},
})
if (interval) {
dispatch('deleteNoticeTimer', { id })
dispatch('addNoticeTimer', { id, interval })
}
},
}

82
store/provider/actions.js Normal file
View File

@ -0,0 +1,82 @@
import networkConfig from '@/networkConfig'
import { localStorage } from '@/utillites'
import {
SET_ACCOUNT,
SET_NETWORK,
CLEAR_STATE,
SET_BALANCE,
SET_NETWORK_NAME,
SET_PROVIDER_NAME,
} from './constant'
export default {
async initProvider({ commit, state, getters, dispatch }, { name, network }) {
try {
commit(SET_PROVIDER_NAME, name)
commit(SET_NETWORK_NAME, network)
localStorage.setItem('provider', { name, network })
const account = await this.$provider.initProvider(getters.getProvider)
const netId = await dispatch('checkNetworkVersion')
this.$provider.initWeb3(networkConfig[`netId${netId}`].rpcUrls.Infura.url)
commit(SET_ACCOUNT, account)
await dispatch('getBalance', account)
dispatch('airdrop/checkAddress', {}, { root: true })
} catch (err) {
throw new Error(err.message)
}
},
async checkNetworkVersion({ commit, state, dispatch }) {
try {
const id = await this.$provider.checkNetworkVersion()
commit(SET_NETWORK, { ...networkConfig[`netId${id}`], id: Number(id) })
return id
} catch (err) {
throw new Error(err.message)
}
},
async sendRequest(_, params) {
try {
return await this.$provider.sendRequest(params)
} catch (err) {
throw new Error(err.message)
}
},
async contractRequest(_, params) {
try {
return await this.$provider.contractRequest(params)
} catch (err) {
throw new Error(err.message)
}
},
async getBalance({ dispatch, commit, getters }, account) {
try {
const balance = await this.$provider.getBalance({ address: account })
commit(SET_BALANCE, balance)
} catch (err) {
throw new Error(err.message)
}
},
clearState({ commit }) {
try {
localStorage.removeItem('provider')
commit(CLEAR_STATE)
} catch (err) {
throw new Error(err.message)
}
},
async waitForTxReceipt({ dispatch, getters }, { txHash }) {
try {
const tx = await this.$provider.waitForTxReceipt({ txHash })
return tx
} catch (err) {
throw new Error(err.message)
}
},
}

View File

@ -0,0 +1,14 @@
export const MAIN = 'provider'
export const SET_BALANCE = 'provider/SET_BALANCE'
export const SET_NETWORK = 'provider/SET_NETWORK'
export const SET_ACCOUNT = 'provider/SET_ACCOUNT'
export const SET_NETWORK_NAME = 'provider/SET_NETWORK_NAME'
export const CLEAR_STATE = 'provider/CLEAR_STATE'
export const SET_PROVIDER = 'provider/SET_PROVIDER'
export const SET_PROVIDER_API = 'provider/SET_PROVIDER_API'
export const SET_PROVIDER_NAME = 'provider/SET_PROVIDER_NAME'
export const SET_GAS_PRICE = 'provider/SET_GAS_PRICE'

39
store/provider/getters.js Normal file
View File

@ -0,0 +1,39 @@
import Web3 from 'web3'
import networkConfig from '@/networkConfig'
export default {
getProvider: (state, getters) => {
switch (getters.getProviderName) {
case 'metamask':
case 'trustwallet':
case 'genericWeb3':
default:
if (window.ethereum) {
return window.ethereum
} else {
throw new Error(
`${getters.getProviderName} does not have ethereum property`
)
}
}
},
getProviderName: ({ provider }) => {
return provider.name
},
getWeb3: (state, getters) => {
const provider = getters.getProvider
return Object.freeze(new Web3(provider))
},
getBalance: (state) => {
return state.balance
},
getNetwork: (state) => {
const id = state.network.id
return { ...networkConfig[`netId${id}`], id: Number(id) }
},
getAccount: (state) => {
return state.account
},
}

View File

@ -0,0 +1,48 @@
import {
SET_ACCOUNT,
SET_NETWORK,
SET_BALANCE,
CLEAR_STATE,
SET_PROVIDER,
SET_PROVIDER_API,
SET_NETWORK_NAME,
SET_PROVIDER_NAME,
} from './constant'
export default {
[SET_PROVIDER](state, provider) {
this._vm.$set(state, 'provider', provider)
},
[CLEAR_STATE](state) {
state.provider = {
account: null,
network: {
name: 'mainnet',
id: 1,
},
provider: {
name: '',
version: '',
},
balance: 0,
}
},
[SET_BALANCE](state, balance) {
this._vm.$set(state, 'balance', balance)
},
[SET_PROVIDER_API](state, version) {
this._vm.$set(state.provider, 'version', version)
},
[SET_PROVIDER_NAME](state, name) {
this._vm.$set(state.provider, 'name', name)
},
[SET_ACCOUNT](state, account) {
this._vm.$set(state, 'account', account)
},
[SET_NETWORK](state, network) {
this._vm.$set(state, 'network', network)
},
[SET_NETWORK_NAME](state, name) {
this._vm.$set(state.network, 'name', name)
},
}

12
store/provider/state.js Normal file
View File

@ -0,0 +1,12 @@
export default () => ({
account: null,
network: {
name: 'mainnet',
id: 1,
},
provider: {
name: '',
version: '',
},
balance: 0,
})

82
store/steps.js Normal file
View File

@ -0,0 +1,82 @@
/* eslint-disable no-console */
import deploymentActions from '../static/deploymentActions.json'
const state = () => {
return {
steps: deploymentActions.actions,
}
}
const getters = {
deployedCount: (state) => {
const deployed = state.steps.filter((step) => !!step.deployerAddress).length
const all = state.steps.length
return `${deployed}/${all}`
},
canDeploy: (state) => (domain) => {
const { dependsOn } = state.steps.find((s) => s.domain === domain)
return dependsOn.every(
(d) => !!state.steps.find((s) => s.domain === d).deployerAddress
)
},
}
const SET_DEPLOYER = 'SET_DEPLOYER'
const SET_PENDING_STATE = 'SET_PENDING_STATE'
const mutations = {
[SET_DEPLOYER](state, { stepIndex, deployerAddress, deployTransaction }) {
this._vm.$set(state.steps[stepIndex], 'deployerAddress', deployerAddress)
this._vm.$set(
state.steps[stepIndex],
'deployTransaction',
deployTransaction
)
},
[SET_PENDING_STATE](state, { status, stepIndex }) {
this._vm.$set(state.steps[stepIndex], 'isPending', status)
},
}
const actions = {
async fetchDeploymentStatus({ state, dispatch, commit, rootGetters }) {
const deployContract = rootGetters['deploy/deployerContract'](false)
const events = await deployContract.getPastEvents('Deployed', {
fromBlock: 0,
toBlock: 'latest',
})
for (const event of events) {
const step = state.steps.find(
(s) => s.expectedAddress === event.returnValues.addr
)
if (!step) {
continue
}
commit(SET_DEPLOYER, {
stepIndex: state.steps.indexOf(step),
deployerAddress: event.returnValues.sender,
deployTransaction: event.transactionHash,
})
}
},
statusPooling({ dispatch }) {
setTimeout(async () => {
try {
console.log('Fetching deployment status...')
await dispatch('fetchDeploymentStatus')
} finally {
dispatch('statusPooling')
}
}, 15000)
},
setPendingState({ commit }, { status, stepIndex }) {
commit(SET_PENDING_STATE, { status, stepIndex })
},
}
export default {
namespaced: true,
state,
getters,
mutations,
actions,
}

8
store/txStatus.js Normal file
View File

@ -0,0 +1,8 @@
const statuses = Object.freeze({
nonExistent: 0,
waitingForReciept: 1,
success: 2,
fail: 3,
})
export default statuses

29
store/txStorage.js Normal file
View File

@ -0,0 +1,29 @@
/* eslint-disable no-console */
import txStatus from './txStatus'
const { hexToNumber } = require('web3-utils')
export const getters = {
txExplorerUrl: (state, getters, rootState, rootGetters) => (txHash) => {
const { explorerUrl } = rootGetters['provider/getNetwork']
return explorerUrl.tx + txHash
},
}
export const actions = {
async runTxWatcher({ commit, dispatch }, { txHash }) {
const result = await dispatch(
'provider/waitForTxReceipt',
{ txHash },
{ root: true }
)
if (!result || !result.status) {
return false
}
const status =
hexToNumber(result.status) === 1 ? txStatus.success : txStatus.fail
return status === txStatus.success
},
}

1
utillites/index.js Normal file
View File

@ -0,0 +1 @@
export { localStorage } from './localStorage'

63
utillites/localStorage.js Normal file
View File

@ -0,0 +1,63 @@
let isLocalStorageEnabled = null
try {
window.localStorage.setItem('test', 'test')
window.localStorage.removeItem('test')
isLocalStorageEnabled = true
} catch (e) {
isLocalStorageEnabled = false
}
const setItem = (key, value) => {
if (isLocalStorageEnabled) {
window.localStorage.setItem(key, JSON.stringify(value))
}
}
const getItem = (key) => {
if (isLocalStorageEnabled) {
const value = window.localStorage.getItem(key)
try {
return JSON.parse(String(value))
} catch (err) {
return value
}
}
return undefined
}
const removeItem = (key) => {
if (isLocalStorageEnabled) {
return window.localStorage.removeItem(key)
}
}
const clear = () => {
if (isLocalStorageEnabled) {
window.localStorage.clear()
}
}
const subscribe = (key, originalListener) => {
const listener = (event) => {
if (event.storageArea === window.localStorage && event.key === key) {
originalListener(event.newValue, event.oldValue)
}
}
window.addEventListener('storage', listener, false)
return listener
}
const unsubscribe = (listener) => {
window.removeEventListener('storage', listener, false)
}
export const localStorage = {
setItem,
getItem,
removeItem,
clear,
subscribe,
unsubscribe,
}

11413
yarn.lock Normal file

File diff suppressed because it is too large Load Diff