Generalize tokens instead of ERC20 tokens in API

This commit is contained in:
Martin Boehm 2019-01-07 15:45:00 +01:00
parent 07108b8c4f
commit 2c3af5129e
5 changed files with 88 additions and 73 deletions

View File

@ -104,27 +104,34 @@ type Vout struct {
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
} }
// Erc20Token contains info about ERC20 token held by an address // TokenType specifies type of token
type Erc20Token struct { type TokenType string
Contract string `json:"contract"`
Transfers int `json:"transfers"` // ERC20TokenType is Ethereum ERC20 token
Name string `json:"name"` const ERC20TokenType TokenType = "ERC20"
Symbol string `json:"symbol"`
Decimals int `json:"decimals"` // Token contains info about tokens held by an address
BalanceSat *Amount `json:"balance,omitempty"` type Token struct {
ContractIndex string `json:"-"` Type TokenType `json:"type"`
Contract string `json:"contract"`
Transfers int `json:"transfers"`
Name string `json:"name"`
Symbol string `json:"symbol"`
Decimals int `json:"decimals"`
BalanceSat *Amount `json:"balance,omitempty"`
ContractIndex string `json:"-"`
} }
// TokenTransfer contains info about a token transfer done in a transaction // TokenTransfer contains info about a token transfer done in a transaction
type TokenTransfer struct { type TokenTransfer struct {
Type string `json:"type"` Type TokenType `json:"type"`
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
Token string `json:"token"` Token string `json:"token"`
Name string `json:"name"` Name string `json:"name"`
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Decimals int `json:"decimals"` Decimals int `json:"decimals"`
Value *Amount `json:"value"` Value *Amount `json:"value"`
} }
// EthereumSpecific contains ethereum specific transaction data // EthereumSpecific contains ethereum specific transaction data
@ -186,19 +193,20 @@ type AddressFilter struct {
// Address holds information about address and its transactions // Address holds information about address and its transactions
type Address struct { type Address struct {
Paging Paging
AddrStr string `json:"addrStr"` AddrStr string `json:"addrStr"`
BalanceSat *Amount `json:"balance"` BalanceSat *Amount `json:"balance"`
TotalReceivedSat *Amount `json:"totalReceived,omitempty"` TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalSentSat *Amount `json:"totalSent,omitempty"` TotalSentSat *Amount `json:"totalSent,omitempty"`
UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"` UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"` UnconfirmedTxs int `json:"unconfirmedTxs"`
TxApperances int `json:"txApperances"` Txs int `json:"txs"`
Transactions []*Tx `json:"transactions,omitempty"` NonTokenTxs int `json:"nontokenTxs,omitempty"`
Txids []string `json:"txids,omitempty"` Transactions []*Tx `json:"transactions,omitempty"`
Nonce string `json:"nonce,omitempty"` Txids []string `json:"txids,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"` Nonce string `json:"nonce,omitempty"`
Erc20Tokens []Erc20Token `json:"erc20tokens,omitempty"` Tokens []Token `json:"tokens,omitempty"`
Filter string `json:"-"` Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
Filter string `json:"-"`
} }
// AddressUtxo holds information about address and its transactions // AddressUtxo holds information about address and its transactions

View File

@ -184,10 +184,10 @@ func (w *Worker) AddressToV1(a *Address) *AddressV1 {
TotalReceived: a.TotalReceivedSat.DecimalString(d), TotalReceived: a.TotalReceivedSat.DecimalString(d),
TotalSent: a.TotalSentSat.DecimalString(d), TotalSent: a.TotalSentSat.DecimalString(d),
Transactions: w.transactionsToV1(a.Transactions), Transactions: w.transactionsToV1(a.Transactions),
TxApperances: a.TxApperances, TxApperances: a.Txs,
Txids: a.Txids, Txids: a.Txids,
UnconfirmedBalance: a.UnconfirmedBalanceSat.DecimalString(d), UnconfirmedBalance: a.UnconfirmedBalanceSat.DecimalString(d),
UnconfirmedTxApperances: a.UnconfirmedTxApperances, UnconfirmedTxApperances: a.UnconfirmedTxs,
} }
} }

View File

@ -242,7 +242,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
erc20c = &bchain.Erc20Contract{Name: e.Contract} erc20c = &bchain.Erc20Contract{Name: e.Contract}
} }
tokens[i] = TokenTransfer{ tokens[i] = TokenTransfer{
Type: "ERC20", Type: ERC20TokenType,
Token: e.Contract, Token: e.Contract,
From: e.From, From: e.From,
To: e.To, To: e.To,
@ -460,16 +460,16 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page }, from, to, page
} }
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Erc20Token, *bchain.Erc20Contract, uint64, error) { func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, error) {
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
erc20t []Erc20Token tokens []Token
ci *bchain.Erc20Contract ci *bchain.Erc20Contract
n uint64 n uint64
) )
ca, err := w.db.GetAddrDescContracts(addrDesc) ca, err := w.db.GetAddrDescContracts(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true) return nil, nil, nil, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
} }
if ca != nil { if ca != nil {
ba = &db.AddrBalance{ ba = &db.AddrBalance{
@ -478,23 +478,23 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
var b *big.Int var b *big.Int
b, err = w.chain.EthereumTypeGetBalance(addrDesc) b, err = w.chain.EthereumTypeGetBalance(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc) return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
} }
if b != nil { if b != nil {
ba.BalanceSat = *b ba.BalanceSat = *b
} }
n, err = w.chain.EthereumTypeGetNonce(addrDesc) n, err = w.chain.EthereumTypeGetNonce(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc) return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
} }
var filterDesc bchain.AddressDescriptor var filterDesc bchain.AddressDescriptor
if filter.Contract != "" { if filter.Contract != "" {
filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract) filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract)
if err != nil { if err != nil {
return nil, nil, nil, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true) return nil, nil, nil, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
} }
} }
erc20t = make([]Erc20Token, len(ca.Contracts)) tokens = make([]Token, len(ca.Contracts))
var j int var j int
for i, c := range ca.Contracts { for i, c := range ca.Contracts {
if len(filterDesc) > 0 { if len(filterDesc) > 0 {
@ -506,7 +506,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
} }
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract) ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
if err != nil { if err != nil {
return nil, nil, nil, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract) return nil, nil, nil, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
} }
if ci == nil { if ci == nil {
ci = &bchain.Erc20Contract{} ci = &bchain.Erc20Contract{}
@ -526,7 +526,8 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
} else { } else {
b = nil b = nil
} }
erc20t[j] = Erc20Token{ tokens[j] = Token{
Type: ERC20TokenType,
BalanceSat: (*Amount)(b), BalanceSat: (*Amount)(b),
Contract: ci.Contract, Contract: ci.Contract,
Name: ci.Name, Name: ci.Name,
@ -537,13 +538,13 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
} }
j++ j++
} }
erc20t = erc20t[:j] tokens = tokens[:j]
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc) ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
if err != nil { if err != nil {
return nil, nil, nil, 0, err return nil, nil, nil, 0, 0, err
} }
} }
return ba, erc20t, ci, n, nil return ba, tokens, ci, n, int(ca.NonContractTxs), nil
} }
// GetAddress computes address value and gets transactions for given address // GetAddress computes address value and gets transactions for given address
@ -559,7 +560,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
} }
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
erc20t []Erc20Token tokens []Token
erc20c *bchain.Erc20Contract erc20c *bchain.Erc20Contract
txm []string txm []string
txs []*Tx txs []*Tx
@ -568,10 +569,11 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
uBalSat big.Int uBalSat big.Int
totalReceived, totalSent *big.Int totalReceived, totalSent *big.Int
nonce string nonce string
nonTokenTxs int
) )
if w.chainType == bchain.ChainEthereumType { if w.chainType == bchain.ChainEthereumType {
var n uint64 var n uint64
ba, erc20t, erc20c, n, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter) ba, tokens, erc20c, n, nonTokenTxs, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -684,19 +686,20 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
totalSent = &ba.SentSat totalSent = &ba.SentSat
} }
r := &Address{ r := &Address{
Paging: pg, Paging: pg,
AddrStr: address, AddrStr: address,
BalanceSat: (*Amount)(&ba.BalanceSat), BalanceSat: (*Amount)(&ba.BalanceSat),
TotalReceivedSat: (*Amount)(totalReceived), TotalReceivedSat: (*Amount)(totalReceived),
TotalSentSat: (*Amount)(totalSent), TotalSentSat: (*Amount)(totalSent),
TxApperances: int(ba.Txs), Txs: int(ba.Txs),
UnconfirmedBalanceSat: (*Amount)(&uBalSat), NonTokenTxs: nonTokenTxs,
UnconfirmedTxApperances: len(txm), UnconfirmedBalanceSat: (*Amount)(&uBalSat),
Transactions: txs, UnconfirmedTxs: len(txm),
Txids: txids, Transactions: txs,
Erc20Contract: erc20c, Txids: txids,
Erc20Tokens: erc20t, Tokens: tokens,
Nonce: nonce, Erc20Contract: erc20c,
Nonce: nonce,
} }
glog.Info("GetAddress ", address, " finished in ", time.Since(start)) glog.Info("GetAddress ", address, " finished in ", time.Since(start))
return r, nil return r, nil

View File

@ -433,7 +433,7 @@ func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK, status: http.StatusOK,
contentType: "application/json; charset=utf-8", contentType: "application/json; charset=utf-8",
body: []string{ body: []string{
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"1234567890123","totalSent":"1234567890123","unconfirmedBalance":"0","unconfirmedTxApperances":0,"txApperances":2,"txids":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`, `{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"1234567890123","totalSent":"1234567890123","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":2,"txids":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
}, },
}, },
{ {

View File

@ -17,13 +17,17 @@
</tr> </tr>
<tr> <tr>
<td>Transactions</td> <td>Transactions</td>
<td class="data">{{$addr.TxApperances}}</td> <td class="data">{{$addr.Txs}}</td>
</tr>
<tr>
<td>Non-contract Transactions</td>
<td class="data">{{$addr.NonTokenTxs}}</td>
</tr> </tr>
<tr> <tr>
<td>Nonce</td> <td>Nonce</td>
<td class="data">{{$addr.Nonce}}</td> <td class="data">{{$addr.Nonce}}</td>
</tr> </tr>
{{- if $addr.Erc20Tokens -}} {{- if $addr.Tokens -}}
<tr> <tr>
<td>ERC20 Tokens</td> <td>ERC20 Tokens</td>
<td style="padding: 0;"> <td style="padding: 0;">
@ -34,11 +38,11 @@
<th>Tokens</th> <th>Tokens</th>
<th style="width: 15%;">Transfers</th> <th style="width: 15%;">Transfers</th>
</tr> </tr>
{{- range $et := $addr.Erc20Tokens -}} {{- range $t := $addr.Tokens -}}
<tr> <tr>
<td class="data ellipsis"><a href="/address/{{$et.Contract}}">{{$et.Name}}</a></td> <td class="data ellipsis">{{if $t.Contract}}<a href="/address/{{$t.Contract}}">{{$t.Name}}</a>{{else}}{{$t.Name}}{{end}}</td>
<td class="data">{{formatAmountWithDecimals $et.BalanceSat $et.Decimals}} {{$et.Symbol}}</td> <td class="data">{{formatAmountWithDecimals $t.BalanceSat $t.Decimals}} {{$t.Symbol}}</td>
<td class="data">{{$et.Transfers}}</td> <td class="data">{{$t.Transfers}}</td>
</tr> </tr>
{{- end -}} {{- end -}}
</tbody> </tbody>
@ -62,7 +66,7 @@
</tr> </tr>
<tr> <tr>
<td>No. Transactions</td> <td>No. Transactions</td>
<td class="data">{{$addr.TxApperances}}</td> <td class="data">{{$addr.Txs}}</td>
</tr> </tr>
{{- end -}} {{- end -}}
</tbody> </tbody>
@ -76,7 +80,7 @@
</script> </script>
</div> </div>
</div> </div>
{{- if $addr.UnconfirmedTxApperances -}} {{- if $addr.UnconfirmedTxs -}}
<h3>Unconfirmed</h3> <h3>Unconfirmed</h3>
<div class="data-div"> <div class="data-div">
<table class="table data-table"> <table class="table data-table">
@ -87,7 +91,7 @@
</tr> </tr>
<tr> <tr>
<td>No. Transactions</td> <td>No. Transactions</td>
<td class="data">{{$addr.UnconfirmedTxApperances}}</td> <td class="data">{{$addr.UnconfirmedTxs}}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -99,10 +103,10 @@
<option>All</option> <option>All</option>
<option {{if eq $addr.Filter "inputs" -}} selected{{end}} value="inputs">Inputs</option> <option {{if eq $addr.Filter "inputs" -}} selected{{end}} value="inputs">Inputs</option>
<option {{if eq $addr.Filter "outputs" -}} selected{{end}} value="outputs">Outputs</option> <option {{if eq $addr.Filter "outputs" -}} selected{{end}} value="outputs">Outputs</option>
{{- if $addr.Erc20Tokens -}} {{- if $addr.Tokens -}}
<option {{if eq $addr.Filter "0" -}} selected{{end}} value="0">Non-contract</option> <option {{if eq $addr.Filter "0" -}} selected{{end}} value="0">Non-contract</option>
{{- range $et := $addr.Erc20Tokens -}} {{- range $t := $addr.Tokens -}}
<option {{if eq $addr.Filter $et.ContractIndex -}} selected{{end}} value="{{$et.ContractIndex}}">{{$et.Name}}</option> <option {{if eq $addr.Filter $t.ContractIndex -}} selected{{end}} value="{{$t.ContractIndex}}">{{$t.Name}}</option>
{{- end -}} {{- end -}}
{{- end -}} {{- end -}}
</select> </select>