Add initial implementation of address explorer
This commit is contained in:
parent
20643756e5
commit
7e68630377
16
api/types.go
16
api/types.go
@ -46,6 +46,20 @@ type Tx struct {
|
|||||||
Size int `json:"size,omitempty"`
|
Size int `json:"size,omitempty"`
|
||||||
ValueIn float64 `json:"valueIn"`
|
ValueIn float64 `json:"valueIn"`
|
||||||
Fees float64 `json:"fees"`
|
Fees float64 `json:"fees"`
|
||||||
CoinShortcut string `json:"coinShortcut"`
|
|
||||||
WithSpends bool `json:"withSpends,omitempty"`
|
WithSpends bool `json:"withSpends,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
AddrStr string `json:"addrStr"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
BalanceSat int64 `json:"balanceSat"`
|
||||||
|
TotalReceived float64 `json:"totalReceived"`
|
||||||
|
TotalReceivedSat int64 `json:"totalReceivedSat"`
|
||||||
|
TotalSent float64 `json:"totalSent"`
|
||||||
|
TotalSentSat int64 `json:"totalSentSat"`
|
||||||
|
UnconfirmedBalance float64 `json:"unconfirmedBalance"`
|
||||||
|
UnconfirmedBalanceSat int64 `json:"unconfirmedBalanceSat"`
|
||||||
|
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
|
||||||
|
TxApperances int `json:"txApperances"`
|
||||||
|
Transactions []*Tx `json:"transactions"`
|
||||||
|
}
|
||||||
|
|||||||
104
api/worker.go
104
api/worker.go
@ -4,6 +4,8 @@ import (
|
|||||||
"blockbook/bchain"
|
"blockbook/bchain"
|
||||||
"blockbook/common"
|
"blockbook/common"
|
||||||
"blockbook/db"
|
"blockbook/db"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Worker is handle to api worker
|
// Worker is handle to api worker
|
||||||
@ -91,7 +93,6 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
|
|||||||
Blockhash: blockhash,
|
Blockhash: blockhash,
|
||||||
Blockheight: int(height),
|
Blockheight: int(height),
|
||||||
Blocktime: bchainTx.Blocktime,
|
Blocktime: bchainTx.Blocktime,
|
||||||
CoinShortcut: w.is.CoinShortcut,
|
|
||||||
Confirmations: bchainTx.Confirmations,
|
Confirmations: bchainTx.Confirmations,
|
||||||
Fees: fees,
|
Fees: fees,
|
||||||
Locktime: bchainTx.LockTime,
|
Locktime: bchainTx.LockTime,
|
||||||
@ -106,3 +107,104 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
|
|||||||
}
|
}
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Worker) getAddressTxids(address string, mempool bool) ([]string, error) {
|
||||||
|
var err error
|
||||||
|
txids := make([]string, 0)
|
||||||
|
if !mempool {
|
||||||
|
err = s.db.GetTransactions(address, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
|
||||||
|
txids = append(txids, txid)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m, err := s.chain.GetMempoolTransactions(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txids = append(txids, m...)
|
||||||
|
}
|
||||||
|
return txids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tx) getAddrVoutValue(addrID string) float64 {
|
||||||
|
var val float64
|
||||||
|
for _, vout := range t.Vout {
|
||||||
|
for _, a := range vout.ScriptPubKey.Addresses {
|
||||||
|
if a == addrID {
|
||||||
|
val += vout.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tx) getAddrVinValue(addrID string) float64 {
|
||||||
|
var val float64
|
||||||
|
for _, vin := range t.Vin {
|
||||||
|
if vin.Addr == addrID {
|
||||||
|
val += vin.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress computes address value and gets transactions for given address
|
||||||
|
func (w *Worker) GetAddress(addrID string) (*Address, error) {
|
||||||
|
glog.Info(addrID, " start")
|
||||||
|
txc, err := w.getAddressTxids(addrID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txm, err := w.getAddressTxids(addrID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bestheight, _, err := w.db.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txs := make([]*Tx, len(txc)+len(txm))
|
||||||
|
txi := 0
|
||||||
|
var uBal, bal, totRecv, totSent float64
|
||||||
|
for _, tx := range txm {
|
||||||
|
tx, err := w.GetTransaction(tx, bestheight, false)
|
||||||
|
// mempool transaction may fail
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("GetTransaction ", tx, ": ", err)
|
||||||
|
} else {
|
||||||
|
txs[txi] = tx
|
||||||
|
uBal = tx.getAddrVoutValue(addrID) - tx.getAddrVinValue(addrID)
|
||||||
|
txi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := len(txc) - 1; i >= 0; i-- {
|
||||||
|
tx, err := w.GetTransaction(txc[i], bestheight, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
txs[txi] = tx
|
||||||
|
totRecv += tx.getAddrVoutValue(addrID)
|
||||||
|
totSent += tx.getAddrVinValue(addrID)
|
||||||
|
txi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bal = totRecv - totSent
|
||||||
|
r := &Address{
|
||||||
|
AddrStr: addrID,
|
||||||
|
Balance: bal,
|
||||||
|
BalanceSat: int64(bal*1E8 + 0.5),
|
||||||
|
TotalReceived: totRecv,
|
||||||
|
TotalReceivedSat: int64(totRecv*1E8 + 0.5),
|
||||||
|
TotalSent: totSent,
|
||||||
|
TotalSentSat: int64(totSent*1E8 + 0.5),
|
||||||
|
Transactions: txs[:txi],
|
||||||
|
TxApperances: len(txc),
|
||||||
|
UnconfirmedBalance: uBal,
|
||||||
|
UnconfirmedTxApperances: len(txm),
|
||||||
|
}
|
||||||
|
glog.Info(addrID, " finished")
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ type PublicServer struct {
|
|||||||
metrics *common.Metrics
|
metrics *common.Metrics
|
||||||
is *common.InternalState
|
is *common.InternalState
|
||||||
txTpl *template.Template
|
txTpl *template.Template
|
||||||
|
addressTpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublicServerS creates new public server http interface to blockbook and returns its handle
|
// NewPublicServerS creates new public server http interface to blockbook and returns its handle
|
||||||
@ -80,26 +81,30 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
|
|||||||
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
||||||
// explorer
|
// explorer
|
||||||
serveMux.HandleFunc(path+"explorer/tx/", s.explorerTx)
|
serveMux.HandleFunc(path+"explorer/tx/", s.explorerTx)
|
||||||
|
serveMux.HandleFunc(path+"explorer/address/", s.explorerAddress)
|
||||||
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||||
// API calls
|
// API calls
|
||||||
serveMux.HandleFunc(path+"api/block-index/", s.apiBlockIndex)
|
serveMux.HandleFunc(path+"api/block-index/", s.apiBlockIndex)
|
||||||
serveMux.HandleFunc(path+"api/tx/", s.apiTx)
|
serveMux.HandleFunc(path+"api/tx/", s.apiTx)
|
||||||
|
serveMux.HandleFunc(path+"api/address/", s.apiAddress)
|
||||||
// handle socket.io
|
// handle socket.io
|
||||||
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
||||||
// default handler
|
// default handler
|
||||||
serveMux.HandleFunc(path, s.index)
|
serveMux.HandleFunc(path, s.index)
|
||||||
|
|
||||||
s.txTpl = parseTemplates()
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTemplates() (txTpl *template.Template) {
|
func parseTemplates() (txTpl, addressTpl *template.Template) {
|
||||||
templateFuncMap := template.FuncMap{
|
templateFuncMap := template.FuncMap{
|
||||||
"formatUnixTime": formatUnixTime,
|
"formatUnixTime": formatUnixTime,
|
||||||
"formatAmount": formatAmount,
|
"formatAmount": formatAmount,
|
||||||
|
"setTxToTemplateData": setTxToTemplateData,
|
||||||
}
|
}
|
||||||
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||||
|
addressTpl = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,6 +180,19 @@ func (s *PublicServer) addressRedirect(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TemplateData struct {
|
||||||
|
CoinName string
|
||||||
|
CoinShortcut string
|
||||||
|
Address *api.Address
|
||||||
|
AddrStr string
|
||||||
|
Tx *api.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
|
||||||
|
td.Tx = tx
|
||||||
|
return td
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
||||||
var tx *api.Tx
|
var tx *api.Tx
|
||||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
@ -191,17 +209,45 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// temporarily reread the template on each request
|
// temporarily reread the template on each request
|
||||||
// to reflect changes during development
|
// to reflect changes during development
|
||||||
s.txTpl = parseTemplates()
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
data := struct {
|
data := &TemplateData{
|
||||||
CoinName string
|
CoinName: s.is.Coin,
|
||||||
Specific *api.Tx
|
CoinShortcut: s.is.CoinShortcut,
|
||||||
}{s.is.Coin, tx}
|
Tx: tx,
|
||||||
|
}
|
||||||
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||||
glog.Error(err)
|
glog.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var address *api.Address
|
||||||
|
var err error
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
addrID := r.URL.Path[i+1:]
|
||||||
|
address, err = s.api.GetAddress(addrID)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
|
// temporarily reread the template on each request
|
||||||
|
// to reflect changes during development
|
||||||
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
|
data := &TemplateData{
|
||||||
|
CoinName: s.is.Coin,
|
||||||
|
CoinShortcut: s.is.CoinShortcut,
|
||||||
|
AddrStr: address.AddrStr,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
if err := s.addressTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type resAboutBlockbookPublic struct {
|
type resAboutBlockbookPublic struct {
|
||||||
Coin string `json:"coin"`
|
Coin string `json:"coin"`
|
||||||
Host string `json:"host"`
|
Host string `json:"host"`
|
||||||
@ -288,3 +334,19 @@ func (s *PublicServer) apiTx(w http.ResponseWriter, r *http.Request) {
|
|||||||
json.NewEncoder(w).Encode(tx)
|
json.NewEncoder(w).Encode(tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) apiAddress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var address *api.Address
|
||||||
|
var err error
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
addrID := r.URL.Path[i+1:]
|
||||||
|
address, err = s.api.GetAddress(addrID)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
52
static/templates/address.html
Normal file
52
static/templates/address.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
|
||||||
|
<h1>Address
|
||||||
|
<small class="text-muted">{{formatAmount $addr.Balance}} {{$cs}}</small>
|
||||||
|
</h1>
|
||||||
|
<div class="alert alert-data">
|
||||||
|
<span class="ellipsis data">{{$addr.AddrStr}}</span>
|
||||||
|
</div>
|
||||||
|
<h3>Confirmed</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Total Received</td>
|
||||||
|
<td class="data">{{formatAmount $addr.TotalReceived}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Sent</td>
|
||||||
|
<td class="data">{{formatAmount $addr.TotalSent}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Final Balance</td>
|
||||||
|
<td class="data">{{formatAmount $addr.Balance}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No. Transactions</td>
|
||||||
|
<td class="data">{{$addr.TxApperances}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{if $addr.UnconfirmedTxApperances}}
|
||||||
|
<h3>Unconfirmed</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Unconfirmed Balance</td>
|
||||||
|
<td class="data">{{formatAmount $addr.UnconfirmedBalance}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No. Transactions</td>
|
||||||
|
<td class="data">{{$addr.UnconfirmedTxApperances}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}} {{if $addr.Transactions}}
|
||||||
|
<h3>Transactions</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
{{range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data }}{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}} {{end}}
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main id="wrap">
|
<main id="wrap">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{{template "specific" .Specific}}
|
{{template "specific" .}}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<footer id="footer" class="footer">
|
<footer id="footer" class="footer">
|
||||||
|
|||||||
@ -1,40 +1,44 @@
|
|||||||
{{define "specific"}}
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}
|
||||||
<h1>Transaction</h1>
|
<h1>Transaction</h1>
|
||||||
<div class="alert alert-data">
|
<div class="alert alert-data">
|
||||||
<span class="ellipsis data">{{.Txid}}</span>
|
<span class="ellipsis data">{{$tx.Txid}}</span>
|
||||||
</div>
|
</div>
|
||||||
<h3>Summary</h3>
|
<h3>Summary</h3>
|
||||||
<div class="data-div">
|
<div class="data-div">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{if .Confirmations}}<tr>
|
{{if $tx.Confirmations}}
|
||||||
|
<tr>
|
||||||
<td style="width: 25%;">Mined Time</td>
|
<td style="width: 25%;">Mined Time</td>
|
||||||
<td class="data">{{formatUnixTime .Blocktime}}</td>
|
<td class="data">{{formatUnixTime $tx.Blocktime}}</td>
|
||||||
</tr>{{end}}
|
</tr>{{end}}
|
||||||
<tr>
|
<tr>
|
||||||
<td style="width: 25%;">In Block</td>
|
<td style="width: 25%;">In Block</td>
|
||||||
<td class="ellipsis data">{{if .Confirmations}}{{.Blockhash}}{{else}}Unconfirmed{{end}}</td>
|
<td class="ellipsis data">{{if $tx.Confirmations}}{{$tx.Blockhash}}{{else}}Unconfirmed{{end}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{if .Confirmations}}<tr>
|
{{if $tx.Confirmations}}
|
||||||
|
<tr>
|
||||||
<td>In Block Height</td>
|
<td>In Block Height</td>
|
||||||
<td class="data">{{.Blockheight}}</td>
|
<td class="data">{{$tx.Blockheight}}</td>
|
||||||
</tr>{{end}}
|
</tr>{{end}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>Total Input</td>
|
<td>Total Input</td>
|
||||||
<td class="data">{{formatAmount .ValueIn}} {{.CoinShortcut}}</td>
|
<td class="data">{{formatAmount $tx.ValueIn}} {{$cs}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Total Output</td>
|
<td>Total Output</td>
|
||||||
<td class="data">{{formatAmount .ValueOut}} {{.CoinShortcut}}</td>
|
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{if .Fees}}<tr>
|
{{if $tx.Fees}}
|
||||||
|
<tr>
|
||||||
<td>Fees</td>
|
<td>Fees</td>
|
||||||
<td class="data">{{formatAmount .Fees}} {{.CoinShortcut}}</td>
|
<td class="data">{{formatAmount $tx.Fees}} {{$cs}}</td>
|
||||||
</tr>{{end}}
|
</tr>{{end}}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<h3>Details</h3>
|
<h3>Details</h3>
|
||||||
<div class="data-div">
|
<div class="data-div">
|
||||||
{{template "txdetail" .}} {{end}}
|
{{template "txdetail" .}}
|
||||||
</div>
|
</div>
|
||||||
|
{{end}}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
{{define "txdetail"}}{{$cs := .CoinShortcut}}
|
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}
|
||||||
<div class="alert alert-data">
|
<div class="alert alert-data">
|
||||||
<div class="row line-bot">
|
<div class="row line-bot">
|
||||||
<div class="col-xs-7 col-md-8 ellipsis">
|
<div class="col-xs-7 col-md-8 ellipsis">
|
||||||
<a href="/explorer/tx/{{.Txid}}">{{.Txid}}</a>
|
<a href="/explorer/tx/{{$tx.Txid}}">{{$tx.Txid}}</a>
|
||||||
</div>
|
</div>
|
||||||
{{if .Confirmations}}
|
{{if $tx.Confirmations}}
|
||||||
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime .Blocktime}}</div>
|
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime $tx.Blocktime}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="row line-mid">
|
<div class="row line-mid">
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range $vin := .Vin}}
|
{{range $vin := $tx.Vin}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{if $vin.Txid}}
|
{{if $vin.Txid}}
|
||||||
@ -40,7 +40,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{range $vout := .Vout}}
|
{{range $vout := $tx.Vout}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
{{range $a := $vout.ScriptPubKey.Addresses}}
|
{{range $a := $vout.ScriptPubKey.Addresses}}
|
||||||
@ -61,17 +61,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row line-top">
|
<div class="row line-top">
|
||||||
<div class="col-xs-6 col-sm-4 col-md-4">
|
<div class="col-xs-6 col-sm-4 col-md-4">
|
||||||
{{if .Fees}}
|
{{if $tx.Fees}}
|
||||||
<span class="txvalues txvalues-default">Fee: {{formatAmount .Fees}} {{$cs}}</span>
|
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.Fees}} {{$cs}}</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
|
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
|
||||||
{{if .Confirmations}}
|
{{if $tx.Confirmations}}
|
||||||
<span class="txvalues txvalues-success">{{.Confirmations}} Confirmations</span>
|
<span class="txvalues txvalues-success">{{$tx.Confirmations}} Confirmations</span>
|
||||||
{{else}}
|
{{else}}
|
||||||
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
|
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
|
||||||
{{end}}
|
{{end}}
|
||||||
<span class="txvalues txvalues-primary">{{formatAmount .ValueOut}} {{$cs}}</span>
|
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOut}} {{$cs}}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user