diff --git a/api/types.go b/api/types.go
index 41c446b9..d43ce8d6 100644
--- a/api/types.go
+++ b/api/types.go
@@ -10,6 +10,20 @@ import (
"time"
)
+// GetAddressOption specifies what data returns GetAddress api call
+type GetAddressOption int
+
+const (
+ // ExistOnly - only that address is indexed
+ ExistOnly GetAddressOption = iota
+ // BalancesOnly - only balances
+ BalancesOnly
+ // TxidHistory - balances and txids, subject to paging
+ TxidHistory
+ // TxHistory - balances and full tx data, subject to paging
+ TxHistory
+)
+
// APIError extends error by information if the error details should be returned to the end user
type APIError struct {
Text string
@@ -125,16 +139,17 @@ type Paging struct {
// Address holds information about address and its transactions
type Address struct {
Paging
- AddrStr string `json:"addrStr"`
- Balance string `json:"balance"`
- TotalReceived string `json:"totalReceived"`
- TotalSent string `json:"totalSent"`
- UnconfirmedBalance string `json:"unconfirmedBalance"`
- UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
- TxApperances int `json:"txApperances"`
- Transactions []*Tx `json:"txs,omitempty"`
- Txids []string `json:"transactions,omitempty"`
- Erc20Tokens []*Erc20Token `json:"erc20tokens,omitempty"`
+ AddrStr string `json:"addrStr"`
+ Balance string `json:"balance"`
+ TotalReceived string `json:"totalReceived,omitempty"`
+ TotalSent string `json:"totalSent,omitempty"`
+ UnconfirmedBalance string `json:"unconfirmedBalance"`
+ UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
+ TxApperances int `json:"txApperances"`
+ Transactions []*Tx `json:"txs,omitempty"`
+ Txids []string `json:"transactions,omitempty"`
+ Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
+ Erc20Tokens []Erc20Token `json:"erc20tokens,omitempty"`
}
// AddressUtxo holds information about address and its transactions
diff --git a/api/worker.go b/api/worker.go
index 36a41bbc..c31e4db4 100644
--- a/api/worker.go
+++ b/api/worker.go
@@ -229,7 +229,9 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool, specificJSON bool
erc20c, err := w.chain.EthereumTypeGetErc20ContractInfo(cd)
if err != nil {
glog.Errorf("GetErc20ContractInfo error %v, contract %v", err, e.Contract)
- erc20c = &bchain.Erc20Contract{}
+ }
+ if erc20c == nil {
+ erc20c = &bchain.Erc20Contract{Name: e.Contract}
}
erc20t[i] = Erc20Transfer{
Contract: e.Contract,
@@ -418,8 +420,67 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page
}
+func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption) (*db.AddrBalance, []Erc20Token, *bchain.Erc20Contract, error) {
+ var (
+ ba *db.AddrBalance
+ erc20t []Erc20Token
+ ci *bchain.Erc20Contract
+ )
+ ca, err := w.db.GetAddrDescContracts(addrDesc)
+ if err != nil {
+ return nil, nil, nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
+ }
+ if ca != nil {
+ ba = &db.AddrBalance{
+ Txs: uint32(ca.EthTxs),
+ }
+ // do not read balances etc in case of ExistOnly option
+ if option != ExistOnly {
+ var b *big.Int
+ b, err = w.chain.EthereumTypeGetBalance(addrDesc)
+ if err != nil {
+ return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
+ }
+ if b != nil {
+ ba.BalanceSat = *b
+ }
+ erc20t = make([]Erc20Token, len(ca.Contracts))
+ for i, c := range ca.Contracts {
+ ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
+ if err != nil {
+ return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
+ }
+ if ci == nil {
+ ci = &bchain.Erc20Contract{}
+ addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
+ if len(addresses) > 0 {
+ ci.Contract = addresses[0]
+ ci.Name = addresses[0]
+ }
+ }
+ b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
+ if err != nil {
+ return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
+ }
+ erc20t[i] = Erc20Token{
+ Balance: bchain.AmountToDecimalString(b, ci.Decimals),
+ Contract: ci.Contract,
+ Name: ci.Name,
+ Symbol: ci.Symbol,
+ Txs: int(c.Txs),
+ }
+ }
+ ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
+ if err != nil {
+ return nil, nil, nil, err
+ }
+ }
+ }
+ return ba, erc20t, ci, nil
+}
+
// GetAddress computes address value and gets transactions for given address
-func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids, existOnly bool) (*Address, error) {
+func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetAddressOption) (*Address, error) {
start := time.Now()
page--
if page < 0 {
@@ -430,15 +491,15 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
}
var (
- ba *db.AddrBalance
- ca *db.AddrContracts
+ ba *db.AddrBalance
+ erc20t []Erc20Token
+ erc20c *bchain.Erc20Contract
)
if w.chainType == bchain.ChainEthereumType {
- ca, err = w.db.GetAddrDescContracts(addrDesc)
+ ba, erc20t, erc20c, err = w.getEthereumTypeAddressBalances(addrDesc, option)
if err != nil {
- return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
+ return nil, err
}
- glog.Infof("%+v", ca)
} else {
// ba can be nil if the address is only in mempool!
ba, err = w.db.GetAddrDescBalance(addrDesc)
@@ -447,7 +508,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
}
}
// if only check that the address exist, return if we have the address
- if existOnly && ba != nil {
+ if option == ExistOnly && ba != nil {
return &Address{AddrStr: address}, nil
}
// convert the address to the format defined by the parser
@@ -460,7 +521,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
}
txc, err := w.getAddressTxids(addrDesc, false)
if err != nil {
- return nil, errors.Annotatef(err, "getAddressTxids %v false", address)
+ return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
}
txc = UniqueTxidsInReverse(txc)
var txm []string
@@ -471,11 +532,11 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
}
txm, err = w.getAddressTxids(addrDesc, true)
if err != nil {
- return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
+ return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
txm = UniqueTxidsInReverse(txm)
// check if the address exist
- if len(txc)+len(txm) == 0 {
+ if len(txc)+len(txm) == 0 || option == ExistOnly {
return &Address{
AddrStr: address,
}, nil
@@ -487,7 +548,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
pg, from, to, page := computePaging(len(txc), page, txsOnPage)
var txs []*Tx
var txids []string
- if onlyTxids {
+ if option == TxidHistory {
txids = make([]string, len(txm)+to-from)
} else {
txs = make([]*Tx, len(txm)+to-from)
@@ -504,7 +565,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
if page == 0 {
- if onlyTxids {
+ if option == TxidHistory {
txids[txi] = tx.Txid
} else {
txs[txi] = tx
@@ -513,12 +574,12 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
}
}
}
- if len(txc) != int(ba.Txs) {
+ if len(txc) != int(ba.Txs) && w.chainType == bchain.ChainBitcoinType {
glog.Warning("DB inconsistency for address ", address, ": number of txs from column addresses ", len(txc), ", from addressBalance ", ba.Txs)
}
for i := from; i < to; i++ {
txid := txc[i]
- if onlyTxids {
+ if option == TxidHistory {
txids[txi] = txid
} else {
if w.chainType == bchain.ChainEthereumType {
@@ -548,7 +609,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
}
txi++
}
- if onlyTxids {
+ if option == TxidHistory {
txids = txids[:txi]
} else {
txs = txs[:txi]
@@ -564,6 +625,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids,
UnconfirmedTxApperances: len(txm),
Transactions: txs,
Txids: txids,
+ Erc20Contract: erc20c,
+ Erc20Tokens: erc20t,
}
glog.Info("GetAddress ", address, " finished in ", time.Since(start))
return r, nil
diff --git a/bchain/baseparser.go b/bchain/baseparser.go
index fbd4b58f..cb1ae68f 100644
--- a/bchain/baseparser.go
+++ b/bchain/baseparser.go
@@ -56,6 +56,9 @@ func (p *BaseParser) AmountToBigInt(n json.Number) (big.Int, error) {
// AmountToDecimalString converts amount in big.Int to string with decimal point in the place defined by the parameter d
func AmountToDecimalString(a *big.Int, d int) string {
+ if a == nil {
+ return ""
+ }
n := a.String()
var s string
if n[0] == '-' {
diff --git a/bchain/coins/eth/erc20.go b/bchain/coins/eth/erc20.go
index 6d9e2bc4..fb10dbea 100644
--- a/bchain/coins/eth/erc20.go
+++ b/bchain/coins/eth/erc20.go
@@ -43,7 +43,7 @@ type Erc20Transfer struct {
Tokens big.Int
}
-var cachedContracts = make(map[string]bchain.Erc20Contract)
+var cachedContracts = make(map[string]*bchain.Erc20Contract)
var cachedContractsMux sync.Mutex
func addressFromPaddedHex(s string) (string, error) {
@@ -146,30 +146,38 @@ func (b *EthereumRPC) EthereumTypeGetErc20ContractInfo(contractDesc bchain.Addre
return nil, err
}
name := parseErc20StringProperty(contractDesc, data)
- data, err = b.ethCall(erc20SymbolSignature, address)
- if err != nil {
- return nil, err
- }
- symbol := parseErc20StringProperty(contractDesc, data)
- data, err = b.ethCall(erc20DecimalsSignature, address)
- if err != nil {
- return nil, err
- }
- contract = bchain.Erc20Contract{
- Name: name,
- Symbol: symbol,
- }
- d := parseErc20NumericProperty(contractDesc, data)
- if d != nil {
- contract.Decimals = int(uint8(d.Uint64()))
+ if name != "" {
+ data, err = b.ethCall(erc20SymbolSignature, address)
+ if err != nil {
+ return nil, err
+ }
+ symbol := parseErc20StringProperty(contractDesc, data)
+ data, err = b.ethCall(erc20DecimalsSignature, address)
+ if err != nil {
+ return nil, err
+ }
+ if name == "" {
+ name = address
+ }
+ contract = &bchain.Erc20Contract{
+ Contract: address,
+ Name: name,
+ Symbol: symbol,
+ }
+ d := parseErc20NumericProperty(contractDesc, data)
+ if d != nil {
+ contract.Decimals = int(uint8(d.Uint64()))
+ } else {
+ contract.Decimals = EtherAmountDecimalPoint
+ }
} else {
- contract.Decimals = EtherAmountDecimalPoint
+ contract = nil
}
cachedContractsMux.Lock()
cachedContracts[cds] = contract
cachedContractsMux.Unlock()
}
- return &contract, nil
+ return contract, nil
}
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
diff --git a/bchain/types.go b/bchain/types.go
index db6b06e4..3a86379b 100644
--- a/bchain/types.go
+++ b/bchain/types.go
@@ -163,10 +163,10 @@ func (ad AddressDescriptor) String() string {
// Erc20Contract contains info about ERC20 contract
type Erc20Contract struct {
- Contract string
- Name string
- Symbol string
- Decimals int
+ Contract string `json:"contract"`
+ Name string `json:"name"`
+ Symbol string `json:"symbol"`
+ Decimals int `json:"decimals"`
}
// OnNewBlockFunc is used to send notification about a new block
diff --git a/server/public.go b/server/public.go
index 76e065dc..8294499c 100644
--- a/server/public.go
+++ b/server/public.go
@@ -247,6 +247,7 @@ func (s *PublicServer) newTemplateData() *TemplateData {
CoinName: s.is.Coin,
CoinShortcut: s.is.CoinShortcut,
CoinLabel: s.is.CoinLabel,
+ ChainType: s.chainParser.GetChainType(),
InternalExplorer: s.internalExplorer && !s.is.InitialSync,
TOSLink: api.Text.TOSLink,
}
@@ -336,6 +337,7 @@ type TemplateData struct {
CoinShortcut string
CoinLabel string
InternalExplorer bool
+ ChainType bchain.ChainType
Address *api.Address
AddrStr string
Tx *api.Tx
@@ -447,7 +449,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
if ec != nil {
page = 0
}
- address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, false, false)
+ address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.TxHistory)
if err != nil {
return errorTpl, nil, err
}
@@ -531,7 +533,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
return noTpl, nil, nil
}
- address, err = s.api.GetAddress(q, 0, 1, true, true)
+ address, err = s.api.GetAddress(q, 0, 1, api.ExistOnly)
if err == nil {
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
return noTpl, nil, nil
@@ -686,7 +688,7 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
if ec != nil {
page = 0
}
- address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, true, false)
+ address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory)
}
return address, err
}
diff --git a/static/css/main.css b/static/css/main.css
index 28e7344e..c4ef6a48 100644
--- a/static/css/main.css
+++ b/static/css/main.css
@@ -214,6 +214,11 @@ h3 {
font-weight: bold;
}
+table.data-table table.data-table th {
+ border-top: 0;
+ font-weight: normal;
+}
+
.alert .data-table {
margin: 0;
}
@@ -239,19 +244,7 @@ h3 {
font-size: 14px;
}
-.h-container {
- display: -webkit-box;
- display: -ms-flexbox;
- -ms-flex-wrap: wrap;
- flex-wrap: wrap;
-}
-
-.h-container-6 {
- width: 50%;
- margin: 0;
-}
-
-.h-container ul {
+.h-container ul, .h-container h3 {
margin: 0;
}
diff --git a/static/templates/address.html b/static/templates/address.html
index 7a56e729..30c50855 100644
--- a/static/templates/address.html
+++ b/static/templates/address.html
@@ -1,5 +1,5 @@
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
-
Address
+{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}}
{{formatAmount $addr.Balance}} {{$cs}}
@@ -10,6 +10,40 @@
+ {{- if eq .ChainType 1 -}}
+
+ | Balance |
+ {{formatAmount $addr.Balance}} {{$cs}} |
+
+
+ | Non-contract Transactions |
+ {{$addr.TxApperances}} |
+
+ {{- if $addr.Erc20Tokens -}}
+
+ | ERC20 Tokens |
+
+
+
+
+ | Contract |
+ Tokens |
+ No. Txs |
+
+ {{- range $et := $addr.Erc20Tokens -}}
+
+ | {{$et.Name}} |
+ {{formatAmount $et.Balance}} {{$et.Symbol}} |
+ {{$et.Txs}} |
+
+ {{- end -}}
+
+
+ |
+
+ {{- end -}}
+
+ {{- else -}}
| Total Received |
{{formatAmount $addr.TotalReceived}} {{$cs}} |
@@ -26,6 +60,7 @@
No. Transactions |
{{$addr.TxApperances}} |
+ {{- end -}}
@@ -54,9 +89,9 @@
{{- end}}{{if $addr.Transactions -}}
-
-
Transactions
-
+
+
Transactions
+
{{- range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}}