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 -}} + + + + + + + + + {{- if $addr.Erc20Tokens -}} + + + + + {{- end -}} + + {{- else -}} @@ -26,6 +60,7 @@ + {{- end -}}
Balance{{formatAmount $addr.Balance}} {{$cs}}
Non-contract Transactions{{$addr.TxApperances}}
ERC20 Tokens + + + + + + + + {{- range $et := $addr.Erc20Tokens -}} + + + + + + {{- end -}} + +
ContractTokensNo. Txs
{{$et.Name}}{{formatAmount $et.Balance}} {{$et.Symbol}}{{$et.Txs}}
+
Total Received {{formatAmount $addr.TotalReceived}} {{$cs}}No. Transactions {{$addr.TxApperances}}
@@ -54,9 +89,9 @@
{{- end}}{{if $addr.Transactions -}} -
-

Transactions

- +
+

Transactions

+
{{- range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}}