Add type api.Amount for proper formatting of amounts in json

This commit is contained in:
Martin Boehm 2018-12-13 00:41:58 +01:00
parent d01081fd83
commit 9c142663ce
11 changed files with 216 additions and 156 deletions

View File

@ -2,7 +2,6 @@ package api
import ( import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/bchain/coins/eth"
"blockbook/common" "blockbook/common"
"blockbook/db" "blockbook/db"
"encoding/json" "encoding/json"
@ -42,6 +41,17 @@ func NewAPIError(s string, public bool) error {
} }
} }
// Amount is datatype holding amounts
type Amount big.Int
// MarshalJSON Amount serialization
func (a *Amount) MarshalJSON() (out []byte, err error) {
if a == nil {
return []byte(`"0"`), nil
}
return []byte(`"` + (*big.Int)(a).String() + `"`), nil
}
// ScriptSig contains input script // ScriptSig contains input script
type ScriptSig struct { type ScriptSig struct {
Hex string `json:"hex"` Hex string `json:"hex"`
@ -58,8 +68,7 @@ type Vin struct {
AddrDesc bchain.AddressDescriptor `json:"-"` AddrDesc bchain.AddressDescriptor `json:"-"`
Addresses []string `json:"addresses"` Addresses []string `json:"addresses"`
Searchable bool `json:"-"` Searchable bool `json:"-"`
Value string `json:"value"` ValueSat *Amount `json:"value,omitempty"`
ValueSat big.Int `json:"-"`
} }
// ScriptPubKey contains output script and addresses derived from it // ScriptPubKey contains output script and addresses derived from it
@ -74,8 +83,7 @@ type ScriptPubKey struct {
// Vout contains information about single transaction output // Vout contains information about single transaction output
type Vout struct { type Vout struct {
Value string `json:"value"` ValueSat *Amount `json:"value,omitempty"`
ValueSat big.Int `json:"-"`
N int `json:"n"` N int `json:"n"`
ScriptPubKey ScriptPubKey `json:"scriptPubKey"` ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
Spent bool `json:"spent"` Spent bool `json:"spent"`
@ -86,50 +94,57 @@ type Vout struct {
// Erc20Token contains info about ERC20 token held by an address // Erc20Token contains info about ERC20 token held by an address
type Erc20Token struct { type Erc20Token struct {
Contract string `json:"contract"` Contract string `json:"contract"`
Txs int `json:"txs"` Txs int `json:"txs"`
Name string `json:"name"` Name string `json:"name"`
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Decimals int `json:"decimals"` Decimals int `json:"decimals"`
Balance string `json:"balance,omitempty"` BalanceSat *Amount `json:"balance,omitempty"`
BalanceSat string `json:"balanceSat,omitempty"` ContractIndex string `json:"-"`
ContractIndex string `json:"-"`
} }
// Erc20Transfer contains info about ERC20 transfer done in a transaction // Erc20Transfer contains info about ERC20 transfer done in a transaction
type Erc20Transfer struct { type Erc20Transfer struct {
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
Contract string `json:"contract"` Contract string `json:"contract"`
Name string `json:"name"` Name string `json:"name"`
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Tokens string `json:"tokens"` Decimals int `json:"decimals"`
Tokens *Amount `json:"tokens"`
}
// EthereumSpecific contains ethereum specific transaction data
type EthereumSpecific struct {
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending
Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gaslimit"`
GasUsed *big.Int `json:"gasused"`
GasPrice *Amount `json:"gasprice"`
} }
// Tx holds information about a transaction // Tx holds information about a transaction
type Tx struct { type Tx struct {
Txid string `json:"txid"` Txid string `json:"txid"`
Version int32 `json:"version,omitempty"` Version int32 `json:"version,omitempty"`
Locktime uint32 `json:"locktime,omitempty"` Locktime uint32 `json:"locktime,omitempty"`
Vin []Vin `json:"vin"` Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"` Vout []Vout `json:"vout"`
Blockhash string `json:"blockhash,omitempty"` Blockhash string `json:"blockhash,omitempty"`
Blockheight int `json:"blockheight"` Blockheight int `json:"blockheight"`
Confirmations uint32 `json:"confirmations"` Confirmations uint32 `json:"confirmations"`
Time int64 `json:"time,omitempty"` Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime"` Blocktime int64 `json:"blocktime"`
ValueOut string `json:"valueOut"` ValueOut string `json:"valueOut"`
ValueOutSat big.Int `json:"-"` ValueOutSat *Amount `json:"-"`
Size int `json:"size,omitempty"` Size int `json:"size,omitempty"`
ValueIn string `json:"valueIn"` ValueInSat *Amount `json:"valueIn,omitempty"`
ValueInSat big.Int `json:"-"` FeesSat *Amount `json:"fees,omitempty"`
Fees string `json:"fees"` Hex string `json:"hex,omitempty"`
FeesSat big.Int `json:"-"` CoinSpecificData interface{} `json:"-"`
Hex string `json:"hex,omitempty"` CoinSpecificJSON json.RawMessage `json:"-"`
CoinSpecificData interface{} `json:"-"` Erc20Transfers []Erc20Transfer `json:"erc20transfers,omitempty"`
CoinSpecificJSON json.RawMessage `json:"-"` EthereumSpecific *EthereumSpecific `json:"ethereumspecific,omitempty"`
Erc20Transfers []Erc20Transfer `json:"erc20transfers,omitempty"`
EthereumSpecific *eth.EthereumTxData `json:"ethereumspecific,omitempty"`
} }
// Paging contains information about paging for address, blocks and block // Paging contains information about paging for address, blocks and block
@ -152,16 +167,14 @@ const AddressFilterOutputs = -3
type Address struct { type Address struct {
Paging Paging
AddrStr string `json:"addrStr"` AddrStr string `json:"addrStr"`
Balance string `json:"balance"` BalanceSat *Amount `json:"balance"`
BalanceSat string `json:"balanceSat"` TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
TotalReceived string `json:"totalReceived,omitempty"` TotalSentSat *Amount `json:"totalSent,omitempty"`
TotalSent string `json:"totalSent,omitempty"` UnconfirmedBalanceSat *Amount `json:"unconfirmedBalance"`
UnconfirmedBalance string `json:"unconfirmedBalance"`
UnconfirmedBalanceSat string `json:"unconfirmedBalanceSat"`
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"` UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
TxApperances int `json:"txApperances"` TxApperances int `json:"txApperances"`
Transactions []*Tx `json:"txs,omitempty"` Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"transactions,omitempty"` Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"` Nonce string `json:"nonce,omitempty"`
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"` Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
Erc20Tokens []Erc20Token `json:"erc20tokens,omitempty"` Erc20Tokens []Erc20Token `json:"erc20tokens,omitempty"`
@ -172,8 +185,7 @@ type Address struct {
type AddressUtxo struct { type AddressUtxo struct {
Txid string `json:"txid"` Txid string `json:"txid"`
Vout int32 `json:"vout"` Vout int32 `json:"vout"`
Amount string `json:"amount"` AmountSat *Amount `json:"value"`
AmountSat big.Int `json:"satoshis"`
Height int `json:"height,omitempty"` Height int `json:"height,omitempty"`
Confirmations int `json:"confirmations"` Confirmations int `json:"confirmations"`
} }

51
api/types_test.go Normal file
View File

@ -0,0 +1,51 @@
// build unittest
package api
import (
"encoding/json"
"math/big"
"reflect"
"testing"
)
func TestAmount_MarshalJSON(t *testing.T) {
type amounts struct {
A1 Amount `json:"a1"`
A2 Amount `json:"a2,omitempty"`
PA1 *Amount `json:"pa1"`
PA2 *Amount `json:"pa2,omitempty"`
}
tests := []struct {
name string
a amounts
want string
}{
{
name: "empty",
want: `{"a1":"0","a2":"0","pa1":null}`,
},
{
name: "1",
a: amounts{
A1: (Amount)(*big.NewInt(123456)),
A2: (Amount)(*big.NewInt(787901)),
PA1: (*Amount)(big.NewInt(234567)),
PA2: (*Amount)(big.NewInt(890123)),
},
want: `{"a1":"123456","a2":"787901","pa1":"234567","pa2":"890123"}`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b, err := json.Marshal(&tt.a)
if err != nil {
t.Errorf("json.Marshal() error = %v", err)
return
}
if !reflect.DeepEqual(string(b), tt.want) {
t.Errorf("json.Marshal() = %v, want %v", string(b), tt.want)
}
})
}
}

View File

@ -59,7 +59,7 @@ func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) err
} else if tsp == nil { } else if tsp == nil {
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses") glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
} else if len(tsp.Inputs) > int(index) { } else if len(tsp.Inputs) > int(index) {
if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 { if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 {
spentTx, spentHeight, err := w.txCache.GetTransaction(t) spentTx, spentHeight, err := w.txCache.GetTransaction(t)
if err != nil { if err != nil {
glog.Warning("Tx ", t, ": not found") glog.Warning("Tx ", t, ": not found")
@ -113,7 +113,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
var err error var err error
var ta *db.TxAddresses var ta *db.TxAddresses
var erc20t []Erc20Transfer var erc20t []Erc20Transfer
var ethSpecific *eth.EthereumTxData var ethSpecific *EthereumSpecific
var blockhash string var blockhash string
if bchainTx.Confirmations > 0 { if bchainTx.Confirmations > 0 {
if w.chainType == bchain.ChainBitcoinType { if w.chainType == bchain.ChainBitcoinType {
@ -157,7 +157,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
} }
if len(otx.Vout) > int(vin.Vout) { if len(otx.Vout) > int(vin.Vout) {
vout := &otx.Vout[vin.Vout] vout := &otx.Vout[vin.Vout]
vin.ValueSat = vout.ValueSat vin.ValueSat = (*Amount)(&vout.ValueSat)
vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout) vin.AddrDesc, vin.Addresses, vin.Searchable, err = w.getAddressesFromVout(vout)
if err != nil { if err != nil {
glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout) glog.Errorf("getAddressesFromVout error %v, vout %+v", err, vout)
@ -166,8 +166,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
} else { } else {
if len(tas.Outputs) > int(vin.Vout) { if len(tas.Outputs) > int(vin.Vout) {
output := &tas.Outputs[vin.Vout] output := &tas.Outputs[vin.Vout]
vin.ValueSat = output.ValueSat vin.ValueSat = (*Amount)(&output.ValueSat)
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat)
vin.AddrDesc = output.AddrDesc vin.AddrDesc = output.AddrDesc
vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser) vin.Addresses, vin.Searchable, err = output.Addresses(w.chainParser)
if err != nil { if err != nil {
@ -175,8 +174,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
} }
} }
} }
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) valInSat.Add(&valInSat, (*big.Int)(vin.ValueSat))
valInSat.Add(&valInSat, &vin.ValueSat)
} }
} else if w.chainType == bchain.ChainEthereumType { } else if w.chainType == bchain.ChainEthereumType {
if len(bchainVin.Addresses) > 0 { if len(bchainVin.Addresses) > 0 {
@ -194,8 +192,7 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
bchainVout := &bchainTx.Vout[i] bchainVout := &bchainTx.Vout[i]
vout := &vouts[i] vout := &vouts[i]
vout.N = i vout.N = i
vout.ValueSat = bchainVout.ValueSat vout.ValueSat = (*Amount)(&bchainVout.ValueSat)
vout.Value = w.chainParser.AmountToDecimalString(&bchainVout.ValueSat)
valOutSat.Add(&valOutSat, &bchainVout.ValueSat) valOutSat.Add(&valOutSat, &bchainVout.ValueSat)
vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex
vout.ScriptPubKey.AddrDesc, vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = w.getAddressesFromVout(bchainVout) vout.ScriptPubKey.AddrDesc, vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = w.getAddressesFromVout(bchainVout)
@ -242,20 +239,28 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
Contract: e.Contract, Contract: e.Contract,
From: e.From, From: e.From,
To: e.To, To: e.To,
Tokens: bchain.AmountToDecimalString(&e.Tokens, erc20c.Decimals), Decimals: erc20c.Decimals,
Tokens: (*Amount)(&e.Tokens),
Name: erc20c.Name, Name: erc20c.Name,
Symbol: erc20c.Symbol, Symbol: erc20c.Symbol,
} }
} }
ethSpecific = eth.GetEthereumTxData(bchainTx) ethTxData := eth.GetEthereumTxData(bchainTx)
// mempool txs do not have fees yet // mempool txs do not have fees yet
if ethSpecific.GasUsed != nil { if ethTxData.GasUsed != nil {
feesSat.Mul(ethSpecific.GasPriceNum, ethSpecific.GasUsed) feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
} }
if len(bchainTx.Vout) > 0 { if len(bchainTx.Vout) > 0 {
valInSat = bchainTx.Vout[0].ValueSat valInSat = bchainTx.Vout[0].ValueSat
} }
valOutSat = valInSat valOutSat = valInSat
ethSpecific = &EthereumSpecific{
GasLimit: ethTxData.GasLimit,
GasPrice: (*Amount)(ethTxData.GasPrice),
GasUsed: ethTxData.GasUsed,
Nonce: ethTxData.Nonce,
Status: ethTxData.Status,
}
} }
// for now do not return size, we would have to compute vsize of segwit transactions // for now do not return size, we would have to compute vsize of segwit transactions
// size:=len(bchainTx.Hex) / 2 // size:=len(bchainTx.Hex) / 2
@ -271,15 +276,12 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
Blockheight: int(height), Blockheight: int(height),
Blocktime: bchainTx.Blocktime, Blocktime: bchainTx.Blocktime,
Confirmations: bchainTx.Confirmations, Confirmations: bchainTx.Confirmations,
Fees: w.chainParser.AmountToDecimalString(&feesSat), FeesSat: (*Amount)(&feesSat),
FeesSat: feesSat,
Locktime: bchainTx.LockTime, Locktime: bchainTx.LockTime,
Time: bchainTx.Time, Time: bchainTx.Time,
Txid: bchainTx.Txid, Txid: bchainTx.Txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat), ValueInSat: (*Amount)(&valInSat),
ValueInSat: valInSat, ValueOutSat: (*Amount)(&valOutSat),
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
ValueOutSat: valOutSat,
Version: bchainTx.Version, Version: bchainTx.Version,
Hex: bchainTx.Hex, Hex: bchainTx.Hex,
Vin: vins, Vin: vins,
@ -331,7 +333,7 @@ func (t *Tx) getAddrVoutValue(addrDesc bchain.AddressDescriptor) *big.Int {
var val big.Int var val big.Int
for _, vout := range t.Vout { for _, vout := range t.Vout {
if bytes.Equal(vout.ScriptPubKey.AddrDesc, addrDesc) { if bytes.Equal(vout.ScriptPubKey.AddrDesc, addrDesc) {
val.Add(&val, &vout.ValueSat) val.Add(&val, (*big.Int)(vout.ValueSat))
} }
} }
return &val return &val
@ -341,7 +343,7 @@ func (t *Tx) getAddrVinValue(addrDesc bchain.AddressDescriptor) *big.Int {
var val big.Int var val big.Int
for _, vin := range t.Vin { for _, vin := range t.Vin {
if bytes.Equal(vin.AddrDesc, addrDesc) { if bytes.Equal(vin.AddrDesc, addrDesc) {
val.Add(&val, &vin.ValueSat) val.Add(&val, (*big.Int)(vin.ValueSat))
} }
} }
return &val return &val
@ -371,9 +373,8 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
tai := &ta.Inputs[i] tai := &ta.Inputs[i]
vin := &vins[i] vin := &vins[i]
vin.N = i vin.N = i
vin.ValueSat = tai.ValueSat vin.ValueSat = (*Amount)(&tai.ValueSat)
vin.Value = w.chainParser.AmountToDecimalString(&vin.ValueSat) valInSat.Add(&valInSat, &tai.ValueSat)
valInSat.Add(&valInSat, &vin.ValueSat)
vin.Addresses, vin.Searchable, err = tai.Addresses(w.chainParser) vin.Addresses, vin.Searchable, err = tai.Addresses(w.chainParser)
if err != nil { if err != nil {
glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai) glog.Errorf("tai.Addresses error %v, tx %v, input %v, tai %+v", err, txid, i, tai)
@ -384,9 +385,8 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
tao := &ta.Outputs[i] tao := &ta.Outputs[i]
vout := &vouts[i] vout := &vouts[i]
vout.N = i vout.N = i
vout.ValueSat = tao.ValueSat vout.ValueSat = (*Amount)(&tao.ValueSat)
vout.Value = w.chainParser.AmountToDecimalString(&vout.ValueSat) valOutSat.Add(&valOutSat, &tao.ValueSat)
valOutSat.Add(&valOutSat, &vout.ValueSat)
vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = tao.Addresses(w.chainParser) vout.ScriptPubKey.Addresses, vout.ScriptPubKey.Searchable, err = tao.Addresses(w.chainParser)
if err != nil { if err != nil {
glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao) glog.Errorf("tai.Addresses error %v, tx %v, output %v, tao %+v", err, txid, i, tao)
@ -403,11 +403,11 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
Blockheight: int(ta.Height), Blockheight: int(ta.Height),
Blocktime: bi.Time, Blocktime: bi.Time,
Confirmations: bestheight - ta.Height + 1, Confirmations: bestheight - ta.Height + 1,
Fees: w.chainParser.AmountToDecimalString(&feesSat), FeesSat: (*Amount)(&feesSat),
Time: bi.Time, Time: bi.Time,
Txid: txid, Txid: txid,
ValueIn: w.chainParser.AmountToDecimalString(&valInSat), ValueInSat: (*Amount)(&valInSat),
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat), ValueOutSat: (*Amount)(&valOutSat),
Vin: vins, Vin: vins,
Vout: vouts, Vout: vouts,
} }
@ -486,14 +486,8 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
} else { } else {
b = nil b = nil
} }
var balance, balanceSat string
if b != nil {
balance = bchain.AmountToDecimalString(b, ci.Decimals)
balanceSat = b.String()
}
erc20t[i] = Erc20Token{ erc20t[i] = Erc20Token{
Balance: balance, BalanceSat: (*Amount)(b),
BalanceSat: balanceSat,
Contract: ci.Contract, Contract: ci.Contract,
Name: ci.Name, Name: ci.Name,
Symbol: ci.Symbol, Symbol: ci.Symbol,
@ -522,15 +516,16 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
} }
var ( var (
ba *db.AddrBalance ba *db.AddrBalance
erc20t []Erc20Token erc20t []Erc20Token
erc20c *bchain.Erc20Contract erc20c *bchain.Erc20Contract
txm []string txm []string
txs []*Tx txs []*Tx
txids []string txids []string
pg Paging pg Paging
uBalSat big.Int uBalSat big.Int
totalReceived, totalSent, nonce string totalReceived, totalSent *big.Int
nonce string
) )
if w.chainType == bchain.ChainEthereumType { if w.chainType == bchain.ChainEthereumType {
var n uint64 var n uint64
@ -646,19 +641,17 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
} }
} }
if w.chainType == bchain.ChainBitcoinType { if w.chainType == bchain.ChainBitcoinType {
totalReceived = w.chainParser.AmountToDecimalString(ba.ReceivedSat()) totalReceived = ba.ReceivedSat()
totalSent = w.chainParser.AmountToDecimalString(&ba.SentSat) totalSent = &ba.SentSat
} }
r := &Address{ r := &Address{
Paging: pg, Paging: pg,
AddrStr: address, AddrStr: address,
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat), BalanceSat: (*Amount)(&ba.BalanceSat),
BalanceSat: ba.BalanceSat.String(), TotalReceivedSat: (*Amount)(totalReceived),
TotalReceived: totalReceived, TotalSentSat: (*Amount)(totalSent),
TotalSent: totalSent,
TxApperances: int(ba.Txs), TxApperances: int(ba.Txs),
UnconfirmedBalance: w.chainParser.AmountToDecimalString(&uBalSat), UnconfirmedBalanceSat: (*Amount)(&uBalSat),
UnconfirmedBalanceSat: uBalSat.String(),
UnconfirmedTxApperances: len(txm), UnconfirmedTxApperances: len(txm),
Transactions: txs, Transactions: txs,
Txids: txids, Txids: txids,
@ -714,8 +707,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
r = append(r, AddressUtxo{ r = append(r, AddressUtxo{
Txid: bchainTx.Txid, Txid: bchainTx.Txid,
Vout: int32(i), Vout: int32(i),
AmountSat: vout.ValueSat, AmountSat: (*Amount)(&vout.ValueSat),
Amount: w.chainParser.AmountToDecimalString(&vout.ValueSat),
}) })
} }
} }
@ -734,7 +726,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
outpoints := make([]bchain.Outpoint, 0, 8) outpoints := make([]bchain.Outpoint, 0, 8)
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout int32, isOutput bool) error { err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout int32, isOutput bool) error {
if isOutput { if isOutput {
outpoints = append(outpoints, bchain.Outpoint{txid, vout}) outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: vout})
} }
return nil return nil
}) })
@ -772,8 +764,7 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
r = append(r, AddressUtxo{ r = append(r, AddressUtxo{
Txid: o.Txid, Txid: o.Txid,
Vout: o.Vout, Vout: o.Vout,
AmountSat: v, AmountSat: (*Amount)(&v),
Amount: w.chainParser.AmountToDecimalString(&v),
Height: int(ta.Height), Height: int(ta.Height),
Confirmations: bestheight - int(ta.Height) + 1, Confirmations: bestheight - int(ta.Height) + 1,
}) })

View File

@ -414,12 +414,11 @@ func GetErc20FromTx(tx *bchain.Tx) ([]Erc20Transfer, error) {
// EthereumTxData contains ethereum specific transaction data // EthereumTxData contains ethereum specific transaction data
type EthereumTxData struct { type EthereumTxData struct {
Status int `json:"status"` // 1 OK, 0 Fail, -1 pending Status int `json:"status"` // 1 OK, 0 Fail, -1 pending
Nonce uint64 `json:"nonce"` Nonce uint64 `json:"nonce"`
GasLimit *big.Int `json:"gaslimit"` GasLimit *big.Int `json:"gaslimit"`
GasUsed *big.Int `json:"gasused"` GasUsed *big.Int `json:"gasused"`
GasPrice string `json:"gasprice"` GasPrice *big.Int `json:"gasprice"`
GasPriceNum *big.Int `json:"-"`
} }
// GetEthereumTxData returns EthereumTxData from bchain.Tx // GetEthereumTxData returns EthereumTxData from bchain.Tx
@ -430,8 +429,7 @@ func GetEthereumTxData(tx *bchain.Tx) *EthereumTxData {
if csd.Tx != nil { if csd.Tx != nil {
etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce) etd.Nonce, _ = hexutil.DecodeUint64(csd.Tx.AccountNonce)
etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit) etd.GasLimit, _ = hexutil.DecodeBig(csd.Tx.GasLimit)
etd.GasPriceNum, _ = hexutil.DecodeBig(csd.Tx.GasPrice) etd.GasPrice, _ = hexutil.DecodeBig(csd.Tx.GasPrice)
etd.GasPrice = bchain.AmountToDecimalString(etd.GasPriceNum, EtherAmountDecimalPoint)
} }
if csd.Receipt != nil { if csd.Receipt != nil {
if csd.Receipt.Status == "0x1" { if csd.Receipt.Status == "0x1" {

View File

@ -369,11 +369,12 @@ type TemplateData struct {
func (s *PublicServer) parseTemplates() []*template.Template { func (s *PublicServer) parseTemplates() []*template.Template {
templateFuncMap := template.FuncMap{ templateFuncMap := template.FuncMap{
"formatTime": formatTime, "formatTime": formatTime,
"formatUnixTime": formatUnixTime, "formatUnixTime": formatUnixTime,
"formatAmount": formatAmount, "formatAmount": s.formatAmount,
"setTxToTemplateData": setTxToTemplateData, "formatAmountWithDecimals": formatAmountWithDecimals,
"stringInSlice": stringInSlice, "setTxToTemplateData": setTxToTemplateData,
"stringInSlice": stringInSlice,
} }
t := make([]*template.Template, tplCount) t := make([]*template.Template, tplCount)
t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html")) t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
@ -403,11 +404,18 @@ func formatTime(t time.Time) string {
// for now return the string as it is // for now return the string as it is
// in future could be used to do coin specific formatting // in future could be used to do coin specific formatting
func formatAmount(a string) string { func (s *PublicServer) formatAmount(a *api.Amount) string {
if a == "" { if a == nil {
return "0" return "0"
} }
return a return s.chainParser.AmountToDecimalString((*big.Int)(a))
}
func formatAmountWithDecimals(a *api.Amount, d int) string {
if a == nil {
return "0"
}
return bchain.AmountToDecimalString((*big.Int)(a), d)
} }
// called from template to support txdetail.html functionality // called from template to support txdetail.html functionality

View File

@ -312,7 +312,7 @@ func txToResTx(tx *api.Tx) resTx {
Script: &script, Script: &script,
Sequence: int64(vin.Sequence), Sequence: int64(vin.Sequence),
OutputIndex: int(vin.Vout), OutputIndex: int(vin.Vout),
Satoshis: vin.ValueSat.Int64(), Satoshis: (*big.Int)(vin.ValueSat).Int64(),
} }
if len(vin.Addresses) > 0 { if len(vin.Addresses) > 0 {
a := vin.Addresses[0] a := vin.Addresses[0]
@ -325,7 +325,7 @@ func txToResTx(tx *api.Tx) resTx {
vout := &tx.Vout[i] vout := &tx.Vout[i]
script := vout.ScriptPubKey.Hex script := vout.ScriptPubKey.Hex
output := txOutputs{ output := txOutputs{
Satoshis: vout.ValueSat.Int64(), Satoshis: (*big.Int)(vout.ValueSat).Int64(),
Script: &script, Script: &script,
} }
if len(vout.ScriptPubKey.Addresses) > 0 { if len(vout.ScriptPubKey.Addresses) > 0 {
@ -342,15 +342,15 @@ func txToResTx(tx *api.Tx) resTx {
} }
return resTx{ return resTx{
BlockTimestamp: tx.Blocktime, BlockTimestamp: tx.Blocktime,
FeeSatoshis: tx.FeesSat.Int64(), FeeSatoshis: (*big.Int)(tx.FeesSat).Int64(),
Hash: tx.Txid, Hash: tx.Txid,
Height: h, Height: h,
Hex: tx.Hex, Hex: tx.Hex,
Inputs: inputs, Inputs: inputs,
InputSatoshis: tx.ValueInSat.Int64(), InputSatoshis: (*big.Int)(tx.ValueInSat).Int64(),
Locktime: int(tx.Locktime), Locktime: int(tx.Locktime),
Outputs: outputs, Outputs: outputs,
OutputSatoshis: tx.ValueOutSat.Int64(), OutputSatoshis: (*big.Int)(tx.ValueOutSat).Int64(),
Version: int(tx.Version), Version: int(tx.Version),
} }
} }
@ -407,7 +407,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r
ads[a] = hi ads[a] = hi
} }
hi.InputIndexes = append(hi.InputIndexes, int(vin.N)) hi.InputIndexes = append(hi.InputIndexes, int(vin.N))
totalSat.Sub(&totalSat, &vin.ValueSat) totalSat.Sub(&totalSat, (*big.Int)(vin.ValueSat))
} }
} }
for i := range tx.Vout { for i := range tx.Vout {
@ -420,7 +420,7 @@ func (s *SocketIoServer) getAddressHistory(addr []string, opts *addrOpts) (res r
ads[a] = hi ads[a] = hi
} }
hi.OutputIndexes = append(hi.OutputIndexes, int(vout.N)) hi.OutputIndexes = append(hi.OutputIndexes, int(vout.N))
totalSat.Add(&totalSat, &vout.ValueSat) totalSat.Add(&totalSat, (*big.Int)(vout.ValueSat))
} }
} }
ahi := addressHistoryItem{} ahi := addressHistoryItem{}

View File

@ -91,7 +91,7 @@ func NewWebsocketServer(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxC
return s, nil return s, nil
} }
// allow all origins, at least for now // allow all origins
func checkOrigin(r *http.Request) bool { func checkOrigin(r *http.Request) bool {
return true return true
} }

View File

@ -1,6 +1,6 @@
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}} {{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
<h1>{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}} <h1>{{if $addr.Erc20Contract}}Contract {{$addr.Erc20Contract.Name}} ({{$addr.Erc20Contract.Symbol}}){{else}}Address{{end}}
<small class="text-muted">{{formatAmount $addr.Balance}} {{$cs}}</small> <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small>
</h1> </h1>
<div class="alert alert-data ellipsis"> <div class="alert alert-data ellipsis">
<span class="data">{{$addr.AddrStr}}</span> <span class="data">{{$addr.AddrStr}}</span>
@ -13,7 +13,7 @@
{{- if eq .ChainType 1 -}} {{- if eq .ChainType 1 -}}
<tr> <tr>
<td style="width: 25%;">Balance</td> <td style="width: 25%;">Balance</td>
<td class="data">{{formatAmount $addr.Balance}} {{$cs}}</td> <td class="data">{{formatAmount $addr.BalanceSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>Non-contract Transactions</td> <td>Non-contract Transactions</td>
@ -37,7 +37,7 @@
{{- range $et := $addr.Erc20Tokens -}} {{- range $et := $addr.Erc20Tokens -}}
<tr> <tr>
<td class="data ellipsis"><a href="/address/{{$et.Contract}}">{{$et.Name}}</a></td> <td class="data ellipsis"><a href="/address/{{$et.Contract}}">{{$et.Name}}</a></td>
<td class="data">{{formatAmount $et.Balance}} {{$et.Symbol}}</td> <td class="data">{{formatAmountWithDecimals $et.BalanceSat $et.Decimals}} {{$et.Symbol}}</td>
<td class="data">{{$et.Txs}}</td> <td class="data">{{$et.Txs}}</td>
</tr> </tr>
{{- end -}} {{- end -}}
@ -50,15 +50,15 @@
{{- else -}} {{- else -}}
<tr> <tr>
<td style="width: 25%;">Total Received</td> <td style="width: 25%;">Total Received</td>
<td class="data">{{formatAmount $addr.TotalReceived}} {{$cs}}</td> <td class="data">{{formatAmount $addr.TotalReceivedSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>Total Sent</td> <td>Total Sent</td>
<td class="data">{{formatAmount $addr.TotalSent}} {{$cs}}</td> <td class="data">{{formatAmount $addr.TotalSentSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>Final Balance</td> <td>Final Balance</td>
<td class="data">{{formatAmount $addr.Balance}} {{$cs}}</td> <td class="data">{{formatAmount $addr.BalanceSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>No. Transactions</td> <td>No. Transactions</td>
@ -83,7 +83,7 @@
<tbody> <tbody>
<tr> <tr>
<td style="width: 25%;">Unconfirmed Balance</td> <td style="width: 25%;">Unconfirmed Balance</td>
<td class="data">{{formatAmount $addr.UnconfirmedBalance}} {{$cs}}</td> <td class="data">{{formatAmount $addr.UnconfirmedBalanceSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>No. Transactions</td> <td>No. Transactions</td>

View File

@ -36,7 +36,7 @@
</tr> </tr>
<tr> <tr>
<td>Value</td> <td>Value</td>
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td> <td class="data">{{formatAmount $tx.ValueOutSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>Gas Used / Limit</td> <td>Gas Used / Limit</td>
@ -49,17 +49,17 @@
{{- else -}} {{- else -}}
<tr> <tr>
<td>Total Input</td> <td>Total Input</td>
<td class="data">{{formatAmount $tx.ValueIn}} {{$cs}}</td> <td class="data">{{formatAmount $tx.ValueInSat}} {{$cs}}</td>
</tr> </tr>
<tr> <tr>
<td>Total Output</td> <td>Total Output</td>
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td> <td class="data">{{formatAmount $tx.ValueOutSat}} {{$cs}}</td>
</tr> </tr>
{{- end -}} {{- end -}}
{{- if $tx.Fees -}} {{- if $tx.FeesSat -}}
<tr> <tr>
<td>Fees</td> <td>Fees</td>
<td class="data">{{formatAmount $tx.Fees}} {{$cs}}</td> <td class="data">{{formatAmount $tx.FeesSat}} {{$cs}}</td>
</tr>{{end -}} </tr>{{end -}}
</tbody> </tbody>
</table> </table>

View File

@ -26,7 +26,7 @@
{{- else -}} {{- else -}}
<span class="float-left">{{- if $vin.ScriptSig.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span> <span class="float-left">{{- if $vin.ScriptSig.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span>
{{- end -}}{{- if $vin.Addresses -}} {{- end -}}{{- if $vin.Addresses -}}
<span class="float-right{{if stringInSlice $addr $vin.Addresses}} text-danger{{end}}">{{formatAmount $vin.Value}} {{$cs}}</span> <span class="float-right{{if stringInSlice $addr $vin.Addresses}} text-danger{{end}}">{{formatAmount $vin.ValueSat}} {{$cs}}</span>
{{- end -}} {{- end -}}
</td> </td>
</tr> </tr>
@ -59,7 +59,7 @@
<span class="float-left">Unparsed address</span> <span class="float-left">Unparsed address</span>
{{- end -}} {{- end -}}
<span class="float-right{{if stringInSlice $addr $vout.ScriptPubKey.Addresses}} text-success{{end}}"> <span class="float-right{{if stringInSlice $addr $vout.ScriptPubKey.Addresses}} text-success{{end}}">
{{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent"></a>{{else -}} {{formatAmount $vout.ValueSat}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent"></a>{{else -}}
<span class="text-success" title="Unspent"> <b>×</b></span> <span class="text-success" title="Unspent"> <b>×</b></span>
{{- end -}} {{- end -}}
</span> </span>
@ -77,8 +77,8 @@
</div> </div>
<div class="row line-top"> <div class="row line-top">
<div class="col-xs-6 col-sm-4 col-md-4"> <div class="col-xs-6 col-sm-4 col-md-4">
{{- if $tx.Fees -}} {{- if $tx.FeesSat -}}
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.Fees}} {{$cs}}</span> <span class="txvalues txvalues-default">Fee: {{formatAmount $tx.FeesSat}} {{$cs}}</span>
{{- end -}} {{- end -}}
</div> </div>
<div class="col-xs-6 col-sm-8 col-md-8 text-right"> <div class="col-xs-6 col-sm-8 col-md-8 text-right">
@ -87,7 +87,7 @@
{{- else -}} {{- else -}}
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span> <span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
{{- end -}} {{- end -}}
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOut}} {{$cs}}</span> <span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOutSat}} {{$cs}}</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -66,7 +66,7 @@
</div> </div>
</div> </div>
<div class="col-md-3 text-right" style="padding: .4rem 0;"> <div class="col-md-3 text-right" style="padding: .4rem 0;">
{{formatAmount $tx.ValueOut}} {{$cs}} {{formatAmount $tx.ValueOutSat}} {{$cs}}
</div> </div>
</div> </div>
{{- if $tx.Erc20Transfers -}} {{- if $tx.Erc20Transfers -}}
@ -106,15 +106,15 @@
</table> </table>
</div> </div>
</div> </div>
<div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmount $erc20.Tokens}} {{$erc20.Symbol}}</div> <div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmountWithDecimals $erc20.Tokens $erc20.Decimals}} {{$erc20.Symbol}}</div>
</div> </div>
{{- end -}} {{- end -}}
<div class="row" style="padding: 6px 15px;"></div> <div class="row" style="padding: 6px 15px;"></div>
{{- end -}} {{- end -}}
<div class="row line-top"> <div class="row line-top">
<div class="col-xs-6 col-sm-4 col-md-4"> <div class="col-xs-6 col-sm-4 col-md-4">
{{- if $tx.Fees -}} {{- if $tx.FeesSat -}}
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.Fees}} {{$cs}}</span> <span class="txvalues txvalues-default">Fee: {{formatAmount $tx.FeesSat}} {{$cs}}</span>
{{- end -}} {{- end -}}
</div> </div>
<div class="col-xs-6 col-sm-8 col-md-8 text-right"> <div class="col-xs-6 col-sm-8 col-md-8 text-right">
@ -123,7 +123,7 @@
{{- else -}} {{- else -}}
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span> <span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
{{- end -}} {{- end -}}
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOut}} {{$cs}}</span> <span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOutSat}} {{$cs}}</span>
</div> </div>
</div> </div>
</div> </div>