Merge pull request #193 from loki-project/development
v.1.4.6 Bug fixes and service node min/max
This commit is contained in:
commit
77fa635f0a
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
- development
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
@ -16,28 +17,36 @@ jobs:
|
|||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
# Read node version from `.nvmrc` file
|
||||
- name: Read nvm rc
|
||||
id: nvmrc
|
||||
uses: browniebroke/read-nvmrc-action@v1
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "11.9.0"
|
||||
node-version: ${{ steps.nvmrc.outputs.node_version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Download lokid binaries
|
||||
run: node ./build/download-binaries.js
|
||||
run: ./download-asset.sh
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
OS: ${{ runner.os }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
- name: Extract zip binaries
|
||||
if: runner.os != 'Linux'
|
||||
run: unzip latest.zip
|
||||
run: unzip latest
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
- name: Extract xz binaries
|
||||
if: runner.os == 'Linux'
|
||||
run: tar -xf latest.xz
|
||||
run: tar -xf latest
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
|
@ -51,15 +60,15 @@ jobs:
|
|||
run: ls ./bin
|
||||
shell: bash
|
||||
|
||||
- name: Publish window and linux binaries
|
||||
- name: Build window and linux binaries
|
||||
if: runner.os != 'macOS'
|
||||
run: npm run release
|
||||
run: npm run build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish mac binaries
|
||||
- name: Build mac binaries
|
||||
if: runner.os == 'macOS'
|
||||
run: npm run release
|
||||
run: npm run build
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
name: Loki Electron Wallet Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest, ubuntu-latest]
|
||||
steps:
|
||||
- name: Checkout git repo
|
||||
uses: actions/checkout@v1
|
||||
|
||||
# Read node version from `.nvmrc` file
|
||||
- name: Read nvm rc
|
||||
id: nvmrc
|
||||
uses: browniebroke/read-nvmrc-action@v1
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ steps.nvmrc.outputs.node_version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Download lokid binaries
|
||||
run: ./download-asset.sh
|
||||
env:
|
||||
OS: ${{ runner.os }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
- name: Extract zip binaries
|
||||
if: runner.os != 'Linux'
|
||||
run: unzip latest.zip
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
- name: Extract xz binaries
|
||||
if: runner.os == 'Linux'
|
||||
run: tar -xf latest.xz
|
||||
shell: bash
|
||||
working-directory: ./downloads
|
||||
|
||||
- name: Move lokid binaries
|
||||
run: |
|
||||
find ./downloads -type f -name "lokid*" -exec cp '{}' ./bin \;
|
||||
find ./downloads -type f -name "loki-wallet-rpc*" -exec cp '{}' ./bin \;
|
||||
shell: bash
|
||||
|
||||
- name: Verify binaries
|
||||
run: ls ./bin
|
||||
shell: bash
|
||||
|
||||
- name: Publish window and linux binaries
|
||||
if: runner.os != 'macOS'
|
||||
run: npm run release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Publish mac binaries
|
||||
if: runner.os == 'macOS'
|
||||
run: npm run release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
|
||||
SIGNING_APPLE_ID: ${{ secrets.SIGNING_APPLE_ID }}
|
||||
SIGNING_APP_PASSWORD: ${{ secrets.SIGNING_APP_PASSWORD }}
|
||||
SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }}
|
|
@ -28,6 +28,7 @@ bin/*
|
|||
|
||||
.env
|
||||
|
||||
/downloads
|
||||
downloads/*
|
||||
!downloads/*.sh
|
||||
|
||||
dev-app-update.yml
|
||||
|
|
|
@ -1,5 +1 @@
|
|||
// Must be included inside .stylintrc, otherwise will conflict and cause weird
|
||||
// linting errors
|
||||
module.exports = {
|
||||
printWidth: 120
|
||||
};
|
||||
// intentionally blank
|
||||
|
|
|
@ -32,5 +32,4 @@
|
|||
"valid": true,
|
||||
"zeroUnits": "never",
|
||||
"zIndexNormalize": false,
|
||||
"printWidth": 120
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ Please submit any changes as pull requests to the development branch, all change
|
|||
#### Commands
|
||||
|
||||
```
|
||||
nvm use 11.9.0
|
||||
npm install -g quasar-cli
|
||||
nvm use 14.11.0
|
||||
npm install -g @quasar/cli
|
||||
git clone https://github.com/loki-project/loki-electron-gui-wallet
|
||||
cd loki-electron-wallet
|
||||
cd loki-electron-gui-wallet
|
||||
cp path_to_lokid_binaries/lokid bin/
|
||||
cp path_to_lokid_binaries/loki-wallet-rpc bin/
|
||||
npm install
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
const axios = require("axios").default;
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
async function download() {
|
||||
const { platform, env } = process;
|
||||
const repoUrl = "https://api.github.com/repos/loki-project/loki-core/releases/latest";
|
||||
try {
|
||||
const pwd = process.cwd();
|
||||
const downloadDir = path.join(pwd, "downloads");
|
||||
await fs.ensureDir(downloadDir);
|
||||
|
||||
const headers = {
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "Loki-Electron-Wallet"
|
||||
};
|
||||
if (env.GH_TOKEN) {
|
||||
headers.Authorisation = `Bearer ${env.GH_TOKEN}`;
|
||||
}
|
||||
|
||||
const { data } = await axios.get(repoUrl, { headers });
|
||||
const { name } = data;
|
||||
console.log("Latest release: " + name);
|
||||
|
||||
const url = (data.assets || [])
|
||||
.map(asset => asset["browser_download_url"])
|
||||
.find(url => {
|
||||
if (platform === "darwin") {
|
||||
return url.includes("osx") || url.includes("mac");
|
||||
} else if (platform === "win32") {
|
||||
return url.includes("win") || url.includes("windows");
|
||||
}
|
||||
return url.includes("linux");
|
||||
});
|
||||
|
||||
if (!url) {
|
||||
throw new Error("Download url not found for " + process);
|
||||
}
|
||||
console.log("Downloading binary at url: " + url);
|
||||
|
||||
const extension = path.extname(url);
|
||||
const filePath = path.join(downloadDir, "latest" + extension);
|
||||
const { data: artifact } = await axios.get(url, {
|
||||
responseType: "stream"
|
||||
});
|
||||
artifact.pipe(fs.createWriteStream(filePath));
|
||||
console.log("Downloaded binary to: " + filePath);
|
||||
} catch (err) {
|
||||
console.error("Failed to download file: " + err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
download();
|
|
@ -0,0 +1,40 @@
|
|||
#!/bin/bash
|
||||
# Source from: https://github.com/houqp/download-release-assets-action
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$OS" ]; then
|
||||
echo "OS must be set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$RENAME" ]; then
|
||||
RENAME="latest"
|
||||
fi
|
||||
|
||||
REPO="loki-project/loki-core"
|
||||
RELEASE="latest"
|
||||
|
||||
if [ "$OS" == "Linux" ]; then
|
||||
FILE_NAME_REGEX="linux"
|
||||
elif [ "$OS" == "Windows" ]; then
|
||||
FILE_NAME_REGEX="win"
|
||||
elif [ "$OS" == "macOS" ]; then
|
||||
FILE_NAME_REGEX="osx"
|
||||
else
|
||||
echo "OS must be Linux, Windows or macOS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
ASSET_URL=$(curl -sL --fail \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
"https://api.github.com/repos/${REPO}/releases/${RELEASE}" \
|
||||
| jq -r ".assets | .[] | select(.name | test(\"${FILE_NAME_REGEX}\")) | .url")
|
||||
|
||||
curl -sL --fail \
|
||||
-H "Accept: application/octet-stream" \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
-o "${RENAME}" \
|
||||
"$ASSET_URL"
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loki-electron-wallet",
|
||||
"version": "1.4.5",
|
||||
"version": "1.4.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2671,8 +2671,7 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"base": {
|
||||
"version": "0.11.2",
|
||||
|
@ -2784,9 +2783,9 @@
|
|||
}
|
||||
},
|
||||
"bl": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
|
||||
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
|
||||
"integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer": "^5.5.0",
|
||||
|
@ -2890,7 +2889,8 @@
|
|||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/boolean/-/boolean-3.0.1.tgz",
|
||||
"integrity": "sha512-HRZPIjPcbwAVQvOTxR4YE3o8Xs98NqbbL1iEZDCz7CL8ql0Lt5iOyJFxfnAB0oFs8Oh02F/lLlg30Mexv46LjA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"boxen": {
|
||||
"version": "4.2.0",
|
||||
|
@ -3002,7 +3002,6 @@
|
|||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -3247,6 +3246,17 @@
|
|||
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
|
||||
"dev": true
|
||||
},
|
||||
"bunyan": {
|
||||
"version": "1.8.14",
|
||||
"resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz",
|
||||
"integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==",
|
||||
"requires": {
|
||||
"dtrace-provider": "~0.8",
|
||||
"moment": "^2.19.3",
|
||||
"mv": "~2",
|
||||
"safe-json-stringify": "~1"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
|
||||
|
@ -4033,8 +4043,7 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "1.6.2",
|
||||
|
@ -5229,6 +5238,15 @@
|
|||
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
|
||||
"dev": true
|
||||
},
|
||||
"dtrace-provider": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
|
||||
"integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nan": "^2.14.0"
|
||||
}
|
||||
},
|
||||
"duplexer": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
|
||||
|
@ -8575,7 +8593,6 @@
|
|||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -8584,8 +8601,7 @@
|
|||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
|
@ -10074,7 +10090,6 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -10180,8 +10195,7 @@
|
|||
"moment": {
|
||||
"version": "2.27.0",
|
||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||
},
|
||||
"move-concurrently": {
|
||||
"version": "1.0.1",
|
||||
|
@ -10235,11 +10249,45 @@
|
|||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"mv": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
|
||||
"integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"mkdirp": "~0.5.1",
|
||||
"ncp": "~2.0.0",
|
||||
"rimraf": "~2.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "2 || 3",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.4.5",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
|
||||
"integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"glob": "^6.0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.14.1",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw=="
|
||||
},
|
||||
"nanomatch": {
|
||||
"version": "1.2.13",
|
||||
|
@ -10266,6 +10314,12 @@
|
|||
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
|
||||
"dev": true
|
||||
},
|
||||
"ncp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||
"integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
|
||||
"optional": true
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
|
||||
|
@ -10906,7 +10960,6 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -11253,8 +11306,7 @@
|
|||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"path-is-inside": {
|
||||
"version": "1.0.2",
|
||||
|
@ -12985,6 +13037,12 @@
|
|||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safe-json-stringify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
|
||||
"integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
|
||||
"optional": true
|
||||
},
|
||||
"safe-regex": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
|
||||
|
@ -15456,6 +15514,7 @@
|
|||
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
|
||||
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"arr-flatten": "^1.1.0",
|
||||
"array-unique": "^0.3.2",
|
||||
|
@ -15474,6 +15533,7 @@
|
|||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -15506,6 +15566,7 @@
|
|||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
|
||||
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"extend-shallow": "^2.0.1",
|
||||
"is-number": "^3.0.0",
|
||||
|
@ -15518,6 +15579,7 @@
|
|||
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
|
||||
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-extendable": "^0.1.0"
|
||||
}
|
||||
|
@ -15573,6 +15635,7 @@
|
|||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
|
||||
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"kind-of": "^3.0.2"
|
||||
},
|
||||
|
@ -15582,6 +15645,7 @@
|
|||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
|
||||
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-buffer": "^1.1.5"
|
||||
}
|
||||
|
@ -15593,6 +15657,7 @@
|
|||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"arr-diff": "^4.0.0",
|
||||
"array-unique": "^0.3.2",
|
||||
|
@ -15642,6 +15707,7 @@
|
|||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
|
||||
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"is-number": "^3.0.0",
|
||||
"repeat-string": "^1.6.1"
|
||||
|
@ -16608,8 +16674,7 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"write": {
|
||||
"version": "1.0.3",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "loki-electron-wallet",
|
||||
"version": "1.4.5",
|
||||
"version": "1.4.6",
|
||||
"description": "Modern GUI interface for Loki Currency",
|
||||
"productName": "Loki Electron Wallet",
|
||||
"repository": {
|
||||
|
@ -9,13 +9,13 @@
|
|||
},
|
||||
"cordovaId": "com.lokinetwork.wallet",
|
||||
"author": {
|
||||
"name": "Loki",
|
||||
"name": "Loki Project",
|
||||
"email": "team@loki.network"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "quasar dev -m electron",
|
||||
"build": "quasar build -m electron",
|
||||
"build": "quasar build -m electron --publish=never",
|
||||
"release": "quasar build -m electron --publish=always",
|
||||
"lint": "eslint --fix .",
|
||||
"format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|md|vue)\"",
|
||||
|
@ -23,6 +23,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.1",
|
||||
"bunyan": "^1.8.14",
|
||||
"electron-is-dev": "^1.0.1",
|
||||
"electron-updater": "^4.2.0",
|
||||
"electron-window-state": "^5.0.3",
|
||||
|
|
|
@ -158,7 +158,8 @@ module.exports = function() {
|
|||
|
||||
appId: "com.loki-project.electron-wallet",
|
||||
productName: "Loki Electron Wallet",
|
||||
copyright: "Copyright © 2018-2019 Loki Project, 2018 Ryo Currency Project",
|
||||
copyright:
|
||||
"Copyright © 2018-2020 Loki Project, 2018 Ryo Currency Project",
|
||||
afterSign: "build/notarize.js",
|
||||
artifactName: "loki-electron-wallet-${version}-${os}.${ext}",
|
||||
publish: "github",
|
||||
|
@ -192,7 +193,13 @@ module.exports = function() {
|
|||
allowToChangeInstallationDirectory: true
|
||||
},
|
||||
|
||||
files: ["!build/*.js", "!.env", "!dev-app-update.yml"],
|
||||
files: [
|
||||
"!build/*.js",
|
||||
"!.env",
|
||||
"!dev-app-update.yml",
|
||||
"!downloads/**",
|
||||
"!dist/**"
|
||||
],
|
||||
|
||||
extraResources: ["bin"]
|
||||
}
|
||||
|
|
|
@ -26,7 +26,11 @@ let installUpdate = false;
|
|||
|
||||
const title = `${productName} v${version}`;
|
||||
|
||||
const selectionMenu = Menu.buildFromTemplate([{ role: "copy" }, { type: "separator" }, { role: "selectall" }]);
|
||||
const selectionMenu = Menu.buildFromTemplate([
|
||||
{ role: "copy" },
|
||||
{ type: "separator" },
|
||||
{ role: "selectall" }
|
||||
]);
|
||||
|
||||
const inputMenu = Menu.buildFromTemplate([
|
||||
{ role: "cut" },
|
||||
|
@ -54,7 +58,13 @@ function createWindow() {
|
|||
minWidth: 640,
|
||||
minHeight: 480,
|
||||
icon: require("path").join(__statics, "icon_512x512.png"),
|
||||
title
|
||||
title,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
nodeIntegrationInWorker: true,
|
||||
// anything we want preloaded, e.g. global vars
|
||||
preload: path.resolve(__dirname, "electron-preload.js")
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.on("close", e => {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
const path = require("upath");
|
||||
|
||||
require(path.resolve(__dirname, "logging.js"));
|
|
@ -0,0 +1,78 @@
|
|||
// This allows for logging (to a file) from the frontend by creating global logging functions
|
||||
// That send ipc calls (from renderer) to the electron main process
|
||||
// create global logging functions for the frontend. It sends messages to the main
|
||||
// process which then log to file
|
||||
|
||||
const electron = require("electron");
|
||||
|
||||
const _ = require("lodash");
|
||||
|
||||
const ipc = electron.ipcRenderer;
|
||||
|
||||
function log(...args) {
|
||||
logAtLevel("info", "INFO ", ...args);
|
||||
}
|
||||
|
||||
if (window.console) {
|
||||
console._log = console.log;
|
||||
console.log = log;
|
||||
console._trace = console.trace;
|
||||
console._debug = console.debug;
|
||||
console._info = console.info;
|
||||
console._warn = console.warn;
|
||||
console._error = console.error;
|
||||
console._fatal = console.error;
|
||||
}
|
||||
|
||||
// To avoid [Object object] in our log since console.log handles non-strings
|
||||
// smoothly
|
||||
function cleanArgsForIPC(args) {
|
||||
const str = args.map(item => {
|
||||
if (typeof item !== "string") {
|
||||
try {
|
||||
return JSON.stringify(item);
|
||||
} catch (error) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
return str.join(" ");
|
||||
}
|
||||
|
||||
// Backwards-compatible logging, simple strings and no level (defaulted to INFO)
|
||||
function now() {
|
||||
const date = new Date();
|
||||
return date.toJSON();
|
||||
}
|
||||
|
||||
// The Bunyan API: https://github.com/trentm/node-bunyan#log-method-api
|
||||
function logAtLevel(level, prefix, ...args) {
|
||||
const fn = `_${level}`;
|
||||
console[fn](prefix, now(), ...args);
|
||||
|
||||
const logText = cleanArgsForIPC(args);
|
||||
ipc.send(`log-${level}`, logText);
|
||||
}
|
||||
|
||||
window.log = {
|
||||
fatal: _.partial(logAtLevel, "fatal", "FATAL"),
|
||||
error: _.partial(logAtLevel, "error", "ERROR"),
|
||||
warn: _.partial(logAtLevel, "warn", "WARN "),
|
||||
info: _.partial(logAtLevel, "info", "INFO "),
|
||||
debug: _.partial(logAtLevel, "debug", "DEBUG"),
|
||||
trace: _.partial(logAtLevel, "trace", "TRACE")
|
||||
};
|
||||
|
||||
window.onerror = (message, script, line, col, error) => {
|
||||
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error);
|
||||
window.log.error(`Top-level unhandled error: ${errorInfo}`);
|
||||
};
|
||||
|
||||
window.addEventListener("unhandledrejection", rejectionEvent => {
|
||||
const error = rejectionEvent.reason;
|
||||
const errorInfo = error && error.stack ? error.stack : error;
|
||||
window.log.error("Top-level unhandled promise rejection:", errorInfo);
|
||||
});
|
|
@ -5,13 +5,19 @@ import { dialog } from "electron";
|
|||
import semver from "semver";
|
||||
import axios from "axios";
|
||||
import { version } from "../../../package.json";
|
||||
const bunyan = require("bunyan");
|
||||
|
||||
const WebSocket = require("ws");
|
||||
const electron = require("electron");
|
||||
const os = require("os");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("upath");
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
|
||||
const { ipcMain: ipc } = electron;
|
||||
|
||||
const LOG_LEVELS = ["fatal", "error", "warn", "info", "debug", "trace"];
|
||||
|
||||
export class Backend {
|
||||
constructor(mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
|
@ -24,6 +30,7 @@ export class Backend {
|
|||
this.config_file = null;
|
||||
this.config_data = {};
|
||||
this.scee = new SCEE();
|
||||
this.log = null;
|
||||
}
|
||||
|
||||
init(config) {
|
||||
|
@ -142,7 +149,10 @@ export class Backend {
|
|||
data
|
||||
};
|
||||
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token);
|
||||
let encrypted_data = this.scee.encryptString(
|
||||
JSON.stringify(message),
|
||||
this.token
|
||||
);
|
||||
|
||||
this.wss.clients.forEach(function each(client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
|
@ -185,14 +195,22 @@ export class Backend {
|
|||
case "quick_save_config":
|
||||
// save only partial config settings
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key]);
|
||||
});
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
});
|
||||
this.config_data[key] = Object.assign(
|
||||
this.config_data[key],
|
||||
params[key]
|
||||
);
|
||||
});
|
||||
fs.writeFile(
|
||||
this.config_file,
|
||||
JSON.stringify(this.config_data, null, 4),
|
||||
"utf8",
|
||||
() => {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
});
|
||||
}
|
||||
);
|
||||
break;
|
||||
case "save_config_init":
|
||||
case "save_config": {
|
||||
|
@ -208,12 +226,18 @@ export class Backend {
|
|||
}
|
||||
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key]);
|
||||
this.config_data[key] = Object.assign(
|
||||
this.config_data[key],
|
||||
params[key]
|
||||
);
|
||||
});
|
||||
|
||||
const validated = Object.keys(this.defaults)
|
||||
.filter(k => k in this.config_data)
|
||||
.map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])])
|
||||
.map(k => [
|
||||
k,
|
||||
this.validate_values(this.config_data[k], this.defaults[k])
|
||||
])
|
||||
.reduce((map, obj) => {
|
||||
map[obj[0]] = obj[1];
|
||||
return map;
|
||||
|
@ -225,19 +249,24 @@ export class Backend {
|
|||
...validated
|
||||
};
|
||||
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
if (data.method == "save_config_init") {
|
||||
this.startup();
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
});
|
||||
if (config_changed) {
|
||||
this.send("settings_changed_reboot");
|
||||
fs.writeFile(
|
||||
this.config_file,
|
||||
JSON.stringify(this.config_data, null, 4),
|
||||
"utf8",
|
||||
() => {
|
||||
if (data.method == "save_config_init") {
|
||||
this.startup();
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
});
|
||||
if (config_changed) {
|
||||
this.send("settings_changed_reboot");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "init":
|
||||
|
@ -255,7 +284,10 @@ export class Backend {
|
|||
}
|
||||
|
||||
if (path) {
|
||||
const baseUrl = net_type === "testnet" ? "https://lokitestnet.com" : "https://lokiblocks.com";
|
||||
const baseUrl =
|
||||
net_type === "testnet"
|
||||
? "https://lokitestnet.com"
|
||||
: "https://lokiblocks.com";
|
||||
const url = `${baseUrl}/${path}/`;
|
||||
require("electron").shell.openExternal(url + params.id);
|
||||
}
|
||||
|
@ -279,12 +311,18 @@ export class Backend {
|
|||
if (err) {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
i18n: ["notification.errors.errorSavingItem", { item: params.type }],
|
||||
i18n: [
|
||||
"notification.errors.errorSavingItem",
|
||||
{ item: params.type }
|
||||
],
|
||||
timeout: 2000
|
||||
});
|
||||
} else {
|
||||
this.send("show_notification", {
|
||||
i18n: ["notification.positive.itemSaved", { item: params.type, filename }],
|
||||
i18n: [
|
||||
"notification.positive.itemSaved",
|
||||
{ item: params.type, filename }
|
||||
],
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
|
@ -317,6 +355,36 @@ export class Backend {
|
|||
}
|
||||
}
|
||||
|
||||
initLogger(logPath) {
|
||||
let log = bunyan.createLogger({
|
||||
name: "log",
|
||||
streams: [
|
||||
{
|
||||
type: "rotating-file",
|
||||
path: path.join(logPath, "electron.log"),
|
||||
period: "1d", // daily rotation
|
||||
count: 4 // keep 4 days of logs
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
LOG_LEVELS.forEach(level => {
|
||||
ipc.on(`log-${level}`, (first, ...rest) => {
|
||||
log[level](...rest);
|
||||
});
|
||||
});
|
||||
|
||||
this.log = log;
|
||||
|
||||
process.on("uncaughtException", error => {
|
||||
log.error("Unhandled Error", error);
|
||||
});
|
||||
|
||||
process.on("unhandledRejection", error => {
|
||||
log.error("Unhandled Promise Rejection", error);
|
||||
});
|
||||
}
|
||||
|
||||
startup() {
|
||||
this.send("set_app_data", {
|
||||
remotes: this.remotes,
|
||||
|
@ -344,14 +412,20 @@ export class Backend {
|
|||
if (!this.config_data.hasOwnProperty(key)) {
|
||||
this.config_data[key] = {};
|
||||
}
|
||||
this.config_data[key] = Object.assign(this.config_data[key], disk_config_data[key]);
|
||||
this.config_data[key] = Object.assign(
|
||||
this.config_data[key],
|
||||
disk_config_data[key]
|
||||
);
|
||||
});
|
||||
|
||||
// here we may want to check if config data is valid, if not also send code -1
|
||||
// i.e. check ports are integers and > 1024, check that data dir path exists, etc
|
||||
const validated = Object.keys(this.defaults)
|
||||
.filter(k => k in this.config_data)
|
||||
.map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])])
|
||||
.map(k => [
|
||||
k,
|
||||
this.validate_values(this.config_data[k], this.defaults[k])
|
||||
])
|
||||
.reduce((map, obj) => {
|
||||
map[obj[0]] = obj[1];
|
||||
return map;
|
||||
|
@ -364,7 +438,12 @@ export class Backend {
|
|||
};
|
||||
|
||||
// save config file back to file, so updated options are stored on disk
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {});
|
||||
fs.writeFile(
|
||||
this.config_file,
|
||||
JSON.stringify(this.config_data, null, 4),
|
||||
"utf8",
|
||||
() => {}
|
||||
);
|
||||
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
|
@ -427,6 +506,8 @@ export class Backend {
|
|||
fs.mkdirpSync(log_dir);
|
||||
}
|
||||
|
||||
this.initLogger(log_dir);
|
||||
|
||||
this.daemon = new Daemon(this);
|
||||
this.walletd = new WalletRPC(this);
|
||||
|
||||
|
@ -605,7 +686,11 @@ export class Backend {
|
|||
|
||||
// Replace any invalid value with default values
|
||||
validate_values(values, defaults) {
|
||||
const isDictionary = v => typeof v === "object" && v !== null && !(v instanceof Array) && !(v instanceof Date);
|
||||
const isDictionary = v =>
|
||||
typeof v === "object" &&
|
||||
v !== null &&
|
||||
!(v instanceof Array) &&
|
||||
!(v instanceof Date);
|
||||
const modified = { ...values };
|
||||
|
||||
// Make sure we have valid defaults
|
||||
|
@ -616,7 +701,10 @@ export class Backend {
|
|||
if (!(key in defaults)) continue;
|
||||
|
||||
const defaultValue = defaults[key];
|
||||
const invalidDefault = defaultValue === null || defaultValue === undefined || Number.isNaN(defaultValue);
|
||||
const invalidDefault =
|
||||
defaultValue === null ||
|
||||
defaultValue === undefined ||
|
||||
Number.isNaN(defaultValue);
|
||||
if (invalidDefault) continue;
|
||||
|
||||
const value = modified[key];
|
||||
|
@ -626,7 +714,12 @@ export class Backend {
|
|||
modified[key] = this.validate_values(value, defaultValue);
|
||||
} else {
|
||||
// Check if we need to replace the value
|
||||
const isValidValue = !(value === undefined || value === null || value === "" || Number.isNaN(value));
|
||||
const isValidValue = !(
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
value === "" ||
|
||||
Number.isNaN(value)
|
||||
);
|
||||
if (isValidValue) continue;
|
||||
|
||||
// Otherwise set the default value
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<q-dialog v-model="show" persistent>
|
||||
<q-card class="confirm-tx-card" dark>
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{ $t("dialog.confirmTransaction.title") }}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="confirm-list">
|
||||
<div>
|
||||
<span class="label">{{ $t("dialog.confirmTransaction.sendTo") }}: </span>
|
||||
<br />
|
||||
<span class="address-value">{{ sendTo }}</span>
|
||||
</div>
|
||||
<br />
|
||||
<span class="label">{{ $t("strings.transactions.amount") }}: </span>
|
||||
{{ amount }} Loki
|
||||
<br />
|
||||
<span class="label">{{ $t("strings.transactions.fee") }}: </span> {{ fee }} Loki
|
||||
<br />
|
||||
<span class="label">{{ $t("dialog.confirmTransaction.priority") }}: </span>
|
||||
{{ isBlinkToTranslatedLabel(isBlink) }}
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn flat :label="$t('dialog.buttons.cancel')" color="negative" @click="onCancelTransaction" />
|
||||
<q-btn class="confirm-send-btn" flat :label="$t('buttons.send')" @click="onConfirmTransaction" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "ConfirmTransactionDialog",
|
||||
props: {
|
||||
sendTo: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
amount: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
fee: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
isBlink: {
|
||||
type: Boolean,
|
||||
required: true
|
||||
},
|
||||
onConfirmTransaction: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
onCancelTransaction: {
|
||||
type: Function,
|
||||
required: true
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isBlinkToTranslatedLabel(isBlink) {
|
||||
const blinkOrSlow = isBlink ? "strings.priorityOptions.blink" : "strings.priorityOptions.slow";
|
||||
return this.$t(blinkOrSlow);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<div class="language-select column items-center justify-center">
|
||||
<h6 class="q-my-md" style="font-weight: 300">{{ $t("strings.selectLanguage") }}:</h6>
|
||||
<h6 class="q-my-md" style="font-weight: 300">
|
||||
{{ $t("strings.selectLanguage") }}:
|
||||
</h6>
|
||||
<div class="row justify-center">
|
||||
<q-btn
|
||||
v-for="option in options"
|
||||
|
|
|
@ -6,13 +6,18 @@
|
|||
<div class="header">{{ $t("titles.availableForContribution") }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<q-btn class="float-right vertical-top" icon="refresh" flat @click="updateServiceNodeList" />
|
||||
<q-btn
|
||||
class="float-right vertical-top"
|
||||
icon="refresh"
|
||||
flat
|
||||
@click="updateServiceNodeList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="awaiting_service_nodes.length > 0">
|
||||
<div v-if="awaitingServiceNodes.length > 0">
|
||||
<ServiceNodeList
|
||||
v-if="awaiting_service_nodes"
|
||||
:service-nodes="awaiting_service_nodes"
|
||||
v-if="awaitingServiceNodes"
|
||||
:service-nodes="awaitingServiceNodes"
|
||||
button-i18n="buttons.stake"
|
||||
:details="details"
|
||||
:action="contributeToNode"
|
||||
|
@ -20,7 +25,11 @@
|
|||
</div>
|
||||
<div v-else>{{ $t("strings.noServiceNodesCurrentlyAvailable") }}</div>
|
||||
</div>
|
||||
<ServiceNodeDetails ref="serviceNodeDetailsContribute" :action="contributeToNode" action-i18n="buttons.stake" />
|
||||
<ServiceNodeDetails
|
||||
ref="serviceNodeDetailsContribute"
|
||||
:action="contributeToNode"
|
||||
action-i18n="buttons.stake"
|
||||
/>
|
||||
<q-inner-loading :showing="fetching" :dark="theme == 'dark'">
|
||||
<q-spinner color="primary" size="30" />
|
||||
</q-inner-loading>
|
||||
|
@ -37,28 +46,17 @@ export default {
|
|||
ServiceNodeList,
|
||||
ServiceNodeDetails
|
||||
},
|
||||
props: {
|
||||
awaitingServiceNodes: {
|
||||
type: Array,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
awaiting_service_nodes(state) {
|
||||
const nodes = state.gateway.daemon.service_nodes.nodes;
|
||||
const isAwaitingContribution = node => !node.active && !node.funded && node.requested_unlock_height === 0;
|
||||
const compareFee = (n1, n2) => (this.getFeeDecimal(n1) > this.getFeeDecimal(n2) ? 1 : -1);
|
||||
const awaitingContributionNodes = nodes.filter(isAwaitingContribution).map(n => {
|
||||
return {
|
||||
...n,
|
||||
awaitingContribution: true
|
||||
};
|
||||
});
|
||||
awaitingContributionNodes.sort(compareFee);
|
||||
return awaitingContributionNodes;
|
||||
},
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
fetching: state => state.gateway.daemon.service_nodes.fetching
|
||||
}),
|
||||
methods: {
|
||||
getFeeDecimal(node) {
|
||||
const operatorPortion = node.portions_for_operator;
|
||||
return (operatorPortion / 18446744073709551612) * 100;
|
||||
},
|
||||
scrollToTop() {
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
|
|
|
@ -15,7 +15,12 @@
|
|||
:label="$t(actionI18n)"
|
||||
@click="action(node, $event)"
|
||||
/>
|
||||
<q-btn v-if="can_open" color="primary" :label="$t('buttons.viewOnExplorer')" @click="openExplorer" />
|
||||
<q-btn
|
||||
v-if="can_open"
|
||||
color="primary"
|
||||
:label="$t('buttons.viewOnExplorer')"
|
||||
@click="openExplorer"
|
||||
/>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<q-page-container>
|
||||
|
@ -29,27 +34,37 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.stakingRequirement") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.stakingRequirement")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span><FormatLoki :amount="node.staking_requirement" raw-value/></span>
|
||||
<span
|
||||
><FormatLoki :amount="node.staking_requirement" raw-value
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.totalContributed") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.totalContributed")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span><FormatLoki :amount="node.total_contributed" raw-value/></span>
|
||||
<span
|
||||
><FormatLoki :amount="node.total_contributed" raw-value
|
||||
/></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.registrationHeight") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.registrationHeight")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span>{{ node.registration_height }}</span>
|
||||
|
@ -59,7 +74,9 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.operatorFee") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.operatorFee")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span>{{ operatorFee }}</span>
|
||||
|
@ -69,7 +86,9 @@
|
|||
<div v-if="node.requested_unlock_height > 0" class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.unlockHeight") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.unlockHeight")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span>{{ node.requested_unlock_height }}</span>
|
||||
|
@ -79,7 +98,9 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.lastUptimeProof") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.lastUptimeProof")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span>{{ formatDate(node.last_uptime_proof * 1000) }}</span>
|
||||
|
@ -89,7 +110,9 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text">
|
||||
<span>{{ $t("strings.serviceNodeDetails.lastRewardBlockHeight") }}</span>
|
||||
<span>{{
|
||||
$t("strings.serviceNodeDetails.lastRewardBlockHeight")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="value">
|
||||
<span>{{ node.last_reward_block_height }}</span>
|
||||
|
@ -98,7 +121,11 @@
|
|||
</div>
|
||||
</div>
|
||||
<q-list no-border :dark="theme == 'dark'" class="loki-list">
|
||||
<q-item-label class="contributors-title">{{ $t("strings.serviceNodeDetails.contributors") }}:</q-item-label>
|
||||
<q-item-label class="contributors-title"
|
||||
>{{
|
||||
$t("strings.serviceNodeDetails.contributors")
|
||||
}}:</q-item-label
|
||||
>
|
||||
<q-item
|
||||
v-for="contributor in contributors"
|
||||
:key="contributor.address"
|
||||
|
@ -107,20 +134,36 @@
|
|||
@click="openUserWalletInfo(contributor.address)"
|
||||
>
|
||||
<q-item-label>
|
||||
<q-item-label v-if="isMe(contributor)" class="name non-selectable">{{ $t("strings.me") }}</q-item-label>
|
||||
<q-item-label v-else class="name non-selectable">{{ contributor.name }}</q-item-label>
|
||||
<q-item-label class="address ellipsis non-selectable">{{ contributor.address }}</q-item-label>
|
||||
<q-item-label
|
||||
v-if="isMe(contributor)"
|
||||
class="name non-selectable"
|
||||
>{{ $t("strings.me") }}</q-item-label
|
||||
>
|
||||
<q-item-label v-else class="name non-selectable">{{
|
||||
contributor.name
|
||||
}}</q-item-label>
|
||||
<q-item-label class="address ellipsis non-selectable">{{
|
||||
contributor.address
|
||||
}}</q-item-label>
|
||||
<q-item-label class="non-selectable" caption>
|
||||
<span v-if="isOperator(contributor)">{{ $t("strings.operator") }} • </span>
|
||||
<span v-if="isOperator(contributor)"
|
||||
>{{ $t("strings.operator") }} •
|
||||
</span>
|
||||
{{ $t("strings.contribution") }}:
|
||||
<FormatLoki :amount="contributor.amount" raw-value />
|
||||
</q-item-label>
|
||||
</q-item-label>
|
||||
<ContextMenu :menu-items="menuItems" @copyAddress="copyAddress(contributor.address)" />
|
||||
<ContextMenu
|
||||
:menu-items="menuItems"
|
||||
@copyAddress="copyAddress(contributor.address)"
|
||||
/>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</div>
|
||||
<q-inner-loading :showing="unlock_status.sending" :dark="theme == 'dark'">
|
||||
<q-inner-loading
|
||||
:showing="unlock_status.sending"
|
||||
:dark="theme == 'dark'"
|
||||
>
|
||||
<q-spinner color="primary" size="30" />
|
||||
</q-inner-loading>
|
||||
</q-page-container>
|
||||
|
@ -151,7 +194,9 @@ export default {
|
|||
}
|
||||
},
|
||||
data() {
|
||||
const menuItems = [{ action: "copyAddress", i18n: "menuItems.copyAddress" }];
|
||||
const menuItems = [
|
||||
{ action: "copyAddress", i18n: "menuItems.copyAddress" }
|
||||
];
|
||||
|
||||
return {
|
||||
isVisible: false,
|
||||
|
@ -180,7 +225,9 @@ export default {
|
|||
|
||||
for (const contributor of this.node.contributors) {
|
||||
let values = { ...contributor };
|
||||
const address = address_book.find(a => a.address === contributor.address);
|
||||
const address = address_book.find(
|
||||
a => a.address === contributor.address
|
||||
);
|
||||
if (address) {
|
||||
const { name, description } = address;
|
||||
const separator = description === "" ? "" : " - ";
|
||||
|
|
|
@ -8,21 +8,38 @@
|
|||
>
|
||||
<q-item-section>
|
||||
<q-item-label class="ellipsis"
|
||||
>{{ $t("strings.serviceNodeDetails.snKey") }}: {{ node.service_node_pubkey }}</q-item-label
|
||||
>{{ $t("strings.serviceNodeDetails.snKey") }}:
|
||||
{{ node.service_node_pubkey }}</q-item-label
|
||||
>
|
||||
<q-item-label class="non-selectable">
|
||||
<span v-if="getRole(node)">{{ getRole(node) }} •</span>
|
||||
<span v-if="node.ourContributionAmount">
|
||||
• {{ $t("strings.contribution") }}: <FormatLoki :amount="node.ourContributionAmount"
|
||||
/></span>
|
||||
<span v-if="node.ourContributionAmount > 0">
|
||||
<span v-if="getRole(node)">{{ getRole(node) }} •</span>
|
||||
<span>
|
||||
{{ $t("strings.contribution") }}:
|
||||
<FormatLoki :amount="node.ourContributionAmount" />
|
||||
</span>
|
||||
</span>
|
||||
<!-- you only have a contribution amount of 0 if you are a "contributor"
|
||||
by way of the node having reserved a spot for you on the node -->
|
||||
<span
|
||||
v-if="
|
||||
node.ourContributionAmount === 0 && node.awaitingContribution
|
||||
"
|
||||
>
|
||||
{{ $t("strings.serviceNodeDetails.reserved") }} •
|
||||
</span>
|
||||
<span v-if="node.awaitingContribution">
|
||||
{{ $t("strings.serviceNodeDetails.minContribution") }}: {{ getMinContribution(node) }} LOKI •
|
||||
{{ $t("strings.serviceNodeDetails.maxContribution") }}: {{ openForContributionLoki(node) }} LOKI
|
||||
{{ $t("strings.serviceNodeDetails.minContribution") }}:
|
||||
{{ getMinContribution(node) }} LOKI •
|
||||
{{ $t("strings.serviceNodeDetails.maxContribution") }}:
|
||||
{{ openForContributionLoki(node) }} LOKI
|
||||
</span>
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section v-if="!getRole(node)" side>
|
||||
<span style="font-size: 16px; color: #cecece"> {{ getFee(node) }} </span>
|
||||
<q-item-section side>
|
||||
<span style="font-size: 16px; color: #cecece">{{
|
||||
getFee(node)
|
||||
}}</span>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn
|
||||
|
@ -56,8 +73,8 @@
|
|||
import { clipboard } from "electron";
|
||||
import ContextMenu from "components/menus/contextmenu";
|
||||
import FormatLoki from "components/format_loki";
|
||||
|
||||
const MAX_NUMBER_OF_CONTRIBUTORS = 4;
|
||||
import ServiceNodeMixin from "src/mixins/service_node_mixin";
|
||||
import { mapState } from "vuex";
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeList",
|
||||
|
@ -65,6 +82,7 @@ export default {
|
|||
ContextMenu,
|
||||
FormatLoki
|
||||
},
|
||||
mixins: [ServiceNodeMixin],
|
||||
props: {
|
||||
serviceNodes: {
|
||||
type: Array,
|
||||
|
@ -92,29 +110,30 @@ export default {
|
|||
menuItems
|
||||
};
|
||||
},
|
||||
computed: mapState({
|
||||
our_address: state => {
|
||||
const primary = state.gateway.wallet.address_list.primary[0];
|
||||
return (primary && primary.address) || null;
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
nodeWithMinContribution(node) {
|
||||
const nodeWithMinContribution = { ...node, minContribution: this.getMinContribution(node) };
|
||||
const nodeWithMinContribution = {
|
||||
...node,
|
||||
minContribution: this.getMinContribution(node)
|
||||
};
|
||||
return nodeWithMinContribution;
|
||||
},
|
||||
openForContribution(node) {
|
||||
const openContributionRemaining =
|
||||
node.staking_requirement > node.total_reserved ? node.staking_requirement - node.total_reserved : 0;
|
||||
return openContributionRemaining;
|
||||
},
|
||||
openForContributionLoki(node) {
|
||||
return (this.openForContribution(node) / 1e9).toFixed(4);
|
||||
},
|
||||
is_ready() {
|
||||
return this.$store.getters["gateway/isReady"];
|
||||
},
|
||||
getRole(node) {
|
||||
// don't show a role if the user is not an operator or contributor
|
||||
let role = "";
|
||||
const opAddress = node.operator_address;
|
||||
if (node.operator_address === this.our_address) {
|
||||
if (opAddress === this.our_address) {
|
||||
role = "strings.operator";
|
||||
} else if (node.ourContributionAmount && opAddress !== this.our_address) {
|
||||
// if we're not the operator and we have a contribution amount
|
||||
role = "strings.contributor";
|
||||
}
|
||||
return this.$t(role);
|
||||
|
@ -122,21 +141,12 @@ export default {
|
|||
getNumContributors(node) {
|
||||
return node.contributors.length;
|
||||
},
|
||||
getMinContribution(node) {
|
||||
// This is calculated in the same way it is calculated on the LokiBlocks site
|
||||
const openContributionRemaining = this.openForContribution(node);
|
||||
const minContributionAtomicUnits =
|
||||
!node.funded && node.contributors.length < MAX_NUMBER_OF_CONTRIBUTORS
|
||||
? openContributionRemaining / (MAX_NUMBER_OF_CONTRIBUTORS - node.contributors.length)
|
||||
: 0;
|
||||
const minContributionLoki = minContributionAtomicUnits / 1e9;
|
||||
// ceiling to 4 decimal places
|
||||
return minContributionLoki.toFixed(4);
|
||||
},
|
||||
getFee(node) {
|
||||
const operatorPortion = node.portions_for_operator;
|
||||
const percentageFee = (operatorPortion / 18446744073709551612) * 100;
|
||||
return `${percentageFee.toFixed(2)}% ${this.$t("strings.transactions.fee")}`;
|
||||
return `${percentageFee.toFixed(2)}% ${this.$t(
|
||||
"strings.transactions.fee"
|
||||
)}`;
|
||||
},
|
||||
copyKey(key) {
|
||||
clipboard.writeText(key);
|
||||
|
|
|
@ -3,11 +3,16 @@
|
|||
<div class="q-px-md q-pt-md">
|
||||
<p style="color: #cecece">
|
||||
{{ $t("strings.serviceNodeContributionDescription") }}
|
||||
<span style="cursor: pointer; text-decoration: underline;" @click="lokiWebsite"
|
||||
<span
|
||||
style="cursor: pointer; text-decoration: underline;"
|
||||
@click="lokiWebsite"
|
||||
>Loki {{ $t("strings.website") }}.</span
|
||||
>
|
||||
</p>
|
||||
<LokiField :label="$t('fieldLabels.serviceNodeKey')" :error="$v.service_node.key.$error">
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.serviceNodeKey')"
|
||||
:error="$v.service_node.key.$error"
|
||||
>
|
||||
<q-input
|
||||
v-model.trim="service_node.key"
|
||||
:dark="theme == 'dark'"
|
||||
|
@ -17,8 +22,11 @@
|
|||
@blur="$v.service_node.key.$touch"
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<LokiField :label="$t('fieldLabels.amount')" class="q-mt-md" :error="$v.service_node.amount.$error">
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.amount')"
|
||||
class="q-mt-md"
|
||||
:error="$v.service_node.amount.$error"
|
||||
>
|
||||
<q-input
|
||||
v-model.trim="service_node.amount"
|
||||
:dark="theme == 'dark'"
|
||||
|
@ -33,12 +41,25 @@
|
|||
<q-btn
|
||||
color="secondary"
|
||||
:text-color="theme == 'dark' ? 'white' : 'dark'"
|
||||
@click="service_node.amount = unlocked_balance / 1e9"
|
||||
>{{ $t("buttons.all") }}</q-btn
|
||||
>
|
||||
:label="$t('buttons.min')"
|
||||
:disable="!areButtonsEnabled()"
|
||||
@click="service_node.amount = minStake(service_node.key)"
|
||||
/>
|
||||
<q-btn
|
||||
color="secondary"
|
||||
:text-color="theme == 'dark' ? 'white' : 'dark'"
|
||||
:label="$t('buttons.max')"
|
||||
:disable="!areButtonsEnabled()"
|
||||
@click="service_node.amount = maxStake(service_node.key)"
|
||||
/>
|
||||
</LokiField>
|
||||
<div class="submit-button">
|
||||
<q-btn :disable="!is_able_to_send" color="primary" :label="$t('buttons.stake')" @click="stake()" />
|
||||
<q-btn
|
||||
:disable="!is_able_to_send"
|
||||
color="primary"
|
||||
:label="$t('buttons.stake')"
|
||||
@click="stake()"
|
||||
/>
|
||||
<q-btn
|
||||
:disable="!is_able_to_send"
|
||||
color="secondary"
|
||||
|
@ -47,8 +68,24 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceNodeContribute class="contribute" @contribute="fillStakingFields" />
|
||||
<q-inner-loading :showing="stake_status.sending || tx_status.sending" :dark="theme == 'dark'">
|
||||
<ServiceNodeContribute
|
||||
:awaiting-service-nodes="awaiting_service_nodes"
|
||||
class="contribute"
|
||||
@contribute="fillStakingFields"
|
||||
/>
|
||||
<ConfirmTransactionDialog
|
||||
:show="confirmSweepAll"
|
||||
:amount="confirmFields.totalAmount"
|
||||
:is-blink="confirmFields.isBlink"
|
||||
:send-to="confirmFields.destination"
|
||||
:fee="confirmFields.totalFees"
|
||||
:on-confirm-transaction="onConfirmTransaction"
|
||||
:on-cancel-transaction="onCancelTransaction"
|
||||
/>
|
||||
<q-inner-loading
|
||||
:showing="stake_status.sending || sweep_all_status.sending"
|
||||
:dark="theme == 'dark'"
|
||||
>
|
||||
<q-spinner color="primary" size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
|
@ -61,20 +98,37 @@ import { required, decimal } from "vuelidate/lib/validators";
|
|||
import { service_node_key, greater_than_zero } from "src/validators/common";
|
||||
import LokiField from "components/loki_field";
|
||||
import WalletPassword from "src/mixins/wallet_password";
|
||||
import ConfirmDialogMixin from "src/mixins/confirm_dialog_mixin";
|
||||
import ServiceNodeContribute from "./service_node_contribute";
|
||||
import ServiceNodeMixin from "src/mixins/service_node_mixin";
|
||||
import ConfirmTransactionDialog from "components/confirm_tx_dialog";
|
||||
|
||||
const DO_NOTHING = 10;
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeStaking",
|
||||
components: {
|
||||
LokiField,
|
||||
ServiceNodeContribute
|
||||
ServiceNodeContribute,
|
||||
ConfirmTransactionDialog
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
mixins: [WalletPassword, ConfirmDialogMixin, ServiceNodeMixin],
|
||||
data() {
|
||||
return {
|
||||
service_node: {
|
||||
key: "",
|
||||
amount: 0
|
||||
amount: 0,
|
||||
// the min and max are for that particular SN,
|
||||
// start at min/max for the wallet
|
||||
minStakeAmount: 0,
|
||||
maxStakeAmount: this.unlocked_balance / 1e9
|
||||
},
|
||||
confirmFields: {
|
||||
metadataList: [],
|
||||
isBlink: false,
|
||||
totalAmount: -1,
|
||||
destination: "",
|
||||
totalFees: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
|
@ -83,8 +137,9 @@ export default {
|
|||
unlocked_balance: state => state.gateway.wallet.info.unlocked_balance,
|
||||
info: state => state.gateway.wallet.info,
|
||||
stake_status: state => state.gateway.service_node_status.stake,
|
||||
tx_status: state => state.gateway.tx_status,
|
||||
sweep_all_status: state => state.gateway.sweep_all_status,
|
||||
award_address: state => state.gateway.wallet.info.address,
|
||||
confirmSweepAll: state => state.gateway.sweep_all_status.code === 1,
|
||||
is_ready() {
|
||||
return this.$store.getters["gateway/isReady"];
|
||||
},
|
||||
|
@ -95,6 +150,49 @@ export default {
|
|||
const wallet = state.gateway.wallet.info;
|
||||
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
|
||||
return `${prefix}..`;
|
||||
},
|
||||
awaiting_service_nodes(state) {
|
||||
const nodes = state.gateway.daemon.service_nodes.nodes;
|
||||
// a reserved node is one on which someone is a "contributor" of amount = 0
|
||||
const getOurContribution = node =>
|
||||
node.contributors.find(
|
||||
c => c.address === this.our_address && c.amount > 0
|
||||
);
|
||||
const isAwaitingContribution = node =>
|
||||
!node.active && !node.funded && node.requested_unlock_height === 0;
|
||||
const isAwaitingContributionNonReserved = node =>
|
||||
isAwaitingContribution(node) && !getOurContribution(node);
|
||||
const isAwaitingContributionReserved = node =>
|
||||
isAwaitingContribution(node) && getOurContribution(node);
|
||||
|
||||
// we want the reserved nodes sorted by fee at the top
|
||||
const awaitingContributionNodesReserved = nodes
|
||||
.filter(isAwaitingContributionReserved)
|
||||
.map(n => {
|
||||
return {
|
||||
...n,
|
||||
awaitingContribution: true
|
||||
};
|
||||
});
|
||||
const awaitingContributionNodesNonReserved = nodes
|
||||
.filter(isAwaitingContributionNonReserved)
|
||||
.map(n => {
|
||||
return {
|
||||
...n,
|
||||
awaitingContribution: true
|
||||
};
|
||||
});
|
||||
|
||||
const compareFee = (n1, n2) =>
|
||||
this.getFeeDecimal(n1) > this.getFeeDecimal(n2) ? 1 : -1;
|
||||
awaitingContributionNodesReserved.sort(compareFee);
|
||||
awaitingContributionNodesNonReserved.sort(compareFee);
|
||||
|
||||
const nodesForContribution = [
|
||||
...awaitingContributionNodesReserved,
|
||||
...awaitingContributionNodesNonReserved
|
||||
];
|
||||
return nodesForContribution;
|
||||
}
|
||||
}),
|
||||
validations: {
|
||||
|
@ -136,22 +234,44 @@ export default {
|
|||
},
|
||||
deep: true
|
||||
},
|
||||
tx_status: {
|
||||
sweep_all_status: {
|
||||
handler(val, old) {
|
||||
if (val.code == old.code) return;
|
||||
switch (this.tx_status.code) {
|
||||
const { code, message } = val;
|
||||
switch (code) {
|
||||
// the "nothing", so we can update state without doing anything
|
||||
// in particular
|
||||
case DO_NOTHING:
|
||||
break;
|
||||
case 1:
|
||||
this.buildDialogFieldsSweepAll(val);
|
||||
break;
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.tx_status.message
|
||||
message
|
||||
});
|
||||
this.$v.$reset();
|
||||
this.newTx = {
|
||||
amount: 0,
|
||||
address: "",
|
||||
payment_id: "",
|
||||
// blink
|
||||
priority: 5,
|
||||
address_book: {
|
||||
save: false,
|
||||
name: "",
|
||||
description: ""
|
||||
},
|
||||
note: ""
|
||||
};
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 3000,
|
||||
message: this.tx_status.message
|
||||
message
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -170,6 +290,61 @@ export default {
|
|||
this.service_node.key = key;
|
||||
this.service_node.amount = minContribution;
|
||||
},
|
||||
minStake() {
|
||||
const node = this.getNodeWithPubKey();
|
||||
return this.getMinContribution(node);
|
||||
},
|
||||
maxStake() {
|
||||
const node = this.getNodeWithPubKey();
|
||||
return this.openForContributionLoki(node);
|
||||
},
|
||||
getFeeDecimal(node) {
|
||||
const operatorPortion = node.portions_for_operator;
|
||||
return (operatorPortion / 18446744073709551612) * 100;
|
||||
},
|
||||
getNodeWithPubKey() {
|
||||
const key = this.service_node.key;
|
||||
const nodeOfKey = this.awaiting_service_nodes.find(
|
||||
n => n.service_node_pubkey === key
|
||||
);
|
||||
if (!nodeOfKey) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.$t("notification.errors.invalidServiceNodeKey")
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
return nodeOfKey;
|
||||
}
|
||||
},
|
||||
onConfirmTransaction() {
|
||||
// put the loading spinner up
|
||||
this.$store.commit("gateway/set_sweep_all_status", {
|
||||
code: DO_NOTHING,
|
||||
message: "Getting sweep all tx information",
|
||||
sending: true
|
||||
});
|
||||
|
||||
const metadataList = this.confirmFields.metadataList;
|
||||
const isBlink = this.confirmFields.isBlink;
|
||||
|
||||
const relayTxData = {
|
||||
metadataList,
|
||||
isBlink,
|
||||
isSweepAll: true
|
||||
};
|
||||
|
||||
// Commit the transaction
|
||||
this.$gateway.send("wallet", "relay_tx", relayTxData);
|
||||
},
|
||||
onCancelTransaction() {
|
||||
this.$store.commit("gateway/set_sweep_all_status", {
|
||||
code: DO_NOTHING,
|
||||
message: "Cancel the transaction from confirm dialog",
|
||||
sending: false
|
||||
});
|
||||
},
|
||||
sweepAllWarning() {
|
||||
this.$q
|
||||
.dialog({
|
||||
|
@ -192,6 +367,16 @@ export default {
|
|||
.onDismiss(() => {})
|
||||
.onCancel(() => {});
|
||||
},
|
||||
buildDialogFieldsSweepAll(txData) {
|
||||
this.confirmFields = this.buildDialogFields(txData);
|
||||
},
|
||||
areButtonsEnabled() {
|
||||
// if we can find the service node key in the list of service nodes
|
||||
const key = this.service_node.key;
|
||||
return !!this.awaiting_service_nodes.find(
|
||||
n => n.service_node_pubkey === key
|
||||
);
|
||||
},
|
||||
async sweepAll() {
|
||||
const { unlocked_balance } = this.info;
|
||||
|
||||
|
@ -214,12 +399,15 @@ export default {
|
|||
passwordDialog
|
||||
.onOk(password => {
|
||||
password = password || "";
|
||||
this.$store.commit("gateway/set_tx_status", {
|
||||
code: 1,
|
||||
this.$store.commit("gateway/set_sweep_all_status", {
|
||||
code: DO_NOTHING,
|
||||
message: "Sweeping all",
|
||||
sending: true
|
||||
});
|
||||
const newTx = objectAssignDeep.noMutate(tx, { password });
|
||||
const newTx = objectAssignDeep.noMutate(tx, {
|
||||
password,
|
||||
isSweepAll: true
|
||||
});
|
||||
this.$gateway.send("wallet", "transfer", newTx);
|
||||
})
|
||||
.onDismiss(() => {})
|
||||
|
@ -272,7 +460,7 @@ export default {
|
|||
noPasswordMessage: this.$t("dialog.stake.message"),
|
||||
ok: {
|
||||
label: this.$t("dialog.stake.ok"),
|
||||
color: this.theme == "dark" ? "white" : "dark"
|
||||
color: "primary"
|
||||
},
|
||||
dark: this.theme == "dark",
|
||||
color: this.theme == "dark" ? "white" : "dark"
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
<span v-if="service_nodes.length">
|
||||
{{ $t("titles.currentlyStakedNodes") }}
|
||||
</span>
|
||||
<span v-else>{{ $t("strings.serviceNodeStartStakingDescription") }}</span>
|
||||
<span v-else>{{
|
||||
$t("strings.serviceNodeStartStakingDescription")
|
||||
}}</span>
|
||||
</div>
|
||||
<div v-if="service_nodes">
|
||||
<ServiceNodeList
|
||||
|
@ -15,10 +17,17 @@
|
|||
:action="unlockWarning"
|
||||
/>
|
||||
</div>
|
||||
<q-inner-loading :showing="unlock_status.sending || fetching" :dark="theme == 'dark'">
|
||||
<q-inner-loading
|
||||
:showing="unlock_status.sending || fetching"
|
||||
:dark="theme == 'dark'"
|
||||
>
|
||||
<q-spinner color="primary" size="30" />
|
||||
</q-inner-loading>
|
||||
<ServiceNodeDetails ref="serviceNodeDetailsUnlock" :action="unlockWarning" action-i18n="buttons.unlock" />
|
||||
<ServiceNodeDetails
|
||||
ref="serviceNodeDetailsUnlock"
|
||||
:action="unlockWarning"
|
||||
action-i18n="buttons.unlock"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -57,8 +66,12 @@ export default {
|
|||
},
|
||||
// just SNs the user has contributed to
|
||||
service_nodes(state) {
|
||||
const nodes = state.gateway.daemon.service_nodes.nodes;
|
||||
const getOurContribution = node => node.contributors.find(c => c.address === this.our_address);
|
||||
let nodes = state.gateway.daemon.service_nodes.nodes;
|
||||
// don't count reserved nodes in my stakes (where they are a contributor of amount 0)
|
||||
const getOurContribution = node =>
|
||||
node.contributors.find(
|
||||
c => c.address === this.our_address && c.amount > 0
|
||||
);
|
||||
return nodes.filter(getOurContribution).map(n => {
|
||||
const ourContribution = getOurContribution(n);
|
||||
return {
|
||||
|
@ -219,7 +232,10 @@ export default {
|
|||
});
|
||||
},
|
||||
getRole(node) {
|
||||
const key = node.operator_address === this.our_address ? "strings.operator" : "strings.contributor";
|
||||
const key =
|
||||
node.operator_address === this.our_address
|
||||
? "strings.operator"
|
||||
: "strings.contributor";
|
||||
return this.$t(key);
|
||||
},
|
||||
getFee(node) {
|
||||
|
|
|
@ -373,6 +373,35 @@ footer,
|
|||
color: white;
|
||||
}
|
||||
}
|
||||
.confirm-tx-card {
|
||||
color: "primary";
|
||||
width: 450px;
|
||||
max-width: 450x;
|
||||
|
||||
.confirm-list {
|
||||
.q-item {
|
||||
max-height: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 4px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #cecece;
|
||||
padding-right: 6px;
|
||||
}
|
||||
.address-value {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.confirm-send-btn {
|
||||
color: white;
|
||||
background: $positive;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.header-popover {
|
||||
background: $primary;
|
||||
|
|
|
@ -13,10 +13,14 @@ export class Gateway extends EventEmitter {
|
|||
this.scee = new SCEE();
|
||||
|
||||
// Set the initial language
|
||||
let language = LocalStorage.has("language") ? LocalStorage.getItem("language") : "en-us";
|
||||
let language = LocalStorage.has("language")
|
||||
? LocalStorage.getItem("language")
|
||||
: "en-us";
|
||||
this.setLanguage(language);
|
||||
|
||||
let theme = LocalStorage.has("theme") ? LocalStorage.getItem("theme") : "dark";
|
||||
let theme = LocalStorage.has("theme")
|
||||
? LocalStorage.getItem("theme")
|
||||
: "dark";
|
||||
this.app.store.commit("gateway/set_app_data", {
|
||||
config: {
|
||||
appearance: {
|
||||
|
@ -90,7 +94,10 @@ export class Gateway extends EventEmitter {
|
|||
cancel: {
|
||||
flat: true,
|
||||
label: i18n.t("dialog.buttons.cancel"),
|
||||
color: this.app.store.state.gateway.app.config.appearance.theme === "dark" ? "white" : "dark"
|
||||
color:
|
||||
this.app.store.state.gateway.app.config.appearance.theme === "dark"
|
||||
? "white"
|
||||
: "dark"
|
||||
},
|
||||
dark: this.app.store.state.gateway.app.config.appearance.theme === "dark"
|
||||
})
|
||||
|
@ -111,7 +118,10 @@ export class Gateway extends EventEmitter {
|
|||
method,
|
||||
data
|
||||
};
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token);
|
||||
let encrypted_data = this.scee.encryptString(
|
||||
JSON.stringify(message),
|
||||
this.token
|
||||
);
|
||||
this.ws.send(encrypted_data);
|
||||
}
|
||||
|
||||
|
@ -122,7 +132,9 @@ export class Gateway extends EventEmitter {
|
|||
receive(message) {
|
||||
// should wrap this in a try catch, and if fail redirect to error screen
|
||||
// shouldn't happen outside of dev environment
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(message, this.token));
|
||||
let decrypted_data = JSON.parse(
|
||||
this.scee.decryptString(message, this.token)
|
||||
);
|
||||
|
||||
if (
|
||||
typeof decrypted_data !== "object" ||
|
||||
|
@ -173,6 +185,15 @@ export class Gateway extends EventEmitter {
|
|||
break;
|
||||
}
|
||||
|
||||
case "set_sweep_all_status": {
|
||||
const data = { ...decrypted_data.data };
|
||||
if (data.i18n) {
|
||||
data.message = this.geti18n(data.i18n);
|
||||
}
|
||||
this.app.store.commit("gateway/set_sweep_all_status", data);
|
||||
break;
|
||||
}
|
||||
|
||||
case "set_lns_status": {
|
||||
const data = { ...decrypted_data.data };
|
||||
if (data.i18n) {
|
||||
|
@ -217,7 +238,10 @@ export class Gateway extends EventEmitter {
|
|||
break;
|
||||
}
|
||||
case "set_old_gui_import_status":
|
||||
this.app.store.commit("gateway/set_old_gui_import_status", decrypted_data.data);
|
||||
this.app.store.commit(
|
||||
"gateway/set_old_gui_import_status",
|
||||
decrypted_data.data
|
||||
);
|
||||
break;
|
||||
|
||||
case "wallet_list":
|
||||
|
@ -260,7 +284,10 @@ export class Gateway extends EventEmitter {
|
|||
break;
|
||||
|
||||
case "set_update_required":
|
||||
this.app.store.commit("gateway/set_update_required", decrypted_data.data);
|
||||
this.app.store.commit(
|
||||
"gateway/set_update_required",
|
||||
decrypted_data.data
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ export default {
|
|||
import: "IMPORT",
|
||||
importWallet: "IMPORT WALLET | IMPORT WALLETS",
|
||||
lns: "LOKI NAME SERVICE",
|
||||
max: "MAX",
|
||||
min: "MIN",
|
||||
next: "NEXT",
|
||||
openWallet: "OPEN WALLET",
|
||||
purchase: "PURCHASE",
|
||||
|
@ -62,12 +64,14 @@ export default {
|
|||
},
|
||||
copyAddress: {
|
||||
title: "Copy address",
|
||||
message: "There is a payment id associated with this address.\nBe sure to copy the payment id separately."
|
||||
message:
|
||||
"There is a payment id associated with this address.\nBe sure to copy the payment id separately."
|
||||
},
|
||||
copyPrivateKeys: {
|
||||
// Copy {seedWords/viewKey/spendKey}
|
||||
title: "Copy {type}",
|
||||
message: "Be careful who you send your private keys to as they control your funds.",
|
||||
message:
|
||||
"Be careful who you send your private keys to as they control your funds.",
|
||||
seedWords: "Seed Words",
|
||||
viewKey: "View Key",
|
||||
spendKey: "Spend Key"
|
||||
|
@ -115,7 +119,8 @@ export default {
|
|||
},
|
||||
rescan: {
|
||||
title: "Rescan wallet",
|
||||
message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
|
||||
message:
|
||||
"Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
|
||||
ok: "RESCAN"
|
||||
},
|
||||
restart: {
|
||||
|
@ -312,7 +317,8 @@ export default {
|
|||
},
|
||||
errors: {
|
||||
banningPeer: "Error banning peer",
|
||||
cannotAccessRemoteNode: "Could not access remote node, please try another remote node",
|
||||
cannotAccessRemoteNode:
|
||||
"Could not access remote node, please try another remote node",
|
||||
changingPassword: "Error changing password",
|
||||
copyWalletFail: "Failed to copy wallet",
|
||||
copyingPrivateKeys: "Error copying private keys",
|
||||
|
@ -335,8 +341,10 @@ export default {
|
|||
invalidAmount: "Amount not valid",
|
||||
invalidBackupOwner: "Backup owner address not valid",
|
||||
invalidNameLength: "Name must be between 1 and 64 characters long",
|
||||
invalidNameFormat: "Name may only contain alphanumerics, hyphens and underscore",
|
||||
invalidNameHypenNotAllowed: "Name may only begin or end with alphanumerics or an underscore",
|
||||
invalidNameFormat:
|
||||
"Name may only contain alphanumerics, hyphens and underscore",
|
||||
invalidNameHypenNotAllowed:
|
||||
"Name may only begin or end with alphanumerics or an underscore",
|
||||
invalidOldPassword: "Invalid old password",
|
||||
invalidOwner: "Owner address not valid",
|
||||
invalidPassword: "Invalid password",
|
||||
|
@ -346,7 +354,8 @@ export default {
|
|||
invalidRestoreDate: "Invalid restore date",
|
||||
invalidRestoreHeight: "Invalid restore height",
|
||||
invalidSeedLength: "Invalid seed word length",
|
||||
invalidServiceNodeCommand: "Please enter the service node registration command",
|
||||
invalidServiceNodeCommand:
|
||||
"Please enter the service node registration command",
|
||||
invalidServiceNodeKey: "Service node key not valid",
|
||||
invalidSessionId: "Session ID not valid",
|
||||
invalidWalletPath: "Invalid wallet path",
|
||||
|
@ -384,7 +393,8 @@ export default {
|
|||
mnemonicSeed: "25 (or 24) word mnemonic seed",
|
||||
pasteTransactionId: "Paste transaction ID",
|
||||
pasteTransactionProof: "Paste transaction proof",
|
||||
proveOptionalMessage: "Optional message against which the signature is signed",
|
||||
proveOptionalMessage:
|
||||
"Optional message against which the signature is signed",
|
||||
recipientWalletAddress: "Recipient's wallet address",
|
||||
selectAFile: "Please select a file",
|
||||
sessionId: "The Session ID to link to Loki Name Service",
|
||||
|
@ -442,7 +452,8 @@ export default {
|
|||
},
|
||||
remote: {
|
||||
title: "Remote Daemon Only",
|
||||
description: "Less security, wallet will connect to a remote node to make all transactions."
|
||||
description:
|
||||
"Less security, wallet will connect to a remote node to make all transactions."
|
||||
}
|
||||
},
|
||||
destinationUnknown: "Destination Unknown",
|
||||
|
@ -472,9 +483,11 @@ export default {
|
|||
proveTransactionDescription:
|
||||
"Generate a proof of your incoming/outgoing payment by supplying the transaction ID, the recipient address and an optional message.\nFor the case of outgoing payments, you can get a 'Spend Proof' that proves the authorship of a transaction. In this case, you don't need to specify the recipient address.",
|
||||
readingWalletList: "Reading wallet list",
|
||||
recentIncomingTransactionsToAddress: "Recent incoming transactions to this address",
|
||||
recentIncomingTransactionsToAddress:
|
||||
"Recent incoming transactions to this address",
|
||||
recentTransactionsWithAddress: "Recent transactions with this address",
|
||||
rescanModalDescription: "Select full rescan or rescan of spent outputs only.",
|
||||
rescanModalDescription:
|
||||
"Select full rescan or rescan of spent outputs only.",
|
||||
saveSeedWarning: "Please copy and save these in a secure location!",
|
||||
saveToAddressBook: "Save to address book",
|
||||
seedWords: "Seed words",
|
||||
|
@ -483,8 +496,10 @@ export default {
|
|||
"Staking contributes to the safety of the Loki network. For your contribution, you earn LOKI. Once staked, you will have to wait either 15 or 30 days to have your Loki unlocked, depending on if a stake was unlocked by a contributor or the node was deregistered. To learn more about staking, please visit the",
|
||||
serviceNodeRegistrationDescription:
|
||||
'Enter the {registerCommand} command produced by the daemon that is registering to become a Service Node using the "{prepareCommand}" command',
|
||||
serviceNodeStartStakingDescription: "To start staking, please visit the Staking tab",
|
||||
noServiceNodesCurrentlyAvailable: "There are currently no service nodes available for contribution",
|
||||
serviceNodeStartStakingDescription:
|
||||
"To start staking, please visit the Staking tab",
|
||||
noServiceNodesCurrentlyAvailable:
|
||||
"There are currently no service nodes available for contribution",
|
||||
serviceNodeDetails: {
|
||||
contributors: "Contributors",
|
||||
lastRewardBlockHeight: "Last reward block height",
|
||||
|
@ -494,6 +509,7 @@ export default {
|
|||
operatorFee: "Operator Fee",
|
||||
registrationHeight: "Registration height",
|
||||
unlockHeight: "Unlock height",
|
||||
reserved: "Reserved",
|
||||
serviceNodeKey: "Service Node Key",
|
||||
snKey: "SN Key",
|
||||
stakingRequirement: "Staking requirement",
|
||||
|
@ -535,7 +551,8 @@ export default {
|
|||
userNotUsedAddress: "You have not used this address",
|
||||
userUsedAddress: "You have used this address",
|
||||
viewKey: "View key",
|
||||
viewOnlyMode: "View only mode. Please load full wallet in order to send coins.",
|
||||
viewOnlyMode:
|
||||
"View only mode. Please load full wallet in order to send coins.",
|
||||
website: "website"
|
||||
},
|
||||
titles: {
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
export default {
|
||||
methods: {
|
||||
buildDialogFields(val) {
|
||||
const { feeList, amountList, destinations, metadataList, priority, isSweepAll, address } = val.txData;
|
||||
const totalFees = feeList.reduce((a, b) => a + b, 0) / 1e9;
|
||||
const totalAmount = amountList.reduce((a, b) => a + b, 0) / 1e9;
|
||||
// If the tx is a sweep all, we're sending to the wallet's primary address
|
||||
// a tx can be split, but only sent to one address
|
||||
let destination = isSweepAll ? address : destinations[0].address;
|
||||
const isBlink = [0, 2, 3, 4, 5].includes(priority) ? true : false;
|
||||
const confirmFields = {
|
||||
metadataList,
|
||||
isBlink,
|
||||
destination,
|
||||
totalAmount,
|
||||
totalFees
|
||||
};
|
||||
return confirmFields;
|
||||
}
|
||||
}
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
export default {
|
||||
methods: {
|
||||
getMinContribution(node) {
|
||||
const MAX_NUMBER_OF_CONTRIBUTORS = 4;
|
||||
// This is calculated in the same way it is calculated on the LokiBlocks site
|
||||
const openContributionRemaining = this.openForContribution(node);
|
||||
const minContributionAtomicUnits =
|
||||
!node.funded && node.contributors.length < MAX_NUMBER_OF_CONTRIBUTORS
|
||||
? openContributionRemaining /
|
||||
(MAX_NUMBER_OF_CONTRIBUTORS - node.contributors.length)
|
||||
: 0;
|
||||
const minContributionLoki = minContributionAtomicUnits / 1e9;
|
||||
// ceiling to 4 decimal places
|
||||
return minContributionLoki.toFixed(4);
|
||||
},
|
||||
openForContribution(node) {
|
||||
const openContributionRemaining =
|
||||
node.staking_requirement > node.total_reserved
|
||||
? node.staking_requirement - node.total_reserved
|
||||
: 0;
|
||||
return openContributionRemaining;
|
||||
},
|
||||
openForContributionLoki(node) {
|
||||
return (this.openForContribution(node) / 1e9).toFixed(4);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<q-page class="create-wallet">
|
||||
<div class="fields q-mx-md q-mt-md">
|
||||
<LokiField :label="$t('fieldLabels.walletName')" :error="$v.wallet.name.$error">
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.walletName')"
|
||||
:error="$v.wallet.name.$error"
|
||||
>
|
||||
<q-input
|
||||
v-model="wallet.name"
|
||||
:dark="theme == 'dark'"
|
||||
|
@ -48,7 +51,12 @@
|
|||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-btn class="submit-button" color="primary" :label="$t('buttons.createWallet')" @click="create" />
|
||||
<q-btn
|
||||
class="submit-button"
|
||||
color="primary"
|
||||
:label="$t('buttons.createWallet')"
|
||||
@click="create"
|
||||
/>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
@ -79,7 +87,7 @@ export default {
|
|||
return {
|
||||
wallet: {
|
||||
name: "",
|
||||
language: languageOptions[0],
|
||||
language: languageOptions[0].value,
|
||||
password: "",
|
||||
password_confirm: ""
|
||||
},
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
<div class="row gutter-md">
|
||||
<!-- Amount -->
|
||||
<div class="col-6 amount">
|
||||
<LokiField :label="$t('fieldLabels.amount')" :error="$v.newTx.amount.$error">
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.amount')"
|
||||
:error="$v.newTx.amount.$error"
|
||||
>
|
||||
<q-input
|
||||
v-model="newTx.amount"
|
||||
:dark="theme == 'dark'"
|
||||
|
@ -50,7 +53,10 @@
|
|||
|
||||
<!-- Address -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField :label="$t('fieldLabels.address')" :error="$v.newTx.address.$error">
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.address')"
|
||||
:error="$v.newTx.address.$error"
|
||||
>
|
||||
<q-input
|
||||
v-model.trim="newTx.address"
|
||||
:dark="theme == 'dark'"
|
||||
|
@ -59,7 +65,11 @@
|
|||
dense
|
||||
@blur="$v.newTx.address.$touch"
|
||||
/>
|
||||
<q-btn color="secondary" :text-color="theme == 'dark' ? 'white' : 'dark'" to="addressbook">
|
||||
<q-btn
|
||||
color="secondary"
|
||||
:text-color="theme == 'dark' ? 'white' : 'dark'"
|
||||
to="addressbook"
|
||||
>
|
||||
{{ $t("buttons.contacts") }}
|
||||
</q-btn>
|
||||
</LokiField>
|
||||
|
@ -67,7 +77,11 @@
|
|||
|
||||
<!-- Payment ID -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField :label="$t('fieldLabels.paymentId')" :error="$v.newTx.payment_id.$error" optional>
|
||||
<LokiField
|
||||
:label="$t('fieldLabels.paymentId')"
|
||||
:error="$v.newTx.payment_id.$error"
|
||||
optional
|
||||
>
|
||||
<!-- TODO: count to be '16 or 64 after RPC fixed -->
|
||||
<q-input
|
||||
v-model.trim="newTx.payment_id"
|
||||
|
@ -139,40 +153,15 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<q-dialog v-model="confirmTransaction" persistent>
|
||||
<q-card class="confirm-tx-card" dark>
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{ $t("dialog.confirmTransaction.title") }}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="confirm-list">
|
||||
<div>
|
||||
<span class="label">{{ $t("dialog.confirmTransaction.sendTo") }}: </span>
|
||||
<br />
|
||||
<span class="address-value">{{ confirmFields.destination }}</span>
|
||||
</div>
|
||||
<br />
|
||||
<span class="label">{{ $t("strings.transactions.amount") }}: </span>
|
||||
{{ confirmFields.totalAmount }} Loki
|
||||
<br />
|
||||
<span class="label">{{ $t("strings.transactions.fee") }}: </span> {{ confirmFields.totalFees }} Loki
|
||||
<br />
|
||||
<span class="label">{{ $t("dialog.confirmTransaction.priority") }}: </span>
|
||||
{{ confirmFields.translatedBlinkOrSlow }}
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-card-actions align="right">
|
||||
<q-btn v-close-popup flat :label="$t('dialog.buttons.cancel')" color="negative" />
|
||||
<q-btn
|
||||
v-close-popup
|
||||
class="confirm-send-btn"
|
||||
flat
|
||||
:label="$t('buttons.send')"
|
||||
@click="onConfirmTransaction"
|
||||
/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
<ConfirmTransactionDialog
|
||||
:show="confirmTransaction"
|
||||
:amount="confirmFields.totalAmount"
|
||||
:is-blink="confirmFields.isBlink"
|
||||
:send-to="confirmFields.destination"
|
||||
:fee="confirmFields.totalFees"
|
||||
:on-confirm-transaction="onConfirmTransaction"
|
||||
:on-cancel-transaction="onCancelTransaction"
|
||||
/>
|
||||
<q-inner-loading :showing="tx_status.sending" :dark="theme == 'dark'">
|
||||
<q-spinner color="primary" size="30" />
|
||||
</q-inner-loading>
|
||||
|
@ -186,6 +175,8 @@ import { required, decimal } from "vuelidate/lib/validators";
|
|||
import { payment_id, address, greater_than_zero } from "src/validators/common";
|
||||
import LokiField from "components/loki_field";
|
||||
import WalletPassword from "src/mixins/wallet_password";
|
||||
import ConfirmDialogMixin from "src/mixins/confirm_dialog_mixin";
|
||||
import ConfirmTransactionDialog from "components/confirm_tx_dialog";
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
|
||||
// the case for doing nothing on a tx_status update
|
||||
|
@ -193,9 +184,10 @@ const DO_NOTHING = 10;
|
|||
|
||||
export default {
|
||||
components: {
|
||||
LokiField
|
||||
LokiField,
|
||||
ConfirmTransactionDialog
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
mixins: [WalletPassword, ConfirmDialogMixin],
|
||||
data() {
|
||||
let priorityOptions = [
|
||||
{ label: this.$t("strings.priorityOptions.blink"), value: 5 }, // Blink
|
||||
|
@ -214,8 +206,13 @@ export default {
|
|||
}
|
||||
},
|
||||
priorityOptions: priorityOptions,
|
||||
confirmTransaction: false,
|
||||
confirmFields: {}
|
||||
confirmFields: {
|
||||
metadataList: [],
|
||||
isBlink: false,
|
||||
totalAmount: -1,
|
||||
destination: "",
|
||||
totalFees: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
computed: mapState({
|
||||
|
@ -233,7 +230,8 @@ export default {
|
|||
const wallet = state.gateway.wallet.info;
|
||||
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
|
||||
return `${prefix}..`;
|
||||
}
|
||||
},
|
||||
confirmTransaction: state => state.gateway.tx_status.code === 1
|
||||
}),
|
||||
validations: {
|
||||
newTx: {
|
||||
|
@ -268,7 +266,7 @@ export default {
|
|||
case DO_NOTHING:
|
||||
break;
|
||||
case 1:
|
||||
this.buildDialogFields(val);
|
||||
this.buildDialogFieldsSend(val);
|
||||
break;
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
|
@ -308,7 +306,10 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$route.path == "/wallet/send" && this.$route.query.hasOwnProperty("address")) {
|
||||
if (
|
||||
this.$route.path == "/wallet/send" &&
|
||||
this.$route.query.hasOwnProperty("address")
|
||||
) {
|
||||
this.autoFill(this.$route.query);
|
||||
}
|
||||
},
|
||||
|
@ -317,25 +318,9 @@ export default {
|
|||
this.newTx.address = info.address;
|
||||
this.newTx.payment_id = info.payment_id;
|
||||
},
|
||||
buildDialogFields(val) {
|
||||
this.confirmTransaction = true;
|
||||
const { feeList, amountList, destinations, metadataList, priority } = val.txData;
|
||||
const totalFees = feeList.reduce((a, b) => a + b, 0) / 1e9;
|
||||
const totalAmount = amountList.reduce((a, b) => a + b, 0) / 1e9;
|
||||
// a tx can be split, but only sent to one address
|
||||
const destination = destinations[0].address;
|
||||
|
||||
const isBlink = [0, 2, 3, 4, 5].includes(priority) ? true : false;
|
||||
const blinkOrSlow = isBlink ? "strings.priorityOptions.blink" : "strings.priorityOptions.slow";
|
||||
const translatedBlinkOrSlow = this.$t(blinkOrSlow);
|
||||
this.confirmFields = {
|
||||
metadataList,
|
||||
isBlink,
|
||||
translatedBlinkOrSlow,
|
||||
destination,
|
||||
totalAmount,
|
||||
totalFees
|
||||
};
|
||||
buildDialogFieldsSend(txData) {
|
||||
// build using mixin method
|
||||
this.confirmFields = this.buildDialogFields(txData);
|
||||
},
|
||||
onConfirmTransaction() {
|
||||
// put the loading spinner up
|
||||
|
@ -366,9 +351,16 @@ export default {
|
|||
note
|
||||
};
|
||||
|
||||
// Commit the transaction
|
||||
this.$gateway.send("wallet", "relay_tx", relayTxData);
|
||||
},
|
||||
// helper for constructing a dialog for confirming transactions
|
||||
onCancelTransaction() {
|
||||
this.$store.commit("gateway/set_tx_status", {
|
||||
code: DO_NOTHING,
|
||||
message: "Cancel the transaction from confirm dialog",
|
||||
sending: false
|
||||
});
|
||||
},
|
||||
|
||||
async send() {
|
||||
this.$v.newTx.$touch();
|
||||
|
@ -454,35 +446,6 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.confirm-tx-card {
|
||||
color: "primary";
|
||||
width: 450px;
|
||||
max-width: 450x;
|
||||
|
||||
.confirm-list {
|
||||
.q-item {
|
||||
max-height: 100%;
|
||||
margin-top: 0;
|
||||
margin-bottom: 4px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #cecece;
|
||||
padding-right: 6px;
|
||||
}
|
||||
.address-value {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.confirm-send-btn {
|
||||
color: white;
|
||||
background: $positive;
|
||||
}
|
||||
}
|
||||
|
||||
.send {
|
||||
.send-btn {
|
||||
margin-top: 6px;
|
||||
|
|
|
@ -18,8 +18,14 @@ export const set_old_gui_import_status = (state, data) => {
|
|||
export const set_tx_status = (state, data) => {
|
||||
state.tx_status = data;
|
||||
};
|
||||
export const set_sweep_all_status = (state, data) => {
|
||||
state.sweep_all_status = data;
|
||||
};
|
||||
export const set_snode_status = (state, data) => {
|
||||
state.service_node_status = objectAssignDeep.noMutate(state.service_node_status, data);
|
||||
state.service_node_status = objectAssignDeep.noMutate(
|
||||
state.service_node_status,
|
||||
data
|
||||
);
|
||||
};
|
||||
export const set_prove_transaction_status = (state, data) => {
|
||||
state.prove_transaction_status = {
|
||||
|
|
|
@ -57,6 +57,13 @@ export default {
|
|||
i18n: "",
|
||||
sending: false
|
||||
},
|
||||
// differentiate between a tx and sweep_all
|
||||
sweep_all_status: {
|
||||
code: 0,
|
||||
message: "",
|
||||
i18n: "",
|
||||
sending: false
|
||||
},
|
||||
service_node_status: {
|
||||
stake: {
|
||||
code: 0,
|
||||
|
|
Loading…
Reference in New Issue