Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const (
AccountDetailsTokenBalances
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
AccountDetailsTxidHistory
// SYSCOIN
// AccountDetailsTxHistorySummary - basic + token balances + compact account-context tx summaries, subject to paging
AccountDetailsTxHistorySummary
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
AccountDetailsTxHistoryLight
// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
Expand Down Expand Up @@ -393,6 +396,11 @@ type Tx struct {
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty" ts_type:"any" ts_doc:"Blockchain-specific extended data."`
ChainExtraData *TxChainExtraData `json:"chainExtraData,omitempty" ts_type:"{ payloadType: 'tron'; payload?: TronChainExtraData } | { payloadType: string; payload?: any }" ts_doc:"Additional normalized chain-specific transaction data. Use payloadType as discriminator for payload."`
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty" ts_doc:"List of token transfers that occurred in this transaction."`
// SYSCOIN: account-context transaction summary fields for compact wallet history.
Direction string `json:"direction,omitempty" ts_doc:"Transaction direction relative to the queried account, when returned by account summary endpoints."`
AddressValueInSat *Amount `json:"addressValueIn,omitempty" ts_doc:"Total input value belonging to the queried account, when returned by account summary endpoints."`
AddressValueOutSat *Amount `json:"addressValueOut,omitempty" ts_doc:"Total output value belonging to the queried account, when returned by account summary endpoints."`
AccountAssetTransfers []TokenTransfer `json:"accountAssetTransfers,omitempty" ts_doc:"Syscoin SPT transfers summarized relative to the queried account."`
// SYSCOIN: SPT transaction type and optional decoded memo.
TokenType *bchain.TokenType `json:"tokenType,omitempty" ts_doc:"Syscoin SPT transaction type."`
Memo []byte `json:"memo,omitempty" ts_doc:"Syscoin SPT memo decoded from OP_RETURN."`
Expand Down
195 changes: 188 additions & 7 deletions api/worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -1462,6 +1462,138 @@ func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int {
return &val
}

// SYSCOIN
type accountAssetTransferSummary struct {
value big.Int
symbol string
decimals int
}

// SYSCOIN
func addrDescInSet(addrDesc bchain.AddressDescriptor, addrDescs map[string]struct{}) bool {
if len(addrDescs) == 0 {
return false
}
_, found := addrDescs[string(addrDesc)]
return found
}

// SYSCOIN
func (w *Worker) addAccountAssetTransfer(m map[string]*accountAssetTransferSummary, assetInfo *AssetInfo, sign int) {
if assetInfo == nil || assetInfo.AssetGuid == "" || assetInfo.ValueSat == nil {
return
}
summary := m[assetInfo.AssetGuid]
if summary == nil {
summary = &accountAssetTransferSummary{
symbol: assetInfo.Symbol,
decimals: w.chainParser.AmountDecimals(),
}
if guid, err := strconv.ParseUint(assetInfo.AssetGuid, 10, 64); err == nil {
if dbAsset, err := w.db.GetAsset(guid, nil); err == nil {
summary.symbol = string(dbAsset.AssetObj.Symbol)
summary.decimals = int(dbAsset.AssetObj.Precision)
}
}
m[assetInfo.AssetGuid] = summary
}
if summary.symbol == "" {
summary.symbol = assetInfo.Symbol
}
delta := new(big.Int).Set((*big.Int)(assetInfo.ValueSat))
if sign < 0 {
delta.Neg(delta)
}
summary.value.Add(&summary.value, delta)
}

// SYSCOIN
func accountAssetTransfersFromSummary(m map[string]*accountAssetTransferSummary) []TokenTransfer {
if len(m) == 0 {
return nil
}
keys := make([]string, 0, len(m))
for guid, summary := range m {
if summary.value.Sign() != 0 {
keys = append(keys, guid)
}
}
if len(keys) == 0 {
return nil
}
sort.Strings(keys)
transfers := make([]TokenTransfer, 0, len(keys))
for _, guid := range keys {
summary := m[guid]
value := new(big.Int).Abs(&summary.value)
transfers = append(transfers, TokenTransfer{
Type: bchain.TokenStandardName("SPT"),
Standard: bchain.TokenStandardName("SPT"),
Contract: guid,
AssetGuid: guid,
Symbol: summary.symbol,
Decimals: summary.decimals,
Value: (*Amount)(value),
Comment on lines +1529 to +1536

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate SPT transfer identity fields

These summary transfers are returned both as accountAssetTransfers and as TokenTransfers, but the literal leaves the non-omitempty type, standard, and contract fields at empty strings. Existing Syscoin transfer rows from getSyscoinAssetTransfers identify SPT transfers with SPT and the asset GUID as the contract; without the same fields here, clients consuming the normal token-transfer shape cannot reliably classify or key summary SPT movements.

Useful? React with 👍 / 👎.

})
}
return transfers
}

// SYSCOIN
func (w *Worker) summarizeTxForAccount(tx *Tx, addrDescs map[string]struct{}) *Tx {
if tx == nil {
return nil
}
var valueIn, valueOut big.Int
assetTransfers := make(map[string]*accountAssetTransferSummary)
for i := range tx.Vin {
vin := &tx.Vin[i]
if !addrDescInSet(vin.AddrDesc, addrDescs) {
continue
}
if vin.ValueSat != nil {
valueIn.Add(&valueIn, (*big.Int)(vin.ValueSat))
}
w.addAccountAssetTransfer(assetTransfers, vin.AssetInfo, -1)
}
for i := range tx.Vout {
vout := &tx.Vout[i]
if !addrDescInSet(vout.AddrDesc, addrDescs) {
continue
}
if vout.ValueSat != nil {
valueOut.Add(&valueOut, (*big.Int)(vout.ValueSat))
}
w.addAccountAssetTransfer(assetTransfers, vout.AssetInfo, 1)
}
direction := "received"
if valueIn.Cmp(&valueOut) > 0 {
direction = "sent"
}
accountAssetTransfers := accountAssetTransfersFromSummary(assetTransfers)
return &Tx{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Populate vin/vout arrays in txsummary rows

When an account or xpub request uses details=txsummary, this constructor returns a Tx with nil Vin and Vout; those fields do not have omitempty, so the JSON contains "vin": null and "vout": null even though the public Tx schema and generated TypeScript type require arrays. Clients that iterate these fields for every transaction row can break only on the new summary mode, so initialize them to empty slices or expose a summary-specific response shape.

Useful? React with 👍 / 👎.

Txid: tx.Txid,
Vin: []Vin{},
Vout: []Vout{},
Blockhash: tx.Blockhash,
Blockheight: tx.Blockheight,
Confirmations: tx.Confirmations,
ConfirmationETABlocks: tx.ConfirmationETABlocks,
ConfirmationETASeconds: tx.ConfirmationETASeconds,
Blocktime: tx.Blocktime,
ValueInSat: (*Amount)(&valueIn),
ValueOutSat: (*Amount)(&valueOut),
FeesSat: tx.FeesSat,
TokenTransfers: accountAssetTransfers,
Direction: direction,
AddressValueInSat: (*Amount)(&valueIn),
AddressValueOutSat: (*Amount)(&valueOut),
AccountAssetTransfers: accountAssetTransfers,
TokenType: tx.TokenType,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve memos in txsummary rows

For Syscoin SPT transactions that carry a decoded OP_RETURN memo, details=txsummary now returns a compact Tx built here, but it only copies TokenType and drops tx.Memo. The regular light/full transaction paths still populate Memo, and this commit also documents Tx.memo, so wallets using the new summary history cannot display memos for exactly those SPT rows; copy Memo into the summarized transaction alongside TokenType.

Useful? React with 👍 / 👎.

Memo: tx.Memo,
}
}

type syscoinTokenMempoolInfo struct {
used bool
unconfirmedTxs int
Expand Down Expand Up @@ -1574,6 +1706,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
vin.ValueSat = (*Amount)(&tai.ValueSat)
valInSat.Add(&valInSat, &tai.ValueSat)
vin.AssetInfo = w.assetInfoToAPI(tai.AssetInfo) // SYSCOIN
vin.AddrDesc = tai.AddrDesc // SYSCOIN
vin.Addresses, vin.IsAddress, err = tai.Addresses(w.chainParser)
if err != nil {
glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai)
Expand All @@ -1592,6 +1725,7 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
vout.ValueSat = (*Amount)(&tao.ValueSat)
valOutSat.Add(&valOutSat, &tao.ValueSat)
vout.AssetInfo = w.assetInfoToAPI(tao.AssetInfo) // SYSCOIN
vout.AddrDesc = tao.AddrDesc // SYSCOIN
vout.Addresses, vout.IsAddress, err = tao.Addresses(w.chainParser)
if err != nil {
glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao)
Expand Down Expand Up @@ -1679,6 +1813,36 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
}, from, to, page
}

// SYSCOIN
func computePagingWithFirstPageMempool(confirmedCount, mempoolCount, page, itemsOnPage int) (Paging, int, int, int) {
if itemsOnPage <= 0 {
itemsOnPage = 1
}
mempoolSlots := mempoolCount
if mempoolSlots > itemsOnPage {
mempoolSlots = itemsOnPage
}
if mempoolSlots < 0 {
mempoolSlots = 0
}
pg, from, to, page := computePaging(confirmedCount+mempoolSlots, page, itemsOnPage)
from -= mempoolSlots
if from < 0 {
from = 0
}
to -= mempoolSlots
if to < 0 {
to = 0
}
if from > confirmedCount {
from = confirmedCount
}
if to > confirmedCount {
to = confirmedCount
}
return pg, from, to, page
}

func (w *Worker) getEthereumContractBalance(addrDesc bchain.AddressDescriptor, index int, c *db.AddrContract, details AccountDetails, ticker *common.CurrencyRatesTicker, secondaryCoin string, erc20Balance *big.Int, erc20Batched bool) (*Token, error) {
standard := bchain.EthereumTokenStandardMap[c.Standard]
ci, validContract, err := w.getContractDescriptorInfo(c.Contract, standard)
Expand Down Expand Up @@ -2047,8 +2211,8 @@ func (w *Worker) getStakingPoolsData(addrDesc bchain.AddressDescriptor) ([]Staki
func (w *Worker) txFromTxid(txid string, bestHeight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) {
var tx *Tx
var err error
// only ChainBitcoinType supports TxHistoryLight
if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType {
// SYSCOIN: summary history shares the indexed Bitcoin-type light path.
if option >= AccountDetailsTxHistorySummary && option <= AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Populate descriptors before summarizing indexed transactions

When details=txsummary is used on confirmed Bitcoin/Syscoin history, this branch now reuses the light txFromTxAddress path, but that converter only fills Addresses and never copies AddrDesc into each Vin/Vout. summarizeTxForAccount matches exclusively on vin.AddrDesc/vout.AddrDesc, so confirmed summaries from this path report zero account input/output, no asset transfers, and default to received; mempool summaries still work because they come from getTransaction. Please either populate descriptors in the light conversion or summarize against the indexed TxAddresses descriptors.

Useful? React with 👍 / 👎.

ta, err := w.db.GetTxAddresses(txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
Expand Down Expand Up @@ -2184,6 +2348,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
if err != nil {
return nil, err
}
accountAddrDescs := map[string]struct{}{string(addrDesc): {}} // SYSCOIN
accountChainExtraData, err = w.getAccountChainExtraData(addrDesc)
if err != nil {
glog.Warningf("GetAccountChainExtraData error %v, %v", err, address)
Expand Down Expand Up @@ -2250,6 +2415,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
if page == 0 {
if option == AccountDetailsTxidHistory {
txids = append(txids, tx.Txid)
} else if option == AccountDetailsTxHistorySummary {
txs = append(txs, w.summarizeTxForAccount(tx, accountAddrDescs)) // SYSCOIN
} else if option >= AccountDetailsTxHistoryLight {
setIsOwnAddress(tx, address)
txs = append(txs, tx)
Expand All @@ -2271,12 +2438,21 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
return nil, errors.Annotatef(err, "GetBestBlock")
}
var from, to int
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
if option == AccountDetailsTxHistorySummary {
pg, from, to, page = computePagingWithFirstPageMempool(len(txc), unconfirmedTxs, page, txsOnPage) // SYSCOIN
} else {
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
}
if len(txc) >= txsOnPage {
if totalResults < 0 {
pg.TotalPages = -1
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
// SYSCOIN
if option == AccountDetailsTxHistorySummary {
pg, _, _, _ = computePagingWithFirstPageMempool(totalResults, unconfirmedTxs, page, txsOnPage)
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
}
}
}
for i := from; i < to; i++ {
Expand All @@ -2288,8 +2464,12 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
if err != nil {
return nil, err
}
setIsOwnAddress(tx, address)
txs = append(txs, tx)
if option == AccountDetailsTxHistorySummary {
txs = append(txs, w.summarizeTxForAccount(tx, accountAddrDescs)) // SYSCOIN
Comment on lines +2467 to +2468

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Offset confirmed txsummary rows after mempool entries

When an address has unconfirmed transactions, page 1 prepends their summaries above and then this branch adds confirmed rows using from/to computed as if no mempool rows existed; the later page-size trim drops the extra confirmed rows, while page 2 starts at the next full confirmed page. For details=txsummary&pageSize=10 with two mempool transactions, confirmed rows 9-10 are skipped, so the confirmed window needs to be reduced/offset by the mempool count before appending summaries.

Useful? React with 👍 / 👎.

} else {
setIsOwnAddress(tx, address)
txs = append(txs, tx)
}
}
}
}
Expand All @@ -2298,7 +2478,8 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
if page == 0 && txsOnPage > 0 {
if option == AccountDetailsTxidHistory && len(txids) > txsOnPage {
txids = txids[:txsOnPage]
} else if option >= AccountDetailsTxHistoryLight && len(txs) > txsOnPage {
} else if option >= AccountDetailsTxHistorySummary && len(txs) > txsOnPage {
// SYSCOIN
txs = txs[:txsOnPage]
}
}
Expand Down
38 changes: 33 additions & 5 deletions api/xpub.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,12 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
if err != nil {
return nil, err
}
accountAddrDescs := make(map[string]struct{}) // SYSCOIN
for _, da := range data.addresses {
for i := range da {
accountAddrDescs[string(da[i].addrDesc)] = struct{}{}
}
}
// setup filtering of txids
var txidFilter func(txid *xpubTxid, ad *xpubAddress) bool
if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff && filter.AssetsMask == bchain.AllMask) {
Expand Down Expand Up @@ -592,6 +598,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
}
addresses := w.newAddressesMapForAliases()
xpubAssetMempool := make(map[string]map[string]*syscoinTokenMempoolInfo) // SYSCOIN
mempoolEntryCount := 0 // SYSCOIN
// process mempool, only if ToHeight is not specified
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
txmMap = make(map[string]*Tx)
Expand Down Expand Up @@ -654,22 +661,31 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
tx.addAddrVinAssetMempool(ad.addrDesc, assetMempool)
}
// mempool txs are returned only on the first page, uniquely and filtered
if page == 0 && xpubMempoolTxidFilter(&txid) {
if xpubMempoolTxidFilter(&txid) {
if _, added := addedMempoolEntries[txid.txid]; added {
continue
}
addedMempoolEntries[txid.txid] = struct{}{}
mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)})
mempoolEntryCount++ // SYSCOIN
if page == 0 {
mempoolEntries = append(mempoolEntries, bchain.MempoolTxidEntry{Txid: txid.txid, Time: uint32(tx.Blocktime)})
}
}
}
}
}
}
// sort the entries by time descending
sort.Sort(mempoolEntries)
for _, entry := range mempoolEntries {
mempoolEntriesToReturn := mempoolEntries
if option == AccountDetailsTxHistorySummary && txsOnPage > 0 && len(mempoolEntriesToReturn) > txsOnPage {
mempoolEntriesToReturn = mempoolEntriesToReturn[:txsOnPage] // SYSCOIN
}
for _, entry := range mempoolEntriesToReturn {
if option == AccountDetailsTxidHistory {
txids = append(txids, entry.Txid)
} else if option == AccountDetailsTxHistorySummary {
txs = append(txs, w.summarizeTxForAccount(txmMap[entry.Txid], accountAddrDescs)) // SYSCOIN
Comment on lines +687 to +688

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound xpub txsummary pages when mempool is present

For xpubs on the first page with unconfirmed transactions, this appends every mempool summary and the confirmed loop below still appends a full page of confirmed transactions, with no later trim in this path. As a result details=txsummary&pageSize=10 can return more than 10 rows and paging metadata that only reflects confirmed history, so clients relying on fixed page sizes get inconsistent pages when mempool entries exist.

Useful? React with 👍 / 👎.

} else if option >= AccountDetailsTxHistoryLight {
txs = append(txs, txmMap[entry.Txid])
}
Expand Down Expand Up @@ -710,12 +726,21 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
totalResults = -1
}
var from, to int
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
if option == AccountDetailsTxHistorySummary {
pg, from, to, page = computePagingWithFirstPageMempool(len(txc), mempoolEntryCount, page, txsOnPage) // SYSCOIN
} else {
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
}
if len(txc) >= txsOnPage {
if totalResults < 0 {
pg.TotalPages = -1
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
// SYSCOIN
if option == AccountDetailsTxHistorySummary {
pg, _, _, _ = computePagingWithFirstPageMempool(totalResults, mempoolEntryCount, page, txsOnPage)
} else {
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
}
}
}
// get confirmed transactions
Expand All @@ -728,6 +753,9 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
if err != nil {
return nil, err
}
if option == AccountDetailsTxHistorySummary {
tx = w.summarizeTxForAccount(tx, accountAddrDescs) // SYSCOIN
}
txs = append(txs, tx)
}
}
Expand Down
Loading
Loading