Add initial implementation of tx explorer
parent
13de56658c
commit
ce5462118c
|
@ -49,18 +49,21 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
|
|||
vin.N = i
|
||||
vin.Vout = bchainVin.Vout
|
||||
vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex
|
||||
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(otx.Vout) > int(vin.Vout) {
|
||||
vout := &otx.Vout[vin.Vout]
|
||||
vin.Value = vout.Value
|
||||
valIn += vout.Value
|
||||
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
|
||||
if vout.Address != nil {
|
||||
a := vout.Address.String()
|
||||
vin.Addr = a
|
||||
// bchainVin.Txid=="" is coinbase transaction
|
||||
if bchainVin.Txid != "" {
|
||||
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(otx.Vout) > int(vin.Vout) {
|
||||
vout := &otx.Vout[vin.Vout]
|
||||
vin.Value = vout.Value
|
||||
valIn += vout.Value
|
||||
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
|
||||
if vout.Address != nil {
|
||||
a := vout.Address.String()
|
||||
vin.Addr = a
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -77,9 +80,13 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool)
|
|||
// TODO
|
||||
}
|
||||
}
|
||||
// for coinbase transactions valIn is 0
|
||||
fees = valIn - valOut
|
||||
if fees < 0 {
|
||||
fees = 0
|
||||
}
|
||||
// for now do not return size, we would have to compute vsize of segwit transactions
|
||||
// size:=len(bchainTx.Hex) / 2
|
||||
fees = valIn - valOut
|
||||
r := &Tx{
|
||||
Blockhash: blockhash,
|
||||
Blockheight: int(height),
|
||||
|
|
|
@ -89,19 +89,28 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
|
|||
// default handler
|
||||
serveMux.HandleFunc(path, s.index)
|
||||
|
||||
templateFuncMap := template.FuncMap{
|
||||
"formatUnixTime": formatUnixTime,
|
||||
}
|
||||
|
||||
s.txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||
s.txTpl = parseTemplates()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func parseTemplates() (txTpl *template.Template) {
|
||||
templateFuncMap := template.FuncMap{
|
||||
"formatUnixTime": formatUnixTime,
|
||||
"formatAmount": formatAmount,
|
||||
}
|
||||
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||
return
|
||||
}
|
||||
|
||||
func formatUnixTime(ut int64) string {
|
||||
return time.Unix(ut, 0).Format(time.RFC1123)
|
||||
}
|
||||
|
||||
func formatAmount(a float64) string {
|
||||
return fmt.Sprintf("%0.8f", a)
|
||||
}
|
||||
|
||||
// Run starts the server
|
||||
func (s *PublicServer) Run() error {
|
||||
if s.certFiles == "" {
|
||||
|
@ -173,7 +182,8 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
|||
bestheight, _, err := s.db.GetBestBlock()
|
||||
if err == nil {
|
||||
tx, err = s.api.GetTransaction(txid, bestheight, true)
|
||||
} else {
|
||||
}
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
|
@ -181,16 +191,13 @@ func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// temporarily reread the template on each request
|
||||
// to reflect changes during development
|
||||
templateFuncMap := template.FuncMap{
|
||||
"formatUnixTime": formatUnixTime,
|
||||
}
|
||||
txTpl := template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||
s.txTpl = parseTemplates()
|
||||
|
||||
data := struct {
|
||||
CoinName string
|
||||
Specific *api.Tx
|
||||
}{s.is.Coin, tx}
|
||||
if err := txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,28 +4,52 @@ html, body {
|
|||
|
||||
body {
|
||||
width: 100%;
|
||||
min-width: 727px;
|
||||
min-height: 100%;
|
||||
background-color: #ffffff;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #428bca;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.octicon {
|
||||
color: #777;
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
fill: currentColor;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 750px;
|
||||
max-width: 750px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
body {
|
||||
font-size: 12px;
|
||||
}
|
||||
.container {
|
||||
width: 970px;
|
||||
max-width: 970px;
|
||||
}
|
||||
.octicon {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
body {
|
||||
font-size: 14px;
|
||||
}
|
||||
.container {
|
||||
width: 1170px;
|
||||
max-width: 1170px;
|
||||
}
|
||||
.octicon {
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,6 +97,82 @@ body {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.value-group {
|
||||
.alert-data {
|
||||
color: #383d41;
|
||||
background-color: #f4f4f4;
|
||||
border-color: #d6d8db;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.line-top {
|
||||
border-top: 1px solid #EAEAEA;
|
||||
padding: 15px 0 0;
|
||||
}
|
||||
|
||||
.line-mid {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.line-bot {
|
||||
border-bottom: 2px solid #EAEAEA;
|
||||
padding: 0 0 15px;
|
||||
}
|
||||
|
||||
.txvalues {
|
||||
display: inline-block;
|
||||
padding: .7em 2em;
|
||||
font-size: 13px;
|
||||
font-weight: 100;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: .25em;
|
||||
}
|
||||
|
||||
.txvalues-default {
|
||||
background-color: #EBEBEB;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.txvalues-success {
|
||||
background-color: dimgray;
|
||||
}
|
||||
|
||||
.txvalues-primary {
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
.txvalues-danger {
|
||||
background-color: #AC0015;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-div {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
table-layout: fixed;
|
||||
border-radius: .25rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.data-table td, .data-table th {
|
||||
padding: .4rem;
|
||||
}
|
||||
|
||||
.data {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.alert .data-table {
|
||||
margin: 0;
|
||||
}
|
|
@ -2,7 +2,6 @@
|
|||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<base href="/explorer">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
|
@ -50,19 +49,19 @@
|
|||
<main id="wrap">
|
||||
<div class="container">
|
||||
{{template "specific" .Specific}}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="footer">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-trezor">
|
||||
<ul class="navbar-nav flex-row">
|
||||
<li class="nav-item">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="http://satoshilabs.com/" target="_blank" rel="noopener noreferrer">© 2018 SatoshiLabs</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://wallet.trezor.io/tos.pdf" target="_blank" rel="noopener noreferrer">Terms</a>
|
||||
</li>
|
||||
</ul>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://wallet.trezor.io/tos.pdf" target="_blank" rel="noopener noreferrer">Terms</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" rel="noopener noreferrer" href="http://shop.trezor.io">Don't have a TREZOR? Get one!</a>
|
||||
|
|
|
@ -1,27 +1,40 @@
|
|||
{{define "specific"}}
|
||||
<h1>Transaction</h1>
|
||||
<div class="alert alert-secondary">
|
||||
<strong>
|
||||
<span>Transaction</span>
|
||||
</strong>
|
||||
<span class="text-muted">{{.Txid}}</span>
|
||||
<div class="alert alert-data">
|
||||
<span class="ellipsis data">{{.Txid}}</span>
|
||||
</div>
|
||||
<h2>Summary</h2>
|
||||
<div class="value-group">
|
||||
<div class="row">
|
||||
<div class="col-md-4">Mined Time</div>
|
||||
<div class="col-md-8 text-muted">{{formatUnixTime .Blocktime}}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">In Block</div>
|
||||
<div class="col-md-8 text-muted">{{.Blockhash}}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4">In Block Height</div>
|
||||
<div class="col-md-8 text-muted">{{.Blockheight}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2>Details</h2>
|
||||
<div class="value-group">
|
||||
{{template "txdetail" .}} {{end}}
|
||||
<h3>Summary</h3>
|
||||
<div class="data-div">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{if .Confirmations}}<tr>
|
||||
<td style="width: 25%;">Mined Time</td>
|
||||
<td class="data">{{formatUnixTime .Blocktime}}</td>
|
||||
</tr>{{end}}
|
||||
<tr>
|
||||
<td style="width: 25%;">In Block</td>
|
||||
<td class="ellipsis data">{{if .Confirmations}}{{.Blockhash}}{{else}}Unconfirmed{{end}}</td>
|
||||
</tr>
|
||||
{{if .Confirmations}}<tr>
|
||||
<td>In Block Height</td>
|
||||
<td class="data">{{.Blockheight}}</td>
|
||||
</tr>{{end}}
|
||||
<tr>
|
||||
<td>Total Input</td>
|
||||
<td class="data">{{formatAmount .ValueIn}} {{.CoinShortcut}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Output</td>
|
||||
<td class="data">{{formatAmount .ValueOut}} {{.CoinShortcut}}</td>
|
||||
</tr>
|
||||
{{if .Fees}}<tr>
|
||||
<td>Fees</td>
|
||||
<td class="data">{{formatAmount .Fees}} {{.CoinShortcut}}</td>
|
||||
</tr>{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h3>Details</h3>
|
||||
<div class="data-div">
|
||||
{{template "txdetail" .}} {{end}}
|
||||
</div>
|
|
@ -1,3 +1,78 @@
|
|||
{{define "txdetail"}}
|
||||
{{.Txid}}
|
||||
{{define "txdetail"}}{{$cs := .CoinShortcut}}
|
||||
<div class="alert alert-data">
|
||||
<div class="row line-bot">
|
||||
<div class="col-xs-7 col-md-8 ellipsis">
|
||||
<a href="/explorer/tx/{{.Txid}}">{{.Txid}}</a>
|
||||
</div>
|
||||
{{if .Confirmations}}
|
||||
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime .Blocktime}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="row line-mid">
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{range $vin := .Vin}}
|
||||
<tr>
|
||||
<td>
|
||||
{{if $vin.Txid}}
|
||||
<span class="float-left ellipsis">
|
||||
{{if $vin.Addr}}
|
||||
<a href="/explorer/address/{{$vin.Addr}}">{{$vin.Addr}}</a>
|
||||
{{else}}Unparsed address{{end}}
|
||||
</span>
|
||||
<span class="float-right"> {{$vin.Value}} {{$cs}} </span>
|
||||
{{else}}No Inputs (Newly Generated Coins){{end}}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</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-6">
|
||||
<div class="row">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{range $vout := .Vout}}
|
||||
<tr>
|
||||
<td>
|
||||
{{range $a := $vout.ScriptPubKey.Addresses}}
|
||||
<span class="ellipsis float-left">
|
||||
<a href="/explorer/address/{{$a}}">{{$a}}</a>
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="float-left">Unparsed address</span>
|
||||
{{end}}
|
||||
<span class="float-right">{{$vout.Value}} {{$cs}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row line-top">
|
||||
<div class="col-xs-6 col-sm-4 col-md-4">
|
||||
{{if .Fees}}
|
||||
<span class="txvalues txvalues-default">Fee: {{formatAmount .Fees}} {{$cs}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
|
||||
{{if .Confirmations}}
|
||||
<span class="txvalues txvalues-success">{{.Confirmations}} Confirmations</span>
|
||||
{{else}}
|
||||
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
|
||||
{{end}}
|
||||
<span class="txvalues txvalues-primary">{{formatAmount .ValueOut}} {{$cs}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
Loading…
Reference in New Issue