From 03f72f07f264ea768decf0b2e200a83ee96d9204 Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Thu, 24 Nov 2022 01:04:20 +0100 Subject: [PATCH] Explorer redesign part 4 --- server/public.go | 262 ++++++++++---- server/public_ethereumtype_test.go | 7 +- server/public_test.go | 247 +++++++------- static/css/main.css | 109 +++++- static/js/main.js | 29 +- static/templates/address.html | 18 +- static/templates/block.html | 2 +- static/templates/blocks.html | 2 +- static/templates/index.html | 12 +- static/templates/mempool.html | 2 +- static/templates/tokenDetail.html | 18 +- static/templates/tx.html | 92 ++--- static/templates/txdetail.html | 48 +-- static/templates/txdetail_ethereumtype.html | 358 ++++++++------------ static/templates/xpub.html | 16 +- 15 files changed, 697 insertions(+), 525 deletions(-) diff --git a/server/public.go b/server/public.go index 9edac987..6b2688e2 100644 --- a/server/public.go +++ b/server/public.go @@ -5,10 +5,11 @@ import ( "encoding/json" "fmt" "html/template" - "io/ioutil" + "io" "math/big" "net/http" "net/url" + "os" "path/filepath" "reflect" "regexp" @@ -239,12 +240,12 @@ func (s *PublicServer) OnNewTx(tx *bchain.MempoolTx) { } func (s *PublicServer) txRedirect(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302) + http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), http.StatusFound) s.metrics.ExplorerViews.With(common.Labels{"action": "tx-redirect"}).Inc() } func (s *PublicServer) addressRedirect(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302) + http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), http.StatusFound) s.metrics.ExplorerViews.With(common.Labels{"action": "address-redirect"}).Inc() } @@ -371,6 +372,7 @@ func (s *PublicServer) newTemplateData(r *http.Request) *TemplateData { if ticker != nil { t.SecondaryCoin = strings.ToUpper(secondary) t.CurrentSecondaryCoinRate = float64(ticker.Rates[secondary]) + t.CurrentTicker = ticker t.UseSecondaryCoin, _ = strconv.ParseBool(r.URL.Query().Get("use_secondary")) if !t.UseSecondaryCoin { t.UseSecondaryCoin = cookieUseSecondary @@ -470,49 +472,55 @@ const ( // TemplateData is used to transfer data to the templates type TemplateData struct { - CoinName string - CoinShortcut string - CoinLabel string - InternalExplorer bool - ChainType bchain.ChainType - Address *api.Address - AddrStr string - Tx *api.Tx - Error *api.APIError - Blocks *api.Blocks - Block *api.Block - Info *api.SystemInfo - MempoolTxids *api.MempoolTxids - Page int - PrevPage int - NextPage int - PagingRange []int - PageParams template.URL - TOSLink string - SendTxHex string - Status string - NonZeroBalanceTokens bool - TokenId string - URI string - ContractInfo *bchain.ContractInfo - SecondaryCoin string - UseSecondaryCoin bool - CurrentSecondaryCoinRate float64 - TxBlocktime int64 - TxDate string - TxBlocktimeSecondaryCoinRate float64 + CoinName string + CoinShortcut string + CoinLabel string + InternalExplorer bool + ChainType bchain.ChainType + Address *api.Address + AddrStr string + Tx *api.Tx + Error *api.APIError + Blocks *api.Blocks + Block *api.Block + Info *api.SystemInfo + MempoolTxids *api.MempoolTxids + Page int + PrevPage int + NextPage int + PagingRange []int + PageParams template.URL + TOSLink string + SendTxHex string + Status string + NonZeroBalanceTokens bool + TokenId string + URI string + ContractInfo *bchain.ContractInfo + SecondaryCoin string + UseSecondaryCoin bool + CurrentSecondaryCoinRate float64 + CurrentTicker *common.CurrencyRatesTicker + TxBlocktime int64 + TxDate string + TxSecondaryCoinRate float64 + TxTicker *common.CurrencyRatesTicker } func (s *PublicServer) parseTemplates() []*template.Template { templateFuncMap := template.FuncMap{ - "formatTime": formatTime, - "formatUnixTime": formatUnixTime, + "timeSpan": timeSpan, + "unixTimeSpan": unixTimeSpan, + "amountSpan": s.amountSpan, + "tokenAmountSpan": s.tokenAmountSpan, + "amountSatsSpan": s.amountSatsSpan, + "addressAliasSpan": addressAliasSpan, "formatAmount": s.formatAmount, "formatAmountWithDecimals": formatAmountWithDecimals, - "amount": s.amount, "formatInt64": formatInt64, "formatInt": formatInt, "formatUint32": formatUint32, + "formatBigInt": formatBigInt, "setTxToTemplateData": setTxToTemplateData, "feePerByte": feePerByte, "isOwnAddress": isOwnAddress, @@ -537,7 +545,7 @@ func (s *PublicServer) parseTemplates() []*template.Template { } t := template.New(filepath.Base(filenames[0])).Funcs(templateFuncMap) for _, filename := range filenames { - b, err := ioutil.ReadFile(filename) + b, err := os.ReadFile(filename) if err != nil { panic(err) } @@ -614,12 +622,14 @@ func relativeTime(d int64) string { return strconv.FormatInt(d, 10) + u } -func formatUnixTime(ut int64) template.HTML { +func unixTimeSpan(ut int64) template.HTML { t := time.Unix(ut, 0) - return formatTime(&t) + return timeSpan(&t) } -func formatTime(t *time.Time) template.HTML { +var timeNow = time.Now + +func timeSpan(t *time.Time) template.HTML { if t == nil { return "" } @@ -627,7 +637,7 @@ func formatTime(t *time.Time) template.HTML { if u <= 0 { return "" } - d := time.Now().Unix() - u + d := timeNow().Unix() - u f := t.UTC().Format("2006-01-02 15:04:05") if d < 0 { return template.HTML(f) @@ -694,9 +704,7 @@ func appendAmountSpan(rv *strings.Builder, class, amount, shortcut, txDate strin rv.WriteString("") } -func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) template.HTML { - primary := s.formatAmount(a) - var rv strings.Builder +func appendWrappingAmountSpan(rv *strings.Builder, primary, symbol, classes string) { rv.WriteString(``) +} + +func (s *PublicServer) amountSpan(a *api.Amount, td *TemplateData, classes string) template.HTML { + primary := s.formatAmount(a) + var rv strings.Builder + appendWrappingAmountSpan(&rv, primary, td.CoinShortcut, classes) appendAmountSpan(&rv, "prim-amt", primary, td.CoinShortcut, "") if td.SecondaryCoin != "" { - base, err := strconv.ParseFloat(primary, 64) + p, err := strconv.ParseFloat(primary, 64) if err == nil { - currentSecondary := strconv.FormatFloat(base*td.CurrentSecondaryCoinRate, 'f', 2, 64) - blocktimeSecondary := "" - // if tx is specified, secondary amount is at the time of tx and current amount with class "csec-amt" + currentSecondary := strconv.FormatFloat(p*td.CurrentSecondaryCoinRate, 'f', 2, 64) + txSecondary := "" + // if tx is specified, compute secondary amount is at the time of tx and amount with current rate is returned with class "csec-amt" if td.Tx != nil { if td.Tx.Blocktime != td.TxBlocktime { td.TxBlocktime = td.Tx.Blocktime @@ -721,17 +735,18 @@ func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) t secondary := strings.ToLower(td.SecondaryCoin) ticker, _ := s.db.FiatRatesFindTicker(&date, secondary, "") if ticker != nil { - td.TxBlocktimeSecondaryCoinRate = float64(ticker.Rates[secondary]) + td.TxSecondaryCoinRate = float64(ticker.Rates[secondary]) // the ticker is from the midnight, valid for the whole day before td.TxDate = date.Add(-1 * time.Second).Format("2006-01-02") + td.TxTicker = ticker } } - if td.TxBlocktimeSecondaryCoinRate != 0 { - blocktimeSecondary = strconv.FormatFloat(base*td.TxBlocktimeSecondaryCoinRate, 'f', 2, 64) + if td.TxSecondaryCoinRate != 0 { + txSecondary = strconv.FormatFloat(p*td.TxSecondaryCoinRate, 'f', 2, 64) } } - if blocktimeSecondary != "" { - appendAmountSpan(&rv, "sec-amt", blocktimeSecondary, td.SecondaryCoin, td.TxDate) + if txSecondary != "" { + appendAmountSpan(&rv, "sec-amt", txSecondary, td.SecondaryCoin, td.TxDate) appendAmountSpan(&rv, "csec-amt", currentSecondary, td.SecondaryCoin, "") } else { appendAmountSpan(&rv, "sec-amt", currentSecondary, td.SecondaryCoin, "") @@ -742,6 +757,92 @@ func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) t return template.HTML(rv.String()) } +func (s *PublicServer) amountSatsSpan(a *api.Amount, td *TemplateData, classes string) template.HTML { + var sats string + if s.chainParser.GetChainType() == bchain.ChainEthereumType { + sats = a.DecimalString(9) // Gwei + } else { + sats = a.String() + } + var rv strings.Builder + rv.WriteString(``) + appendAmountSpan(&rv, "", sats, "", "") + rv.WriteString("") + return template.HTML(rv.String()) +} + +func getContractRate(ticker *common.CurrencyRatesTicker, contract string) (float64, bool) { + if ticker == nil { + return 0, false + } + rate, found := ticker.TokenRates[contract] + return float64(rate), found +} + +func (s *PublicServer) tokenAmountSpan(t *api.TokenTransfer, td *TemplateData, classes string) template.HTML { + primary := formatAmountWithDecimals(t.Value, t.Decimals) + var rv strings.Builder + appendWrappingAmountSpan(&rv, primary, td.CoinShortcut, classes) + appendAmountSpan(&rv, "prim-amt", primary, t.Symbol, "") + if td.SecondaryCoin != "" { + var currentBase, currentSecondary, txBase, txSecondary string + p, err := strconv.ParseFloat(primary, 64) + if err == nil { + ticker := td.CurrentTicker + baseRate, found := getContractRate(ticker, t.Contract) + if !found { + now := time.Now().UTC() + ticker, _ = s.db.FiatRatesFindTicker(&now, "", t.Contract) + baseRate, found = getContractRate(ticker, t.Contract) + } + if found { + base := p * baseRate + currentBase = strconv.FormatFloat(base, 'g', s.chainParser.AmountDecimals(), 64) + currentSecondary = strconv.FormatFloat(base*td.CurrentSecondaryCoinRate, 'f', 2, 64) + } + ticker = td.TxTicker + baseRate, found = getContractRate(ticker, t.Contract) + if !found { + ticker, _ = s.db.FiatRatesFindTicker(&td.TxTicker.Timestamp, "", t.Contract) + baseRate, found = getContractRate(ticker, t.Contract) + } + if found { + base := p * baseRate + txBase = strconv.FormatFloat(base, 'g', s.chainParser.AmountDecimals(), 64) + txSecondary = strconv.FormatFloat(base*td.CurrentSecondaryCoinRate, 'f', 2, 64) + } + } + if txBase != "" { + appendAmountSpan(&rv, "base-amt", txBase, td.CoinShortcut, td.TxDate) + if currentBase != "" { + appendAmountSpan(&rv, "cbase-amt", currentBase, td.CoinShortcut, "") + } + } else if currentBase != "" { + appendAmountSpan(&rv, "base-amt", currentBase, td.CoinShortcut, "") + } + if txSecondary != "" { + appendAmountSpan(&rv, "sec-amt", txSecondary, td.SecondaryCoin, td.TxDate) + if currentSecondary != "" { + appendAmountSpan(&rv, "csec-amt", currentSecondary, td.SecondaryCoin, "") + } + } else if currentSecondary != "" { + appendAmountSpan(&rv, "sec-amt", currentSecondary, td.SecondaryCoin, "") + } else { + appendAmountSpan(&rv, "sec-amt", "-", "", "") + } + } + rv.WriteString("") + return template.HTML(rv.String()) +} + func formatInt(i int) template.HTML { return formatInt64(int64(i)) } @@ -794,13 +895,50 @@ func formatInt64(i int64) template.HTML { return template.HTML(rv.String()) } +func formatBigInt(i *big.Int) template.HTML { + if i == nil { + return "" + } + s := i.String() + var rv strings.Builder + appendSeparatedNumberSpans(&rv, s, "ns") + return template.HTML(rv.String()) +} + +func addressAliasSpan(a string, td *TemplateData) template.HTML { + var alias api.AddressAlias + var found bool + if td.Block != nil { + alias, found = td.Block.AddressAliases[a] + } else if td.Address != nil { + alias, found = td.Address.AddressAliases[a] + } else if td.Tx != nil { + alias, found = td.Tx.AddressAliases[a] + } + var rv strings.Builder + if !found { + rv.WriteString(``) + rv.WriteString(a) + } else { + rv.WriteString(``) + rv.WriteString(alias.Alias) + } + rv.WriteString("") + return template.HTML(rv.String()) +} + // called from template to support txdetail.html functionality func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData { td.Tx = tx // reset the TxBlocktimeSecondaryCoinRate if different Blocktime if td.TxBlocktime != tx.Blocktime { td.TxBlocktime = 0 - td.TxBlocktimeSecondaryCoinRate = 0 + td.TxSecondaryCoinRate = 0 + td.TxTicker = nil } return td } @@ -875,7 +1013,7 @@ func (s *PublicServer) explorerSpendingTx(w http.ResponseWriter, r *http.Request if ec == nil { spendingTx, err := s.api.GetSpendingTxid(tx, n) if err == nil && spendingTx != "" { - http.Redirect(w, r, joinURL("/tx/", spendingTx), 302) + http.Redirect(w, r, joinURL("/tx/", spendingTx), http.StatusFound) return noTpl, nil, nil } } @@ -1102,22 +1240,22 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t if len(q) > 0 { address, err = s.api.GetXpubAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0) if err == nil { - http.Redirect(w, r, joinURL("/xpub/", url.QueryEscape(address.AddrStr)), 302) + http.Redirect(w, r, joinURL("/xpub/", url.QueryEscape(address.AddrStr)), http.StatusFound) return noTpl, nil, nil } block, err = s.api.GetBlock(q, 0, 1) if err == nil { - http.Redirect(w, r, joinURL("/block/", block.Hash), 302) + http.Redirect(w, r, joinURL("/block/", block.Hash), http.StatusFound) return noTpl, nil, nil } tx, err = s.api.GetTransaction(q, false, false) if err == nil { - http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302) + http.Redirect(w, r, joinURL("/tx/", tx.Txid), http.StatusFound) return noTpl, nil, nil } address, err = s.api.GetAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}) if err == nil { - http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302) + http.Redirect(w, r, joinURL("/address/", address.AddrStr), http.StatusFound) return noTpl, nil, nil } } @@ -1465,7 +1603,7 @@ func (s *PublicServer) apiSendTx(r *http.Request, apiVersion int) (interface{}, var hex string s.metrics.ExplorerViews.With(common.Labels{"action": "api-sendtx"}).Inc() if r.Method == http.MethodPost { - data, err := ioutil.ReadAll(r.Body) + data, err := io.ReadAll(r.Body) if err != nil { return nil, api.NewAPIError("Missing tx blob", true) } diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go index 41d31643..30d41661 100644 --- a/server/public_ethereumtype_test.go +++ b/server/public_ethereumtype_test.go @@ -24,7 +24,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Address 0.000000000123450123 FAKE

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

Confirmed

Balance0.000000000123450123 FAKE
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ERC20 Tokens
ContractTokensTransfers
Contract 740.001000123074 S741
Contract 130.000000001000123013 S131
ERC721 Tokens
ContractTokensTransfers
Contract 20511

Transactions

ERC721 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
ID 1 S205
Fee: 0.00008794500041041 FAKE
Unconfirmed Transaction!0 FAKE
ERC20 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
871.180000950184 S74
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
7.674999999999991915 S13
Fee: 0.000216368 FAKE
Unconfirmed Transaction!0 FAKE
`, + `Trezor Fake Coin Explorer

Address 0.000000000123450123 FAKE

0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b

Confirmed

Balance0.000000000123450123 FAKE
Transactions2
Non-contract Transactions0
Internal Transactions0
Nonce123
ERC20 Tokens
ContractTokensTransfers
Contract 740.001000123074 S741
Contract 130.000000001000123013 S131
ERC721 Tokens
ContractTokensTransfers
Contract 20511

Transactions

ERC721 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
ID 1 S205
Fee: 0.00008794500041041 FAKE
Unconfirmed Transaction!0 FAKE
ERC20 Token Transfers
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
871.180000950184 S74
0x7B62EB7fe80350DC7EC945C0B73242cb9877FB1b
7.674999999999991915 S13
Fee: 0.000216368 FAKE
Unconfirmed Transaction!0 FAKE
`, }, }, { @@ -33,7 +33,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Address 0.000000000123450093 FAKE

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

Confirmed

Balance0.000000000123450093 FAKE
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ERC1155 Tokens
ContractTokensTransfers
Contract 1111 of ID 1776, 10 of ID 18981

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
0 FAKE
ERC1155 Token Transfers
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1 S111 of ID 1776, 10 S111 of ID 1898
Fee: 0.000081891755740665 FAKE
Unconfirmed Transaction!0 FAKE
`, + `Trezor Fake Coin Explorer

Address 0.000000000123450093 FAKE

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e

Confirmed

Balance0.000000000123450093 FAKE
Transactions1
Non-contract Transactions1
Internal Transactions0
Nonce93
ERC1155 Tokens
ContractTokensTransfers
Contract 1111 of ID 1776, 10 of ID 18981

Transactions

0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
0 FAKE
ERC1155 Token Transfers
0x5Dc6288b35E0807A3d6fEB89b3a2Ff4aB773168e
1 S111 of ID 1776, 10 S111 of ID 1898
Fee: 0.000081891755740665 FAKE
Unconfirmed Transaction!0 FAKE
`, }, }, { @@ -42,7 +42,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101

Summary

In BlockUnconfirmed
StatusSuccess
Value0 FAKE
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE
Fees0.002081 FAKE
RBFON

Details

Input Data
Transfer
Method ID: 0xa9059cbb
Function: transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, + `Trezor Fake Coin Explorer

Transaction

0xa9cd088aba2131000da6f38a33c20169baee476218deea6b78720700b895b101

Summary

In BlockUnconfirmed
StatusSuccess
Value0 FAKE
Gas Used / Limit52025 / 78037
Gas Price0.00000004 FAKE
Fees0.002081 FAKE
RBFON

Details

Input Data
Transfer
Method ID: 0xa9059cbb
Function: transfer(address, uint256)
#TypeData
0address0x555Ee11FBDDc0E49A9bAB358A8941AD95fFDB48f
1uint25610000000000000000000000
Raw Transaction
`, }, }, { name: "explorerTokenDetail " + dbtestdata.EthAddr7b, @@ -204,6 +204,7 @@ func initTestFiatRatesEthereumType(d *db.RocksDB) error { } func Test_PublicServer_EthereumType(t *testing.T) { + timeNow = fixedTimeNow parser := eth.NewEthereumParser(1, true) chain, err := dbtestdata.NewFakeBlockChainEthereumType(parser) if err != nil { diff --git a/server/public_test.go b/server/public_test.go index 33039bc8..4b607456 100644 --- a/server/public_test.go +++ b/server/public_test.go @@ -269,14 +269,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Transaction

`, - `fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db`, - `td class="data">0 FAKE`, - `mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj`, - `13.60030331 FAKE`, - `No Inputs (Newly Generated Coins)`, - ``, + `Trezor Fake Coin Explorer

Transaction

fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db
Mined Time1639 days 11 hours ago
In Block00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
In Block Height225494
Total Input0 FAKE
Total Output13.60030331 FAKE
Fees0 FAKE
No Inputs (Newly Generated Coins)
 
Unparsed address0 FAKE×
Raw Transaction
`, }, }, { @@ -285,18 +278,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Address`, - `0.00012345 FAKE`, - `mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz`, - `0.00012345 FAKE`, - `7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25`, - `3172.83951061 FAKE `, - `mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `td>mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `9172.83951061 FAKE ×`, - `00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840`, - ``, + `Trezor Fake Coin Explorer

Address

mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz

0.00012345 FAKE

Confirmed
Total Received0.0002469 FAKE
Total Sent0.00012345 FAKE
Final Balance0.00012345 FAKE
No. Transactions2

Transactions

mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE
 
OP_RETURN 2020f1686f6a200 FAKE×
No Inputs
 
mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE
mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE×
`, }, }, { @@ -305,11 +287,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Transaction

`, - `3d90d15ed026dc45e19ffb52875ed18fa9e8012ad123d7f7212176e2b0ebdb71`, - `0.00000062 FAKE`, - ``, + `Trezor Fake Coin Explorer

Transaction

3d90d15ed026dc45e19ffb52875ed18fa9e8012ad123d7f7212176e2b0ebdb71
Mined Time1639 days 11 hours ago
In Block00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
In Block Height225494
Total Input3172.83951062 FAKE
Total Output3172.83951 FAKE
Fees0.00000062 FAKE
Raw Transaction
`, }, }, { @@ -318,10 +296,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Error

`, - `

Transaction not found

`, - ``, + `Trezor Fake Coin Explorer

Error

Transaction not found

`, }, }, { @@ -330,14 +305,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Blocks`, - `225494`, - `00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6`, - `0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997`, - `2`, - `1234567`, - ``, + `Trezor Fake Coin Explorer

Blocks

HeightHashTimestampTransactionsSize
22549400000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b61639 days 11 hours ago42345678
2254930000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e29971640 days 9 hours ago21234567
`, }, }, { @@ -346,14 +314,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Block 225494

`, - `00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6`, - `4`, // number of transactions - `mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `9172.83951061 FAKE`, - `fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db`, - ``, + `Trezor Fake Coin Explorer

Block

225494
00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
Transactions4
Height225494
Confirmations1
Timestamp1639 days 11 hours ago
Size (bytes)2345678
Version
Merkle Root
Nonce
Bits
Difficulty

Transactions

 
OP_RETURN 2020f1686f6a200 FAKE×
No Inputs (Newly Generated Coins)
 
Unparsed address0 FAKE×
`, }, }, { @@ -362,12 +323,9 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Application status

`, - `

Synchronization with backend is disabled, the state of index is not up to date.

`, - `225494`, - `/Fakecoin:0.0.1/`, - ``, + `Trezor Fake Coin Explorer

Application status

Synchronization with backend is disabled, the state of index is not up to date.

`, + `

Blockbook

CoinFakecoin
Host
Version / Commit / Buildunknown / unknown / unknown
Synchronized
true
Last Block225494
Last Block Update`, + `
Mempool in Sync
false
Last Mempool Update
Transactions in Mempool0
Size On Disk

Backend

Chainfakecoin
Version001001
Subversion/Fakecoin:0.0.1/
Last Block2
Difficulty
Blockbook - blockchain indexer for Trezor wallet https://trezor.io/. Do not use for any other purpose.
`, }, }, { @@ -376,14 +334,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Block 225494

`, - `00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6`, - `4`, // number of transactions - `mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `9172.83951061 FAKE`, - `fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db`, - ``, + `Trezor Fake Coin Explorer

Block

225494
00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
Transactions4
Height225494
Confirmations1
Timestamp1639 days 11 hours ago
Size (bytes)2345678
Version
Merkle Root
Nonce
Bits
Difficulty

Transactions

 
OP_RETURN 2020f1686f6a200 FAKE×
No Inputs (Newly Generated Coins)
 
Unparsed address0 FAKE×
`, }, }, { @@ -392,14 +343,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Block 225494

`, - `00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6`, - `4`, // number of transactions - `mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `9172.83951061 FAKE`, - `fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db`, - ``, + `Trezor Fake Coin Explorer

Block

225494
00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
Transactions4
Height225494
Confirmations1
Timestamp1639 days 11 hours ago
Size (bytes)2345678
Version
Merkle Root
Nonce
Bits
Difficulty

Transactions

 
OP_RETURN 2020f1686f6a200 FAKE×
No Inputs (Newly Generated Coins)
 
Unparsed address0 FAKE×
`, }, }, { @@ -408,14 +352,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Transaction

`, - `fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db`, - `td class="data">0 FAKE`, - `mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj`, - `13.60030331 FAKE`, - `No Inputs (Newly Generated Coins)`, - ``, + `Trezor Fake Coin Explorer

Transaction

fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db
Mined Time1639 days 11 hours ago
In Block00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6
In Block Height225494
Total Input0 FAKE
Total Output13.60030331 FAKE
Fees0 FAKE
No Inputs (Newly Generated Coins)
 
Unparsed address0 FAKE×
Raw Transaction
`, }, }, { @@ -424,18 +361,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Address`, - `0.00012345 FAKE`, - `mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz`, - `0.00012345 FAKE`, - `7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25`, - `3172.83951061 FAKE `, - `mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `td>mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL`, - `9172.83951061 FAKE ×`, - `00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840`, - ``, + `Trezor Fake Coin Explorer

Address

mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz

0.00012345 FAKE

Confirmed
Total Received0.0002469 FAKE
Total Sent0.00012345 FAKE
Final Balance0.00012345 FAKE
No. Transactions2

Transactions

mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE
 
OP_RETURN 2020f1686f6a200 FAKE×
No Inputs
 
mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE
mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz0.00012345 FAKE×
`, }, }, { @@ -444,16 +370,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

XPUB 1186.419755 FAKE

upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q
`, - `Total Received1186.41975501 FAKE`, - `Total Sent0.00000001 FAKE`, - `Used XPUB Addresses2`, - `2N6utyMZfPNUb1Bk8oz7p2JqJrXkq83gegu1186.419755 FAKE1m/49'/1'/33'/1/3`, - ``, - ``, - `2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu10.00009876 FAKE `, - ``, + `Trezor Fake Coin Explorer

XPUB

upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q

1186.419755 FAKE

Confirmed
Total Received1186.41975501 FAKE
Total Sent0.00000001 FAKE
Final Balance1186.419755 FAKE
No. Transactions2
Used XPUB Addresses2
XPUB Addresses with Balance
AddressBalanceTxsPath
2N6utyMZfPNUb1Bk8oz7p2JqJrXkq83gegu1186.419755 FAKE1m/49'/1'/33'/1/3

Transactions

`, }, }, { @@ -462,12 +379,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

XPUB 0 FAKE

tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1}/*)#4rqwxvej

Confirmed

`, - `Total Received0 FAKE`, - `Total Sent0 FAKE`, - `Used XPUB Addresses0`, - ``, + `Trezor Fake Coin Explorer

XPUB

tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1}/*)#4rqwxvej

0 FAKE

Confirmed
Total Received0 FAKE
Total Sent0 FAKE
Final Balance0 FAKE
No. Transactions0
Used XPUB Addresses0
XPUB Addresses with Balance
No addresses
`, }, }, { @@ -476,10 +388,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Error

`, - `

No matching records found for '1234'

`, - ``, + `Trezor Fake Coin Explorer

Error

No matching records found for '1234'

`, }, }, { @@ -488,10 +397,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Send Raw Transaction

`, - ``, - ``, + `Trezor Fake Coin Explorer

Send Raw Transaction

`, }, }, { @@ -500,11 +406,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) { status: http.StatusOK, contentType: "text/html; charset=utf-8", body: []string{ - `Fake Coin Explorer`, - `

Send Raw Transaction

`, - ``, - `
Invalid data
`, - ``, + `Trezor Fake Coin Explorer

Send Raw Transaction

Invalid data
`, }, }, { @@ -1582,7 +1484,13 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) { } } +// fixedTimeNow returns always 2022-09-15 12:43:56 UTC +func fixedTimeNow() time.Time { + return time.Date(2022, 9, 15, 12, 43, 56, 0, time.UTC) +} + func Test_PublicServer_BitcoinType(t *testing.T) { + timeNow = fixedTimeNow parser := btc.NewBitcoinParser( btc.GetChainParams("test"), &btc.Configuration{ @@ -1635,3 +1543,110 @@ func Test_formatInt64(t *testing.T) { }) } } + +func Test_formatTime(t *testing.T) { + timeNow = fixedTimeNow + tests := []struct { + name string + want template.HTML + }{ + { + name: "2020-12-23 15:16:17", + want: `630 days 21 hours ago`, + }, + { + name: "2022-08-23 11:12:13", + want: `23 days 1 hour ago`, + }, + { + name: "2022-09-14 11:12:13", + want: `1 day 1 hour ago`, + }, + { + name: "2022-09-14 14:12:13", + want: `22 hours 31 mins ago`, + }, + { + name: "2022-09-15 09:33:26", + want: `3 hours 10 mins ago`, + }, + { + name: "2022-09-15 12:23:56", + want: `20 mins ago`, + }, + { + name: "2022-09-15 12:24:07", + want: `19 mins ago`, + }, + { + name: "2022-09-15 12:43:21", + want: `35 secs ago`, + }, + { + name: "2022-09-15 12:43:56", + want: `0 secs ago`, + }, + { + name: "2022-09-16 12:43:56", + want: `2022-09-16 12:43:56`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tm, _ := time.Parse("2006-01-02 15:04:05", tt.name) + if got := timeSpan(&tm); !reflect.DeepEqual(got, tt.want) { + t.Errorf("formatTime() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_appendAmountSpan(t *testing.T) { + tests := []struct { + name string + class string + amount string + shortcut string + txDate string + want string + }{ + { + name: "prim-amt 1.23456789 BTC", + class: "prim-amt", + amount: "1.23456789", + shortcut: "BTC", + want: `1.23456789 BTC`, + }, + { + name: "prim-amt 1432134.23456 BTC", + class: "prim-amt", + amount: "1432134.23456", + shortcut: "BTC", + want: `1432134.23456 BTC`, + }, + { + name: "sec-amt 431341.23 EUR", + class: "sec-amt", + amount: "4321341.23", + shortcut: "EUR", + want: `4321341.23 EUR`, + }, + { + name: "sec-amt 43141.29 EUR", + class: "sec-amt", + amount: "43141.29", + shortcut: "EUR", + txDate: "2022-03-14", + want: `43141.29 EUR`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var rv strings.Builder + appendAmountSpan(&rv, tt.class, tt.amount, tt.shortcut, tt.txDate) + if got := rv.String(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("formatTime() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/static/css/main.css b/static/css/main.css index 5914af12..0e520cbc 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -63,6 +63,12 @@ select { min-height: 50px; } +.form-control:focus { + outline: 0; + box-shadow: none; + border-color: #00854d; +} + .badge { vertical-align: middle; filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.15)); @@ -73,6 +79,20 @@ select { --bs-badge-border-radius: 0.6rem; } +.accordion { + --bs-accordion-border-radius: 10px; + --bs-accordion-inner-border-radius: calc(10px - 1px); + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-active-color: var(--bs-body-color); + --bs-accordion-active-bg: white; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,"); +} + +.accordion-button:focus { + outline: 0; + box-shadow: none; +} + .bb-group { border: 0.6rem solid #f6f6f6; background-color: #f6f6f6; @@ -180,7 +200,7 @@ span.btn-paging:hover { position: absolute; top: 16px; background-size: cover; - background-image: url("data:image/svg+xml, %3Csvg style='background: white%3B' width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.24976 12.5C10.1493 12.5 12.4998 10.1495 12.4998 7.25C12.4998 4.35051 10.1493 2 7.24976 2C4.35026 2 1.99976 4.35051 1.99976 7.25C1.99976 10.1495 4.35026 12.5 7.24976 12.5Z' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3Cpath d='M10.962 10.9625L13.9996 14.0001' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3C/svg%3E"); + background-image: url("data:image/svg+xml, %3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7.24976 12.5C10.1493 12.5 12.4998 10.1495 12.4998 7.25C12.4998 4.35051 10.1493 2 7.24976 2C4.35026 2 1.99976 4.35051 1.99976 7.25C1.99976 10.1495 4.35026 12.5 7.24976 12.5Z' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3Cpath d='M10.962 10.9625L13.9996 14.0001' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' /%3E%3C/svg%3E"); } .navbar-form ::placeholder { @@ -251,6 +271,10 @@ span.btn-paging:hover { color: var(--bs-body-color); } +.accordion .table.data-table>thead>*>* { + padding-bottom: 0; +} + .info-table tbody { display: inline-table; width: 100%; @@ -339,12 +363,16 @@ span.btn-paging:hover { max-width: 100%; } -.tx-detail>.head { +.tx-detail>.head, +.tx-detail>.footer { padding: 1.5rem; - border-radius: 10px 10px 0 0; --bs-gutter-x: 0; } +.tx-detail>.head { + border-radius: 10px 10px 0 0; +} + .tx-detail .txid { font-size: 106%; letter-spacing: -0.01em; @@ -356,20 +384,38 @@ span.btn-paging:hover { letter-spacing: -0.01em; } -.tx-detail>.footer { - padding: 1.5rem; + +.tx-detail>.subhead { + padding: 1.5rem 1.5rem 0.4rem 1.5rem; --bs-gutter-x: 0; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--bs-body-color); +} + +.tx-detail>.subhead-2 { + padding: 0.3rem 1.5rem 0 1.5rem; + --bs-gutter-x: 0; + font-size: .875em; + color: var(--bs-body-color); } .tx-in .col-12, -.tx-out .col-12 { +.tx-out .col-12, +.tx-addr .col-12 { background-color: white; padding: 1.2rem 1.3rem; border-bottom: 1px solid #f6f6f6; } +.amt-out { + padding: 1.2rem 0 1.2rem 1rem; + text-align: right; + overflow-wrap: break-word; +} + .tx-in .col-12:last-child, -.tx-out .co-12l:last-child { +.tx-out .col-12:last-child { border-bottom: none; } @@ -427,8 +473,15 @@ span.btn-paging:hover { color: white; } +.txerror .copyable::before, +.txerror .copied::before { + /* turn svg stroke to white */ + filter: hue-rotate(180deg) brightness(1000%) contrast(100%); +} + .tx-amt .amt:hover, -.tx-amt.amt:hover { +.tx-amt.amt:hover, +.amt-out>.amt:hover { color: var(--bs-body-color); } @@ -444,9 +497,17 @@ span.btn-paging:hover { display: none; } +.base-amt { + display: none; +} + +.cbase-amt { + display: none; +} + .tooltip { --bs-tooltip-opacity: 1; - --bs-tooltip-max-width: 320px; + --bs-tooltip-max-width: 380px; --bs-tooltip-bg: #fff; --bs-tooltip-color: var(--bs-body-color); --bs-tooltip-padding-x: 1rem; @@ -454,27 +515,29 @@ span.btn-paging:hover { filter: drop-shadow(0px 24px 64px rgba(22, 27, 45, 0.25)); } -.amt-tooltip { +.l-tooltip { text-align: start; display: inline-block; } -.amt-tooltip .prim-amt, -.amt-tooltip .sec-amt, -.amt-tooltip .csec-amt { +.l-tooltip .prim-amt, +.l-tooltip .sec-amt, +.l-tooltip .csec-amt, +.l-tooltip .base-amt, +.l-tooltip .cbase-amt { display: initial; float: right; } -.amt-dec { - font-size: 97%; -} - -.amt-tooltip .amt-time { +.l-tooltip .amt-time { padding-right: 3rem; float: left; } +.amt-dec { + font-size: 95%; +} + .unconfirmed { color: white; background-color: #c51e13; @@ -539,6 +602,16 @@ span.btn-paging:hover { .table.data-table>:not(caption)>*>* { padding: 0.8rem 0.4rem; } + + .tx-in .col-12, + .tx-out .col-12, + .tx-addr .col-12 { + padding: 0.7rem 1.1rem; + } + + .amt-out { + padding: 0.7rem 0 0.7rem 1rem + } } @media (min-width: 769px) { diff --git a/static/js/main.js b/static/js/main.js index 77755139..3b8eb37e 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -50,7 +50,19 @@ function amountTooltip() { const prim = this.querySelector(".prim-amt"); const sec = this.querySelector(".sec-amt"); const csec = this.querySelector(".csec-amt"); + const base = this.querySelector(".base-amt"); + const cbase = this.querySelector(".cbase-amt"); let s = `${prim.outerHTML}
`; + if (base) { + let t = base.getAttribute("tm"); + if (!t) { + t = "now"; + } + s += `${t}${base.outerHTML}
`; + } + if (cbase) { + s += `now${cbase.outerHTML}
`; + } if (sec) { let t = sec.getAttribute("tm"); if (!t) { @@ -59,9 +71,15 @@ function amountTooltip() { s += `${t}${sec.outerHTML}
`; } if (csec) { - s += `now${csec.outerHTML}`; + s += `now${csec.outerHTML}
`; } - return `${s}`; + return `${s}`; +} + +function addressAliasTooltip() { + const type = this.getAttribute("alias-type"); + const address = this.getAttribute("cc"); + return `${type}
${address}
`; } window.addEventListener("DOMContentLoaded", () => { @@ -78,6 +96,13 @@ window.addEventListener("DOMContentLoaded", () => { ); } + document + .querySelectorAll("[alias-type]") + .forEach( + (e) => + new bootstrap.Tooltip(e, { title: addressAliasTooltip, html: true }) + ); + document .querySelectorAll("[tt]") .forEach((e) => new bootstrap.Tooltip(e, { title: e.getAttribute("tt") })); diff --git a/static/templates/address.html b/static/templates/address.html index 5af0b4cb..1fb0e1eb 100644 --- a/static/templates/address.html +++ b/static/templates/address.html @@ -3,7 +3,7 @@

{{if $addr.ContractInfo}}Contract {{$addr.ContractInfo.Name}}{{if $addr.ContractInfo.Symbol}} ({{$addr.ContractInfo.Symbol}}){{end}}{{else}}Address{{end}}

{{$addr.AddrStr}}
-

{{amount $addr.BalanceSat $data "copyable"}}

+

{{amountSpan $addr.BalanceSat $data "copyable"}}

@@ -42,7 +42,7 @@ {{end}} Balance - {{amount $addr.BalanceSat $data "copyable"}} + {{amountSpan $addr.BalanceSat $data "copyable"}} Transactions @@ -74,7 +74,7 @@ {{range $t := $addr.Tokens}} {{if eq $t.Type "ERC20"}} - {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} + {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} {{formatAmountWithDecimals $t.BalanceSat $t.Decimals}} {{$t.Symbol}} {{$t.Transfers}} @@ -99,7 +99,7 @@ {{range $t := $addr.Tokens}} {{if eq $t.Type "ERC721"}} - {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} + {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} {{range $i, $iv := $t.Ids}}{{if $i}}, {{end}}{{formatAmountWithDecimals $iv 0}}{{end}} @@ -126,7 +126,7 @@ {{range $t := $addr.Tokens}} {{if eq $t.Type "ERC1155"}} - {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} + {{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}} {{range $i, $iv := $t.MultiTokenValues}}{{if $i}}, {{end}}{{$iv.Value}} of ID {{$iv.Id}}{{end}} @@ -143,15 +143,15 @@ {{else}} Total Received - {{amount $addr.TotalReceivedSat $data "copyable"}} + {{amountSpan $addr.TotalReceivedSat $data "copyable"}} Total Sent - {{amount $addr.TotalSentSat $data "copyable"}} + {{amountSpan $addr.TotalSentSat $data "copyable"}} Final Balance - {{amount $addr.BalanceSat $data "copyable"}} + {{amountSpan $addr.BalanceSat $data "copyable"}} No. Transactions @@ -169,7 +169,7 @@ Unconfirmed Balance - {{amount $addr.UnconfirmedBalanceSat $data "copyable"}} + {{amountSpan $addr.UnconfirmedBalanceSat $data "copyable"}} No. Transactions diff --git a/static/templates/block.html b/static/templates/block.html index 9edcfd24..600175b9 100644 --- a/static/templates/block.html +++ b/static/templates/block.html @@ -28,7 +28,7 @@ Timestamp - {{formatUnixTime $b.Time}} + {{unixTimeSpan $b.Time}} Size (bytes) diff --git a/static/templates/blocks.html b/static/templates/blocks.html index 23017a6e..faa29d2f 100644 --- a/static/templates/blocks.html +++ b/static/templates/blocks.html @@ -20,7 +20,7 @@ {{formatUint32 $b.Height}} {{$b.Hash}} - {{formatUnixTime $b.Time}} + {{unixTimeSpan $b.Time}} {{formatUint32 $b.Txs}} {{formatUint32 $b.Size}} diff --git a/static/templates/index.html b/static/templates/index.html index b7e23060..8e8922f6 100644 --- a/static/templates/index.html +++ b/static/templates/index.html @@ -36,7 +36,7 @@ Last Block Update - {{formatTime $bb.LastBlockTime}} + {{timeSpan $bb.LastBlockTime}} Mempool in Sync @@ -44,7 +44,7 @@ Last Mempool Update - {{formatTime $bb.LastMempoolTime}} + {{timeSpan $bb.LastMempoolTime}} Transactions in Mempool @@ -53,11 +53,11 @@ {{if $bb.HasFiatRates}} Current Fiat rates - {{formatTime $bb.CurrentFiatRatesTime}} + {{timeSpan $bb.CurrentFiatRatesTime}} Historical Fiat rates - {{formatTime $bb.HistoricalFiatRatesTime}}{{if $bb.HasTokenFiatRates}}
tokens {{formatTime $bb.HistoricalTokenFiatRatesTime}}{{end}} + {{timeSpan $bb.HistoricalFiatRatesTime}}{{if $bb.HasTokenFiatRates}}
tokens {{timeSpan $bb.HistoricalTokenFiatRatesTime}}{{end}} {{end}} @@ -77,7 +77,7 @@ {{if $be.BackendError}} Backend Error - {{$be.BackendError}} + {{$be.BackendError}} {{end}} @@ -135,7 +135,7 @@ {{if $be.Warnings}} Warnings - {{$be.Warnings}} + {{$be.Warnings}} {{end}} diff --git a/static/templates/mempool.html b/static/templates/mempool.html index 44da108e..61cf00bf 100644 --- a/static/templates/mempool.html +++ b/static/templates/mempool.html @@ -15,7 +15,7 @@ {{range $tx := $txs}} {{$tx.Txid}} - {{formatUnixTime $tx.Time}} + {{unixTimeSpan $tx.Time}} {{end}} diff --git a/static/templates/tokenDetail.html b/static/templates/tokenDetail.html index ecc7687a..b09abe10 100644 --- a/static/templates/tokenDetail.html +++ b/static/templates/tokenDetail.html @@ -2,32 +2,32 @@

NFT Token Detail

- +
- + - + - + - + - +
Token ID{{$data.TokenId}}{{$data.TokenId}}
Contract{{$data.ContractInfo.Contract}} {{$data.ContractInfo.Name}}{{$data.ContractInfo.Contract}}
{{$data.ContractInfo.Name}}
Contract type{{$data.ContractInfo.Type}}{{$data.ContractInfo.Type}}
-
+
Metadata
@@ -38,7 +38,7 @@