├── .docker ├── Dockerfile └── vhost.conf ├── .dockerignore ├── .editorconfig ├── .env.master ├── .env.test ├── .env.testnet ├── .env.toronet ├── .eslintrc.js ├── .github └── workflows │ ├── build-and-publish-electron-desktop.yml │ ├── deploy-github-pages.yml │ ├── deploy.yml │ └── test.yml ├── .gitignore ├── .ncurc.js ├── LICENSE ├── README.md ├── api ├── auto-delegation.js ├── chainik.js ├── ethersacn.js ├── explorer.js ├── gate.js ├── hub.js ├── mns.js └── web3.js ├── assets ├── README.md ├── abi-erc20.js ├── abi-hub.js ├── abi-weth.js ├── axios-debounce.js ├── axios-default-adapter.js ├── axios-prevent-concurrency.js ├── axios-time-offset.js ├── axios-to-camel.js ├── big.js ├── debounce-promise.js ├── event-bus.js ├── focus-element.js ├── fraction.js ├── get-title.js ├── img │ ├── icon-auth-logout-menu.svg │ ├── icon-auth-logout.svg │ ├── icon-auth-register.svg │ ├── icon-auth-sign-in.svg │ ├── icon-auth-trezor-logo.svg │ ├── icon-auth-trezor.svg │ ├── icon-coin-bip.svg │ ├── icon-coin-fallback.svg │ ├── icon-coin-lp.svg │ ├── icon-copy-black.svg │ ├── icon-copy.svg │ ├── icon-delegate.svg │ ├── icon-dropdown.svg │ ├── icon-feature-account.svg │ ├── icon-feature-broadcast.svg │ ├── icon-feature-check.svg │ ├── icon-feature-coin-creation.svg │ ├── icon-feature-coin-transfer.svg │ ├── icon-feature-convert.svg │ ├── icon-feature-hub.svg │ ├── icon-feature-mining-automation.svg │ ├── icon-feature-mining.svg │ ├── icon-feature-multisignature.svg │ ├── icon-feature-node-management.svg │ ├── icon-feature-order.svg │ ├── icon-feature-pco.svg │ ├── icon-feature-pool.svg │ ├── icon-feature-support.svg │ ├── icon-feature-vote.svg │ ├── icon-flag-en.png │ ├── icon-flag-en@2x.png │ ├── icon-flag-ru.png │ ├── icon-flag-ru@2x.png │ ├── icon-metamask.svg │ ├── icon-minus.svg │ ├── icon-move.svg │ ├── icon-plus.svg │ ├── icon-qr.svg │ ├── icon-remove.svg │ ├── icon-reverse.svg │ ├── icon-send.svg │ ├── icon-sort.svg │ ├── icon-time.svg │ ├── icon-unbond.svg │ ├── icon-verified.svg │ ├── icon-wallet.svg │ ├── icon-walletconnect.png │ ├── icon-walletconnect.svg │ ├── icon-walletconnect@2x.png │ ├── index-bg.svg │ ├── minter-logo-circle.svg │ ├── minter-logo-white.svg │ ├── social-share-account-ru.png │ ├── social-share-account.png │ ├── social-share-checks-ru.png │ ├── social-share-checks.png │ ├── social-share-coiner-ru.png │ ├── social-share-coiner.png │ ├── social-share-convert-ru.png │ ├── social-share-convert.png │ ├── social-share-delegation-ru.png │ ├── social-share-delegation.png │ ├── social-share-masternode-ru.png │ ├── social-share-masternode.png │ ├── social-share-wallet-ru.png │ └── social-share-wallet.png ├── less │ ├── include │ │ ├── general.less │ │ ├── layout.less │ │ ├── modal.less │ │ ├── suggest.less │ │ ├── utils.less │ │ └── variables.less │ └── style.less ├── lodash5-debounce.js ├── server-error.js ├── utils-support.js ├── utils.js ├── utils │ └── collection.js ├── v-check-empty.js └── variables.js ├── chart ├── .helmignore ├── Chart.yaml ├── templates │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── ingress.yaml │ ├── service.yaml │ └── serviceaccount.yaml └── values.yaml ├── components ├── AuthAdvancedForm.vue ├── AuthAdvancedGenerate.vue ├── AuthRegisterForm.vue ├── AuthSignInForm.vue ├── BroadcastNonceForm.vue ├── BroadcastSendForm.vue ├── CheckIssueForm.vue ├── CheckRedeemForm.vue ├── CoinBurnTokenForm.vue ├── CoinCreateForm.vue ├── CoinEditOwnerForm.vue ├── CoinList.vue ├── CoinLockForm.vue ├── CoinLockList.vue ├── CoinMintTokenForm.vue ├── CoinRecreateForm.vue ├── ConnectionNotice.vue ├── ConvertBuyForm.vue ├── ConvertSellAllForm.vue ├── ConvertSellForm.vue ├── HubBuyForm.vue ├── HubBuySpeedup.vue ├── HubBuyTxListItem.vue ├── HubCoinList.vue ├── HubDepositAccount.vue ├── HubDepositAccountMetamask.vue ├── HubDepositAccountMinter.vue ├── HubDepositAccountWalletConnect.vue ├── HubDepositForm.vue ├── HubDepositTxListItem.vue ├── HubWithdrawForm.vue ├── HubWithdrawTxList.vue ├── LimitOrderCancelForm.vue ├── LimitOrderCreateForm.vue ├── LimitOrderList.vue ├── MultisigCreateForm.vue ├── MultisigEditForm.vue ├── PoolAddLiquidityForm.vue ├── PoolCreateForm.vue ├── PoolList.vue ├── PoolRemoveLiquidityForm.vue ├── README.md ├── SendForm.vue ├── StakeDelegateForm.vue ├── StakeListTable.vue ├── StakeLockForm.vue ├── StakeMoveForm.vue ├── StakeReinvestForm.vue ├── StakeReinvestStartForm.vue ├── StakeUnbondForm.vue ├── TransactionLatestList.vue ├── ValidatorDeclareCandidacyForm.vue ├── ValidatorEditCandidateCommissionForm.vue ├── ValidatorEditCandidateForm.vue ├── ValidatorEditCandidatePublicKeyForm.vue ├── ValidatorSetCandidateOnOffForm.vue ├── ValidatorVoteCommissionForm.vue ├── ValidatorVoteHaltBlockForm.vue ├── ValidatorVotePriceForm.vue ├── ValidatorVoteUpdateForm.vue └── common │ ├── BaseAmount.vue │ ├── BaseDataList.vue │ ├── ButtonCopy.vue │ ├── ButtonCopyIcon.vue │ ├── FieldCoin.vue │ ├── FieldDomain.vue │ ├── FieldFee.vue │ ├── FieldPercentage.vue │ ├── FieldQr.vue │ ├── FieldUseMax.vue │ ├── InputMaskedAmount.vue │ ├── InputMaskedInteger.vue │ ├── InputMaskedName.vue │ ├── InputUppercase.vue │ ├── Loader.vue │ ├── Modal.vue │ ├── PoolPair.vue │ ├── QrScan.vue │ ├── SignatureList.vue │ ├── Snackbar.vue │ ├── TableLink.vue │ ├── TxForm.vue │ └── TxFormBlocksToUpdateStake.vue ├── composables ├── use-fee.js ├── use-hub-discount.js ├── use-last-update-time.js └── use-now.js ├── desktop ├── electron-builder-x86.config.js ├── electron-builder.config.js ├── electron.dev.js ├── electron.js ├── icons │ ├── 256x256.png │ ├── icon.icns │ └── icon.ico └── utils │ ├── delete-logs.js │ └── menu.js ├── docker-compose.yml ├── gulpfile.js ├── jest-babel.config.js ├── jest.config.js ├── lang ├── en.js └── ru.js ├── layouts ├── README.md ├── _footer.vue ├── _language.vue ├── default.vue ├── error.vue └── nonAuth.vue ├── middleware ├── README.md ├── auth.js ├── balance.js ├── explorer.js └── profile.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── account │ └── index.vue ├── broadcast │ └── index.vue ├── buy │ └── index.vue ├── checks │ └── index.vue ├── coiner │ └── index.vue ├── convert.vue ├── dao │ └── index.vue ├── delegation │ └── index.vue ├── hub │ └── index.vue ├── index.vue ├── lock │ └── index.vue ├── masternode │ └── index.vue ├── multisig │ └── index.vue ├── order │ └── index.vue ├── pool │ └── index.vue ├── support │ └── index.vue ├── swap │ └── index.vue └── wallet │ └── index.vue ├── plugins ├── README.md ├── base-url-prefix.js ├── classlist-svg-polyfill.js ├── click-blur.js ├── online.js └── persisted-state.js ├── static ├── README.md ├── apple-touch-icon.png ├── favicon.ico ├── favicon.png ├── fonts │ ├── ubuntu-v11-cyrillic_latin-700.woff │ ├── ubuntu-v11-cyrillic_latin-700.woff2 │ ├── ubuntu-v11-cyrillic_latin-regular.woff │ ├── ubuntu-v11-cyrillic_latin-regular.woff2 │ ├── ubuntu-v14-latin_cyrillic-500.woff │ └── ubuntu-v14-latin_cyrillic-500.woff2 ├── social-share-ru.png └── social-share.png ├── store ├── README.md ├── actions.js ├── explorer.js ├── getters.js ├── hub.js ├── mutations.js ├── state.js └── web3Account.js ├── test ├── jest-environment.js ├── jest-setup.js ├── jest-teardown.js ├── jest-utils.js ├── pages │ ├── convert.test.js │ ├── index.test.js │ └── wallet.test.js ├── utils.js └── variables.js └── webpack.config.js /.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 as builder 2 | 3 | ARG BUILD_BRANCH_ENV_PREFIX 4 | 5 | WORKDIR /app 6 | COPY . . 7 | # Two steps to copy .env.branch only if no .env exists 8 | # (it is needed to support docker-compose with local env and kubernetes prod build at the same time) 9 | # 1. just copy 10 | COPY .env${BUILD_BRANCH_ENV_PREFIX} .env 11 | # 2. overwrite .env back if it exists (dummy package.json to ensure COPY not to fail) 12 | COPY package.json .en[v] ./ 13 | RUN npm ci && npm run production 14 | 15 | FROM nginx:stable-alpine 16 | RUN rm -rf /usr/share/nginx/html 17 | COPY --from=builder /app/dist /usr/share/nginx/html 18 | COPY --from=builder /app/.docker/vhost.conf /etc/nginx/conf.d/default.conf 19 | EXPOSE 80 20 | CMD ["nginx", "-g", "daemon off;"] 21 | -------------------------------------------------------------------------------- /.docker/vhost.conf: -------------------------------------------------------------------------------- 1 | server { 2 | root /usr/share/nginx/html; 3 | error_page 404 /404.html; 4 | 5 | # request /exist/index.html, serve /exist/index.html, location HTML 6 | # request /notexist/index.html, serve /200.html, location HTML 7 | # request /exist, serve /exist/indext.html, location ROOT 8 | # request /notexist, serve /200.html, location HTML 9 | # request /, serve /index.html, location ROOT 10 | 11 | # ROOT 12 | # handles existent / with /index.html, /asd with /asd/index.html 13 | # rewrite unexistent /asd/asd with /200.html and move to html location 14 | # non html files should be handled in last location, otherwise they will get no-cache header 15 | location / { 16 | try_files $uri $uri/index.html /200.html; 17 | add_header Cache-Control "no-cache"; 18 | # add_header x-root "root" always; 19 | 20 | # security 21 | add_header X-Frame-Options "DENY" always; 22 | add_header X-Content-Type-Options "nosniff" always; 23 | add_header Reporting-Endpoints default=\"https://1ba68dd21788a2dfc5522a62c6674f25.report-uri.com/a/d/g\"; 24 | } 25 | 26 | # HTML 27 | location ~* \.html$ { 28 | try_files $uri /200.html; 29 | add_header Cache-Control "no-cache"; 30 | # add_header x-html "html" always; 31 | 32 | # security 33 | add_header X-Frame-Options "DENY" always; 34 | add_header X-Content-Type-Options "nosniff" always; 35 | add_header Reporting-Endpoints default=\"https://1ba68dd21788a2dfc5522a62c6674f25.report-uri.com/a/d/g\"; 36 | } 37 | 38 | # FILES 39 | # allow 404 errors for files with non-html extension 40 | location ~* "\.\w{2,6}$" { 41 | try_files $uri =404; 42 | # add_header x-full-static "full-static" always; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules/ 3 | 4 | *.log 5 | 6 | # Nuxt build 7 | .nuxt 8 | 9 | tmp 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [{.babelrc,.stylelintrc,.eslintrc,jest.config,*.json,*.jsb3,*.jsb2,*.bowerrc,*.yaml,*.yml}] 12 | indent_size=2 13 | -------------------------------------------------------------------------------- /.env.master: -------------------------------------------------------------------------------- 1 | ### COMMON 2 | #APP_BASE_URL=/ 3 | APP_MNS_API_URL=https://mns.hashex.org/ 4 | APP_MNS_PUBLIC_KEY=Mp60f782726fe03fd5d7fe293f4c5441dc5609009898ebfaa8d40bc8df1464e6e9048f7fd69022b065c9172aef98a63fcaf8e509faec3febb987a265524b13968c 5 | 6 | # mainnet 7 | APP_ENV=mainnet 8 | APP_ACCOUNTS_API_URL=https://my.minter.network/api/v1/ 9 | APP_GATE_API_URL=https://gate-api.minter.network/api/v2/ 10 | APP_AUTO_DELEGATION_API_URL=https://autodelegator-api.minter.network/api/v1/ 11 | APP_EXPLORER_API_URL=https://explorer-api.minter.network/api/v2/ 12 | APP_EXPLORER_RTM_URL=wss://explorer-rtm.minter.network/connection/websocket 13 | APP_EXPLORER_HOST=https://explorer.minter.network 14 | APP_EXPLORER_STATIC_HOST=https://explorer-static.minter.network 15 | APP_HUB_API_URL=https://hub-api.minter.network/ 16 | APP_ETHEREUM_API_URL=https://rpc.ankr.com/eth 17 | #APP_BSC_API_URL=https://bsc-dataseed.binance.org/ 18 | APP_BSC_API_URL=https://binance.llamarpc.com 19 | APP_MEGACHAIN_API_URL= 20 | APP_HUB_ETHEREUM_CONTRACT_ADDRESS=0x897c27Fa372AA730D4C75B1243E7EA38879194E2 21 | APP_HUB_BSC_CONTRACT_ADDRESS=0xF5b0ed82a0b3e11567081694cC66c3df133f7C8F 22 | APP_HUB_MEGACHAIN_CONTRACT_ADDRESS= 23 | APP_HUB_MINTER_MULTISIG_ADDRESS=Mx68f4839d7f32831b9234f9575f3b95e1afe21a56 24 | 25 | 26 | 27 | WEB_PORT_FOR_DOCKER_COMPOSE=5002 28 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | ### .env.test will be copied to .env before travis build for tests 2 | 3 | 4 | ### COMMON 5 | #APP_BASE_URL=/ 6 | APP_MNS_API_URL=https://mns.hashex.org/ 7 | APP_MNS_PUBLIC_KEY=Mp60f782726fe03fd5d7fe293f4c5441dc5609009898ebfaa8d40bc8df1464e6e9048f7fd69022b065c9172aef98a63fcaf8e509faec3febb987a265524b13968c 8 | 9 | 10 | ### TESTNET 11 | APP_ENV=testnet 12 | APP_ACCOUNTS_API_URL=https://my.beta.minter.network/api/v1/ 13 | APP_GATE_API_URL=https://gate-api.testnet.minter.network/api/v2/ 14 | APP_AUTO_DELEGATION_API_URL=https://autodelegator.testnet.minter.network/api/v1/ 15 | APP_EXPLORER_API_URL=https://explorer-api.testnet.minter.network/api/v2/ 16 | APP_EXPLORER_RTM_URL=wss://explorer-rtm.testnet.minter.network/connection/websocket 17 | APP_EXPLORER_HOST=https://explorer.testnet.minter.network 18 | APP_EXPLORER_STATIC_HOST=https://explorer-static.testnet.minter.network 19 | APP_HUB_API_URL=https://hub-api.kubernetes.icu/ 20 | APP_ETHEREUM_API_URL=https://rpc.sepolia.org 21 | APP_BSC_API_URL=https://data-seed-prebsc-2-s3.binance.org:8545/ 22 | APP_MEGACHAIN_API_URL=https://rpc.testnet.metagarden.io/ 23 | APP_HUB_ETHEREUM_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 24 | APP_HUB_BSC_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 25 | APP_HUB_MEGACHAIN_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 26 | APP_HUB_MINTER_MULTISIG_ADDRESS=Mx3a14ae11a49cbac592216d8eb81de298414a2c31 27 | -------------------------------------------------------------------------------- /.env.testnet: -------------------------------------------------------------------------------- 1 | ### TESTNET 2 | APP_ENV=testnet 3 | APP_ACCOUNTS_API_URL=https://my.beta.minter.network/api/v1/ 4 | APP_GATE_API_URL=https://gate-api.testnet.minter.network/api/v2/ 5 | APP_AUTO_DELEGATION_API_URL=https://autodelegator.testnet.minter.network/api/v1/ 6 | APP_EXPLORER_API_URL=https://explorer-api.testnet.minter.network/api/v2/ 7 | APP_EXPLORER_RTM_URL=wss://explorer-rtm.testnet.minter.network/connection/websocket 8 | APP_EXPLORER_HOST=https://explorer.testnet.minter.network 9 | APP_EXPLORER_STATIC_HOST=https://explorer-static.testnet.minter.network 10 | APP_HUB_API_URL=https://hub-api.kubernetes.icu/ 11 | APP_ETHEREUM_API_URL=https://rpc.sepolia.org 12 | APP_BSC_API_URL=https://data-seed-prebsc-2-s3.binance.org:8545/ 13 | APP_MEGACHAIN_API_URL=https://rpc.testnet.metagarden.io/ 14 | APP_HUB_ETHEREUM_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 15 | APP_HUB_BSC_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 16 | APP_HUB_MEGACHAIN_CONTRACT_ADDRESS=0xce8223acbf08B85EAf2354cD86104BA38bD0e3f6 17 | APP_HUB_MINTER_MULTISIG_ADDRESS=Mx3a14ae11a49cbac592216d8eb81de298414a2c31 18 | -------------------------------------------------------------------------------- /.env.toronet: -------------------------------------------------------------------------------- 1 | # toronet 2 | APP_ENV=testnet 3 | APP_ACCOUNTS_API_URL=https://my.beta.minter.network/api/v1/ 4 | APP_GATE_API_URL=https://gate-api.toronet.minter.network/api/v2/ 5 | APP_AUTO_DELEGATION_API_URL=https://autodelegator.testnet.minter.network/api/v1/ 6 | APP_EXPLORER_API_URL=https://explorer-api.toronet.minter.network/api/v2/ 7 | APP_EXPLORER_RTM_URL=wss://explorer-rtm.toronet.minter.network/connection/websocket 8 | APP_EXPLORER_HOST=https://explorer.toronet.minter.network 9 | APP_EXPLORER_STATIC_HOST=https://explorer-static.toronet.minter.network 10 | APP_HUB_API_URL=http://46.101.215.17:9091 11 | APP_ETHEREUM_API_URL=https://rpc.ankr.com/eth 12 | APP_BSC_API_URL=https://data-seed-prebsc-1-s3.binance.org:8545/ 13 | APP_HUB_ETHEREUM_CONTRACT_ADDRESS=0xcD53640C87Acd89BD7935765167D1E6330201C89 14 | APP_HUB_BSC_CONTRACT_ADDRESS=0x32d19e88E478d9135b5CcaB6D7468858C5a83800 15 | APP_HUB_MINTER_MULTISIG_ADDRESS=Mxa5c82b5c9674e854eef75f81412b019e6005ce49 16 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish-electron-desktop.yml: -------------------------------------------------------------------------------- 1 | name: electron desktop 2 | 3 | on: 4 | push: 5 | # looks like it works as OR instead of AND 6 | # branches: [ master, testnet ] 7 | tags: [ 'v*.*.*' ] 8 | env: 9 | RELEASE_NAME_master: "" 10 | RELEASE_NAME_testnet: "-testnet" 11 | jobs: 12 | build-electron: 13 | runs-on: macos-latest 14 | steps: 15 | - name: set env BUILD_BRANCH 16 | run: echo "BUILD_BRANCH=$(echo ${{ github.event.base_ref }} | sed 's:refs/heads/::')" >> $GITHUB_ENV 17 | 18 | - name: set branch prefix env to be used during docker build, .e.g. "refs/heads/testnet" => ".testnet" 19 | run: echo "BUILD_BRANCH_ENV_PREFIX=$(echo .$BUILD_BRANCH)" >> $GITHUB_ENV 20 | 21 | - name: set env BUILD_TAG 22 | run: echo "BUILD_TAG=$(echo $GITHUB_REF | sed 's:refs/tags/::')" >> $GITHUB_ENV 23 | 24 | # To build app in 32 bit from a machine with 64 bit 25 | # - name: Install gcc-multilib 26 | # run: | 27 | # sudo apt-get update 28 | # sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib 29 | 30 | # To build app for Windows on Linux 31 | # https://github.com/actions/virtual-environments/issues/743#issuecomment-616349857 32 | # - name: Install Wine 33 | # run: | 34 | # sudo dpkg --add-architecture i386 35 | # wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add - 36 | # sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport 37 | # sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main" 38 | # sudo apt install --install-recommends winehq-stable 39 | 40 | # - name: Wine version 41 | # run: wine --version 42 | 43 | 44 | - uses: actions/checkout@v3 45 | 46 | - run: cp .env$BUILD_BRANCH_ENV_PREFIX .env 47 | 48 | - name: Setup Node 49 | uses: actions/setup-node@v3 50 | with: 51 | node-version: '16.x' 52 | cache: 'npm' 53 | 54 | - run: npm ci 55 | 56 | - name: Cache imagemin 57 | uses: actions/cache@v3 58 | with: 59 | path: tmp/gulp-cache/imagemin 60 | key: gulp-imagemin-${{ hashFiles('node_modules/imagemin-?*/package.json') }} 61 | 62 | 63 | - name: Build 64 | run: npm run electron:build 65 | # run: npm run electron:build-x64 66 | 67 | - name: Release 68 | uses: softprops/action-gh-release@35d938cf01f60fbe522917c81be1e892074f6ad6 69 | # if: startsWith(github.ref, 'refs/tags/') 70 | with: 71 | draft: false 72 | tag_name: ${{ env.BUILD_TAG }} 73 | name: ${{ env.BUILD_TAG }} 74 | files: | 75 | tmp/electron/minter-console-*.dmg 76 | tmp/electron/minter-console-*.snap 77 | tmp/electron/minter-console-*.zip 78 | tmp/electron/minter-console-*.exe 79 | tmp/electron/minter-console-*.AppImage 80 | fail_on_unmatched_files: true 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | 84 | - name: echo GITHUB_HEAD_REF 85 | run: ls tmp/electron 86 | -------------------------------------------------------------------------------- /.github/workflows/deploy-github-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | deploy: 12 | name: Deploy to github pages 13 | runs-on: ubuntu-latest 14 | if: "!contains(github.event.head_commit.message, 'skip deploy')" 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Setup Node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: '16.x' 22 | cache: 'npm' 23 | 24 | - run: npm ci 25 | 26 | - name: Cache imagemin 27 | uses: actions/cache@v3 28 | with: 29 | path: tmp/gulp-cache/imagemin 30 | key: gulp-imagemin-${{ hashFiles('node_modules/imagemin-?*/package.json') }} 31 | 32 | 33 | - run: cp .env.master .env 34 | - name: Build 35 | run: npm run production 36 | env: 37 | APP_BASE_URL: /minter-console-web/ 38 | - run: cp ./dist/200.html ./dist/404.html 39 | 40 | - name: Deploy 41 | uses: peaceiris/actions-gh-pages@v3 42 | with: 43 | github_token: ${{ secrets.GITHUB_TOKEN }} 44 | publish_dir: ./dist 45 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | test: 9 | name: Test e2e 10 | runs-on: ubuntu-latest 11 | if: "!contains(github.event.head_commit.message, 'skip test')" 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: lts/* 18 | cache: 'npm' 19 | 20 | - run: cp .env.test .env 21 | 22 | - run: npm ci 23 | 24 | - name: Cache imagemin 25 | uses: actions/cache@v3 26 | with: 27 | path: tmp/gulp-cache/imagemin 28 | key: gulp-imagemin-${{ hashFiles('node_modules/imagemin-?*/package.json') }} 29 | 30 | - name: Test 31 | run: npm run test 32 | 33 | - name: Upload failed tests 34 | if: failure() && github.event_name == 'pull_request' 35 | uses: edunad/actions-image@v1.0.1 36 | with: 37 | path: './tmp/test-failed/*.jpg' 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | title: 'Failed E2E tests 🙀' 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /static/css/ 2 | /static/img/ 3 | /tmp/ 4 | .env 5 | 6 | 7 | # dependencies 8 | node_modules 9 | 10 | # logs 11 | npm-debug.log 12 | 13 | # Nuxt build 14 | .nuxt 15 | 16 | # Nuxt generate 17 | dist 18 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | format: [ 3 | 'group', 4 | 'repo', 5 | 'ownerChanged', 6 | ], 7 | reject: [ 8 | // requires backend update 9 | 'centrifuge', 10 | // require test 11 | 'qr-scanner', 12 | // vue 3 13 | 'vuex', 14 | '@nuxt/content', 15 | 'qrcode.vue', 16 | // nuxt 3 (webpack5) 17 | 'less-loader', 18 | // es modules 19 | 'beeper', 20 | 'camelcase-keys', 21 | 'del', 22 | 'gulp-imagemin', 23 | 'imagemin-mozjpeg', 24 | 'nanoid', 25 | // broken 26 | 'nuxt-i18n-default', 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Minter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Minter Console Website 2 | 3 | [![Build Status](https://img.shields.io/github/workflow/status/MinterTeam/minter-console-web/Test?label=test&style=flat-square)](https://github.com/MinterTeam/minter-console-web/actions/workflows/test.yml) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square&label=license)](https://github.com/MinterTeam/minter-console-web/blob/master/LICENSE) 5 | 6 | This is the repository containing the code for the official Minter Console website [console.minter.network](https://console.minter.network) 7 | 8 | ## Install 9 | 10 | - clone the repo 11 | - ensure latest stable Node.js and NPM are installed 12 | - install node_modules `npm ci` 13 | - copy .env.master `cp .env.master .env` 14 | - set correct .env variables 15 | - build `npm run production` 16 | - now you have static assets in the `./dist/` folder, you have to distribute them with some web server like Nginx (or run `npm run start`, but it's not recommended for production) 17 | 18 | 19 | ## Deployment script 20 | 21 | Build in Nuxt SPA mode 22 | ``` 23 | npm ci && npm run production 24 | ``` 25 | Root folder: `./dist/` 26 | 27 | 28 | ### Nuxt build cheatsheet 29 | 30 | ``` bash 31 | # install dependencies 32 | $ npm install # Or yarn install 33 | 34 | # serve with hot reload at localhost:3000 35 | $ npm run dev 36 | 37 | # build for production and launch server 38 | $ npm run build 39 | $ npm start 40 | 41 | # generate static project 42 | $ npm run generate 43 | ``` 44 | 45 | For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js). 46 | -------------------------------------------------------------------------------- /api/auto-delegation.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {AUTO_DELEGATION_API_URL} from "~/assets/variables"; 3 | 4 | const instance = axios.create({ 5 | baseURL: AUTO_DELEGATION_API_URL, 6 | }); 7 | 8 | 9 | /** 10 | * @param txList 11 | * @return {Promise} 12 | */ 13 | export function postAutoDelegationTxList(txList) { 14 | return instance 15 | .post('transactions', {transactions: txList}); 16 | } 17 | -------------------------------------------------------------------------------- /api/chainik.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {cacheAdapterEnhancer, Cache} from 'axios-extensions'; 3 | import {CHAINIK_API_URL, NETWORK, MAINNET} from "~/assets/variables.js"; 4 | import addToCamelInterceptor from '~/assets/axios-to-camel.js'; 5 | import {getDefaultAdapter} from '~/assets/axios-default-adapter.js'; 6 | 7 | const instance = axios.create({ 8 | baseURL: CHAINIK_API_URL, 9 | adapter: cacheAdapterEnhancer(getDefaultAdapter(), { enabledByDefault: false}), 10 | }); 11 | addToCamelInterceptor(instance); 12 | 13 | // 10 min cache 14 | const coinsCache = new Cache({ttl: 10 * 60 * 1000, max: 100}); 15 | /** 16 | * @return {Promise>} 17 | */ 18 | export function getCoinIconList() { 19 | if (NETWORK !== MAINNET) { 20 | return Promise.resolve({}); 21 | } 22 | return instance.get('coins.json', { 23 | cache: coinsCache, 24 | }) 25 | .then((response) => { 26 | const coins = response.data; 27 | let iconMap = {}; 28 | coins.forEach((coin) => { 29 | iconMap[coin.id] = coin.icon; 30 | }); 31 | return iconMap; 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /api/ethersacn.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import {ETHERSCAN_API_URL, ETHERSCAN_API_KEY} from "~/assets/variables.js"; 3 | import addToCamelInterceptor from '~/assets/axios-to-camel.js'; 4 | 5 | const instance = axios.create({ 6 | baseURL: ETHERSCAN_API_URL, 7 | params: { 8 | apikey: ETHERSCAN_API_KEY, 9 | }, 10 | }); 11 | addToCamelInterceptor(instance); 12 | 13 | /** 14 | * @param {string} address 15 | * @param {{page: number, offset: number}} [options] 16 | * @return {Promise>} 17 | */ 18 | export function getAddressTransactionList(address, options) { 19 | return instance.get(`?module=account&action=txlist&address=${address}&sort=desc`, { 20 | params: options, 21 | }) 22 | .then((response) => { 23 | let seen = {}; 24 | return response.data.result 25 | // result may contain duplicates, filter them out 26 | .filter((tx) => { 27 | if (seen[tx.hash]) { 28 | // remove duplicate 29 | return false; 30 | } else { 31 | // save 32 | seen[tx.hash] = true; 33 | // keep 34 | return true; 35 | } 36 | }) 37 | .map((tx) => { 38 | // align with web3 getTransactionReceipt 39 | tx.status = !!Number(tx.txreceiptStatus); 40 | return tx; 41 | }); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | # ASSETS 2 | 3 | This directory contains your un-compiled assets such as LESS, SASS, or JavaScript. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/assets#webpacked 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /assets/abi-erc20.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // balanceOf 3 | { 4 | "constant":true, 5 | "inputs":[{"name":"_owner", "type":"address"}], 6 | "name":"balanceOf", 7 | "outputs":[{"name":"balance", "type":"uint256"}], 8 | "type":"function", 9 | }, 10 | // decimals 11 | { 12 | "constant": true, 13 | "inputs": [], 14 | "name": "decimals", 15 | "outputs": [ 16 | { 17 | "name": "", 18 | "type": "uint8", 19 | }, 20 | ], 21 | "payable": false, 22 | "type": "function", 23 | }, 24 | { 25 | "constant": true, 26 | "inputs": [ 27 | {"name": "_owner", "type": "address"}, 28 | {"name": "_spender", "type": "address"}, 29 | ], 30 | "name": "allowance", 31 | "outputs": [ 32 | {"name": "", "type": "uint256"}, 33 | ], 34 | "payable": false, 35 | "stateMutability": "view", 36 | "type": "function", 37 | }, 38 | { 39 | "constant": false, 40 | "inputs": [ 41 | {"name": "_spender", "type": "address"}, 42 | {"name": "_value", "type": "uint256"}, 43 | ], 44 | "name": "approve", 45 | "outputs": [ 46 | {"name": "", "type": "bool"}, 47 | ], 48 | "payable": false, 49 | "stateMutability": "nonpayable", 50 | "type": "function", 51 | }, 52 | ]; 53 | -------------------------------------------------------------------------------- /assets/axios-debounce.js: -------------------------------------------------------------------------------- 1 | import debounce from '~/assets/debounce-promise.js'; 2 | 3 | /** 4 | * Store of debounced functions created per each ID 5 | * @type {Object.} 6 | */ 7 | const store = {}; 8 | 9 | /** 10 | * @param {import('axios').AxiosAdapter} adapter 11 | * @param {object} [options] 12 | * @param {number} [options.time=1000] 13 | * @return {import('axios').AxiosAdapter} 14 | */ 15 | export default function debounceAdapter(adapter, options) { 16 | return async function(config) { 17 | // get request id 18 | const id = config.idDebounce; 19 | // do nothing 20 | if (!id) { 21 | return adapter(config); 22 | } 23 | 24 | // create debounced functions (config.debounceOptions take effect only on fn init) 25 | if (!store[id]) { 26 | const time = config.debounceOptions?.time || options.time || 1000; 27 | store[id] = debounce(function(config) { 28 | return adapter(config); 29 | }, time, {...options, ...config.debounceOptions}); 30 | } 31 | 32 | return store[id](config); 33 | }; 34 | } 35 | 36 | /** 37 | * @typedef {object} ConcurrentRequestListItem 38 | * @property {string} url - url of active request 39 | * @property {function|import('axios').Canceler} [canceler] - cancel previous request to keep only one active 40 | */ 41 | -------------------------------------------------------------------------------- /assets/axios-default-adapter.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import adapters from 'axios/lib/adapters/adapters.js'; 3 | 4 | const getAdapter = adapters.getAdapter; 5 | 6 | /** 7 | * 8 | * @return {import('axios').AxiosAdapter} 9 | */ 10 | export function getDefaultAdapter() { 11 | return getAdapter(axios.defaults.adapter); 12 | } 13 | -------------------------------------------------------------------------------- /assets/axios-prevent-concurrency.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const CANCEL_MESSAGE = 'Cancel previous request'; 4 | /** 5 | * list of active requests 6 | * @type {Object.} 7 | */ 8 | const activeList = {}; 9 | 10 | /** 11 | * Prevent concurrent request for given id. It will cancel previous request if it is pending upon new request is received. It will prevent situations when newer request finishes later than old request. 12 | * Useful when input fires different requests and newer request always make older request obsolete, so requests are identified by ID key and not by path. 13 | * Must be used in front of cache adapter to work properly. 14 | * @param {import('axios').AxiosAdapter} adapter 15 | * @return {import('axios').AxiosAdapter} 16 | */ 17 | export default function preventConcurrencyAdapter(adapter) { 18 | return async function(config) { 19 | // get request id 20 | const id = config.idPreventConcurrency; 21 | // do nothing 22 | if (!id) { 23 | return adapter(config); 24 | } 25 | 26 | //@TODO handle unsorted query params and duplicate slashes (maybe use buildSortedUrl from axios-extensions) 27 | const url = config.baseURL + config.url; 28 | // do nothing for sequential duplicates, they will get response from the cache (anyway if 3rd request will come, this 2nd will be canceled with original request, because 2nd will be same as 1st cached) 29 | if (activeList[id]?.url === url) { 30 | return adapter(config); 31 | } 32 | // cancel previous request if exist 33 | if (typeof activeList[id]?.canceler === 'function') { 34 | activeList[id].canceler(CANCEL_MESSAGE); 35 | delete activeList[id]; 36 | } 37 | // save ability to cancel outgoing request 38 | config.cancelToken = new axios.CancelToken((canceler) => { 39 | activeList[id] = {url, canceler}; 40 | }); 41 | 42 | try { 43 | const result = await adapter(config); 44 | delete activeList[id]; 45 | return result; 46 | } catch (error) { 47 | if (error.message === CANCEL_MESSAGE) { 48 | error.isCanceled = true; 49 | } else { 50 | // clean only not canceled, no need to clean for canceled request, because it was cleaned upon cancelation 51 | delete activeList[id]; 52 | } 53 | throw error; 54 | } 55 | }; 56 | } 57 | 58 | /** 59 | * @typedef {object} ConcurrentRequestListItem 60 | * @property {string} url - url of active request 61 | * @property {function|import('axios').Canceler} [canceler] - cancel previous request to keep only one active 62 | */ 63 | -------------------------------------------------------------------------------- /assets/axios-time-offset.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates time offset based on response date header 3 | * NTP-like 4 | */ 5 | 6 | let averageOffset = 0; 7 | let offsetArray = []; 8 | 9 | // minimum offsets count to calculate average 10 | const MIN_OFFSET_COUNT = 3; 11 | // maximum offsets count to store 12 | const MAX_OFFSET_COUNT = 100; 13 | 14 | /** 15 | * @return {number} - milliseconds 16 | */ 17 | export function getTimeOffset() { 18 | return averageOffset; 19 | } 20 | 21 | export function addTimeInterceptor(instance) { 22 | instance.interceptors.request.use((request) => { 23 | request.ts = Date.now(); 24 | return request; 25 | }); 26 | 27 | instance.interceptors.response.use((response) => { 28 | const responseTime = new Date(response.headers.date).getTime(); 29 | updateOffset(response.config.ts, responseTime); 30 | return response; 31 | }); 32 | } 33 | 34 | function updateOffset(requestTime, responseTime) { 35 | // responseTime = performance.now() + 10 * 1000; 36 | if (!responseTime || isNaN(responseTime)) { 37 | return; 38 | } 39 | const currentOffset = ntpOffset(requestTime, responseTime, responseTime, Date.now()); 40 | if (offsetArray.length === MAX_OFFSET_COUNT) { 41 | offsetArray.shift(); 42 | } 43 | offsetArray.push(currentOffset); 44 | 45 | if (offsetArray.length >= MIN_OFFSET_COUNT) { 46 | const offsetSum = offsetArray.reduce((a, b) => a + b); 47 | averageOffset = Math.round(offsetSum / offsetArray.length); 48 | } 49 | } 50 | 51 | // the NTP algorithm 52 | // t0 is the client's timestamp of the request packet transmission, 53 | // t1 is the server's timestamp of the request packet reception, 54 | // t2 is the server's timestamp of the response packet transmission and 55 | // t3 is the client's timestamp of the response packet reception. 56 | function ntpOffset(t0, t1, t2, t3) { 57 | return Math.round(((t1 - t0) + (t2 - t3)) / 2); 58 | } 59 | -------------------------------------------------------------------------------- /assets/axios-to-camel.js: -------------------------------------------------------------------------------- 1 | import camelcaseKeys from 'camelcase-keys'; 2 | 3 | export function toCamel(obj) { 4 | return camelcaseKeys(obj, {deep: true}); 5 | } 6 | 7 | /** 8 | * 9 | * @param {AxiosInstance} instance 10 | */ 11 | export default function addToCamelInterceptor(instance) { 12 | instance.interceptors.response.use(function(response) { 13 | response.data = toCamel(response.data); 14 | return response; 15 | }, function(error) { 16 | if (error.response && error.response.data) { 17 | error.response.data = toCamel(error.response.data); 18 | } 19 | return Promise.reject(error); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /assets/big.js: -------------------------------------------------------------------------------- 1 | import Big from 'big.js'; 2 | import stripZeros from 'pretty-num/src/strip-zeros.js'; 3 | 4 | export const BIG_ROUND_DOWN = 0; 5 | export const BIG_ROUND_HALF_EVEN = 2; 6 | 7 | // support division of 15 whole digits and 18 decimal 8 | export const COMPUTATION_PRECISION = 15 + 18 + 1; // minter node precision is 34 9 | export const VISIBLE_PRECISION = 18; 10 | // set defaults 11 | // precision 12 | Big.DP = COMPUTATION_PRECISION; 13 | // ROUND_HALF_EVEN (same as in minter-node) 14 | Big.RM = BIG_ROUND_HALF_EVEN; 15 | 16 | // fix toString method, by default toFixed doesn't consider global Big.DP value 17 | Big.prototype.toString = function(dp = VISIBLE_PRECISION, rm = Big.RM) { 18 | return stripZeros(this.toFixed(dp, rm)); 19 | }; 20 | 21 | export default Big; 22 | -------------------------------------------------------------------------------- /assets/debounce-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on https://github.com/bjoerge/debounce-promise 3 | */ 4 | 5 | /** 6 | * @param {function(T): Promise} fn 7 | * @param {number} wait 8 | * @param {object} options 9 | * @return {function(T): Promise} 10 | * @template T, T2 11 | */ 12 | export default function debouncePromise(fn, wait = 0, options = {}) { 13 | let lastCallAt; 14 | let deferred; 15 | let timer; 16 | let pendingArgs = []; 17 | return function debounced(...args) { 18 | const currentWait = getWait(wait); 19 | const currentTime = new Date().getTime(); 20 | 21 | const isCold = !lastCallAt || (currentTime - lastCallAt) > currentWait; 22 | 23 | lastCallAt = currentTime; 24 | 25 | if (isCold && options.leading) { 26 | return Promise.resolve(fn.call(this, ...args)); 27 | } 28 | 29 | if (deferred) { 30 | clearTimeout(timer); 31 | // cancel previous request @TODO add option for it? 32 | deferred.reject(new CancelError()); 33 | deferred = null; 34 | } 35 | if (!deferred) { 36 | deferred = defer(); 37 | } 38 | 39 | pendingArgs.push(args); 40 | timer = setTimeout(flush.bind(this), currentWait); 41 | 42 | return deferred.promise; 43 | }; 44 | 45 | function flush() { 46 | const thisDeferred = deferred; 47 | clearTimeout(timer); 48 | 49 | Promise.resolve( 50 | fn.apply(this, pendingArgs[pendingArgs.length - 1]), 51 | ) 52 | .then(thisDeferred.resolve, thisDeferred.reject); 53 | 54 | pendingArgs = []; 55 | deferred = null; 56 | } 57 | } 58 | 59 | function getWait(wait) { 60 | return (typeof wait === 'function') ? wait() : wait; 61 | } 62 | 63 | function defer() { 64 | const deferred = {}; 65 | deferred.promise = new Promise((resolve, reject) => { 66 | deferred.resolve = resolve; 67 | deferred.reject = reject; 68 | }); 69 | return deferred; 70 | } 71 | 72 | export class CancelError extends Error { 73 | constructor(message = 'Canceled') { 74 | super(message); 75 | this.name = 'CancelError'; 76 | this.isCanceled = true; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /assets/event-bus.js: -------------------------------------------------------------------------------- 1 | import {TinyEmitter} from 'tiny-emitter'; 2 | const eventBus = new TinyEmitter(); 3 | export default eventBus; 4 | -------------------------------------------------------------------------------- /assets/focus-element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {HTMLElement|Element} inputEl 4 | */ 5 | export default function focusElement(inputEl) { 6 | // calculate scroll 7 | const inputClientRectTop = inputEl.getBoundingClientRect().top; 8 | const windowTop = window.pageYOffset; 9 | const inputOffsetTop = inputClientRectTop + windowTop; 10 | const windowHeight = window.document.documentElement.clientHeight; 11 | // if inputEl not centered 12 | const shouldScroll = inputClientRectTop < windowHeight * 0.25 || inputClientRectTop > windowHeight * 0.75; 13 | 14 | setTimeout(() => { 15 | // focus 16 | inputEl.focus({preventScroll:true}); 17 | // prevent focus scroll, set initial position 18 | window.scrollTo(0, windowTop); 19 | 20 | // scroll 21 | if (shouldScroll) { 22 | let targetOffset = inputOffsetTop - windowHeight / 3; 23 | // not needed for browser auto scroll 24 | // targetOffset = Math.max(targetOffset, 0); 25 | // targetOffset = Math.min(targetOffset, window.document.body.offsetHeight - window.document.documentElement.clientHeight); 26 | window.scrollTo({top: targetOffset, behavior: 'smooth'}); 27 | } 28 | }, 0); 29 | } 30 | -------------------------------------------------------------------------------- /assets/get-title.js: -------------------------------------------------------------------------------- 1 | 2 | import {BASE_TITLE_NETWORK, BASE_TITLE_END} from '~/assets/variables'; 3 | 4 | export default function getTitle(text, locale) { 5 | const console = locale === 'ru' ? 'Консоль' : 'Console'; 6 | if (text) { 7 | return BASE_TITLE_NETWORK + console + '. ' + text + BASE_TITLE_END; 8 | } else { 9 | return BASE_TITLE_NETWORK + console + BASE_TITLE_END; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /assets/img/icon-auth-logout-menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-auth-logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-auth-register.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-auth-sign-in.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-auth-trezor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | path7 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/img/icon-coin-bip.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/img/icon-coin-fallback.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/img/icon-coin-lp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/img/icon-copy-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-delegate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /assets/img/icon-dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-account.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-broadcast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-coin-creation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-coin-transfer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-convert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-hub.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-mining-automation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | asd 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /assets/img/icon-feature-mining.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-multisignature.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-node-management.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-order.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-pco.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-support.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-feature-vote.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-flag-en.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-flag-en.png -------------------------------------------------------------------------------- /assets/img/icon-flag-en@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-flag-en@2x.png -------------------------------------------------------------------------------- /assets/img/icon-flag-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-flag-ru.png -------------------------------------------------------------------------------- /assets/img/icon-flag-ru@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-flag-ru@2x.png -------------------------------------------------------------------------------- /assets/img/icon-metamask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/img/icon-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-move.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/img/icon-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-qr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-remove.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-reverse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /assets/img/icon-sort.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-time.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-unbond.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/img/icon-verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-walletconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-walletconnect.png -------------------------------------------------------------------------------- /assets/img/icon-walletconnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /assets/img/icon-walletconnect@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/icon-walletconnect@2x.png -------------------------------------------------------------------------------- /assets/img/minter-logo-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /assets/img/minter-logo-white.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /assets/img/social-share-account-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-account-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-account.png -------------------------------------------------------------------------------- /assets/img/social-share-checks-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-checks-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-checks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-checks.png -------------------------------------------------------------------------------- /assets/img/social-share-coiner-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-coiner-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-coiner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-coiner.png -------------------------------------------------------------------------------- /assets/img/social-share-convert-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-convert-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-convert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-convert.png -------------------------------------------------------------------------------- /assets/img/social-share-delegation-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-delegation-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-delegation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-delegation.png -------------------------------------------------------------------------------- /assets/img/social-share-masternode-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-masternode-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-masternode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-masternode.png -------------------------------------------------------------------------------- /assets/img/social-share-wallet-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-wallet-ru.png -------------------------------------------------------------------------------- /assets/img/social-share-wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/assets/img/social-share-wallet.png -------------------------------------------------------------------------------- /assets/less/include/layout.less: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | 4 | 5 | // container 6 | .u-container {padding: 0 16px; margin: 0 auto;} 7 | .u-container--small {max-width: 360px + 16px * 2;} 8 | .u-container-margin {margin: 0 auto;} 9 | @media (min-width: @breakpoint-small-up) { 10 | .u-container {padding: 0 @container-padding;} 11 | .u-container--small {max-width: 360px + @container-padding * 2;} 12 | } 13 | @media (min-width: @breakpoint-medium-up) { 14 | /*.u-container {padding: 0 @container-padding-large;}*/ 15 | .u-container--medium {max-width: @container-width + @container-padding * 2; } 16 | .u-container--large {max-width: @container-width-large;} 17 | } 18 | 19 | 20 | .u-section {padding-top: 40px; padding-bottom: 40px;} 21 | @media (min-width: @breakpoint-medium-up) { 22 | .u-section {padding-top: 56px; padding-bottom: 56px;} 23 | } 24 | 25 | 26 | 27 | 28 | 29 | // grid 30 | .u-grid { 31 | display: flex; flex-wrap: wrap; margin-left: -@grid-horizontal-margin; 32 | & > .u-cell { padding-left: @grid-horizontal-margin;} 33 | } 34 | .u-grid--small { 35 | margin-left: -16px; 36 | & > .u-cell { padding-left: 16px;} 37 | } 38 | .u-grid--vertical-margin { 39 | margin-top: -@grid-vertical-margin; 40 | & > .u-cell { padding-top: @grid-vertical-margin;} 41 | } 42 | .u-grid--vertical-margin--large { 43 | margin-top: -32px; 44 | & > .u-cell { padding-top: 32px;} 45 | } 46 | .u-grid--vertical-margin--small { 47 | margin-top: -16px; 48 | & > .u-cell { padding-top: 16px;} 49 | } 50 | .u-grid--align-center {align-items: center;} 51 | 52 | .u-cell { width: 100%;} 53 | .u-cell--1-2 { width: 50%;} 54 | .u-cell--1-3 { width: 33.3333%;} 55 | .u-cell--2-3 { width: 66.6666%;} 56 | .u-cell--auto {width: auto; max-width: 100%;} 57 | .u-cell--order-2 {order: 2;} 58 | .u-cell--align-center {align-self: center;} 59 | @media (max-width: @breakpoint-large-down) { 60 | .u-cell--large-down--order-minus {order: -1;} 61 | } 62 | @media (min-width: @breakpoint-small-up) { 63 | .u-cell--small--1-2 {width: 50%;} 64 | .u-cell--small--1-3 {width: 33.3333%;} 65 | .u-cell--small--1-4 { width: 25%;} 66 | .u-cell--small--3-4 { width: 75%;} 67 | .u-cell--small--order-2 {order: 2;} 68 | } 69 | @media (min-width: @breakpoint-medium-up) { 70 | .u-cell--medium--1-2 { width: 50%;} 71 | .u-cell--medium--1-3 { width: 33.3333%;} 72 | .u-cell--medium--2-3 { width: 66.6666%;} 73 | .u-cell--medium--1-4 { width: 25%;} 74 | .u-cell--medium--4-10 { width: 40%;} 75 | .u-cell--medium--6-10 { width: 60%;} 76 | } 77 | @media (min-width: @breakpoint-large-up) { 78 | .u-cell--large--1-2 {width: 50%;} 79 | .u-cell--large--1-3 { width: 33.3333%;} 80 | .u-cell--large--1-4 {width: 25%;} 81 | } 82 | @media (min-width: @breakpoint-xlarge-up) { 83 | .u-cell--xlarge--1-4 { width: 25%;} 84 | .u-cell--xlarge--3-4 { width: 75%;} 85 | .u-cell--xlarge--1-3 { width: 33.3333%;} 86 | .u-cell--xlarge--1-2 { width: 50%;} 87 | .u-cell--xlarge--order-2 {order: 2;} 88 | } 89 | -------------------------------------------------------------------------------- /assets/less/include/modal.less: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | @modal-padding-vertical: 40px; 4 | 5 | .modal-wrap {position: fixed; z-index: 15;} 6 | .modal { 7 | display: flex; padding-top: @modal-padding-vertical; position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 15; overflow-x: hidden; background: rgba(0,0,0,0.8); color: #fff; -webkit-tap-highlight-color: transparent; outline: none; // outline can emit transitionEnd 8 | button, a {-webkit-tap-highlight-color: initial; /* revert after .modal */} 9 | } 10 | //@supports (backdrop-filter: blur(10px)) { 11 | // .modal {background: rgba(255,255,255,0.7); backdrop-filter: blur(10px);} 12 | //} 13 | .v-transition-modal-enter {opacity: 0;} 14 | .v-transition-modal-enter-active {transition: opacity 0.3s;} 15 | .v-transition-modal-leave-active {opacity: 0; transition: opacity 0.2s;} 16 | .modal__wrap {display: flex; width: 100%;} 17 | .modal__close {width: 40px; height: 40px; position: absolute; right: (16px - 7px); top: 11px; z-index: 3; font-size: 0;} 18 | .modal__close-icon { 19 | &::before, &::after {content: ''; position: absolute; height: 4px; width: 28px; left: 50%; top: 50%; margin-left: -14px; margin-top: -2px; background: currentColor;} 20 | &::before {transform: rotate(45deg);} 21 | &::after {transform: rotate(-45deg);} 22 | } 23 | .modal__container {max-width: 450px; width: 100%; margin: auto; text-align: center; padding-bottom: @modal-padding-vertical;} 24 | .modal__title {margin-bottom: 10px;} 25 | .modal__text {max-width: 480px; margin: 0 auto 20px; &:last-child {margin-bottom: 0;}} 26 | 27 | @media (min-width: @breakpoint-small-up) { 28 | .modal__close {right: (@container-padding - 7px);} 29 | } 30 | 31 | @media (min-width: @breakpoint-large-up) { 32 | .modal__close {right: 64px; top: 64px;} 33 | .modal__close-icon { 34 | &::before, &::after {width: 36px; margin-left: -18px;} 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assets/less/include/suggest.less: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .vue-simple-suggest > ul { 4 | list-style: none; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | .vue-simple-suggest { 10 | width: 100%; 11 | //position: relative; 12 | } 13 | 14 | .vue-simple-suggest .suggestions { 15 | pointer-events: auto; 16 | position: absolute; 17 | left: 0; 18 | right: 0; 19 | top: 100%; 20 | margin-top: 5px; 21 | padding: 6px 0; 22 | 23 | color: @c-black; background: #fff; border-radius: 8px; line-height: 20px; 24 | 25 | opacity: 1; 26 | z-index: 1000; 27 | overflow: auto; 28 | 29 | box-shadow: 0px 5px 5px -3px rgba(0, 0, 0, 0.2), 0px 8px 10px 1px rgba(0, 0, 0, 0.14), 0px 3px 14px 2px rgba(0, 0, 0, 0.12); 30 | max-height: 36px * 6 + 6px * 2; 31 | } 32 | 33 | .vue-simple-suggest .suggest-item { 34 | cursor: pointer; 35 | user-select: none; 36 | word-wrap: break-word; 37 | } 38 | .suggest-item--large {font-size: 16px;} 39 | .suggest-item--small {font-size: 12px; font-variant-numeric: proportional-nums;} 40 | 41 | .vue-simple-suggest .suggest-item, 42 | .vue-simple-suggest .misc-item { 43 | padding: 8px 12px; 44 | } 45 | 46 | //.vue-simple-suggest .suggest-item.selected { 47 | // background-color: @c-violet; 48 | // color: #fff; 49 | //} 50 | 51 | .vue-simple-suggest .suggest-item.hover { 52 | background-color: #e5e5e8; 53 | //color: #fff; 54 | } 55 | 56 | .suggestion__coin-icon {margin-right: 4px; /*margin-top: -1px; margin-bottom: -1px;*/ border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.15); vertical-align: top;} 57 | .suggestion__coin-symbol {font-weight: 500;} 58 | .suggestion__coin-verified {vertical-align: top; margin-top: 4px; margin-left: 2px; margin-right: 3px;} 59 | -------------------------------------------------------------------------------- /assets/less/include/utils.less: -------------------------------------------------------------------------------- 1 | @import "variables"; 2 | 3 | .u-semantic-button {background: none; border: none; border-radius: 0; color: inherit; font: inherit; text-align: inherit; padding: 0; cursor: pointer;} 4 | .u-hidden {display: none !important;} 5 | .u-visually-hidden {visibility: hidden !important;} 6 | .u-text-left { text-align: left;} 7 | .u-text-center { text-align: center;} 8 | .u-text-right {text-align: right;} 9 | .u-display-block {display: block !important;} 10 | .u-display-ib {display: inline-block !important;} 11 | .u-relative {position: relative; z-index: 1;} 12 | .u-mt-05 {margin-top: 8px;} 13 | .u-mt-10 {margin-top: 16px;} 14 | .u-mt-15 {margin-top: 24px;} 15 | .u-mb-05 {margin-bottom: 8px;} 16 | .u-mb-10 {margin-bottom: 16px;} 17 | .u-mb-20 {margin-bottom: 32px;} 18 | .u-fw-500 {font-weight: 500;} 19 | .u-fw-700 {font-weight: 700;} 20 | .u-text-white {color: #fff !important;} 21 | .u-text-muted {color: @c-black-light;} 22 | .u-text-error {color: @c-red;} 23 | .u-text-overflow {overflow: hidden; text-overflow: ellipsis; white-space: nowrap;} 24 | .u-text-break {overflow-wrap: break-word;} 25 | .u-text-break-all {word-break: break-all;} 26 | .u-text-nowrap {white-space: nowrap;} 27 | .u-text-normal {white-space: normal;} 28 | .u-emoji {font-family: @emoji-font-family; color: inherit; font-size: 120%; vertical-align: middle; margin-top: -0.1em; display: inline-block;} 29 | .u-select-all {user-select: all;} 30 | @media (min-width: @breakpoint-medium-up) { 31 | .u-select-all {user-select: auto;} 32 | } 33 | @media (min-width: @breakpoint-xlarge-up) { 34 | .u-hidden-xlarge-up {display: none !important;} 35 | } 36 | @media (max-width: @breakpoint-xlarge-down) { 37 | .u-hidden-xlarge-down {display: none !important;} 38 | } 39 | @media (min-width: @breakpoint-large-up) { 40 | .u-hidden-large-up {display: none !important;} 41 | } 42 | @media (max-width: @breakpoint-large-down) { 43 | .u-hidden-large-down {display: none !important;} 44 | } 45 | @media (min-width: @breakpoint-medium-up) { 46 | .u-hidden-medium-up {display: none !important;} 47 | } 48 | @media (max-width: @breakpoint-medium-down) { 49 | .u-hidden-medium-down {display: none !important;} 50 | } 51 | @media (min-width: @breakpoint-small-up) { 52 | .u-hidden-small-up {display: none !important;} 53 | } 54 | @media (max-width: @breakpoint-small-down) { 55 | .u-hidden-small-down {display: none !important;} 56 | } 57 | -------------------------------------------------------------------------------- /assets/less/include/variables.less: -------------------------------------------------------------------------------- 1 | @container-width: 940px; 2 | @container-width-large: 1440px; 3 | @container-padding: 24px; 4 | @container-padding-large: 40px; 5 | @default-indent: 16px; 6 | @paragraph-indent: 10px; 7 | @grid-horizontal-margin: 24px; 8 | @grid-vertical-margin: 24px; 9 | 10 | @breakpoint-mini-up: 350px; 11 | @breakpoint-mini-down: (@breakpoint-mini-up - 1px); 12 | @breakpoint-small-up: 450px; 13 | @breakpoint-small-down: (@breakpoint-small-up - 1px); 14 | @breakpoint-medium-up: 700px; 15 | @breakpoint-medium-down: (@breakpoint-medium-up - 1px); 16 | @breakpoint-large-up: 960px; 17 | @breakpoint-large-down: (@breakpoint-large-up - 1px); 18 | @breakpoint-xlarge-up: 1200px; 19 | @breakpoint-xlarge-down: (@breakpoint-xlarge-up - 1px); 20 | 21 | 22 | 23 | @body-font-family: Ubuntu, system-ui, -apple-system, Segoe UI, SimSun, PingFang SC, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; 24 | @body-bg-color: #f6f6f6; 25 | @heading-font-family: @body-font-family; 26 | @emoji-font-family: Apple Color Emoji, Segoe UI Emoji, sans-serif; 27 | 28 | // Colors 29 | @c-black: #333; // цвет текста на сайте 30 | @c-black-light: tint(@c-black, 40%); 31 | @c-black-icon: #707070; 32 | @c-border: #e5e5e5; 33 | @c-orange: #d15c22; 34 | @c-main: @c-orange; 35 | @c-red: #ff1818; 36 | -------------------------------------------------------------------------------- /assets/utils-support.js: -------------------------------------------------------------------------------- 1 | export let support = {}; 2 | support.passiveListener = (function() { 3 | let supportsPassive = false; 4 | try { 5 | let opts = Object.defineProperty({}, 'passive', { 6 | /* eslint-disable-next-line getter-return */ 7 | get: function() { 8 | supportsPassive = true; 9 | }, 10 | }); 11 | window.addEventListener('testPassiveListener', null, opts); 12 | } catch (e) {} 13 | return supportsPassive; 14 | })(); 15 | 16 | if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support 17 | support.hidden = "hidden"; 18 | support.visibilityChange = "visibilitychange"; 19 | } else if (typeof document.msHidden !== "undefined") { 20 | support.hidden = "msHidden"; 21 | support.visibilityChange = "msvisibilitychange"; 22 | } else if (typeof document.webkitHidden !== "undefined") { 23 | support.hidden = "webkitHidden"; 24 | support.visibilityChange = "webkitvisibilitychange"; 25 | } 26 | -------------------------------------------------------------------------------- /assets/utils/collection.js: -------------------------------------------------------------------------------- 1 | import get from 'lodash-es/get.js'; 2 | 3 | /** 4 | * @template {object} T 5 | * @param {Array} arr 6 | * @param {string} path 7 | * @param {function(T): any} [itemTransformer] 8 | * @return {object} 9 | */ 10 | export function arrayToMap(arr, path, itemTransformer) { 11 | const map = {}; 12 | arr.forEach((item) => { 13 | const key = get(item, path); 14 | const value = typeof itemTransformer === 'function' ? itemTransformer(item) : item; 15 | map[key] = value; 16 | }); 17 | 18 | return map; 19 | } 20 | -------------------------------------------------------------------------------- /assets/v-check-empty.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | bind(el, binding, vnode) { 4 | checkInputElementIsEmpty(el); 5 | if (isSelect(el)) { 6 | el.addEventListener('change', handleInputEvent); 7 | } else { 8 | el.addEventListener('input', handleInputEvent); 9 | } 10 | if (binding.value) { 11 | el.addEventListener(binding.value, handleInputEvent); 12 | } 13 | }, 14 | componentUpdated(el, binding) { 15 | checkInputElementIsEmpty(el); 16 | if (binding.oldValue !== binding.value) { 17 | el.removeEventListener(binding.oldValue, handleInputEvent); 18 | } 19 | if (binding.value) { 20 | el.addEventListener(binding.value, handleInputEvent); 21 | } 22 | }, 23 | unbind(el, binding) { 24 | if (isSelect(el)) { 25 | el.removeEventListener('change', handleInputEvent); 26 | } else { 27 | el.removeEventListener('input', handleInputEvent); 28 | } 29 | if (binding.value) { 30 | el.removeEventListener(binding.value, handleInputEvent); 31 | } 32 | 33 | }, 34 | }; 35 | 36 | /** 37 | * @param {HTMLElement} el 38 | * @return {boolean} 39 | */ 40 | function isSelect(el) { 41 | return el.nodeName.toUpperCase() === 'SELECT'; 42 | } 43 | 44 | /** 45 | * @param {HTMLElement} el 46 | * @return {boolean} 47 | */ 48 | // function isInput(el) { 49 | // return el.nodeName.toUpperCase() === 'INPUT'; 50 | // } 51 | 52 | /** 53 | * @param {Event} e 54 | * @return void 55 | */ 56 | function handleInputEvent(e) { 57 | checkInputElementIsEmpty(e.target); 58 | } 59 | 60 | /** 61 | * @param {HTMLInputElement|EventTarget} el 62 | */ 63 | function checkInputElementIsEmpty(el) { 64 | // let input; 65 | // if (isInput(el) || isSelect(el)) { 66 | // input = el; 67 | // } else { 68 | // input = el.querySelector('input, select'); 69 | // } 70 | // console.log(input) 71 | // if (!input) { 72 | // return 73 | // } 74 | // wait select options to render or wait value updated programmatically by vue 75 | setTimeout(() => { 76 | if (el.value.length) { 77 | el.classList.add('is-not-empty'); 78 | } else { 79 | el.classList.remove('is-not-empty'); 80 | } 81 | }, 0); 82 | } 83 | -------------------------------------------------------------------------------- /chart/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /chart/Chart.yaml: -------------------------------------------------------------------------------- 1 | version: 1.0.0 2 | apiVersion: v2 3 | name: console-web 4 | icon: https://explorer.minter.network/img/minter-logo-circle.svg 5 | description: A Helm chart for Minter Console Web 6 | 7 | appVersion: v1.0.0 8 | 9 | home: https://github.com/MinterTeam/minter-console-web 10 | keywords: 11 | - minter 12 | - console 13 | maintainers: 14 | - name: shrpne 15 | email: shrpne@gmail.com 16 | 17 | -------------------------------------------------------------------------------- /chart/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "chart.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "chart.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "chart.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | 34 | {{/* 35 | Common labels 36 | */}} 37 | {{- define "chart.labels" -}} 38 | helm.sh/chart: {{ include "chart.chart" . }} 39 | {{ include "chart.selectorLabels" . }} 40 | {{- if .Chart.AppVersion }} 41 | app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} 42 | {{- end }} 43 | app.kubernetes.io/managed-by: {{ .Release.Service }} 44 | {{- end -}} 45 | 46 | {{/* 47 | Selector labels 48 | */}} 49 | {{- define "chart.selectorLabels" -}} 50 | app.kubernetes.io/name: {{ include "chart.name" . }} 51 | app.kubernetes.io/instance: {{ .Release.Name }} 52 | {{- end -}} 53 | 54 | {{/* 55 | Create the name of the service account to use 56 | */}} 57 | {{- define "chart.serviceAccountName" -}} 58 | {{- if .Values.serviceAccount.create -}} 59 | {{ default (include "chart.fullname" .) .Values.serviceAccount.name }} 60 | {{- else -}} 61 | {{ default "default" .Values.serviceAccount.name }} 62 | {{- end -}} 63 | {{- end -}} 64 | -------------------------------------------------------------------------------- /chart/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ include "chart.fullname" . }} 5 | labels: 6 | {{- include "chart.labels" . | nindent 4 }} 7 | spec: 8 | replicas: {{ .Values.replicaCount }} 9 | selector: 10 | matchLabels: 11 | {{- include "chart.selectorLabels" . | nindent 6 }} 12 | template: 13 | metadata: 14 | labels: 15 | {{- include "chart.selectorLabels" . | nindent 8 }} 16 | spec: 17 | {{- with .Values.imagePullSecrets }} 18 | imagePullSecrets: 19 | {{- toYaml . | nindent 8 }} 20 | {{- end }} 21 | serviceAccountName: {{ include "chart.serviceAccountName" . }} 22 | securityContext: 23 | {{- toYaml .Values.podSecurityContext | nindent 8 }} 24 | containers: 25 | - name: {{ .Chart.Name }} 26 | securityContext: {{- toYaml .Values.securityContext | nindent 12 }} 27 | image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" 28 | imagePullPolicy: {{ .Values.image.pullPolicy }} 29 | ports: 30 | - name: http 31 | containerPort: 80 32 | protocol: TCP 33 | livenessProbe: 34 | httpGet: 35 | path: / 36 | port: http 37 | readinessProbe: 38 | httpGet: 39 | path: / 40 | port: http 41 | resources: 42 | {{- toYaml .Values.resources | nindent 12 }} 43 | {{- with .Values.nodeSelector }} 44 | nodeSelector: 45 | {{- toYaml . | nindent 8 }} 46 | {{- end }} 47 | {{- with .Values.affinity }} 48 | affinity: 49 | {{- toYaml . | nindent 8 }} 50 | {{- end }} 51 | {{- with .Values.tolerations }} 52 | tolerations: 53 | {{- toYaml . | nindent 8 }} 54 | {{- end }} 55 | -------------------------------------------------------------------------------- /chart/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: {{ include "chart.fullname" . }} 5 | annotations: 6 | kubernetes.io/ingress.class: nginx 7 | cert-manager.io/cluster-issuer: letsencrypt-prod 8 | spec: 9 | rules: 10 | - host: "{{ .Values.ingress.domain }}" 11 | http: 12 | paths: 13 | - backend: 14 | service: 15 | name: "{{ include "chart.fullname" . }}" 16 | port: 17 | number: {{ .Values.service.port }} 18 | path: / 19 | pathType: ImplementationSpecific 20 | tls: 21 | - hosts: 22 | - "{{ .Values.ingress.domain }}" 23 | secretName: {{ .Values.ingress.tlsname }} 24 | -------------------------------------------------------------------------------- /chart/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ include "chart.fullname" . }} 5 | labels: 6 | {{- include "chart.labels" . | nindent 4 }} 7 | spec: 8 | type: {{ .Values.service.type }} 9 | ports: 10 | - port: {{ .Values.service.port }} 11 | targetPort: http 12 | protocol: TCP 13 | name: http 14 | selector: 15 | {{- include "chart.selectorLabels" . | nindent 4 }} 16 | -------------------------------------------------------------------------------- /chart/templates/serviceaccount.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.serviceAccount.create -}} 2 | apiVersion: v1 3 | kind: ServiceAccount 4 | metadata: 5 | name: {{ include "chart.serviceAccountName" . }} 6 | labels: 7 | {{ include "chart.labels" . | nindent 4 }} 8 | {{- end -}} 9 | -------------------------------------------------------------------------------- /chart/values.yaml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | image: 4 | repository: minterteam/console-web 5 | pullPolicy: Always 6 | tag: latest 7 | 8 | nameOverride: "" 9 | fullnameOverride: "" 10 | 11 | serviceAccount: 12 | create: true 13 | 14 | podSecurityContext: {} 15 | 16 | securityContext: {} 17 | 18 | service: 19 | port: 80 20 | 21 | ingress: 22 | domain: "" 23 | tlsname: "" 24 | 25 | resources: {} 26 | 27 | nodeSelector: {} 28 | 29 | tolerations: [] 30 | 31 | affinity: {} 32 | -------------------------------------------------------------------------------- /components/AuthAdvancedForm.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 71 | -------------------------------------------------------------------------------- /components/BroadcastNonceForm.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 76 | -------------------------------------------------------------------------------- /components/CoinList.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 71 | 72 | -------------------------------------------------------------------------------- /components/README.md: -------------------------------------------------------------------------------- 1 | # COMPONENTS 2 | 3 | The components directory contains your Vue.js Components. 4 | Nuxt.js doesn't supercharge these components. 5 | 6 | **This directory is not required, you can delete it if you don't want to use it.** 7 | -------------------------------------------------------------------------------- /components/common/BaseAmount.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 76 | -------------------------------------------------------------------------------- /components/common/BaseDataList.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /components/common/ButtonCopy.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /components/common/ButtonCopyIcon.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 24 | -------------------------------------------------------------------------------- /components/common/FieldPercentage.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 71 | -------------------------------------------------------------------------------- /components/common/InputMaskedAmount.vue: -------------------------------------------------------------------------------- 1 | 78 | 79 | 82 | -------------------------------------------------------------------------------- /components/common/InputMaskedInteger.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 35 | -------------------------------------------------------------------------------- /components/common/InputMaskedName.vue: -------------------------------------------------------------------------------- 1 | 47 | 48 | 51 | -------------------------------------------------------------------------------- /components/common/InputUppercase.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /components/common/Loader.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /components/common/PoolPair.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 53 | -------------------------------------------------------------------------------- /components/common/Snackbar.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 58 | 59 | -------------------------------------------------------------------------------- /components/common/TableLink.vue: -------------------------------------------------------------------------------- 1 | 77 | -------------------------------------------------------------------------------- /components/common/TxFormBlocksToUpdateStake.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /composables/use-hub-discount.js: -------------------------------------------------------------------------------- 1 | import { ref, reactive, computed, watch } from 'vue'; 2 | import debounce from 'debounce-promise'; 3 | import {getDiscountForHolder as _getDiscountForHolder} from '~/api/hub.js'; 4 | 5 | export default function useHubDiscount() { 6 | // two different debounced functions 7 | const debouncedGetDiscountMinter = makeDebouncedGetDiscount(); 8 | const debouncedGetDiscountEth = makeDebouncedGetDiscount(); 9 | 10 | const props = reactive({ 11 | minterAddress: '', 12 | ethAddress: '', 13 | }); 14 | 15 | /** 16 | * @param {{minterAddress?: string, ethAddress?: string}} newProps 17 | */ 18 | function setProps(newProps) { 19 | Object.assign(props, newProps); 20 | } 21 | 22 | const discountMinter = ref(0); 23 | const discountEth = ref(0); 24 | const discount = computed(() => { 25 | return Math.max(discountEth.value, discountMinter.value); 26 | }); 27 | const discountUpsidePercent = computed(() => { 28 | const MAX_DISCOUNT = 0.6; 29 | return Math.round((MAX_DISCOUNT - discount.value) * 100); 30 | }); 31 | 32 | const getMinterDiscount = async () => { 33 | discountMinter.value = await debouncedGetDiscountMinter(props.minterAddress); 34 | }; 35 | const getEthDiscount = async () => { 36 | discountEth.value = await debouncedGetDiscountEth(props.ethAddress); 37 | }; 38 | 39 | watch(() => props.minterAddress, getMinterDiscount); 40 | watch(() => props.ethAddress, getEthDiscount); 41 | 42 | return { 43 | discount, 44 | discountUpsidePercent, 45 | 46 | setDiscountProps: setProps, 47 | }; 48 | } 49 | 50 | function getDiscountForHolder(address) { 51 | return _getDiscountForHolder(address) 52 | .catch((error) => { 53 | console.log(error); 54 | return 0; 55 | }); 56 | } 57 | 58 | function makeDebouncedGetDiscount() { 59 | return debounce(getDiscountForHolder, 1000); 60 | } 61 | -------------------------------------------------------------------------------- /composables/use-last-update-time.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import throttle from 'lodash-es/throttle.js'; 3 | import {getTimeOffset} from '~/assets/axios-time-offset.js'; 4 | import {getTimeDistance} from '~/assets/utils'; 5 | import useNow from '~/composables/use-now.js'; 6 | // ensure composition api is installed 7 | // import '~/plugins/composition-api.js'; 8 | 9 | 10 | // data 11 | const isLastUpdateTimeChanged = ref(false); 12 | const lastUpdateTime = ref(9999999999999); 13 | 14 | // computed 15 | const {now} = useNow(); 16 | /** 17 | * Text representation of distance to now 18 | * @type {ComputedRef} 19 | */ 20 | const lastUpdateTimeDistance = computed(() => { 21 | const dummyVarToDependOnNow = now.value; 22 | return getTimeDistance(lastUpdateTime.value); 23 | }); 24 | const lastUpdateTimeToNow = computed(() => { 25 | return now.value - lastUpdateTime.value; 26 | }); 27 | 28 | // methods 29 | export const setLastUpdateTime = throttle(_setLastUpdateTime, 1000, {leading: true, trailing: true}); 30 | function _setLastUpdateTime(timestamp) { 31 | lastUpdateTime.value = timestamp - getTimeOffset(); 32 | isLastUpdateTimeChanged.value = true; 33 | } 34 | 35 | 36 | 37 | export default function useLastUpdateTime() { 38 | return { 39 | lastUpdateTime, 40 | lastUpdateTimeDistance, 41 | lastUpdateTimeToNow, 42 | isLastUpdateTimeChanged, 43 | setLastUpdateTime, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /composables/use-now.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import {VueNowMixinFactory} from 'vue-now'; 3 | import { toRef } from 'vue'; 4 | 5 | export default function useNow(period) { 6 | const instance = new Vue(VueNowMixinFactory(period)); 7 | const now = toRef(instance, '$now'); 8 | 9 | return { 10 | now, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /desktop/electron-builder-x86.config.js: -------------------------------------------------------------------------------- 1 | const config = require('./electron-builder.config'); 2 | 3 | config.nsis.artifactName = config.nsis.artifactName.replace('x64', 'x86'); 4 | config.win.artifactName = config.win.artifactName.replace('x64', 'x86'); 5 | 6 | module.exports = config; 7 | -------------------------------------------------------------------------------- /desktop/electron-builder.config.js: -------------------------------------------------------------------------------- 1 | const appName = 'minter-console'; 2 | 3 | module.exports = { 4 | "productName": "Minter Console", 5 | "appId": "com.minter.console", 6 | "directories": { 7 | "buildResources": "desktop", 8 | "output": "tmp/electron", 9 | }, 10 | "files": [ 11 | "dist/**/*", 12 | "desktop/electron.js", 13 | "desktop/utils/**/*", 14 | // "desktop/electron.dev.js", 15 | // "nuxt.config.js", 16 | ".nuxt/**/*", 17 | ], 18 | // "publish": ["github"], 19 | "publish": null, 20 | // mac (zip) 21 | "mac": { 22 | "artifactName": `${appName}-\${version}-\${arch}-mac.\${ext}`, 23 | "icon": "desktop/icons/icon.icns", 24 | }, 25 | // mac dmg 26 | "dmg": { 27 | "artifactName": `${appName}-\${version}-\${arch}.\${ext}`, 28 | "contents": [ 29 | { 30 | "x": 410, 31 | "y": 150, 32 | "type": "link", 33 | "path": "/Applications", 34 | }, 35 | { 36 | "x": 130, 37 | "y": 150, 38 | "type": "file", 39 | }, 40 | ], 41 | }, 42 | // win (portable) 43 | "win": { 44 | "artifactName": `${appName}-\${version}-portable-x64.\${ext}`, 45 | "icon": "desktop/icons/icon.ico", 46 | "target": ["portable", "nsis"], 47 | }, 48 | // win setup 49 | "nsis": { 50 | "artifactName": `${appName}-\${version}-setup-x64.\${ext}`, 51 | }, 52 | // linux 53 | "linux": { 54 | "artifactName": `${appName}-\${version}-\${arch}.\${ext}`, 55 | "icon": "desktop/icons", 56 | "category": "Office", 57 | }, 58 | }; 59 | -------------------------------------------------------------------------------- /desktop/electron.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is used specifically and only for development. It installs 3 | * `electron-debug` & `vue-devtools`. There shouldn't be any need to 4 | * modify this file, but it can be used to extend your development 5 | * environment. 6 | */ 7 | 8 | /* eslint-disable */ 9 | 10 | // Install `electron-debug` with `devtron` 11 | require('electron-debug/index')({ showDevTools: true }) 12 | 13 | // Install `vue-devtools` 14 | require('electron').app.on('ready', () => { 15 | // let installExtension = require('electron-devtools-installer') 16 | // installExtension.default(installExtension.VUEJS_DEVTOOLS) 17 | // .then(() => {}) 18 | // .catch(err => { 19 | // console.log('Unable to install `vue-devtools`: \n', err) 20 | // }) 21 | }) 22 | 23 | // Require `main` process to boot app 24 | require('./electron') 25 | -------------------------------------------------------------------------------- /desktop/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/desktop/icons/256x256.png -------------------------------------------------------------------------------- /desktop/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/desktop/icons/icon.icns -------------------------------------------------------------------------------- /desktop/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/desktop/icons/icon.ico -------------------------------------------------------------------------------- /desktop/utils/delete-logs.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | module.exports = function deleteLogs(app) { 5 | try { 6 | const dbPath = path.join(app.getPath('userData'), 'Local Storage/leveldb'); 7 | const logs = findInDir(dbPath, '.log'); 8 | logs.forEach((filePath) => { 9 | fs.unlinkSync(filePath); 10 | }); 11 | } catch (e) { 12 | console.log(e); 13 | } 14 | }; 15 | 16 | function findInDir(startPath, filter) { 17 | let result = []; 18 | 19 | if (!fs.existsSync(startPath)) { 20 | return result; 21 | } 22 | 23 | const files = fs.readdirSync(startPath); 24 | for (let i = 0; i < files.length; i++) { 25 | const filename = path.join(startPath, files[i]); 26 | const stat = fs.lstatSync(filename); 27 | if (stat.isDirectory()) { 28 | result = result.concat(findInDir(filename, filter)); //recurse 29 | } else if (filename.indexOf(filter) >= 0) { 30 | result.push(filename); 31 | } 32 | } 33 | 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /desktop/utils/menu.js: -------------------------------------------------------------------------------- 1 | module.exports = function createMenu(app, Menu) { 2 | const template = [{ 3 | label: "Minter Console", 4 | submenu: [ 5 | { label: "About", selector: "orderFrontStandardAboutPanel:" }, 6 | { type: "separator" }, 7 | { label: "Quit", accelerator: "Command+Q", click: function() { app.quit(); }}, 8 | ]}, { 9 | label: "Edit", 10 | submenu: [ 11 | { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, 12 | { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, 13 | { type: "separator" }, 14 | { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, 15 | { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, 16 | { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, 17 | { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }, 18 | ]}, 19 | ]; 20 | 21 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 22 | }; 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | app: 5 | build: 6 | context: ./ 7 | dockerfile: .docker/Dockerfile 8 | args: 9 | 10 | ports: 11 | - ${WEB_PORT_FOR_DOCKER_COMPOSE}:80 12 | -------------------------------------------------------------------------------- /jest-babel.config.js: -------------------------------------------------------------------------------- 1 | // Custom Jest transform implementation that wraps babel-jest and injects our 2 | // babel presets, so we don't have to use .babelrc. 3 | 4 | const babelJest = require('babel-jest'); 5 | const createTransformer = babelJest.createTransformer || babelJest.default.createTransformer; 6 | module.exports = createTransformer({ 7 | babelrc: false, 8 | "presets": [ 9 | [ 10 | "@nuxt/babel-preset-app", 11 | { 12 | "targets": { 13 | "node": true, 14 | }, 15 | "modules": "commonjs", 16 | }, 17 | ], 18 | ], 19 | // presets: ["babel-preset-vue-app"], // babel 6 20 | // ignore: false, // do nothing, jest's transformIgnorePatterns works instead 21 | }); 22 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | moduleNameMapper: { 3 | '~(.*)$': '/$1', 4 | }, 5 | transform: { 6 | '^.+\\.jsx?$': '/jest-babel.config.js', 7 | }, 8 | // faster test files lookup 9 | testMatch: [ 10 | // default 11 | // "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" 12 | "/test/**/?(*.)+(spec|test).[jt]s?(x)", 13 | ], 14 | testPathIgnorePatterns: [ 15 | '/tmp/', 16 | ], 17 | 'globalSetup': '/test/jest-setup.js', 18 | 'globalTeardown': '/test/jest-teardown.js', 19 | "testEnvironment": "/test/jest-environment.js", 20 | }; 21 | -------------------------------------------------------------------------------- /lang/en.js: -------------------------------------------------------------------------------- 1 | export default { 2 | form: { 3 | 'delegation-delegate-confirm-note': 'Note: in case of unbond, your coins will return to your address in approximately 30 days (518 400 blocks) and will not be generating rewards during this period.', 4 | 'delegation-unbond-confirm-description': 'Are you sure you want to unbond your coins? They will return to your address in approximately 30 days (518 400 blocks) and will not be generating rewards during this period.', 5 | 'delegation-move-confirm-description': 'Are you sure you want to move your coins? They will appear to new masternode in approximately 7 days (134 400 blocks) and will not be generating rewards during this period.', 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /layouts/README.md: -------------------------------------------------------------------------------- 1 | # LAYOUTS 2 | 3 | This directory contains your Application Layouts. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/views#layouts 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /layouts/_footer.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 44 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 53 | -------------------------------------------------------------------------------- /layouts/nonAuth.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 41 | 42 | -------------------------------------------------------------------------------- /middleware/README.md: -------------------------------------------------------------------------------- 1 | # MIDDLEWARE 2 | 3 | This directory contains your Application Middleware. 4 | The middleware lets you define custom function to be ran before rendering a page or a group of pages (layouts). 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing#middleware 8 | 9 | **This directory is not required, you can delete it if you don't want to use it.** 10 | -------------------------------------------------------------------------------- /middleware/auth.js: -------------------------------------------------------------------------------- 1 | export default function({app, store, route, redirect, error}) { 2 | if (process.server) { 3 | return; 4 | } 5 | console.log('CHECK AUTH'); 6 | console.log('-- route', route); 7 | console.log('-- path', route.path); 8 | 9 | /* 10 | const urlRequiresAuth = [ 11 | /^(\/ru)?\/account(\/|$)/, 12 | /^(\/ru)?\/checks(\/|$)/, 13 | /^(\/ru)?\/coiner(\/|$)/, 14 | /^(\/ru)?\/swap(\/|$)/, 15 | /^(\/ru)?\/order(\/|$)/, 16 | /^(\/ru)?\/pool(\/|$)/, 17 | /^(\/ru)?\/dao(\/|$)/, 18 | /^(\/ru)?\/delegation(\/|$)/, 19 | /^(\/ru)?\/masternode(\/|$)/, 20 | /^(\/ru)?\/multisig(\/|$)/, 21 | /^(\/ru)?\/lock(\/|$)/, 22 | /^(\/ru)?\/support(\/|$)/, 23 | /^(\/ru)?\/wallet(\/|$)/, 24 | ].some((pathRegex) => { 25 | return pathRegex.test(route.path); 26 | }); 27 | */ 28 | 29 | const urlAllowsNonAuth = [ 30 | // /^(\/ru)?\/profile\/confirm/, 31 | /^\/(ru)?(\/)?$/, 32 | ].some((pathRegex) => { 33 | return pathRegex.test(route.path); 34 | }); 35 | 36 | // const urlRequiresNonAuth = /^\/auth(\/|$)/.test(route.path); 37 | // const urlRequiresUserWithProfile = [ 38 | // /^\/settings\/profile-/, 39 | // ].some((pathRegex) => { 40 | // return pathRegex.test(route.path); 41 | // }); 42 | 43 | 44 | if (!store.getters.isAuthorized && !urlAllowsNonAuth) { 45 | console.log('-- restricted: redirect to auth'); 46 | return redirect(app.i18nGetPreferredPath('index')); 47 | } 48 | // if (store.getters.isAuthorized && urlRequiresNonAuth) { 49 | // console.log('-- restricted: redirect to index'); 50 | // return redirect('/auth'); 51 | // } 52 | 53 | // if (!store.getters.isUserWithProfile && urlRequiresUserWithProfile) { 54 | // console.log('-- restricted: 404 settings not available'); 55 | // return error({statusCode: 404, message: 'Page not found'}); 56 | // } 57 | 58 | console.log('-- not restricted'); 59 | return Promise.resolve(); 60 | } 61 | -------------------------------------------------------------------------------- /middleware/balance.js: -------------------------------------------------------------------------------- 1 | import Centrifuge from 'centrifuge/src'; 2 | import {prepareBalance} from '~/api/explorer.js'; 3 | import {EXPLORER_RTM_URL} from "~/assets/variables"; 4 | import {toCamel} from '~/assets/axios-to-camel.js'; 5 | import {setLastUpdateTime} from '~/composables/use-last-update-time.js'; 6 | 7 | let centrifuge; 8 | 9 | export default function({app, store, redirect}) { 10 | if (process.server) { 11 | return Promise.resolve(); 12 | } 13 | 14 | if (store.getters.isAuthorized && !store.getters.isOfflineMode) { 15 | // init only once 16 | if (centrifuge) { 17 | return Promise.resolve(); 18 | } 19 | // store.commit('SET_LAST_UPDATE_TIME', Date.now()); 20 | // wait for balance, bc its data need for all pages 21 | return store.dispatch('FETCH_BALANCE') 22 | .then(() => { 23 | centrifuge = new Centrifuge(EXPLORER_RTM_URL, { 24 | // user: connectData.user ? connectData.user : '', 25 | // timestamp: connectData.timestamp.toString(), 26 | // token: connectData.token, 27 | // sockjs: SockJS, 28 | }); 29 | 30 | centrifuge.subscribe(store.getters.address, (response) => { 31 | const balance = toCamel(response.data); 32 | prepareBalance(balance) 33 | .then((preparedBalance) => { 34 | store.commit('SET_BALANCE', preparedBalance); 35 | }); 36 | }); 37 | 38 | centrifuge.subscribe("blocks", (response) => { 39 | const newBlock = toCamel(response.data); 40 | // block timestamp is block's precommit time, fixing it 41 | const fixedTimestamp = new Date(newBlock.timestamp).getTime() + Math.round(newBlock.blockTime * 1000); 42 | setLastUpdateTime(fixedTimestamp); 43 | }); 44 | 45 | centrifuge.connect(); 46 | }); 47 | } 48 | 49 | // not authorized, cleanup 50 | if (centrifuge) { 51 | centrifuge.disconnect(); 52 | centrifuge = null; 53 | } 54 | 55 | return Promise.resolve(); 56 | } 57 | -------------------------------------------------------------------------------- /middleware/explorer.js: -------------------------------------------------------------------------------- 1 | export default function({store}) { 2 | if (process.server) { 3 | return Promise.resolve(); 4 | } 5 | 6 | if (store.getters.isOfflineMode) { 7 | return; 8 | } 9 | 10 | // don't wait 11 | store.dispatch('explorer/FETCH_STATUS') 12 | .catch((e) => { 13 | console.log(e); 14 | }); 15 | 16 | store.dispatch('explorer/FETCH_COIN_LIST') 17 | .catch((e) => { 18 | console.log(e); 19 | }); 20 | 21 | return Promise.resolve(); 22 | } 23 | -------------------------------------------------------------------------------- /middleware/profile.js: -------------------------------------------------------------------------------- 1 | /* 2 | import {hasAuthToken} from "~/api/accounts"; 3 | 4 | export default function({app, store, redirect}) { 5 | if (process.server) { 6 | return Promise.resolve(); 7 | } 8 | 9 | if (store.getters.isUserAdvanced) { 10 | return Promise.resolve(); 11 | } 12 | if (hasAuthToken()) { 13 | // wait for profile, bc its data need for all pages 14 | return store.dispatch('FETCH_PROFILE') 15 | .catch((resError) => { 16 | // Unauthorized: logout bc. auth data is not approved by server 17 | console.log(resError, resError.response); 18 | if (resError.response && resError.response.status === 401) { 19 | store.commit('LOGOUT'); 20 | redirect(app.i18nGetPreferredPath('index')); 21 | } else { 22 | throw resError; 23 | } 24 | }); 25 | } else if (store.getters.isUserWithProfile) { 26 | // no auth token but password stored 27 | store.commit('LOGOUT'); 28 | } 29 | 30 | return Promise.resolve(); 31 | } 32 | */ 33 | -------------------------------------------------------------------------------- /pages/README.md: -------------------------------------------------------------------------------- 1 | # PAGES 2 | 3 | This directory contains your Application Views and Routes. 4 | The framework reads all the .vue files inside this directory and creates the router of your application. 5 | 6 | More information about the usage of this directory in the documentation: 7 | https://nuxtjs.org/guide/routing 8 | -------------------------------------------------------------------------------- /pages/broadcast/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 59 | -------------------------------------------------------------------------------- /pages/buy/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 44 | -------------------------------------------------------------------------------- /pages/checks/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 50 | -------------------------------------------------------------------------------- /pages/coiner/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 60 | -------------------------------------------------------------------------------- /pages/convert.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /pages/dao/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 25 | -------------------------------------------------------------------------------- /pages/delegation/index.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | 75 | -------------------------------------------------------------------------------- /pages/hub/index.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 81 | -------------------------------------------------------------------------------- /pages/lock/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 38 | -------------------------------------------------------------------------------- /pages/masternode/index.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 67 | -------------------------------------------------------------------------------- /pages/multisig/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 40 | -------------------------------------------------------------------------------- /pages/order/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | -------------------------------------------------------------------------------- /pages/pool/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 46 | -------------------------------------------------------------------------------- /pages/support/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 25 | -------------------------------------------------------------------------------- /pages/swap/index.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 42 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # PLUGINS 2 | 3 | This directory contains your Javascript plugins that you want to run before instantiating the root vue.js application. 4 | 5 | More information about the usage of this directory in the documentation: 6 | https://nuxtjs.org/guide/plugins 7 | 8 | **This directory is not required, you can delete it if you don't want to use it.** 9 | -------------------------------------------------------------------------------- /plugins/base-url-prefix.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import {BASE_URL_PREFIX} from '~/assets/variables.js'; 3 | 4 | Vue.mixin({ 5 | computed: { 6 | BASE_URL_PREFIX: () => BASE_URL_PREFIX, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /plugins/classlist-svg-polyfill.js: -------------------------------------------------------------------------------- 1 | if (!("classList" in document.createElementNS("http://www.w3.org/2000/svg", "g"))) { 2 | var descr = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'classList'); 3 | Object.defineProperty(SVGElement.prototype, 'classList', descr); 4 | } 5 | -------------------------------------------------------------------------------- /plugins/click-blur.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remove unnecessary :focus from links and buttons after mouse click 3 | */ 4 | document.addEventListener('click', (e) => { 5 | const el = typeof e.target.closest === 'function' && (e.target.closest('a') || e.target.closest('button') || e.target.closest('input[type=radio]') || e.target.closest('input[type=checkbox]')); 6 | if (e.screenX > 0 && el) { 7 | el.blur(); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /plugins/online.js: -------------------------------------------------------------------------------- 1 | export default ({store}) => { 2 | if (window.navigator.onLine) { 3 | store.commit('SET_ONLINE', true); 4 | } else { 5 | store.commit('SET_ONLINE', false); 6 | } 7 | 8 | window.addEventListener('online', () => { 9 | store.commit('SET_ONLINE', true); 10 | }); 11 | window.addEventListener('offline', () => { 12 | store.commit('SET_ONLINE', false); 13 | }); 14 | }; 15 | -------------------------------------------------------------------------------- /plugins/persisted-state.js: -------------------------------------------------------------------------------- 1 | import createPersistedState from 'vuex-persistedstate'; 2 | import VuexPersistence from 'vuex-persist'; 3 | import localforage from 'localforage'; 4 | import {pruneTxFields} from '@/store/hub.js'; 5 | 6 | window.localforage = localforage; 7 | 8 | let isCreated = false; 9 | 10 | const authMutationList = [ 11 | 'SET_AUTH_PROFILE', 12 | 'SET_AUTH_ADVANCED', 13 | 'LOGOUT', 14 | 'UPDATE_PROFILE_PASSWORD', 15 | ]; 16 | 17 | export default ({store}) => { 18 | // window.onNuxtReady(() => { 19 | // if (isCreated) { 20 | // return; 21 | // } 22 | createPersistedState({ 23 | paths: [ 24 | 'auth', 25 | 'web3Account.selectedAccountType', 26 | 'web3Account.chainId', 27 | // stored in indexedDB to handle large size data 28 | // 'hub.ethList', 29 | ], 30 | // filter(mutation) { 31 | // // is auth mutation 32 | // return authMutationList.indexOf(mutation.type) !== -1; 33 | // }, 34 | })(store); 35 | // isCreated = true; 36 | // }); 37 | 38 | 39 | new VuexPersistence({ 40 | key: 'vuex-persist', 41 | storage: localforage, 42 | asyncStorage: true, 43 | reducer(state) { 44 | // prune on save to clean already stored data 45 | let ethList = state.hub?.ethList || {}; 46 | ethList = Object.fromEntries(Object.entries(ethList).map(([address, txList]) => { 47 | return [address, txList.map(pruneTxFields).map((tx) => { 48 | tx = JSON.parse(JSON.stringify(tx)); 49 | // fix already stored tokenInfo 50 | if (tx.tokenInfo?.tokenContract) { 51 | tx.tokenInfo.tokenContract = tx.tokenInfo.tokenContract.toLowerCase(); 52 | } 53 | return tx; 54 | })]; 55 | })); 56 | return {hub: {ethList}}; 57 | }, 58 | }).plugin(store); 59 | }; 60 | -------------------------------------------------------------------------------- /static/README.md: -------------------------------------------------------------------------------- 1 | # STATIC 2 | 3 | This directory contains your static files. 4 | Each file inside this directory is mapped to /. 5 | 6 | Example: /static/robots.txt is mapped as /robots.txt. 7 | 8 | More information about the usage of this directory in the documentation: 9 | https://nuxtjs.org/guide/assets#static 10 | 11 | **This directory is not required, you can delete it if you don't want to use it.** 12 | -------------------------------------------------------------------------------- /static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/apple-touch-icon.png -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/favicon.ico -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/favicon.png -------------------------------------------------------------------------------- /static/fonts/ubuntu-v11-cyrillic_latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v11-cyrillic_latin-700.woff -------------------------------------------------------------------------------- /static/fonts/ubuntu-v11-cyrillic_latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v11-cyrillic_latin-700.woff2 -------------------------------------------------------------------------------- /static/fonts/ubuntu-v11-cyrillic_latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v11-cyrillic_latin-regular.woff -------------------------------------------------------------------------------- /static/fonts/ubuntu-v11-cyrillic_latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v11-cyrillic_latin-regular.woff2 -------------------------------------------------------------------------------- /static/fonts/ubuntu-v14-latin_cyrillic-500.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v14-latin_cyrillic-500.woff -------------------------------------------------------------------------------- /static/fonts/ubuntu-v14-latin_cyrillic-500.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/fonts/ubuntu-v14-latin_cyrillic-500.woff2 -------------------------------------------------------------------------------- /static/social-share-ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/social-share-ru.png -------------------------------------------------------------------------------- /static/social-share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MinterTeam/minter-console-web/b42c50a78489e7c07aa4e3ccfc0e034927ae388b/static/social-share.png -------------------------------------------------------------------------------- /store/README.md: -------------------------------------------------------------------------------- 1 | # STORE 2 | 3 | This directory contains your Vuex Store files. 4 | Vuex Store option is implemented in the Nuxt.js framework. 5 | Creating a index.js file in this directory activate the option in the framework automatically. 6 | 7 | More information about the usage of this directory in the documentation: 8 | https://nuxtjs.org/guide/vuex-store 9 | 10 | **This directory is not required, you can delete it if you don't want to use it.** 11 | -------------------------------------------------------------------------------- /store/explorer.js: -------------------------------------------------------------------------------- 1 | // import {isCoinId} from 'minter-js-sdk/src/utils.js'; 2 | import {getStatus, getCoinList} from '~/api/explorer.js'; 3 | import {arrayToMap} from '@/assets/utils/collection.js'; 4 | import {ACCOUNTS_API_URL, BASE_URL_PREFIX, EXPLORER_STATIC_HOST} from '~/assets/variables.js'; 5 | 6 | export const state = () => ({ 7 | /** @type Status|null */ 8 | status: null, 9 | /** @type Array */ 10 | coinList: [], 11 | /** @type {Object.} */ 12 | coinMap: {}, 13 | }); 14 | 15 | export const getters = { 16 | bipPriceUsd(state) { 17 | return state.status?.bipPriceUsd || 0; 18 | }, 19 | getCoinIcon(state, getters, rootState, rootGetters) { 20 | return function(coinSymbol) { 21 | // BIP 22 | if (coinSymbol.toUpperCase() === 'BIP') { 23 | return `${BASE_URL_PREFIX}/img/icon-coin-bip.svg`; 24 | } 25 | // LP 26 | if (coinSymbol.indexOf('LP-') === 0) { 27 | return `${BASE_URL_PREFIX}/img/icon-coin-lp.svg`; 28 | } 29 | 30 | const coinIcon = state.coinMap[coinSymbol]?.icon; 31 | 32 | // chainik icon 33 | if (coinIcon) { 34 | return `${EXPLORER_STATIC_HOST}/coins/${state.coinMap[coinSymbol].id}.png`; 35 | } 36 | 37 | // archived coins 38 | if (coinSymbol.indexOf('-') >= 0) { 39 | return `${BASE_URL_PREFIX}/img/icon-coin-fallback.svg`; 40 | } 41 | 42 | // myminter icon 43 | if (!rootGetters.isOfflineMode) { 44 | return `${ACCOUNTS_API_URL}avatar/by/coin/${coinSymbol}`; 45 | } 46 | 47 | // fallback 48 | return `${BASE_URL_PREFIX}/img/icon-coin-fallback.svg`; 49 | }; 50 | }, 51 | getCoinVerified(state) { 52 | return function(coinSymbol) { 53 | // BIP 54 | if (coinSymbol.toUpperCase() === 'BIP') { 55 | return true; 56 | } 57 | 58 | return state.coinMap[coinSymbol].verified; 59 | }; 60 | }, 61 | }; 62 | 63 | export const mutations = { 64 | SET_STATUS(state, statusData) { 65 | state.status = statusData; 66 | }, 67 | SET_COIN_LIST(state, data) { 68 | state.coinList = Object.freeze(data); 69 | state.coinMap = Object.freeze(arrayToMap(data, 'symbol')); 70 | }, 71 | }; 72 | 73 | export const actions = { 74 | FETCH_STATUS({ commit }) { 75 | return getStatus() 76 | .then((statusData) => { 77 | commit('SET_STATUS', statusData); 78 | return statusData; 79 | }); 80 | }, 81 | FETCH_COIN_LIST({ commit }) { 82 | return getCoinList() 83 | .then((data) => { 84 | commit('SET_COIN_LIST', data); 85 | return data; 86 | }); 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /store/getters.js: -------------------------------------------------------------------------------- 1 | // Uint8Array.fill needed for wallet 2 | // import 'core-js/modules/es6.typed.uint8-array'; 3 | import {walletFromMnemonic, isValidMnemonic} from 'minterjs-wallet'; 4 | import {getNameLetter, getExplorerAddressUrl} from "~/assets/utils"; 5 | import {CHAIN_ID, BASE_COIN} from '~/assets/variables'; 6 | 7 | export default { 8 | /** 9 | * Checks if user is authorized 10 | * @return {boolean} 11 | */ 12 | isAuthorized(state, getters) { 13 | return getters.isUserAdvanced; 14 | }, 15 | /** 16 | * Checks if user is authorized by private key 17 | * @return {boolean} 18 | */ 19 | isUserAdvanced(state) { 20 | return !!(state.auth.advanced && isValidMnemonic(state.auth.advanced)); 21 | }, 22 | wallet(state, getters) { 23 | if (getters.isUserAdvanced) { 24 | return walletFromMnemonic(state.auth.advanced); 25 | } 26 | return null; 27 | }, 28 | address(state, getters) { 29 | if (getters.isUserAdvanced) { 30 | return getters.wallet.getAddressString(); 31 | } 32 | 33 | return ''; 34 | }, 35 | addressUrl(state, getters) { 36 | return getExplorerAddressUrl(getters.address); 37 | }, 38 | mnemonic(state, getters) { 39 | return getters.wallet ? getters.wallet.getMnemonic() : ''; 40 | }, 41 | privateKey(state, getters) { 42 | return getters.wallet ? getters.wallet.getPrivateKeyString() : ''; 43 | }, 44 | username(state, getters) { 45 | return getters.address; 46 | }, 47 | COIN_NAME() { 48 | return BASE_COIN; 49 | }, 50 | BASE_COIN() { 51 | return BASE_COIN; 52 | }, 53 | CHAIN_ID() { 54 | return CHAIN_ID; 55 | }, 56 | balance(state, getters) { 57 | if (getters.isOfflineMode) { 58 | return []; 59 | } else { 60 | return state.balance; 61 | } 62 | }, 63 | baseCoin(state) { 64 | return state.balance.find((coinItem) => { 65 | return coinItem.coin.symbol === BASE_COIN; 66 | }); 67 | }, 68 | isOfflineMode(state, getters) { 69 | // keep users with profile in online mode to workaround lack of mnemonic 70 | return !state.onLine && getters.isUserAdvanced; 71 | }, 72 | }; 73 | -------------------------------------------------------------------------------- /store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | SET_SECTION_NAME: (state, sectionName) => { 3 | state.sectionName = sectionName; 4 | }, 5 | // SET_AUTH_PROFILE: (state, {user, token, password}) => { 6 | // LOGOUT(state); 7 | // state.auth.password = password; 8 | // setAuthToken(token); 9 | // SET_PROFILE_USER(state, user); 10 | // }, 11 | SET_AUTH_ADVANCED: (state, address) => { 12 | LOGOUT(state); 13 | state.auth.advanced = address; 14 | }, 15 | LOGOUT, 16 | // SET_PROFILE_USER, 17 | // SET_PROFILE_ADDRESS: (state, address) => { 18 | // Vue.set(state.user, 'mainAddress', address); 19 | // }, 20 | // UPDATE_PROFILE_PASSWORD: (state, password) => { 21 | // state.auth.password = password; 22 | // }, 23 | // SET_PROFILE_ADDRESS_LIST: (state, addressList) => { 24 | // state.profileAddressList = addressList; 25 | // }, 26 | // SET_TRANSACTION_LIST: (state, txListInfo) => { 27 | // state.transactionListInfo = txListInfo; 28 | // }, 29 | SET_BALANCE: (state, balance) => { 30 | state.balance = Object.freeze(balance); 31 | }, 32 | /** 33 | * @param state 34 | * @param {DelegationData} stakeData 35 | */ 36 | SET_STAKE_LIST: (state, stakeData) => { 37 | state.stakeList = Object.freeze(stakeData.list); 38 | SET_STAKE_LOCK(state, stakeData.lock); 39 | }, 40 | SET_STAKE_LOCK, 41 | SET_VALIDATOR_META_LIST(state, validatorList) { 42 | state.validatorMetaList = Object.freeze(validatorList); 43 | }, 44 | // PUSH_HISTORY: (state, historyItem) => { 45 | // state.history.push(historyItem); 46 | // }, 47 | // POP_HISTORY: (state) => { 48 | // state.history.pop(); 49 | // }, 50 | SET_ONLINE(state, onLine) { 51 | state.onLine = onLine; 52 | }, 53 | /** 54 | * Show snackbar if it is inactive 55 | */ 56 | SET_SNACKBAR_ACTIVE(state) { 57 | state.isSnackbarActive = true; 58 | }, 59 | /** 60 | * Set snackbar inactive so it can react to next SET_SNACKBAR_ACTIVE call 61 | */ 62 | SET_SNACKBAR_INACTIVE(state) { 63 | state.isSnackbarActive = false; 64 | }, 65 | }; 66 | 67 | function LOGOUT(state) { 68 | // state.user = {}; 69 | // state.auth.password = null; 70 | state.auth.advanced = null; 71 | // resetAuthToken(); 72 | } 73 | 74 | /** 75 | * @param state 76 | * @param {DelegationData.lock} stakeLock 77 | */ 78 | function SET_STAKE_LOCK(state, stakeLock) { 79 | state.stakeLock = Object.freeze(stakeLock); 80 | } 81 | 82 | // function SET_PROFILE_USER(state, profile) { 83 | // // save encrypted data on refresh 84 | // if (!profile.mainAddress.encrypted && state.user.mainAddress && state.user.mainAddress.address === profile.mainAddress.address) { 85 | // profile.mainAddress.encrypted = state.user.mainAddress.encrypted; 86 | // } 87 | // state.user = profile; 88 | // state.userTimeStamp = Date.now(); 89 | // } 90 | -------------------------------------------------------------------------------- /store/state.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | return { 3 | sectionName: '', 4 | auth: { 5 | /** @type string|null - mnemonic */ 6 | advanced: null, 7 | }, 8 | // userTimeStamp: 0, 9 | /** @type Array */ 10 | balance: [], 11 | /** @type Array */ 12 | stakeList: [], 13 | /** @type Readonly */ 14 | stakeLock: null, 15 | /** @type Array */ 16 | validatorMetaList: [], 17 | // transactionListInfo: { 18 | // data: [], 19 | // meta: {}, 20 | // }, 21 | // history: [], 22 | onLine: false, 23 | isSnackbarActive: false, 24 | }; 25 | } 26 | /** 27 | * vuex-persistedstate enabled in nuxt.config.js 28 | */ 29 | 30 | 31 | 32 | 33 | 34 | /** 35 | * @typedef {Object} TokenData 36 | * @property {string} tokenType 37 | * @property {number} expiresIn 38 | * @property {string} accessToken 39 | * @property {string} refreshToken 40 | */ 41 | 42 | /** 43 | * @typedef {Object} User 44 | * @property {string} username 45 | * @property {string} name 46 | * @property {string} email 47 | * @property {string} phone} 48 | * @property {string} language 49 | * @property {UserAvatar} avatar 50 | * @property {Address} mainAddress 51 | */ 52 | 53 | /** 54 | * @typedef {Object} UserAvatar 55 | * @property {string} src 56 | * @property {string} description 57 | */ 58 | -------------------------------------------------------------------------------- /store/web3Account.js: -------------------------------------------------------------------------------- 1 | export const state = () => ({ 2 | /* Deposit state */ 3 | ethAddress: '', 4 | chainId: 0, 5 | selectedAccountType: '', 6 | }); 7 | 8 | export const mutations = { 9 | /* Deposit methods */ 10 | setEthAddress(state, address) { 11 | state.ethAddress = address.toLowerCase(); 12 | }, 13 | setChainId(state, chainId) { 14 | state.chainId = Number(chainId) || 0; 15 | }, 16 | setSelectedAccountType(state, type) { 17 | state.selectedAccountType = type; 18 | }, 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /test/jest-environment.js: -------------------------------------------------------------------------------- 1 | const puppeteer = require('puppeteer'); 2 | const NodeEnvironment = require('jest-environment-node').default; 3 | const { receiveData } = require('./jest-utils'); 4 | 5 | class TestEnvironment extends NodeEnvironment { 6 | constructor(config) { 7 | super(config); 8 | } 9 | 10 | async setup() { 11 | await super.setup(); 12 | 13 | const data = receiveData(); 14 | 15 | this.global.browser = await puppeteer.connect({ 16 | browserWSEndpoint: data.wsEndpoint, 17 | }); 18 | } 19 | 20 | async teardown() { 21 | await super.teardown(); 22 | } 23 | 24 | runScript(script) { 25 | return super.runScript(script); 26 | } 27 | } 28 | 29 | module.exports = TestEnvironment; 30 | -------------------------------------------------------------------------------- /test/jest-setup.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import _interopRequireDefault from "@babel/runtime/helpers/interopRequireDefault"; 3 | import puppeteer from 'puppeteer'; 4 | // import { Nuxt, Builder } from 'nuxt'; 5 | import {Nuxt} from '@nuxt/core'; 6 | import {Builder} from '@nuxt/builder'; 7 | const { transferData } = require('./jest-utils'); 8 | import {HOST_NAME, PORT, APP_URL_BASE} from './variables'; 9 | 10 | 11 | // process.env.DEBUG = true; 12 | // process.env.NUXT_SKIP_SELF_BUILD = true; 13 | 14 | 15 | /** @type Nuxt */ 16 | let nuxt; 17 | /** @type Browser */ 18 | let browser; 19 | // /** @type Page */ 20 | // let page; 21 | 22 | const initNuxt = async () => { 23 | const rootDir = path.resolve(__dirname, '..'); 24 | let config = {}; 25 | try { config = _interopRequireDefault(require(path.resolve(rootDir, 'nuxt.config.js'))).default; } catch (e) { 26 | throw Error('Couldn\'t find nuxt.config.js'); 27 | } 28 | config.rootDir = rootDir; 29 | if (config.build && config.build.analyze) { 30 | delete config.build.analyze; 31 | } 32 | config.dev = false; 33 | config.ssr = false; 34 | config.modern = false; 35 | nuxt = new Nuxt(config); 36 | await nuxt.ready(); 37 | if (!process.env.NUXT_SKIP_SELF_BUILD) { 38 | await new Builder(nuxt).build(); 39 | } 40 | await nuxt.server.listen(PORT, HOST_NAME); 41 | return nuxt; 42 | }; 43 | 44 | 45 | module.exports = async function() { 46 | // if (!process.env.SELF_START) { 47 | // const nuxt = await initNuxt() 48 | // global.__NUXT__ = nuxt 49 | // } 50 | // 51 | // const browser = await puppeteer.launch({ 52 | // args: ['--no-sandbox', '--disable-setuid-sandbox'] 53 | // }) 54 | // global.__BROWSER__ = browser 55 | // 56 | // transferData({ 57 | // BASE_URL: process.env.SELF_START ? null : BASE_URL, 58 | // wsEndpoint: browser.wsEndpoint() 59 | // }) 60 | 61 | // init nuxt 62 | const nuxt = await initNuxt(); 63 | 64 | // init puppeteer 65 | browser = await puppeteer.launch(Object.assign({ 66 | args: [ 67 | '--disable-dev-shm-usage', 68 | '--shm-size=1gb', 69 | ], 70 | }, process.env.DEBUG 71 | ? { 72 | headless: false, 73 | slowMo: 150, 74 | } 75 | : {})); 76 | // page = await browser.newPage(); 77 | 78 | // console.log(process.env); 79 | 80 | global.__NUXT__ = nuxt; 81 | global.__BROWSER__ = browser; 82 | 83 | // pass initialized `Browser` to test suite's environment 84 | transferData({ 85 | BASE_URL: APP_URL_BASE, 86 | wsEndpoint: browser.wsEndpoint(), 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /test/jest-teardown.js: -------------------------------------------------------------------------------- 1 | const { removeTempDir } = require('./jest-utils'); 2 | 3 | module.exports = async function() { 4 | if (global.__NUXT__) { 5 | await global.__NUXT__.close(); 6 | } 7 | if (!process.env.DEBUG) { 8 | await global.__BROWSER__.close(); 9 | } 10 | 11 | removeTempDir(); 12 | }; 13 | -------------------------------------------------------------------------------- /test/jest-utils.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const path = require('path'); 4 | const mkdirp = require('mkdirp'); 5 | const rimraf = require('rimraf'); 6 | 7 | const TEMP_DIR = path.join(os.tmpdir(), 'nuxt_jest_puppeteer_global_setup'); 8 | const TEMP_FILE = 'nuxt-jest-puppeteer'; 9 | 10 | exports.transferData = function(data) { 11 | mkdirp.sync(TEMP_DIR); 12 | fs.writeFileSync(path.join(TEMP_DIR, TEMP_FILE), JSON.stringify(data)); 13 | }; 14 | 15 | exports.receiveData = function() { 16 | const data = JSON.parse(fs.readFileSync(path.join(TEMP_DIR, TEMP_FILE), 'utf8')); 17 | if (!data) throw new Error('nuxt-jest-puppeteer data not found'); 18 | return data; 19 | }; 20 | 21 | exports.removeTempDir = function() { 22 | rimraf.sync(TEMP_DIR); 23 | }; 24 | -------------------------------------------------------------------------------- /test/pages/convert.test.js: -------------------------------------------------------------------------------- 1 | import {ROUTES, DEFAULT_SELECTOR_TIMEOUT, DEFAULT_TEST_TIMEOUT} from '~/test/variables.js'; 2 | import {login, logout, txSubmit, wait} from '~/test/utils.js'; 3 | 4 | /** @type Browser */ 5 | let browser; 6 | /** @type Page */ 7 | let page; 8 | 9 | 10 | beforeAll(async function beforeAllFn() { 11 | browser = global.browser; 12 | page = await browser.newPage(); 13 | page.setDefaultTimeout(DEFAULT_SELECTOR_TIMEOUT); 14 | await login(page); 15 | }, DEFAULT_TEST_TIMEOUT); 16 | 17 | 18 | afterAll(async function afterAllFn() { 19 | await logout(page); 20 | if (!process.env.DEBUG) { 21 | await page.close(); 22 | } 23 | }); 24 | 25 | 26 | describe('convert page', () => { 27 | beforeAll(async () => { 28 | await page.goto(ROUTES.private.convert); 29 | await page.waitForSelector('[data-test-id="convertSellInputSellCoin"]'); 30 | }); 31 | beforeEach(async () => { 32 | await wait(500); 33 | }); 34 | 35 | test('sell coins', async () => { 36 | await page.type('[data-test-id="convertSellInputSellCoin"]', 'MNT'); 37 | await page.type('[data-test-id="convertSellInputSellAmount"]', '1'); 38 | await page.type('[data-test-id="convertSellInputBuyCoin"]', 'TESTCOIN01'); 39 | await txSubmit(page, 'convertSell'); 40 | }, DEFAULT_TEST_TIMEOUT); 41 | 42 | test('fail sell not enough coins', async () => { 43 | await page.type('[data-test-id="convertSellInputSellCoin"]', 'MNT'); 44 | await page.type('[data-test-id="convertSellInputSellAmount"]', '99999999999999'); 45 | await page.type('[data-test-id="convertSellInputBuyCoin"]', 'TESTCOIN01'); 46 | await txSubmit(page, 'convertSell', {shouldFailPost: true}); 47 | }, DEFAULT_TEST_TIMEOUT); 48 | 49 | test('buy coins', async () => { 50 | await page.type('[data-test-id="convertBuyInputBuyCoin"]', 'TESTCOIN01'); 51 | await page.type('[data-test-id="convertBuyInputBuyAmount"]', '1'); 52 | await page.type('[data-test-id="convertBuyInputSellCoin"]', 'MNT'); 53 | await txSubmit(page, 'convertBuy'); 54 | }, DEFAULT_TEST_TIMEOUT); 55 | 56 | test('fail buy too much coins', async () => { 57 | await page.type('[data-test-id="convertBuyInputBuyCoin"]', 'MNT'); 58 | await page.type('[data-test-id="convertBuyInputBuyAmount"]', '9999999999999999999999999'); 59 | await page.type('[data-test-id="convertBuyInputSellCoin"]', 'TESTCOIN01'); 60 | await txSubmit(page, 'convertBuy', {shouldFailPost: 'estimation', shouldFailModal: true}); 61 | }, DEFAULT_TEST_TIMEOUT); 62 | 63 | test('sell all coins', async () => { 64 | await page.type('[data-test-id="convertSellAllInputSellCoin"]', 'TESTCOIN01'); 65 | await page.type('[data-test-id="convertSellAllInputBuyCoin"]', 'MNT'); 66 | await txSubmit(page, 'convertSellAll'); 67 | }, DEFAULT_TEST_TIMEOUT); 68 | 69 | test('fail sell all to not existent coin', async () => { 70 | await page.type('[data-test-id="convertSellAllInputSellCoin"]', 'MNT'); 71 | await page.type('[data-test-id="convertSellAllInputBuyCoin"]', 'NOTEXIST01'); 72 | await txSubmit(page, 'convertSellAll', {shouldFailPost: 'estimation', shouldFailModal: true}); 73 | }, DEFAULT_TEST_TIMEOUT); 74 | }); 75 | -------------------------------------------------------------------------------- /test/pages/wallet.test.js: -------------------------------------------------------------------------------- 1 | import {ROUTES, USER_MNEMONIC} from '~/test/variables.js'; 2 | import {login, logout, txSubmit, wait} from '~/test/utils.js'; 3 | 4 | /** @type Browser */ 5 | let browser; 6 | /** @type Page */ 7 | let page; 8 | 9 | 10 | beforeAll(async function beforeAllFn() { 11 | browser = global.browser; 12 | page = await browser.newPage(); 13 | await login(page); 14 | }, 30000); 15 | 16 | 17 | afterAll(async function afterAllFn() { 18 | await logout(page); 19 | if (!process.env.DEBUG) { 20 | await page.close(); 21 | } 22 | }); 23 | 24 | 25 | describe('wallet page', () => { 26 | let address; 27 | beforeAll(async () => { 28 | await page.goto(ROUTES.private.wallet); 29 | await page.waitForSelector('[data-test-id="walletAddressLink"]'); 30 | address = await page.$eval('[data-test-id="walletAddressLink"]', (el) => el.textContent); 31 | }); 32 | 33 | test('has address, has balance', async () => { 34 | // wait request fetch balance 35 | await wait(1500); 36 | let balance = await page.$eval('[data-test-id="walletBalanceValue"]', (el) => el.textContent); 37 | balance = balance.replace(/[^0-9.]/g, ''); 38 | console.log({address, balance}); 39 | expect(address.substring(0, 2)).toBe('Mx'); 40 | expect(address).toHaveLength(42); 41 | expect(parseFloat(balance)).toBeGreaterThan(0); 42 | }); 43 | 44 | test('has transactions', async () => { 45 | const txHref = await page.$eval('[data-test-id="walletTxHash"]', (el) => el.getAttribute('href')); 46 | const txHashIndex = txHref.indexOf('/Mt') + 1; 47 | const txHash = txHref.substr(txHashIndex); 48 | expect(txHash).toHaveLength(66); 49 | }); 50 | 51 | test('send coins', async () => { 52 | await page.type('[data-test-id="walletSendInputAddress"]', address); 53 | await page.type('[data-test-id="walletSendInputCoin"]', 'MNT'); 54 | await page.type('[data-test-id="walletSendInputAmount"]', '10'); 55 | await txSubmit(page, 'walletSend'); 56 | }, 30000); 57 | 58 | test('fail send not enough coins', async () => { 59 | await page.type('[data-test-id="walletSendInputAddress"]', address); 60 | await page.type('[data-test-id="walletSendInputCoin"]', 'MNT'); 61 | await page.type('[data-test-id="walletSendInputAmount"]', '9999999999999999'); 62 | await txSubmit(page, 'walletSend', {shouldFailPost: true}); 63 | }, 30000); 64 | 65 | test('fail send, seed phrase in payload', async () => { 66 | await page.type('[data-test-id="walletSendInputAddress"]', address); 67 | await page.type('[data-test-id="walletSendInputCoin"]', 'MNT'); 68 | await page.type('[data-test-id="walletSendInputAmount"]', '0'); 69 | await page.click('[data-test-id="walletTxFormShowAdvanced"]'); 70 | await page.type('[data-test-id="walletTxFormInputPayload"]', USER_MNEMONIC); 71 | await page.$eval('[data-test-id="walletTxFormInputPayload"]', (e) => e.blur()); 72 | await page.waitForSelector('[data-test-id="payloadIsMnemonicErrorMessage"]'); 73 | }, 30000); 74 | }); 75 | -------------------------------------------------------------------------------- /test/variables.js: -------------------------------------------------------------------------------- 1 | export const HOST_NAME = 'localhost'; 2 | export const PORT = 4000; 3 | 4 | export const DEFAULT_TEST_TIMEOUT = 60000; 5 | export const DEFAULT_SELECTOR_TIMEOUT = 15000; 6 | 7 | export const APP_URL_BASE = `http://${HOST_NAME}:${PORT}`; 8 | export const ROUTES = { 9 | public: { 10 | index: APP_URL_BASE, 11 | noMatch: `${APP_URL_BASE}/asdf`, 12 | }, 13 | private: { 14 | wallet: `${APP_URL_BASE}/wallet`, 15 | convert: `${APP_URL_BASE}/swap`, 16 | }, 17 | }; 18 | 19 | export const USER_MNEMONIC = 'exercise fantasy smooth enough arrive steak demise donkey true employ jealous decide blossom bind someone'; 20 | // address: Mx7633980c000139dd3bd24a3f54e06474fa941e16 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | resolve: { 3 | // for WebStorm 4 | alias: { 5 | '@': path.resolve(__dirname), 6 | '~': path.resolve(__dirname), 7 | }, 8 | }, 9 | }; 10 | --------------------------------------------------------------------------------