Control token detail level returned by xpub api
This commit is contained in:
parent
63fb910ecb
commit
4846af9f60
42
api/types.go
42
api/types.go
@ -19,13 +19,13 @@ type GetAddressOption int
|
||||
const (
|
||||
// Basic - only that address is indexed and some basic info
|
||||
Basic GetAddressOption = iota
|
||||
// Balance - only balances
|
||||
Balance
|
||||
// TxidHistory - balances and txids, subject to paging
|
||||
// Tokens - basic info + tokens
|
||||
Tokens
|
||||
// TxidHistory - basic + tokens + txids, subject to paging
|
||||
TxidHistory
|
||||
// TxHistoryLight - balances and easily obtained tx data (not requiring request to backend), subject to paging
|
||||
// TxHistoryLight - basic + tokens + easily obtained tx data (not requiring request to backend), subject to paging
|
||||
TxHistoryLight
|
||||
// TxHistory - balances and full tx data, subject to paging
|
||||
// TxHistory - basic + tokens + full tx data, subject to paging
|
||||
TxHistory
|
||||
)
|
||||
|
||||
@ -138,14 +138,17 @@ const XPUBAddressTokenType TokenType = "XPUBAddress"
|
||||
|
||||
// Token contains info about tokens held by an address
|
||||
type Token struct {
|
||||
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:"-"`
|
||||
Type TokenType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Contract string `json:"contract,omitempty"`
|
||||
Transfers int `json:"transfers"`
|
||||
Symbol string `json:"symbol,omitempty"`
|
||||
Decimals int `json:"decimals,omitempty"`
|
||||
BalanceSat *Amount `json:"balance,omitempty"`
|
||||
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
|
||||
TotalSentSat *Amount `json:"totalSent,omitempty"`
|
||||
ContractIndex string `json:"-"`
|
||||
}
|
||||
|
||||
// TokenTransfer contains info about a token transfer done in a transaction
|
||||
@ -198,6 +201,9 @@ type Paging struct {
|
||||
ItemsOnPage int `json:"itemsOnPage,omitempty"`
|
||||
}
|
||||
|
||||
// TokenDetailLevel specifies detail level of tokens returned by GetAddress and GetXpubAddress
|
||||
type TokenDetailLevel int
|
||||
|
||||
const (
|
||||
// AddressFilterVoutOff disables filtering of transactions by vout
|
||||
AddressFilterVoutOff = -1
|
||||
@ -205,6 +211,13 @@ const (
|
||||
AddressFilterVoutInputs = -2
|
||||
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
|
||||
AddressFilterVoutOutputs = -3
|
||||
|
||||
// TokenDetailNonzeroBalance - use to return only tokens with nonzero balance
|
||||
TokenDetailNonzeroBalance TokenDetailLevel = 0
|
||||
// TokenDetailUsed - use to return tokens with some transfers (even if they have zero balance now)
|
||||
TokenDetailUsed TokenDetailLevel = 1
|
||||
// TokenDetailDiscovered - use to return all discovered tokens
|
||||
TokenDetailDiscovered TokenDetailLevel = 2
|
||||
)
|
||||
|
||||
// AddressFilter is used to filter data returned from GetAddress api method
|
||||
@ -213,8 +226,7 @@ type AddressFilter struct {
|
||||
Contract string
|
||||
FromHeight uint32
|
||||
ToHeight uint32
|
||||
// AllTokens set to true will include xpub addresses with zero balance
|
||||
AllTokens bool
|
||||
TokenLevel TokenDetailLevel
|
||||
// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
|
||||
OnlyConfirmed bool
|
||||
}
|
||||
|
||||
@ -51,7 +51,7 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
|
||||
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
||||
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
|
||||
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
|
||||
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, ^uint32(0), func(t string, height uint32, indexes []int32) error {
|
||||
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error {
|
||||
for _, index := range indexes {
|
||||
// take only inputs
|
||||
if index < 0 {
|
||||
@ -364,7 +364,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool
|
||||
} else {
|
||||
to := filter.ToHeight
|
||||
if to == 0 {
|
||||
to = ^uint32(0)
|
||||
to = maxUint32
|
||||
}
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
|
||||
if err != nil {
|
||||
@ -683,8 +683,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
||||
ba = &db.AddrBalance{}
|
||||
page = 0
|
||||
}
|
||||
// process mempool, only if blockheight filter is off
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
// process mempool, only if toHeight is not specified
|
||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
||||
@ -825,7 +825,7 @@ func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrB
|
||||
// ba can be nil if the address is only in mempool!
|
||||
if ba != nil && !IsZeroBigInt(&ba.BalanceSat) {
|
||||
outpoints := make([]bchain.Outpoint, 0, 8)
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, 0, maxUint32, func(txid string, height uint32, indexes []int32) error {
|
||||
for _, index := range indexes {
|
||||
// take only outputs
|
||||
if index >= 0 {
|
||||
@ -943,7 +943,7 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
|
||||
// if it's a number, must be less than int32
|
||||
var hash string
|
||||
height, err := strconv.Atoi(bid)
|
||||
if err == nil && height < int(^uint32(0)) {
|
||||
if err == nil && height < int(maxUint32) {
|
||||
hash, err = w.db.GetBlockHash(uint32(height))
|
||||
if err != nil {
|
||||
hash = bid
|
||||
|
||||
43
api/xpub.go
43
api/xpub.go
@ -218,19 +218,23 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
|
||||
if len(a) > 0 {
|
||||
address = a[0]
|
||||
}
|
||||
var balance *big.Int
|
||||
var balance, totalReceived, totalSent *big.Int
|
||||
var transfers int
|
||||
if ad.balance != nil {
|
||||
balance = &ad.balance.BalanceSat
|
||||
totalSent = &ad.balance.SentSat
|
||||
totalReceived = ad.balance.ReceivedSat()
|
||||
transfers = int(ad.balance.Txs)
|
||||
}
|
||||
return Token{
|
||||
Type: XPUBAddressTokenType,
|
||||
Name: address,
|
||||
Decimals: w.chainParser.AmountDecimals(),
|
||||
BalanceSat: (*Amount)(balance),
|
||||
Transfers: transfers,
|
||||
Contract: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
|
||||
Type: XPUBAddressTokenType,
|
||||
Name: address,
|
||||
Decimals: w.chainParser.AmountDecimals(),
|
||||
BalanceSat: (*Amount)(balance),
|
||||
TotalReceivedSat: (*Amount)(totalReceived),
|
||||
TotalSentSat: (*Amount)(totalSent),
|
||||
Transfers: transfers,
|
||||
Path: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,8 +375,8 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
|
||||
}
|
||||
totalResults = -1
|
||||
}
|
||||
// process mempool, only if blockheight filter is off
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
// process mempool, only if ToHeight is not specified
|
||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txmMap = make(map[string]*Tx)
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
@ -442,19 +446,27 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Get
|
||||
}
|
||||
}
|
||||
totalTokens := 0
|
||||
xpubAddresses := make(map[string]struct{})
|
||||
tokens := make([]Token, 0, 4)
|
||||
var tokens []Token
|
||||
var xpubAddresses map[string]struct{}
|
||||
if option != Basic {
|
||||
tokens = make([]Token, 0, 4)
|
||||
xpubAddresses = make(map[string]struct{})
|
||||
}
|
||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
token := w.tokenFromXpubAddress(data, ad, ci, i)
|
||||
if ad.balance != nil {
|
||||
totalTokens++
|
||||
if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) {
|
||||
}
|
||||
if option != Basic {
|
||||
token := w.tokenFromXpubAddress(data, ad, ci, i)
|
||||
if filter.TokenLevel == TokenDetailDiscovered ||
|
||||
filter.TokenLevel == TokenDetailUsed && ad.balance != nil ||
|
||||
filter.TokenLevel == TokenDetailNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) {
|
||||
tokens = append(tokens, token)
|
||||
}
|
||||
xpubAddresses[token.Name] = struct{}{}
|
||||
}
|
||||
xpubAddresses[token.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
var totalReceived big.Int
|
||||
@ -483,7 +495,6 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
|
||||
start := time.Now()
|
||||
data, _, err := w.getXpubData(xpub, 0, 1, Basic, &AddressFilter{
|
||||
Vout: AddressFilterVoutOff,
|
||||
AllTokens: false,
|
||||
OnlyConfirmed: onlyConfirmed,
|
||||
}, gap)
|
||||
if err != nil {
|
||||
@ -509,7 +520,7 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
|
||||
for j := range utxos {
|
||||
a := &utxos[j]
|
||||
a.Address = t.Name
|
||||
a.Path = t.Contract
|
||||
a.Path = t.Path
|
||||
}
|
||||
r = append(r, utxos...)
|
||||
}
|
||||
|
||||
@ -385,27 +385,27 @@ const (
|
||||
|
||||
// TemplateData is used to transfer data to the templates
|
||||
type TemplateData struct {
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
CoinLabel string
|
||||
InternalExplorer bool
|
||||
ChainType bchain.ChainType
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.APIError
|
||||
Blocks *api.Blocks
|
||||
Block *api.Block
|
||||
Info *api.SystemInfo
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
PageParams template.URL
|
||||
TOSLink string
|
||||
SendTxHex string
|
||||
Status string
|
||||
AllTokens bool
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
CoinLabel string
|
||||
InternalExplorer bool
|
||||
ChainType bchain.ChainType
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.APIError
|
||||
Blocks *api.Blocks
|
||||
Block *api.Block
|
||||
Info *api.SystemInfo
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
PageParams template.URL
|
||||
TOSLink string
|
||||
SendTxHex string
|
||||
Status string
|
||||
NonZeroBalanceTokens bool
|
||||
}
|
||||
|
||||
func (s *PublicServer) parseTemplates() []*template.Template {
|
||||
@ -600,7 +600,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
|
||||
return addressTpl, data, nil
|
||||
}
|
||||
|
||||
func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.GetAddressOption) (*api.Address, error) {
|
||||
func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.GetAddressOption) (*api.Address, api.TokenDetailLevel, error) {
|
||||
var fn = api.AddressFilterVoutOff
|
||||
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if ec != nil {
|
||||
@ -624,16 +624,26 @@ func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int
|
||||
if ec != nil {
|
||||
gap = 0
|
||||
}
|
||||
allAddresses, _ := strconv.ParseBool(r.URL.Query().Get("alladdresses"))
|
||||
return s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, AllTokens: allAddresses}, gap)
|
||||
tokenLevel := api.TokenDetailNonzeroBalance
|
||||
switch r.URL.Query().Get("tokenlevel") {
|
||||
case "discovered":
|
||||
tokenLevel = api.TokenDetailDiscovered
|
||||
case "used":
|
||||
tokenLevel = api.TokenDetailUsed
|
||||
case "nonzero":
|
||||
tokenLevel = api.TokenDetailNonzeroBalance
|
||||
}
|
||||
a, err := s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, TokenLevel: tokenLevel}, gap)
|
||||
return a, tokenLevel, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var address *api.Address
|
||||
var tokenLevel api.TokenDetailLevel
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "xpub"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
address, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsOnPage, api.TxHistoryLight)
|
||||
address, tokenLevel, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsOnPage, api.TxHistoryLight)
|
||||
if err != nil {
|
||||
return errorTpl, nil, err
|
||||
}
|
||||
@ -648,8 +658,7 @@ func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl
|
||||
data.PageParams = template.URL("&filter=" + filter)
|
||||
data.Address.Filter = filter
|
||||
}
|
||||
allAddresses := r.URL.Query().Get("alladdresses")
|
||||
data.AllTokens, _ = strconv.ParseBool(allAddresses)
|
||||
data.NonZeroBalanceTokens = tokenLevel == api.TokenDetailNonzeroBalance
|
||||
return xpubTpl, data, nil
|
||||
}
|
||||
|
||||
@ -904,7 +913,7 @@ func (s *PublicServer) apiXpub(r *http.Request, apiVersion int) (interface{}, er
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
address, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsInAPI, api.TxidHistory)
|
||||
address, _, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsInAPI, api.TxidHistory)
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.AddressToV1(address), nil
|
||||
}
|
||||
|
||||
@ -349,8 +349,8 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
|
||||
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
|
||||
var opt api.GetAddressOption
|
||||
switch req.Details {
|
||||
case "balance":
|
||||
opt = api.Balance
|
||||
case "tokens":
|
||||
opt = api.Tokens
|
||||
case "txids":
|
||||
opt = api.TxidHistory
|
||||
case "txs":
|
||||
@ -363,7 +363,7 @@ func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address,
|
||||
ToHeight: uint32(req.ToHeight),
|
||||
Contract: req.ContractFilter,
|
||||
Vout: api.AddressFilterVoutOff,
|
||||
AllTokens: true,
|
||||
TokenLevel: api.TokenDetailDiscovered,
|
||||
}
|
||||
a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, 0)
|
||||
if err != nil {
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</tr>
|
||||
{{- if $addr.TotalTokens -}}
|
||||
<tr>
|
||||
<td>{{if $data.AllTokens}}XPUB Addresses{{else}}XPUB Addresses with Balance{{end}}</td>
|
||||
<td>{{if $data.NonZeroBalanceTokens}}XPUB Addresses with Balance{{else}}XPUB Addresses{{end}}</td>
|
||||
<td style="padding: 0;">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
@ -46,12 +46,12 @@
|
||||
<td class="data ellipsis"><a href="/address/{{$t.Name}}">{{$t.Name}}</a></td>
|
||||
<td class="data">{{formatAmount $t.BalanceSat}} {{$cs}}</td>
|
||||
<td class="data">{{$t.Transfers}}</td>
|
||||
<td>{{$t.Contract}}</td>
|
||||
<td>{{$t.Path}}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{- if not $data.AllTokens -}}
|
||||
{{- if $data.NonZeroBalanceTokens -}}
|
||||
<tr>
|
||||
<td colspan="4"><a href="?alladdresses=true">Show all XPUB addresses</a></td>
|
||||
<td colspan="4"><a href="?tokenlevel=used">Show all XPUB addresses</a></td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
|
||||
@ -304,7 +304,7 @@
|
||||
<input type="text" placeholder="descriptor" style="width: 79%" class="form-control" id="getAccountInfoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
||||
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
|
||||
<option value="basic">Basic</option>
|
||||
<option value="balance">Balance</option>
|
||||
<option value="tokens">Tokens</option>
|
||||
<option value="txids">Txids</option>
|
||||
<option value="txs">Transactions</option>
|
||||
</select>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user