|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/wailsapp/wails"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
dummyValue = 1000
|
|
|
|
updateIntervalToken = 30 // Seconds
|
|
|
|
updateIntervalCurrency = 50 // Seconds
|
|
|
|
updateIntervalBlocks = 5 // Seconds
|
|
|
|
updateIntervalPieChart = 60 // Seconds
|
|
|
|
)
|
|
|
|
|
|
|
|
// ChartData 包含图表的所有数据点在 Dashboard 上。
|
|
|
|
type ChartData struct {
|
|
|
|
NodesOnline struct {
|
|
|
|
Labels []string `json:"labels"`
|
|
|
|
Series []int `json:"series"`
|
|
|
|
} `json:"nodes_online"`
|
|
|
|
Transactions struct {
|
|
|
|
Labels []string `json:"labels"`
|
|
|
|
SeriesOne []int `json:"series_one"`
|
|
|
|
SeriesTwo []int `json:"series_two"`
|
|
|
|
} `json:"transactions"`
|
|
|
|
Throughput struct {
|
|
|
|
Labels []string `json:"labels"`
|
|
|
|
SeriesOne []int `json:"series_one"`
|
|
|
|
SeriesTwo []int `json:"series_two"`
|
|
|
|
} `json:"throughput"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChartDataInit 使用以下数据点初始化 ChartData 结构体钱包中的图表。这些作为 chart_data.json 存储在 fs 上
|
|
|
|
func (a *WalletApplication) ChartDataInit() *ChartData {
|
|
|
|
cd := &ChartData{}
|
|
|
|
|
|
|
|
cd.NodesOnline.Labels = []string{"30%", "20%", "50%"}
|
|
|
|
cd.NodesOnline.Series = []int{30, 20, 50}
|
|
|
|
|
|
|
|
cd.Transactions.Labels = []string{
|
|
|
|
"Jan ",
|
|
|
|
"Feb ",
|
|
|
|
"Mar ",
|
|
|
|
"Apr ",
|
|
|
|
"Mai ",
|
|
|
|
"Jun ",
|
|
|
|
"Jul ",
|
|
|
|
"Aug ",
|
|
|
|
"Sep ",
|
|
|
|
"Oct ",
|
|
|
|
"Nov ",
|
|
|
|
"Dec "}
|
|
|
|
cd.Transactions.SeriesOne = []int{542, 543, 520, 680, 653, 753, 326, 434, 568, 610, 756, 895}
|
|
|
|
cd.Transactions.SeriesTwo = []int{230, 293, 380, 480, 503, 553, 600, 664, 698, 710, 736, 795}
|
|
|
|
|
|
|
|
cd.Throughput.Labels = []string{
|
|
|
|
"9AM",
|
|
|
|
"12AM",
|
|
|
|
"3PM",
|
|
|
|
"6PM",
|
|
|
|
"9PM",
|
|
|
|
"12PM",
|
|
|
|
"3AM",
|
|
|
|
"6AM"}
|
|
|
|
cd.Throughput.SeriesOne = []int{287, 385, 490, 562, 594, 626, 698, 895, 952}
|
|
|
|
cd.Throughput.SeriesTwo = []int{67, 152, 193, 240, 387, 435, 535, 642, 744}
|
|
|
|
|
|
|
|
// Init chart widgets with data.
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
for i := 0; i < 2; i++ {
|
|
|
|
a.RT.Events.Emit("tx_stats", cd.Transactions.SeriesOne, cd.Transactions.SeriesTwo, cd.Transactions.Labels)
|
|
|
|
a.RT.Events.Emit("node_stats", cd.NodesOnline.Series, cd.NodesOnline.Labels)
|
|
|
|
a.RT.Events.Emit("network_stats", cd.Throughput.SeriesOne, cd.Throughput.SeriesTwo, cd.Throughput.Labels)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
return cd
|
|
|
|
}
|
|
|
|
|
|
|
|
// 使用区块浏览器中的数据填充 Nodes Online 饼图。
|
|
|
|
func (a *WalletApplication) nodeStats(cd *ChartData) {
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
// 将使用随机数据填充图表
|
|
|
|
for i := range cd.NodesOnline.Series {
|
|
|
|
cd.NodesOnline.Series[i] = rand.Intn(dummyValue)
|
|
|
|
}
|
|
|
|
a.RT.Events.Emit("node_stats", cd.NodesOnline.Series, cd.NodesOnline.Labels)
|
|
|
|
UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalPieChart * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *WalletApplication) txStats(cd *ChartData) {
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
// Will populate the chart with random data
|
|
|
|
for i := range cd.Transactions.SeriesOne {
|
|
|
|
cd.Transactions.SeriesOne[i] = rand.Intn(dummyValue)
|
|
|
|
}
|
|
|
|
for i := range cd.Transactions.SeriesTwo {
|
|
|
|
cd.Transactions.SeriesTwo[i] = rand.Intn(dummyValue)
|
|
|
|
}
|
|
|
|
a.RT.Events.Emit("tx_stats", cd.Transactions.SeriesOne, cd.Transactions.SeriesTwo, cd.Transactions.Labels)
|
|
|
|
//UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalPieChart * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *WalletApplication) networkStats(cd *ChartData) {
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
// Will populate the chart with random data
|
|
|
|
for i := range cd.Throughput.SeriesOne {
|
|
|
|
cd.Throughput.SeriesOne[i] = rand.Intn(dummyValue)
|
|
|
|
}
|
|
|
|
for i := range cd.Throughput.SeriesTwo {
|
|
|
|
cd.Throughput.SeriesTwo[i] = rand.Intn(dummyValue)
|
|
|
|
}
|
|
|
|
a.RT.Events.Emit("network_stats", cd.Throughput.SeriesOne, cd.Throughput.SeriesTwo, cd.Throughput.Labels)
|
|
|
|
//UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalPieChart * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// BlockAmount is a temporary function
|
|
|
|
func (a *WalletApplication) blockAmount() {
|
|
|
|
var randomNumber int
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
randomNumber = rand.Intn(dummyValue)
|
|
|
|
a.RT.Events.Emit("blocks", randomNumber)
|
|
|
|
UpdateCounter(updateIntervalBlocks, "block_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalBlocks * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *WalletApplication) pollTokenBalance() {
|
|
|
|
go func() {
|
|
|
|
retryCounter := 1
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
time.Sleep(time.Duration(retryCounter) * time.Second) // Incremental backoff
|
|
|
|
for retryCounter <= 20 && a.wallet.Address != "" {
|
|
|
|
|
|
|
|
balance, err := a.GetTokenBalance()
|
|
|
|
if err != nil {
|
|
|
|
retryCounter++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
a.log.Infoln("Current Balance: ", balance)
|
|
|
|
a.wallet.Balance, a.wallet.AvailableBalance, a.wallet.TotalBalance = balance, balance, balance
|
|
|
|
a.RT.Events.Emit("token", a.wallet.Balance, a.wallet.AvailableBalance, a.wallet.TotalBalance)
|
|
|
|
UpdateCounter(updateIntervalToken, "token_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalToken * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// pricePoller 轮询 min-api.cryptocompare REST API 以获取 DAG 令牌值。
|
|
|
|
// 轮询后,它会将 token 值发送到 Dashboard.vue 以获取完整 token
|
|
|
|
// 相对于美元的余额评估。
|
|
|
|
func (a *WalletApplication) pricePoller() {
|
|
|
|
|
|
|
|
const (
|
|
|
|
apiKey string = "17b10afdddc411087e2140ec91bd73d88d0c20294541838b192255fc574b1cb7"
|
|
|
|
ticker string = "DAG"
|
|
|
|
url string = "https://min-api.cryptocompare.com/data/pricemulti?fsyms=" + ticker + "&tsyms=BTC,USD,EUR&api_key=" + apiKey
|
|
|
|
)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
retryCounter := 1
|
|
|
|
time.Sleep(3 * time.Second) // Give some space to pollTokenBalance
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-a.killSignal:
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
a.wallet.TokenPrice.DAG.USD = 0
|
|
|
|
a.wallet.TokenPrice.DAG.EUR = 0
|
|
|
|
a.wallet.TokenPrice.DAG.BTC = 0
|
|
|
|
time.Sleep(time.Duration(retryCounter) * time.Second) // Incremental backoff
|
|
|
|
for retryCounter <= 20 && a.wallet.Balance != 0 {
|
|
|
|
a.log.Infoln("Contacting token evaluation API on: " + url + ticker)
|
|
|
|
|
|
|
|
resp, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
a.log.Warnln("Unable to poll token evaluation. Reason: ", err) // Log this
|
|
|
|
retryCounter++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp == nil {
|
|
|
|
retryCounter++
|
|
|
|
a.log.Errorln("Killing pollTokenBalance after 10 failed attempts to get balance from mainnet, Reason: ", err)
|
|
|
|
a.sendWarning("Unable to showcase token USD evaluation. Please check your internet connectivity and restart the wallet application.")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
retryCounter++
|
|
|
|
a.sendError("Unable to read HTTP resonse from Token API. Reason: ", err)
|
|
|
|
a.log.Warnln("Unable to read HTTP resonse from Token API. Reason: ", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
err = json.Unmarshal([]byte(body), &a.wallet.TokenPrice)
|
|
|
|
if err != nil {
|
|
|
|
retryCounter++
|
|
|
|
a.sendError("Unable to display token price. Reason: ", err)
|
|
|
|
a.log.Warnln("Unable to display token price. Reason:", err)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.wallet.Balance != 0 && a.wallet.TokenPrice.DAG.USD == 0 {
|
|
|
|
|
|
|
|
a.log.Infoln("Contacting alternate token evaluation API")
|
|
|
|
a.wallet.TokenPrice.DAG.USD, a.wallet.TokenPrice.DAG.BTC, err = getTokenPriceAlternateRoute()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
a.log.Errorln("Failed to fetch token metrics using alternate endpoint. Reason: ", err)
|
|
|
|
if retryCounter == 10 || retryCounter == 15 || retryCounter == 20 {
|
|
|
|
warn := fmt.Sprintf("No data recieved from Token Price API. Will try again in %v seconds.", retryCounter)
|
|
|
|
a.sendWarning(warn)
|
|
|
|
}
|
|
|
|
retryCounter++
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
a.log.Infof("Collected token price in USD: %v", a.wallet.TokenPrice.DAG.USD)
|
|
|
|
a.log.Infof("Collected token price in EUR: %v", a.wallet.TokenPrice.DAG.EUR)
|
|
|
|
a.log.Infof("Collected token price in BTC: %v", a.wallet.TokenPrice.DAG.BTC)
|
|
|
|
|
|
|
|
totalCurrencyBalance := 0.0
|
|
|
|
if a.wallet.Currency == "USD" {
|
|
|
|
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.USD
|
|
|
|
} else if a.wallet.Currency == "EUR" {
|
|
|
|
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.EUR
|
|
|
|
} else if a.wallet.Currency == "BTC" {
|
|
|
|
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.BTC
|
|
|
|
}
|
|
|
|
a.RT.Events.Emit("totalValue", a.wallet.Currency, totalCurrencyBalance)
|
|
|
|
|
|
|
|
UpdateCounter(updateIntervalCurrency, "value_counter", time.Second, a.RT)
|
|
|
|
time.Sleep(updateIntervalCurrency * time.Second)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
// StoreTermsOfServiceStateDB 将服务条款状态存储在用户 DB 中
|
|
|
|
func (a *WalletApplication) StoreTermsOfServiceStateDB(termsOfService bool) bool {
|
|
|
|
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("TermsOfService", termsOfService).Error; err != nil {
|
|
|
|
a.log.Errorln("无法存储 termsOfService 状态。原因: ", err)
|
|
|
|
a.sendError("无法持久存储 termsOfService 状态。原因: ", err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
type tokenPriceAlt struct {
|
|
|
|
Code string `json:"code"`
|
|
|
|
Data struct {
|
|
|
|
Sequence string `json:"sequence"`
|
|
|
|
BestAsk string `json:"bestAsk"`
|
|
|
|
Size string `json:"size"`
|
|
|
|
Price string `json:"price"`
|
|
|
|
BestBidSize string `json:"bestBidSize"`
|
|
|
|
Time int64 `json:"time"`
|
|
|
|
BestBid string `json:"bestBid"`
|
|
|
|
BestAskSize string `json:"bestAskSize"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// getTokenPriceAlternateRoute 将在主令牌轮询器 API 返回时启动损坏的有效负载。
|
|
|
|
func getTokenPriceAlternateRoute() (float64, float64, error) {
|
|
|
|
|
|
|
|
var tokenpriceUSD, tokenpriceBTC float64
|
|
|
|
var err error
|
|
|
|
|
|
|
|
tpa := new(tokenPriceAlt)
|
|
|
|
|
|
|
|
const (
|
|
|
|
url = "https://api.kucoin.com/api/v1/market/orderbook/level1?symbol="
|
|
|
|
usdTicker = "DAG-USDT"
|
|
|
|
btcTicker = "DAG-BTC"
|
|
|
|
)
|
|
|
|
|
|
|
|
tickers := []string{usdTicker, btcTicker}
|
|
|
|
|
|
|
|
for _, tick := range tickers {
|
|
|
|
resp, err := http.Get(url + tick)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Example resp: {"code":"200000","data":
|
|
|
|
// {"sequence":"1583079038860","bestAsk":"0.01058",
|
|
|
|
// "size":"73908.9903","price":"0.01058",
|
|
|
|
// "bestBidSize":"403.157","time":1589605888009,
|
|
|
|
// "bestBid":"0.010539","bestAskSize":"79091.0097"}}
|
|
|
|
|
|
|
|
if resp == nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(bodyBytes, &tpa)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
s := tpa.Data.Price
|
|
|
|
balance, err := strconv.ParseFloat(s, 64)
|
|
|
|
if err != nil {
|
|
|
|
return 0, 0, err
|
|
|
|
}
|
|
|
|
switch {
|
|
|
|
case tick == usdTicker:
|
|
|
|
tokenpriceUSD = balance
|
|
|
|
case tick == btcTicker:
|
|
|
|
tokenpriceBTC = balance
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tokenpriceUSD, tokenpriceBTC, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// UpdateCounter 将从上次更新卡片开始计数。
|
|
|
|
func UpdateCounter(countFrom int, counter string, unit time.Duration, runtime *wails.Runtime) {
|
|
|
|
go func() {
|
|
|
|
for i := countFrom; i > 0; i-- {
|
|
|
|
runtime.Events.Emit(counter, i)
|
|
|
|
time.Sleep(unit)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|