Explorer redesign part 4

This commit is contained in:
Martin Boehm 2022-11-24 01:04:20 +01:00 committed by Martin
parent 12ca86c601
commit 03f72f07f2
15 changed files with 697 additions and 525 deletions

View File

@ -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("</span>")
}
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(`<span class="amt`)
if classes != "" {
rv.WriteString(` `)
@ -705,15 +713,21 @@ func (s *PublicServer) amount(a *api.Amount, td *TemplateData, classes string) t
rv.WriteString(`" cc="`)
rv.WriteString(primary)
rv.WriteString(" ")
rv.WriteString(td.CoinShortcut)
rv.WriteString(symbol)
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(`<span`)
if classes != "" {
rv.WriteString(` class="`)
rv.WriteString(classes)
rv.WriteString(`"`)
}
rv.WriteString(` cc="`)
rv.WriteString(sats)
rv.WriteString(`">`)
appendAmountSpan(&rv, "", sats, "", "")
rv.WriteString("</span>")
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("</span>")
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(`<span class="copyable">`)
rv.WriteString(a)
} else {
rv.WriteString(`<span class="copyable" cc="`)
rv.WriteString(a)
rv.WriteString(`" alias-type="`)
rv.WriteString(alias.Type)
rv.WriteString(`">`)
rv.WriteString(alias.Alias)
}
rv.WriteString("</span>")
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)
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='black'><path fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/></svg>");
}
.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) {

View File

@ -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}<br>`;
if (base) {
let t = base.getAttribute("tm");
if (!t) {
t = "now";
}
s += `<span class="amt-time">${t}</span>${base.outerHTML}<br>`;
}
if (cbase) {
s += `<span class="amt-time">now</span>${cbase.outerHTML}<br>`;
}
if (sec) {
let t = sec.getAttribute("tm");
if (!t) {
@ -59,9 +71,15 @@ function amountTooltip() {
s += `<span class="amt-time">${t}</span>${sec.outerHTML}<br>`;
}
if (csec) {
s += `<span class="amt-time">now</span>${csec.outerHTML}`;
s += `<span class="amt-time">now</span>${csec.outerHTML}<br>`;
}
return `<span class="amt-tooltip">${s}</span>`;
return `<span class="l-tooltip">${s}</span>`;
}
function addressAliasTooltip() {
const type = this.getAttribute("alias-type");
const address = this.getAttribute("cc");
return `<span class="l-tooltip">${type}<br>${address}</span>`;
}
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") }));

View File

@ -3,7 +3,7 @@
<div class="col-md-10 order-2 order-md-1">
<h1>{{if $addr.ContractInfo}}Contract {{$addr.ContractInfo.Name}}{{if $addr.ContractInfo.Symbol}} ({{$addr.ContractInfo.Symbol}}){{end}}{{else}}Address{{end}}</h1>
<h5 class="col-12 d-flex h-data pb-2"><span class="ellipsis copyable">{{$addr.AddrStr}}</span></h5>
<h4>{{amount $addr.BalanceSat $data "copyable"}}</h4>
<h4>{{amountSpan $addr.BalanceSat $data "copyable"}}</h4>
</div>
<div class="col-md-2 order-1 order-md-2 d-flex justify-content-center justify-content-md-end mb-3 mb-md-0">
<div id="qrcode"></div>
@ -42,7 +42,7 @@
{{end}}
<tr>
<td style="width: 25%;">Balance</td>
<td>{{amount $addr.BalanceSat $data "copyable"}}</td>
<td>{{amountSpan $addr.BalanceSat $data "copyable"}}</td>
</tr>
<tr>
<td>Transactions</td>
@ -74,7 +74,7 @@
{{range $t := $addr.Tokens}}
{{if eq $t.Type "ERC20"}}
<tr>
<td class="data ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td class="ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td>{{formatAmountWithDecimals $t.BalanceSat $t.Decimals}} {{$t.Symbol}}</td>
<td>{{$t.Transfers}}</td>
</tr>
@ -99,7 +99,7 @@
{{range $t := $addr.Tokens}}
{{if eq $t.Type "ERC721"}}
<tr>
<td class="data ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td class="ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td>
{{range $i, $iv := $t.Ids}}{{if $i}}, {{end}}<a href="/nft/{{$t.Contract}}/{{formatAmountWithDecimals $iv 0}}">{{formatAmountWithDecimals $iv 0}}</a>{{end}}
</td>
@ -126,7 +126,7 @@
{{range $t := $addr.Tokens}}
{{if eq $t.Type "ERC1155"}}
<tr>
<td class="data ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td class="ellipsis"><a href="/address/{{$t.Contract}}">{{if $t.Name}}{{$t.Name}}{{else}}{{$t.Contract}}{{end}}</a></td>
<td>
{{range $i, $iv := $t.MultiTokenValues}}{{if $i}}, {{end}}{{$iv.Value}} of ID <a href="/nft/{{$t.Contract}}/{{$iv.Id}}">{{$iv.Id}}</a>{{end}}
</td>
@ -143,15 +143,15 @@
{{else}}
<tr>
<td style="width: 25%;">Total Received</td>
<td>{{amount $addr.TotalReceivedSat $data "copyable"}}</td>
<td>{{amountSpan $addr.TotalReceivedSat $data "copyable"}}</td>
</tr>
<tr>
<td>Total Sent</td>
<td>{{amount $addr.TotalSentSat $data "copyable"}}</td>
<td>{{amountSpan $addr.TotalSentSat $data "copyable"}}</td>
</tr>
<tr>
<td>Final Balance</td>
<td>{{amount $addr.BalanceSat $data "copyable"}}</td>
<td>{{amountSpan $addr.BalanceSat $data "copyable"}}</td>
</tr>
<tr>
<td>No. Transactions</td>
@ -169,7 +169,7 @@
</tr>
<tr>
<td style="width: 25%;">Unconfirmed Balance</td>
<td>{{amount $addr.UnconfirmedBalanceSat $data "copyable"}}</td>
<td>{{amountSpan $addr.UnconfirmedBalanceSat $data "copyable"}}</td>
</tr>
<tr>
<td>No. Transactions</td>

View File

@ -28,7 +28,7 @@
</tr>
<tr>
<td>Timestamp</td>
<td>{{formatUnixTime $b.Time}}</td>
<td>{{unixTimeSpan $b.Time}}</td>
</tr>
<tr>
<td>Size (bytes)</td>

View File

@ -20,7 +20,7 @@
<tr>
<td><a href="/block/{{$b.Height}}">{{formatUint32 $b.Height}}</a></td>
<td class="ellipsis">{{$b.Hash}}</td>
<td>{{formatUnixTime $b.Time}}</td>
<td>{{unixTimeSpan $b.Time}}</td>
<td class="text-end">{{formatUint32 $b.Txs}}</td>
<td class="text-end">{{formatUint32 $b.Size}}</td>
</tr>

View File

@ -36,7 +36,7 @@
</tr>
<tr>
<td>Last Block Update</td>
<td>{{formatTime $bb.LastBlockTime}}</td>
<td>{{timeSpan $bb.LastBlockTime}}</td>
</tr>
<tr>
<td>Mempool in Sync</td>
@ -44,7 +44,7 @@
</tr>
<tr>
<td>Last Mempool Update</td>
<td>{{formatTime $bb.LastMempoolTime}}</td>
<td>{{timeSpan $bb.LastMempoolTime}}</td>
</tr>
<tr>
<td>Transactions in Mempool</td>
@ -53,11 +53,11 @@
{{if $bb.HasFiatRates}}
<tr>
<td>Current Fiat rates</td>
<td>{{formatTime $bb.CurrentFiatRatesTime}}</td>
<td>{{timeSpan $bb.CurrentFiatRatesTime}}</td>
</tr>
<tr>
<td>Historical Fiat rates</td>
<td>{{formatTime $bb.HistoricalFiatRatesTime}}{{if $bb.HasTokenFiatRates}}<br>tokens {{formatTime $bb.HistoricalTokenFiatRatesTime}}{{end}}</td>
<td>{{timeSpan $bb.HistoricalFiatRatesTime}}{{if $bb.HasTokenFiatRates}}<br><span class="fw-normal">tokens</span> {{timeSpan $bb.HistoricalTokenFiatRatesTime}}{{end}}</td>
</tr>
{{end}}
<tr>
@ -77,7 +77,7 @@
{{if $be.BackendError}}
<tr>
<td>Backend Error</td>
<td class="data text-danger">{{$be.BackendError}}</td>
<td class="text-danger">{{$be.BackendError}}</td>
</tr>
{{end}}
<tr>
@ -135,7 +135,7 @@
{{if $be.Warnings}}
<tr>
<td>Warnings</td>
<td class="data text-warning">{{$be.Warnings}}</td>
<td class="text-warning">{{$be.Warnings}}</td>
</tr>
{{end}}
</tbody>

View File

@ -15,7 +15,7 @@
{{range $tx := $txs}}
<tr>
<td class="ellipsis"><a href="/tx/{{$tx.Txid}}">{{$tx.Txid}}</a></td>
<td>{{formatUnixTime $tx.Time}}</td>
<td>{{unixTimeSpan $tx.Time}}</td>
</tr>
{{end}}
</tbody>

View File

@ -2,32 +2,32 @@
<h1>NFT Token Detail</h1>
<div class="row">
<div class="col-md-6">
<table class="table data-table">
<table class="table data-table info-table">
<tbody>
<tr>
<td style="width: 25%;">Token ID</td>
<td class="data">{{$data.TokenId}}</td>
<td>{{$data.TokenId}}</td>
</tr>
<tr id="name" style="display: none;">
<td>NTF Name</td>
<td class="data"></td>
<td></td>
</tr>
<tr id="description" style="display: none;">
<td>NTF Description</td>
<td class="data"></td>
<td></td>
</tr>
<tr>
<td>Contract</td>
<td class="data"><a href="/address/{{$data.ContractInfo.Contract}}">{{$data.ContractInfo.Contract}}</a> {{$data.ContractInfo.Name}}</td>
<td><a href="/address/{{$data.ContractInfo.Contract}}">{{$data.ContractInfo.Contract}}</a><br>{{$data.ContractInfo.Name}}</td>
</tr>
<tr>
<td>Contract type</td>
<td class="data">{{$data.ContractInfo.Type}}</td>
<td>{{$data.ContractInfo.Type}}</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-6" id="image"></div>
<div class="col-md-6 mt-4" id="image"></div>
</div>
<div id="metadatablock">
<h5>Metadata</h5>
@ -38,7 +38,7 @@
<script type="text/javascript">
function showImage(s) {
const img = document.createElement("img");
img.className="border w-100";
img.className="border w-100 bg-white";
img.src = s;
const src = document.getElementById("image");
src.appendChild(img);
@ -46,7 +46,7 @@
}
function nftInfo(id,text) {
const src = document.getElementById(id);
src.getElementsByClassName('data')[0].innerText=text;
src.getElementsByTagName("td")[1].innerText=text;
src.style.display='';
}
async function getMetadata(url) {

View File

@ -10,7 +10,7 @@
{{if $tx.Confirmations}}
<tr>
<td>Mined Time</td>
<td>{{formatUnixTime $tx.Blocktime}}</td>
<td>{{unixTimeSpan $tx.Blocktime}}</td>
</tr>
{{end}}
<tr>
@ -29,7 +29,7 @@
{{if eq $tx.EthereumSpecific.Status 1}}
<td><span class="badge bg-success">Success</span></td>
{{else}}
{{if eq $tx.EthereumSpecific.Status 1}}
{{if eq $tx.EthereumSpecific.Status -1}}
<td>Pending</td>
{{else}}
<td>Unknown</td>
@ -41,24 +41,24 @@
</tr>
<tr>
<td>Value</td>
<td>{{amount $tx.ValueOutSat $data "copyable"}}</td>
<td>{{amountSpan $tx.ValueOutSat $data "copyable"}}</td>
</tr>
<tr>
<td>Gas Used / Limit</td>
<td>{{if $tx.EthereumSpecific.GasUsed}}{{$tx.EthereumSpecific.GasUsed}}{{else}}pending{{end}} / {{$tx.EthereumSpecific.GasLimit}}</td>
<td>{{if $tx.EthereumSpecific.GasUsed}}{{formatBigInt $tx.EthereumSpecific.GasUsed}}{{else}}pending{{end}} / {{formatBigInt $tx.EthereumSpecific.GasLimit}}</td>
</tr>
<tr>
<td>Gas Price</td>
<td>{{amount $tx.EthereumSpecific.GasPrice $data "copyable"}}</td>
<td>{{amountSpan $tx.EthereumSpecific.GasPrice $data "copyable"}} <span class="fw-normal ps-3">({{amountSatsSpan $tx.EthereumSpecific.GasPrice $data "copyable"}} Gwei)</span></td>
</tr>
{{else}}
<tr>
<td>Total Input</td>
<td>{{amount $tx.ValueInSat $data "copyable"}}</td>
<td>{{amountSpan $tx.ValueInSat $data "copyable"}}</td>
</tr>
<tr>
<td>Total Output</td>
<td>{{amount $tx.ValueOutSat $data "copyable"}}</td>
<td>{{amountSpan $tx.ValueOutSat $data "copyable"}}</td>
</tr>
{{if $tx.VSize}}
<tr>
@ -77,7 +77,7 @@
{{if $tx.FeesSat}}
<tr>
<td>Fees</td>
<td>{{amount $tx.FeesSat $data "copyable"}}{{if $tx.Size}} ({{feePerByte $tx}}){{end}}</td>
<td>{{amountSpan $tx.FeesSat $data "copyable"}}{{if $tx.Size}} ({{feePerByte $tx}}){{end}}</td>
</tr>{{end}}
{{if not $tx.Confirmations}}
<tr>
@ -93,51 +93,63 @@
{{end}}
</tbody>
</table>
<h3>Details</h3>
<div>
<div class="pt-1">
{{template "txdetail" .}}
</div>
{{if eq .ChainType 1}}
{{if $tx.EthereumSpecific.ParsedData}}
{{if $tx.EthereumSpecific.ParsedData.Function }}
<div class="data-div">
<div class="pt-2">
<h5>Input Data</h5>
<div class="row">
{{if $tx.EthereumSpecific.ParsedData.Name}}<div class="col-6">{{$tx.EthereumSpecific.ParsedData.Name}}</div>{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}<div class="col-6">Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}</div>{{end}}
{{if $tx.EthereumSpecific.ParsedData.Function}}<div class="col-12">Function: {{$tx.EthereumSpecific.ParsedData.Function}}</div>{{end}}
{{if $tx.EthereumSpecific.ParsedData.Params}}
<div class="col-12">
<table class="table data-table">
<thead>
<tr>
<th style="width: 5%;">#</th>
<th style="width: 25%;">Type</th>
<th>Data</th>
</tr>
</thead>
<tbody>
{{range $i,$p := $tx.EthereumSpecific.ParsedData.Params}}
<tr>
<td>{{$i}}</td>
<td>{{$p.Type}}</td>
<td>
{{range $j,$v := $p.Values}}
{{if $j}}<br>{{end}}
{{if hasPrefix $p.Type "address"}}<a href="/address/{{$v}}">{{$v}}</a>{{else}}{{$v}}{{end}}
<div class="accordion" id="inputData">
<div class="accordion-item">
<h2 class="accordion-header" id="inputDataHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#inputDataBody" aria-expanded="false" aria-controls="inputDataBody">
<h5 class="mb-0">{{if $tx.EthereumSpecific.ParsedData.Name}}{{$tx.EthereumSpecific.ParsedData.Name}} {{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}} <span class="fw-normal small" tt="4-byte signature">{{$tx.EthereumSpecific.ParsedData.MethodId}}</span>{{end}}</h5>
</button>
</h2>
<div id="inputDataBody" class="accordion-collapse collapse" aria-labelledby="inputDataHeading" data-bs-parent="#inputData">
<div class="accordion-body">
<div class="row">
<div class="col-12"><span class="copyable" style="overflow-wrap: break-word;">{{$tx.EthereumSpecific.Data}}</span></div>
<div class="col-12 pt-2"><span class="copyable">{{$tx.EthereumSpecific.ParsedData.Function}}</span></div>
{{if $tx.EthereumSpecific.ParsedData.Params}}
<div class="col-12">
<table class="table data-table mt-2 mb-0">
<thead>
<tr>
<th style="width: 5%;">#</th>
<th style="width: 20%;">Type</th>
<th>Data</th>
</tr>
</thead>
<tbody>
{{range $i,$p := $tx.EthereumSpecific.ParsedData.Params}}
<tr>
<td>{{$i}}</td>
<td>{{$p.Type}}</td>
<td>
{{range $j,$v := $p.Values}}
{{if $j}}<br>{{end}}
{{if hasPrefix $p.Type "address"}}<a href="/address/{{$v}}">{{addressAliasSpan $v $data}}</a>{{else}}<span class="copyable">{{$v}}</span>{{end}}
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
{{end}}
</td>
</tr>
{{end}}
</tbody>
</table>
</div>
</div>
</div>
</div>
{{end}}
</div>
</div>
{{end}}
{{end}}
{{end}}
<div class="data-div">
<div class="pt-4">
<h5>Raw Transaction</h5>
<div class="json">
<pre id="raw"></pre>

View File

@ -5,7 +5,7 @@
<a href="/tx/{{$tx.Txid}}" class="ellipsis copyable txid">{{$tx.Txid}}</a>
{{if $tx.Rbf}}<span class="ps-1" tt="Replace-by-Fee (RBF) transaction, could be overridden"> RBF</span>{{end}}
</div>
{{if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-end">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} <span class="txvalue ms-1">{{formatUnixTime $tx.Blocktime}}</span></div>{{end}}
{{if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-end">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} <span class="txvalue ms-1">{{unixTimeSpan $tx.Blocktime}}</span></div>{{end}}
</div>
<div class="row body">
<div class="col-md-5">
@ -13,16 +13,16 @@
{{range $vin := $tx.Vin}}
<div class="col-12{{if $vin.IsOwn}} tx-own{{end}}">
{{range $a := $vin.Addresses}}
<span class="ellipsis tx-addr copyable">
<span class="ellipsis copyable">
{{if and (ne $a $addr) $vin.IsAddress}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span>
{{else}}
<span class="tx-addr">{{if $vin.Hex}}Unparsed address{{else}}No Inputs (Newly Generated Coins){{end}}</span>
{{if $vin.Hex}}Unparsed address{{else}}No Inputs (Newly Generated Coins){{end}}
{{end}}
{{if $vin.Txid}}
<a class="outpoint" href="/tx/{{$vin.Txid}}" tt="Outpoint {{$vin.Txid}},{{$vin.Vout}}"></a>
{{end}}
{{if $vin.Addresses}}{{amount $vin.ValueSat $data "tx-amt copyable"}}{{end}}
{{if $vin.Addresses}}{{amountSpan $vin.ValueSat $data "tx-amt copyable"}}{{end}}
</div>
{{else}}
<div class="col-12">No Inputs</div>
@ -32,31 +32,31 @@
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-6">
<div class="row tx-out">
{{range $vout := $tx.Vout}}
<div class="col-12{{if $vout.IsOwn}} tx-own{{end}}">
{{range $a := $vout.Addresses}}
<span class="ellipsis tx-addr copyable">
{{if and (ne $a $addr) $vout.IsAddress}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span>
{{else}}
<span class="tx-addr">Unparsed address</span>
{{end}}
<span class="tx-amt">
{{amount $vout.ValueSat $data "copyable"}}{{if $vout.Spent}}<a class="spent" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" tt="Spent"></a>{{else}}<span class="unspent" tt="Unspent">×</span>
{{end}}
</span>
</td>
</div>
{{else}}
<div class="col-12">No Outputs</div>
{{range $vout := $tx.Vout}}
<div class="col-12{{if $vout.IsOwn}} tx-own{{end}}">
{{range $a := $vout.Addresses}}
<span class="ellipsis copyable">
{{if and (ne $a $addr) $vout.IsAddress}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span>
{{else}}
Unparsed address
{{end}}
<span class="tx-amt">
{{amountSpan $vout.ValueSat $data "copyable"}}{{if $vout.Spent}}<a class="spent" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" tt="Spent"></a>{{else}}<span class="unspent" tt="Unspent">×</span>
{{end}}
</span>
</td>
</div>
{{else}}
<div class="col-12">No Outputs</div>
{{end}}
</div>
</div>
</div>
<div class="row footer">
<div class="col-sm-12 col-md-4">
{{if $tx.FeesSat}}
Fee {{amount $tx.FeesSat $data "txvalue copyable ms-3"}}
Fee {{amountSpan $tx.FeesSat $data "txvalue copyable ms-3"}}
{{end}}
</div>
<div class="col-sm-12 col-md-8 text-end">
@ -64,10 +64,10 @@
{{if $tx.Confirmations}}
<span class="txvalue">{{formatUint32 $tx.Confirmations}}</span> confirmations
{{else}}
<span class="txvalue unconfirmed">Unconfirmed Transaction!</span>
<span class="txvalue unconfirmed">Unconfirmed Transaction!</span>
{{end}}
</span>
{{amount $tx.ValueOutSat $data "txvalue copyable"}}
{{amountSpan $tx.ValueOutSat $data "txvalue copyable"}}
</div>
</div>
</div>

View File

@ -1,295 +1,203 @@
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
<div class="tx-detail"{{if eq $tx.EthereumSpecific.Status 0}} style="background-color: #faf2ee;"{{end}}>
<div class="row line-bot">
<div class="col-xs-7 col-md-8 ellipsis">
<a href="/tx/{{$tx.Txid}}">{{$tx.Txid}}</a>
{{if eq $tx.EthereumSpecific.Status 1}}<span class="text-success"></span>{{end}}{{if eq $tx.EthereumSpecific.Status 0}}<span class="text-danger"></span>{{end}}
<div class="tx-detail">
<div class="row head{{if eq $tx.EthereumSpecific.Status 0}} txerror{{end}}">
<div class="col-xs-7 col-md-8">
<a href="/tx/{{$tx.Txid}}" class="ellipsis copyable txid">{{$tx.Txid}}</a>
{{if $tx.Rbf}}<span class="ps-1" tt="Replace-by-Fee (RBF) transaction, could be overridden"> RBF</span>{{end}}
</div>
{{- if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-muted text-right">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}</div>{{end -}}
{{if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-end">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} <span class="txvalue ms-1">{{unixTimeSpan $tx.Blocktime}}</span></div>{{end}}
{{if $tx.EthereumSpecific.ParsedData}}
{{if $tx.EthereumSpecific.ParsedData.Name}}<div class="col-6">{{$tx.EthereumSpecific.ParsedData.Name}}</div>{{end}}{{if $tx.EthereumSpecific.ParsedData.MethodId}}<div class="col-6">Method ID: {{$tx.EthereumSpecific.ParsedData.MethodId}}</div>{{end}}
{{if $tx.EthereumSpecific.ParsedData.Name}}<div class="col-12 small"><span class="txvalue">{{$tx.EthereumSpecific.ParsedData.Name}}</span>{{if $tx.EthereumSpecific.ParsedData.MethodId}}<span class="ms-1" tt="4-byte signature"> ({{$tx.EthereumSpecific.ParsedData.MethodId}})</span>{{end}}</div>{{else}}
{{if $tx.EthereumSpecific.ParsedData.MethodId}}<div class="col-12 small txvalue"><span tt="4-byte signature">{{$tx.EthereumSpecific.ParsedData.MethodId}}</span></div>{{end}}
{{end}}
{{end}}
{{if $tx.EthereumSpecific.Error}}<div class="col-12">Error: {{$tx.EthereumSpecific.Error}}</div>{{end}}
{{if eq $tx.EthereumSpecific.Status 0}}<div col-12><span class="badge bg-danger">Failed</span>{{if $tx.EthereumSpecific.Error}}<span class="small ms-1">{{$tx.EthereumSpecific.Error}}</span>{{end}}</div>{{end}}
</div>
<div class="row line-mid">
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
{{- range $vin := $tx.Vin -}}
<tr{{if $vin.IsOwn}} class="tx-own"{{end}}>
<td>
{{- range $a := $vin.Addresses -}}
<span class="ellipsis tx-addr">
{{if and (ne $a $addr) $vin.IsAddress}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
</span>
{{- else -}}
<span class="tx-addr">Unparsed address</span>
{{- end -}}
</td>
</tr>
{{- else -}}
<tr>
<td>No Inputs</td>
</tr>
{{- end -}}
</tbody>
</table>
{{range $vin := $tx.Vin}}
<div class="col-12{{if $vin.IsOwn}} tx-own{{end}}">
{{range $a := $vin.Addresses}}
<span class="ellipsis">
{{if and (ne $a $addr) $vin.IsAddress}}<a href="/address/{{$a}}">{{addressAliasSpan $a $data}}</a>{{else}}{{addressAliasSpan $a $data}}{{end}}
</span>
{{else}}
Unparsed address
{{end}}
</div>
{{else}}
<div class="col-12">No Inputs</div>
{{end}}
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-4">
<div class="row tx-out">
<table class="table data-table">
<tbody>
{{- range $vout := $tx.Vout -}}
<tr{{if $vout.IsOwn}} class="tx-own"{{end}}>
<td>
{{- range $a := $vout.Addresses -}}
<span class="ellipsis tx-addr">
{{- if and (ne $a $addr) $vout.IsAddress}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
</span>
{{- else -}}
<span class="tx-addr">Unparsed address</span>
{{- end -}}
</td>
</tr>
{{- else -}}
<tr>
<td>No Outputs</td>
</tr>
{{- end -}}
</tbody>
</table>
{{range $vout := $tx.Vout}}
<div class="col-12{{if $vout.IsOwn}} tx-own{{end}}">
{{range $a := $vout.Addresses}}
<span class="ellipsis">
{{if and (ne $a $addr) $vout.IsAddress}}<a href="/address/{{$a}}">{{addressAliasSpan $a $data}}</a>{{else}}{{addressAliasSpan $a $data}}{{end}}
</span>
{{else}}
Unparsed address
{{end}}
</div>
{{else}}
<div class="col-12">No Outputs</div>
{{end}}
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;overflow-wrap: break-word;">
{{formatAmount $tx.ValueOutSat}} {{$cs}}
</div>
</div>
<div class="col-md-3 amt-out">{{amountSpan $tx.ValueOutSat $data "tx-out copyable"}}</div>
</div>
{{if eq $tx.EthereumSpecific.Type 1}}
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
Contract creation
</div>
<div class="row" style="padding: 2px 15px;">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tx.EthereumSpecific.CreatedContract}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tx.EthereumSpecific.CreatedContract $addr}}<a href="/address/{{$tx.EthereumSpecific.CreatedContract}}">{{$tx.EthereumSpecific.CreatedContract}}</a>{{else}}{{$tx.EthereumSpecific.CreatedContract}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row subhead">
Contract creation
</div>
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<div class="col-12 ellipsis{{if isOwnAddress $data $tx.EthereumSpecific.CreatedContract}} tx-own{{end}}">
{{if ne $tx.EthereumSpecific.CreatedContract $addr}}<a href="/address/{{$tx.EthereumSpecific.CreatedContract}}">{{addressAliasSpan $tx.EthereumSpecific.CreatedContract $data}}</a>{{else}}{{addressAliasSpan $tx.EthereumSpecific.CreatedContract $data}}{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{if $tx.EthereumSpecific.InternalTransfers}}
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
<div class="row subhead">
Internal Transactions
</div>
{{- range $tt := $tx.EthereumSpecific.InternalTransfers -}}
{{if eq $tt.Type 1}}Contract creation{{end}}
{{if eq $tt.Type 2}}Contract destruction{{end}}
<div class="row" style="padding: 2px 15px;">
{{range $tt := $tx.EthereumSpecific.InternalTransfers}}
{{if eq $tt.Type 1}}<div class="row subhead-2">Contract creation</div>{{end}}
{{if eq $tt.Type 2}}<div class="row subhead-2">Contract destruction</div>{{end}}
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.From}} tx-own{{end}}">
{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{addressAliasSpan $tt.From $data}}</a>{{else}}{{addressAliasSpan $tt.From $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-4">
<div class="row tx-out">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.To}} tx-own{{end}}">
{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{addressAliasSpan $tt.To $data}}</a>{{else}}{{addressAliasSpan $tt.To $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;overflow-wrap: break-word;">{{formatAmount $tt.Value}} {{$cs}}</div>
<div class="col-md-3 amt-out">{{amountSpan $tt.Value $data "tx-out copyable"}}</div>
</div>
{{- end -}}
<div class="row" style="padding: 6px 15px;"></div>
{{- end -}}
{{end}}
{{end}}
{{- if tokenTransfersCount $tx "ERC20" -}}
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
{{if tokenTransfersCount $tx "ERC20"}}
<div class="row subhead">
ERC20 Token Transfers
</div>
{{- range $tt := $tx.TokenTransfers -}}
{{range $tt := $tx.TokenTransfers}}
{{if eq $tt.Type "ERC20"}}
<div class="row" style="padding: 2px 15px;">
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.From}} tx-own{{end}}">
{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{addressAliasSpan $tt.From $data}}</a>{{else}}{{addressAliasSpan $tt.From $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-4">
<div class="row tx-out">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.To}} tx-own{{end}}">
{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{addressAliasSpan $tt.To $data}}</a>{{else}}{{addressAliasSpan $tt.To $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;overflow-wrap: break-word;">{{formatAmountWithDecimals $tt.Value $tt.Decimals}} {{$tt.Symbol}}</div>
<div class="col-md-3 amt-out">{{tokenAmountSpan $tt $data "tx-out copyable"}}</div>
</div>
{{- end -}}
{{- end -}}
<div class="row" style="padding: 6px 15px;"></div>
{{- end -}}
{{end}}
{{end}}
{{end}}
{{- if tokenTransfersCount $tx "ERC721" -}}
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
{{if tokenTransfersCount $tx "ERC721"}}
<div class="row subhead">
ERC721 Token Transfers
</div>
{{- range $tt := $tx.TokenTransfers -}}
{{range $tt := $tx.TokenTransfers}}
{{if eq $tt.Type "ERC721"}}
<div class="row" style="padding: 2px 15px;">
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.From}} tx-own{{end}}">
{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{addressAliasSpan $tt.From $data}}</a>{{else}}{{addressAliasSpan $tt.From $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-4">
<div class="row tx-out">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.To}} tx-own{{end}}">
{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{addressAliasSpan $tt.To $data}}</a>{{else}}{{addressAliasSpan $tt.To $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;overflow-wrap: break-word;">ID <a href="/nft/{{$tt.Contract}}/{{$tt.Value}}">{{$tt.Value}}</a> {{$tt.Symbol}}</div>
<div class="col-md-3 amt-out">ID <a href="/nft/{{$tt.Contract}}/{{$tt.Value}}">{{$tt.Value}}</a> {{$tt.Symbol}}</div>
</div>
{{- end -}}
{{- end -}}
<div class="row" style="padding: 6px 15px;"></div>
{{- end -}}
{{end}}
{{end}}
{{end}}
{{- if tokenTransfersCount $tx "ERC1155" -}}
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
{{if tokenTransfersCount $tx "ERC1155"}}
<div class="row subhead">
ERC1155 Token Transfers
</div>
{{- range $tt := $tx.TokenTransfers -}}
{{range $tt := $tx.TokenTransfers}}
{{if eq $tt.Type "ERC1155"}}
<div class="row" style="padding: 2px 15px;">
<div class="row body">
<div class="col-md-4">
<div class="row tx-in">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.From}} tx-own{{end}}">
{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{addressAliasSpan $tt.From $data}}</a>{{else}}{{addressAliasSpan $tt.From $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-1 col-xs-12 text-center">
<svg class="octicon" viewBox="0 0 8 16">
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
</svg>
</div>
<div class="col-md-1 col-xs-12 text-center">&nbsp;<span class="octicon"></span></div>
<div class="col-md-4">
<div class="row tx-out">
<table class="table data-table">
<tbody>
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
<td>
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
</td>
</tr>
</tbody>
</table>
<div class="row tx-addr">
<div class="col-12 ellipsis{{if isOwnAddress $data $tt.To}} tx-own{{end}}">
{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{addressAliasSpan $tt.To $data}}</a>{{else}}{{addressAliasSpan $tt.To $data}}{{end}}
</div>
</div>
</div>
<div class="col-md-3 text-right" style="padding: .4rem 0;overflow-wrap: break-word;">
{{- range $i, $iv := $tt.MultiTokenValues -}}
<div class="col-md-3 amt-out">
{{range $i, $iv := $tt.MultiTokenValues}}
{{if $i}}, {{end}}{{$iv.Value}} {{$tt.Symbol}} of ID <a href="/nft/{{$tt.Contract}}/{{$iv.Id}}">{{$iv.Id}}</a>
{{- end -}}
{{end}}
</div>
</div>
{{- end -}}
{{- end -}}
<div class="row" style="padding: 6px 15px;"></div>
{{- end -}}
{{end}}
{{end}}
{{end}}
<div class="row line-top">
<div class="col-xs-6 col-sm-4 col-md-4">
{{- if $tx.FeesSat -}}
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.FeesSat}} {{$cs}}</span>
{{- end -}}
<div class="row footer">
<div class="col-sm-12 col-md-4">
{{if $tx.FeesSat}}
Fee {{amountSpan $tx.FeesSat $data "txvalue copyable ms-3"}}
{{end}}
</div>
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
{{- if $tx.Confirmations -}}
<span class="txvalues txvalues-success">{{$tx.Confirmations}} Confirmations</span>
{{- else -}}
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
{{- end -}}
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOutSat}} {{$cs}}</span>
<div class="col-sm-12 col-md-8 text-end">
<span class="me-4">
{{if $tx.Confirmations}}
<span class="txvalue">{{formatUint32 $tx.Confirmations}}</span> confirmations
{{else}}
<span class="txvalue unconfirmed">Unconfirmed Transaction!</span>
{{end}}
</span>
{{amountSpan $tx.ValueOutSat $data "txvalue copyable"}}
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
<div class="col-md-10 order-2 order-md-1">
<h1>XPUB</h1>
<h5 class="col-12 d-flex h-data pb-2"><span class="ellipsis copyable">{{$addr.AddrStr}}</span></h5>
<h4>{{amount $addr.BalanceSat $data "copyable"}}</h4>
<h4>{{amountSpan $addr.BalanceSat $data "copyable"}}</h4>
</div>
<div class="col-md-2 order-1 order-md-2 d-flex justify-content-center justify-content-md-end mb-3 mb-md-0">
<div id="qrcode"></div>
@ -21,23 +21,23 @@
</tr>
<tr>
<td style="width: 25%;">Total Received</td>
<td class="data">{{amount $addr.TotalReceivedSat $data "copyable"}}</td>
<td>{{amountSpan $addr.TotalReceivedSat $data "copyable"}}</td>
</tr>
<tr>
<td>Total Sent</td>
<td class="data">{{amount $addr.TotalSentSat $data "copyable"}}</td>
<td>{{amountSpan $addr.TotalSentSat $data "copyable"}}</td>
</tr>
<tr>
<td>Final Balance</td>
<td class="data">{{amount $addr.BalanceSat $data "copyable"}}</td>
<td>{{amountSpan $addr.BalanceSat $data "copyable"}}</td>
</tr>
<tr>
<td>No. Transactions</td>
<td class="data">{{formatInt $addr.Txs}}</td>
<td>{{formatInt $addr.Txs}}</td>
</tr>
<tr>
<td>Used XPUB Addresses</td>
<td class="data">{{formatInt $addr.UsedTokens}}</td>
<td>{{formatInt $addr.UsedTokens}}</td>
</tr>
</tbody>
</table>
@ -57,7 +57,7 @@
{{range $t := $addr.Tokens}}
<tr>
<td class="ellipsis"><a href="/address/{{$t.Name}}" class="copyable">{{$t.Name}}</a></td>
<td>{{amount $t.BalanceSat $data "copyable"}}</td>
<td>{{amountSpan $t.BalanceSat $data "copyable"}}</td>
<td>{{formatInt $t.Transfers}}</td>
<td>{{$t.Path}}</td>
</tr>
@ -87,7 +87,7 @@
</tr>
<tr>
<td style="width: 25%;">Unconfirmed Balance</td>
<td>{{amount $addr.UnconfirmedBalanceSat $data "copyable"}}</td>
<td>{{amountSpan $addr.UnconfirmedBalanceSat $data "copyable"}}</td>
</tr>
<tr>
<td>No. Transactions</td>