Return internal data and ERC721 and ERC1155 tokens from API and explorer
This commit is contained in:
parent
ae2d0e3958
commit
72e0ac23bc
26
api/types.go
26
api/types.go
@ -187,16 +187,25 @@ type TokenTransfer struct {
|
|||||||
Values []TokenTransferValues `json:"values,omitempty"`
|
Values []TokenTransferValues `json:"values,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EthereumInternalTransfer struct {
|
||||||
|
Type bchain.EthereumInternalTransactionType `json:"type"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Value *Amount `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
// EthereumSpecific contains ethereum specific transaction data
|
// EthereumSpecific contains ethereum specific transaction data
|
||||||
type EthereumSpecific struct {
|
type EthereumSpecific struct {
|
||||||
TxType string `json:"txType,omitempty"`
|
Type bchain.EthereumInternalTransactionType `json:"type,omitempty"`
|
||||||
Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
|
CreatedContract string `json:"createdContract,omitempty"`
|
||||||
Nonce uint64 `json:"nonce"`
|
Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
|
||||||
GasLimit *big.Int `json:"gasLimit"`
|
Error string `json:"error,omitempty"`
|
||||||
GasUsed *big.Int `json:"gasUsed"`
|
Nonce uint64 `json:"nonce"`
|
||||||
GasPrice *Amount `json:"gasPrice"`
|
GasLimit *big.Int `json:"gasLimit"`
|
||||||
Data string `json:"data,omitempty"`
|
GasUsed *big.Int `json:"gasUsed"`
|
||||||
InternalTransfers []bchain.EthereumInternalTransfer `json:"internalTransfers,omitempty"`
|
GasPrice *Amount `json:"gasPrice"`
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
InternalTransfers []EthereumInternalTransfer `json:"internalTransfers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tx holds information about a transaction
|
// Tx holds information about a transaction
|
||||||
@ -279,6 +288,7 @@ type Address struct {
|
|||||||
UnconfirmedTxs int `json:"unconfirmedTxs"`
|
UnconfirmedTxs int `json:"unconfirmedTxs"`
|
||||||
Txs int `json:"txs"`
|
Txs int `json:"txs"`
|
||||||
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
|
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
|
||||||
|
InternalTxs int `json:"internalTxs,omitempty"`
|
||||||
Transactions []*Tx `json:"transactions,omitempty"`
|
Transactions []*Tx `json:"transactions,omitempty"`
|
||||||
Txids []string `json:"txids,omitempty"`
|
Txids []string `json:"txids,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
|||||||
@ -261,6 +261,15 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
|||||||
}
|
}
|
||||||
tokens = w.getEthereumTokensTransfers(tokenTransfers)
|
tokens = w.getEthereumTokensTransfers(tokenTransfers)
|
||||||
ethTxData := eth.GetEthereumTxData(bchainTx)
|
ethTxData := eth.GetEthereumTxData(bchainTx)
|
||||||
|
|
||||||
|
var internalData *bchain.EthereumInternalData
|
||||||
|
if eth.ProcessInternalTransactions {
|
||||||
|
internalData, err = w.db.GetEthereumInternalData(bchainTx.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mempool txs do not have fees yet
|
// mempool txs do not have fees yet
|
||||||
if ethTxData.GasUsed != nil {
|
if ethTxData.GasUsed != nil {
|
||||||
feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
|
feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
|
||||||
@ -276,6 +285,21 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
|||||||
Status: ethTxData.Status,
|
Status: ethTxData.Status,
|
||||||
Data: ethTxData.Data,
|
Data: ethTxData.Data,
|
||||||
}
|
}
|
||||||
|
if internalData != nil {
|
||||||
|
ethSpecific.Type = internalData.Type
|
||||||
|
ethSpecific.CreatedContract = internalData.Contract
|
||||||
|
ethSpecific.Error = internalData.Error
|
||||||
|
ethSpecific.InternalTransfers = make([]EthereumInternalTransfer, len(internalData.Transfers))
|
||||||
|
for i := range internalData.Transfers {
|
||||||
|
f := &internalData.Transfers[i]
|
||||||
|
t := ðSpecific.InternalTransfers[i]
|
||||||
|
t.From = f.From
|
||||||
|
t.To = f.To
|
||||||
|
t.Type = f.Type
|
||||||
|
t.Value = (*Amount)(&f.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// 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
|
||||||
@ -427,13 +451,25 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []T
|
|||||||
if erc20c == nil {
|
if erc20c == nil {
|
||||||
erc20c = &bchain.Erc20Contract{Name: t.Contract}
|
erc20c = &bchain.Erc20Contract{Name: t.Contract}
|
||||||
}
|
}
|
||||||
|
var value *Amount
|
||||||
|
var values []TokenTransferValues
|
||||||
|
if t.Type == bchain.ERC1155 {
|
||||||
|
values = make([]TokenTransferValues, len(t.IdValues))
|
||||||
|
for j := range values {
|
||||||
|
values[j].Id = (*Amount)(&t.IdValues[j].Id)
|
||||||
|
values[j].Value = (*Amount)(&t.IdValues[j].Value)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = (*Amount)(&t.Value)
|
||||||
|
}
|
||||||
tokens[i] = TokenTransfer{
|
tokens[i] = TokenTransfer{
|
||||||
Type: TokenTypeMap[t.Type],
|
Type: TokenTypeMap[t.Type],
|
||||||
Token: t.Contract,
|
Token: t.Contract,
|
||||||
From: t.From,
|
From: t.From,
|
||||||
To: t.To,
|
To: t.To,
|
||||||
|
Value: value,
|
||||||
|
Values: values,
|
||||||
Decimals: erc20c.Decimals,
|
Decimals: erc20c.Decimals,
|
||||||
Value: (*Amount)(&t.Value),
|
|
||||||
Name: erc20c.Name,
|
Name: erc20c.Name,
|
||||||
Symbol: erc20c.Symbol,
|
Symbol: erc20c.Symbol,
|
||||||
}
|
}
|
||||||
@ -657,29 +693,30 @@ func (w *Worker) getEthereumToken(index int, addrDesc, contract bchain.AddressDe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
|
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, int, error) {
|
||||||
var (
|
var (
|
||||||
ba *db.AddrBalance
|
ba *db.AddrBalance
|
||||||
tokens []Token
|
tokens []Token
|
||||||
ci *bchain.Erc20Contract
|
ci *bchain.Erc20Contract
|
||||||
n uint64
|
n uint64
|
||||||
nonContractTxs int
|
nonContractTxs int
|
||||||
|
internalTxs int
|
||||||
)
|
)
|
||||||
// unknown number of results for paging
|
// unknown number of results for paging
|
||||||
totalResults := -1
|
totalResults := -1
|
||||||
ca, err := w.db.GetAddrDescContracts(addrDesc)
|
ca, err := w.db.GetAddrDescContracts(addrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
|
return nil, nil, nil, 0, 0, 0, 0, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
|
||||||
}
|
}
|
||||||
b, err := w.chain.EthereumTypeGetBalance(addrDesc)
|
b, err := w.chain.EthereumTypeGetBalance(addrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %v", addrDesc)
|
return nil, nil, nil, 0, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetBalance %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, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
|
return nil, nil, nil, 0, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ca != nil {
|
if ca != nil {
|
||||||
@ -691,7 +728,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
}
|
}
|
||||||
n, err = w.chain.EthereumTypeGetNonce(addrDesc)
|
n, err = w.chain.EthereumTypeGetNonce(addrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
|
return nil, nil, nil, 0, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetNonce %v", addrDesc)
|
||||||
}
|
}
|
||||||
if details > AccountDetailsBasic {
|
if details > AccountDetailsBasic {
|
||||||
tokens = make([]Token, len(ca.Contracts))
|
tokens = make([]Token, len(ca.Contracts))
|
||||||
@ -706,7 +743,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
}
|
}
|
||||||
t, err := w.getEthereumToken(i+db.ContractIndexOffset, addrDesc, c.Contract, details, int(c.Txs))
|
t, err := w.getEthereumToken(i+db.ContractIndexOffset, addrDesc, c.Contract, details, int(c.Txs))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, err
|
return nil, nil, nil, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
tokens[j] = *t
|
tokens[j] = *t
|
||||||
j++
|
j++
|
||||||
@ -716,7 +753,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
if len(filterDesc) > 0 && j == 0 && details >= AccountDetailsTokens {
|
if len(filterDesc) > 0 && j == 0 && details >= AccountDetailsTokens {
|
||||||
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
|
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, err
|
return nil, nil, nil, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
tokens = []Token{*t}
|
tokens = []Token{*t}
|
||||||
// switch off query for transactions, there are no transactions
|
// switch off query for transactions, there are no transactions
|
||||||
@ -727,7 +764,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
}
|
}
|
||||||
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
|
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, err
|
return nil, nil, nil, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
||||||
// compute total results for paging
|
// compute total results for paging
|
||||||
@ -742,6 +779,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
nonContractTxs = int(ca.NonContractTxs)
|
nonContractTxs = int(ca.NonContractTxs)
|
||||||
|
internalTxs = int(ca.InternalTxs)
|
||||||
} else {
|
} else {
|
||||||
// addresses without any normal transactions can have internal transactions and therefore balance
|
// addresses without any normal transactions can have internal transactions and therefore balance
|
||||||
if b != nil {
|
if b != nil {
|
||||||
@ -753,14 +791,14 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
if len(filterDesc) > 0 && details >= AccountDetailsTokens {
|
if len(filterDesc) > 0 && details >= AccountDetailsTokens {
|
||||||
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
|
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, err
|
return nil, nil, nil, 0, 0, 0, 0, err
|
||||||
}
|
}
|
||||||
tokens = []Token{*t}
|
tokens = []Token{*t}
|
||||||
// switch off query for transactions, there are no transactions
|
// switch off query for transactions, there are no transactions
|
||||||
filter.Vout = AddressFilterVoutQueryNotNecessary
|
filter.Vout = AddressFilterVoutQueryNotNecessary
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ba, tokens, ci, n, nonContractTxs, totalResults, nil
|
return ba, tokens, ci, n, nonContractTxs, internalTxs, totalResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo) (*Tx, error) {
|
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails, blockInfo *db.BlockInfo) (*Tx, error) {
|
||||||
@ -865,6 +903,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
|||||||
nonce string
|
nonce string
|
||||||
unconfirmedTxs int
|
unconfirmedTxs int
|
||||||
nonTokenTxs int
|
nonTokenTxs int
|
||||||
|
internalTxs int
|
||||||
totalResults int
|
totalResults int
|
||||||
)
|
)
|
||||||
addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address)
|
addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address)
|
||||||
@ -873,7 +912,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
|||||||
}
|
}
|
||||||
if w.chainType == bchain.ChainEthereumType {
|
if w.chainType == bchain.ChainEthereumType {
|
||||||
var n uint64
|
var n uint64
|
||||||
ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
|
ba, tokens, erc20c, n, nonTokenTxs, internalTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -977,6 +1016,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
|
|||||||
TotalSentSat: (*Amount)(totalSent),
|
TotalSentSat: (*Amount)(totalSent),
|
||||||
Txs: int(ba.Txs),
|
Txs: int(ba.Txs),
|
||||||
NonTokenTxs: nonTokenTxs,
|
NonTokenTxs: nonTokenTxs,
|
||||||
|
InternalTxs: internalTxs,
|
||||||
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
|
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
|
||||||
UnconfirmedTxs: unconfirmedTxs,
|
UnconfirmedTxs: unconfirmedTxs,
|
||||||
Transactions: txs,
|
Transactions: txs,
|
||||||
|
|||||||
@ -36,14 +36,15 @@ const (
|
|||||||
|
|
||||||
// Configuration represents json config file
|
// Configuration represents json config file
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
CoinName string `json:"coin_name"`
|
CoinName string `json:"coin_name"`
|
||||||
CoinShortcut string `json:"coin_shortcut"`
|
CoinShortcut string `json:"coin_shortcut"`
|
||||||
RPCURL string `json:"rpc_url"`
|
RPCURL string `json:"rpc_url"`
|
||||||
RPCTimeout int `json:"rpc_timeout"`
|
RPCTimeout int `json:"rpc_timeout"`
|
||||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||||
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
|
||||||
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
|
||||||
ProcessInternalTransactions bool `json:"processInternalTransactions"`
|
ProcessInternalTransactions bool `json:"processInternalTransactions"`
|
||||||
|
ProcessZeroInternalTransactions bool `json:"processZeroInternalTransactions"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthereumRPC is an interface to JSON-RPC eth service.
|
// EthereumRPC is an interface to JSON-RPC eth service.
|
||||||
@ -65,6 +66,9 @@ type EthereumRPC struct {
|
|||||||
ChainConfig *Configuration
|
ChainConfig *Configuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProcessInternalTransactions specifies if internal transactions are processed
|
||||||
|
var ProcessInternalTransactions bool
|
||||||
|
|
||||||
// NewEthereumRPC returns new EthRPC instance.
|
// NewEthereumRPC returns new EthRPC instance.
|
||||||
func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
|
||||||
var err error
|
var err error
|
||||||
@ -90,6 +94,8 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||||||
ChainConfig: &c,
|
ChainConfig: &c,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessInternalTransactions = c.ProcessInternalTransactions
|
||||||
|
|
||||||
// always create parser
|
// always create parser
|
||||||
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
|
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
|
||||||
s.timeout = time.Duration(c.RPCTimeout) * time.Second
|
s.timeout = time.Duration(c.RPCTimeout) * time.Second
|
||||||
@ -498,7 +504,7 @@ func (b *EthereumRPC) getTokenTransferEventsForBlock(blockNumber string) (map[st
|
|||||||
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
|
err := b.rpc.CallContext(ctx, &logs, "eth_getLogs", map[string]interface{}{
|
||||||
"fromBlock": blockNumber,
|
"fromBlock": blockNumber,
|
||||||
"toBlock": blockNumber,
|
"toBlock": blockNumber,
|
||||||
"topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature},
|
// "topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
|
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
|
||||||
@ -543,7 +549,7 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
|
|||||||
From: call.From,
|
From: call.From,
|
||||||
To: call.To,
|
To: call.To,
|
||||||
})
|
})
|
||||||
} else if err == nil && value.BitLen() > 0 {
|
} else if err == nil && (value.BitLen() > 0 || b.ChainConfig.ProcessZeroInternalTransactions) {
|
||||||
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
|
d.Transfers = append(d.Transfers, bchain.EthereumInternalTransfer{
|
||||||
Value: *value,
|
Value: *value,
|
||||||
From: call.From,
|
From: call.From,
|
||||||
@ -561,7 +567,7 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
|
|||||||
// getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts
|
// getInternalDataForBlock fetches debug trace using callTracer, extracts internal transfers and creations and destructions of contracts
|
||||||
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, error) {
|
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, error) {
|
||||||
data := make([]bchain.EthereumInternalData, len(transactions))
|
data := make([]bchain.EthereumInternalData, len(transactions))
|
||||||
if b.ChainConfig.ProcessInternalTransactions {
|
if ProcessInternalTransactions {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
var trace []rpcTraceResult
|
var trace []rpcTraceResult
|
||||||
@ -640,6 +646,7 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
|||||||
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions)
|
internalData, err := b.getInternalDataForBlock(head.Hash, body.Transactions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
blockSpecificData = &bchain.EthereumBlockSpecificData{InternalDataError: err.Error()}
|
blockSpecificData = &bchain.EthereumBlockSpecificData{InternalDataError: err.Error()}
|
||||||
|
glog.Info("InternalDataError ", bbh.Height, ": ", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
btxs := make([]bchain.Tx, len(body.Transactions))
|
btxs := make([]bchain.Tx, len(body.Transactions))
|
||||||
|
|||||||
@ -456,6 +456,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
|
|||||||
"setTxToTemplateData": setTxToTemplateData,
|
"setTxToTemplateData": setTxToTemplateData,
|
||||||
"isOwnAddress": isOwnAddress,
|
"isOwnAddress": isOwnAddress,
|
||||||
"toJSON": toJSON,
|
"toJSON": toJSON,
|
||||||
|
"tokenTransfersCount": tokenTransfersCount,
|
||||||
}
|
}
|
||||||
var createTemplate func(filenames ...string) *template.Template
|
var createTemplate func(filenames ...string) *template.Template
|
||||||
if s.debug {
|
if s.debug {
|
||||||
@ -558,6 +559,17 @@ func isOwnAddress(td *TemplateData, a string) bool {
|
|||||||
return a == td.AddrStr
|
return a == td.AddrStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// called from template, returns count of token transfers of given type
|
||||||
|
func tokenTransfersCount(tx *api.Tx, t api.TokenType) int {
|
||||||
|
count := 0
|
||||||
|
for i := range tx.TokenTransfers {
|
||||||
|
if tx.TokenTransfers[i].Type == t {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
var tx *api.Tx
|
var tx *api.Tx
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@ -34,7 +34,7 @@ func httpTestsEthereumType(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,"address":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","balance":"123450075","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":1,"nonTokenTxs":1,"txids":["0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2"],"nonce":"75","tokens":[{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":2,"symbol":"S13","decimals":18},{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":2,"symbol":"S74","decimals":18}],"erc20Contract":{"contract":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","name":"Contract 75","symbol":"S75","decimals":18}}`,
|
`{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","balance":"123450075","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":1,"nonTokenTxs":1,"internalTxs":1,"txids":["0xc92919ad24ffd58f760b18df7949f06e1190cf54a50a0e3745a385608ed3cbf2"],"nonce":"75","tokens":[{"type":"ERC20","name":"Contract 13","contract":"0x0d0F936Ee4c93e25944694D6C121de94D9760F11","transfers":2,"symbol":"S13","decimals":18},{"type":"ERC20","name":"Contract 74","contract":"0x4af4114F73d1c1C903aC9E0361b379D1291808A2","transfers":2,"symbol":"S74","decimals":18}],"erc20Contract":{"contract":"0x4Bda106325C335dF99eab7fE363cAC8A0ba2a24D","name":"Contract 75","symbol":"S75","decimals":18}}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,10 @@
|
|||||||
<td>Non-contract Transactions</td>
|
<td>Non-contract Transactions</td>
|
||||||
<td class="data">{{$addr.NonTokenTxs}}</td>
|
<td class="data">{{$addr.NonTokenTxs}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Internal Transactions</td>
|
||||||
|
<td class="data">{{$addr.InternalTxs}}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Nonce</td>
|
<td>Nonce</td>
|
||||||
<td class="data">{{$addr.Nonce}}</td>
|
<td class="data">{{$addr.Nonce}}</td>
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
{{if eq $tx.EthereumSpecific.Status 1}}<span class="text-success"> ✔</span>{{end}}{{if eq $tx.EthereumSpecific.Status 0}}<span class="text-danger"> ✘</span>{{end}}
|
{{if eq $tx.EthereumSpecific.Status 1}}<span class="text-success"> ✔</span>{{end}}{{if eq $tx.EthereumSpecific.Status 0}}<span class="text-danger"> ✘</span>{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{- if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-muted text-right">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}</div>{{end -}}
|
{{- if $tx.Blocktime}}<div class="col-xs-5 col-md-4 text-muted text-right">{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}</div>{{end -}}
|
||||||
|
{{if $tx.EthereumSpecific.Error}}<div class="col-12">Error: {{$tx.EthereumSpecific.Error}}</div>{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="row line-mid">
|
<div class="row line-mid">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@ -67,19 +68,20 @@
|
|||||||
{{formatAmount $tx.ValueOutSat}} {{$cs}}
|
{{formatAmount $tx.ValueOutSat}} {{$cs}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{- if $tx.TokenTransfers -}}
|
|
||||||
|
{{if $tx.EthereumSpecific.InternalTransfers}}
|
||||||
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
||||||
ERC20 Token Transfers
|
Internal Transactions
|
||||||
</div>
|
</div>
|
||||||
{{- range $erc20 := $tx.TokenTransfers -}}
|
{{- range $tt := $tx.EthereumSpecific.InternalTransfers -}}
|
||||||
<div class="row" style="padding: 2px 15px;">
|
<div class="row" style="padding: 2px 15px;">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="row tx-in">
|
<div class="row tx-in">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr{{if isOwnAddress $data $erc20.From}} class="tx-own"{{end}}>
|
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
<span class="ellipsis tx-addr">{{if ne $erc20.From $addr}}<a href="/address/{{$erc20.From}}">{{$erc20.From}}</a>{{else}}{{$erc20.From}}{{end}}</span>
|
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -95,20 +97,160 @@
|
|||||||
<div class="row tx-out">
|
<div class="row tx-out">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr{{if isOwnAddress $data $erc20.To}} class="tx-own"{{end}}>
|
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
<span class="ellipsis tx-addr">{{if ne $erc20.To $addr}}<a href="/address/{{$erc20.To}}">{{$erc20.To}}</a>{{else}}{{$erc20.To}}{{end}}</span>
|
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmountWithDecimals $erc20.Value $erc20.Decimals}} {{$erc20.Symbol}}</div>
|
<div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmount $tt.Value}} {{$cs}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<div class="row" style="padding: 6px 15px;"></div>
|
<div class="row" style="padding: 6px 15px;"></div>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- if tokenTransfersCount $tx "ERC20" -}}
|
||||||
|
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
||||||
|
ERC20 Token Transfers
|
||||||
|
</div>
|
||||||
|
{{- range $tt := $tx.TokenTransfers -}}
|
||||||
|
{{if eq $tt.Type "ERC20"}}
|
||||||
|
<div class="row" style="padding: 2px 15px;">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-in">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 col-xs-12 text-center">
|
||||||
|
<svg class="octicon" viewBox="0 0 8 16">
|
||||||
|
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-out">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 text-right" style="padding: .4rem 0;">{{formatAmountWithDecimals $tt.Value $tt.Decimals}} {{$tt.Symbol}}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
<div class="row" style="padding: 6px 15px;"></div>
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- if tokenTransfersCount $tx "ERC721" -}}
|
||||||
|
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
||||||
|
ERC721 Token Transfers
|
||||||
|
</div>
|
||||||
|
{{- range $tt := $tx.TokenTransfers -}}
|
||||||
|
{{if eq $tt.Type "ERC721"}}
|
||||||
|
<div class="row" style="padding: 2px 15px;">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-in">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 col-xs-12 text-center">
|
||||||
|
<svg class="octicon" viewBox="0 0 8 16">
|
||||||
|
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-out">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 text-right" style="padding: .4rem 0;">ID {{formatAmountWithDecimals $tt.Value 0}} {{$tt.Symbol}}</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
<div class="row" style="padding: 6px 15px;"></div>
|
||||||
|
{{- end -}}
|
||||||
|
|
||||||
|
{{- if tokenTransfersCount $tx "ERC1155" -}}
|
||||||
|
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
||||||
|
ERC1155 Token Transfers
|
||||||
|
</div>
|
||||||
|
{{- range $tt := $tx.TokenTransfers -}}
|
||||||
|
{{if eq $tt.Type "ERC1155"}}
|
||||||
|
<div class="row" style="padding: 2px 15px;">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-in">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.From $addr}}<a href="/address/{{$tt.From}}">{{$tt.From}}</a>{{else}}{{$tt.From}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 col-xs-12 text-center">
|
||||||
|
<svg class="octicon" viewBox="0 0 8 16">
|
||||||
|
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="row tx-out">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
|
||||||
|
<td>
|
||||||
|
<span class="ellipsis tx-addr">{{if ne $tt.To $addr}}<a href="/address/{{$tt.To}}">{{$tt.To}}</a>{{else}}{{$tt.To}}{{end}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 text-right" style="padding: .4rem 0;">
|
||||||
|
{{- range $iv := $tt.Values -}}
|
||||||
|
{{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value $tt.Decimals}} {{$tt.Symbol}}
|
||||||
|
{{- end -}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- end -}}
|
||||||
|
{{- end -}}
|
||||||
|
<div class="row" style="padding: 6px 15px;"></div>
|
||||||
|
{{- 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.FeesSat -}}
|
{{- if $tx.FeesSat -}}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user