diff --git a/api/types.go b/api/types.go
index 3e7ffed5..6a0bd165 100644
--- a/api/types.go
+++ b/api/types.go
@@ -187,16 +187,25 @@ type TokenTransfer struct {
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
type EthereumSpecific struct {
- TxType string `json:"txType,omitempty"`
- Status eth.TxStatus `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"`
- Data string `json:"data,omitempty"`
- InternalTransfers []bchain.EthereumInternalTransfer `json:"internalTransfers,omitempty"`
+ Type bchain.EthereumInternalTransactionType `json:"type,omitempty"`
+ CreatedContract string `json:"createdContract,omitempty"`
+ Status eth.TxStatus `json:"status"` // 1 OK, 0 Fail, -1 pending
+ Error string `json:"error,omitempty"`
+ Nonce uint64 `json:"nonce"`
+ GasLimit *big.Int `json:"gasLimit"`
+ GasUsed *big.Int `json:"gasUsed"`
+ GasPrice *Amount `json:"gasPrice"`
+ Data string `json:"data,omitempty"`
+ InternalTransfers []EthereumInternalTransfer `json:"internalTransfers,omitempty"`
}
// Tx holds information about a transaction
@@ -279,6 +288,7 @@ type Address struct {
UnconfirmedTxs int `json:"unconfirmedTxs"`
Txs int `json:"txs"`
NonTokenTxs int `json:"nonTokenTxs,omitempty"`
+ InternalTxs int `json:"internalTxs,omitempty"`
Transactions []*Tx `json:"transactions,omitempty"`
Txids []string `json:"txids,omitempty"`
Nonce string `json:"nonce,omitempty"`
diff --git a/api/worker.go b/api/worker.go
index 19583aa9..ac9cdb90 100644
--- a/api/worker.go
+++ b/api/worker.go
@@ -261,6 +261,15 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
}
tokens = w.getEthereumTokensTransfers(tokenTransfers)
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
if ethTxData.GasUsed != nil {
feesSat.Mul(ethTxData.GasPrice, ethTxData.GasUsed)
@@ -276,6 +285,21 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
Status: ethTxData.Status,
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
// size:=len(bchainTx.Hex) / 2
@@ -427,13 +451,25 @@ func (w *Worker) getEthereumTokensTransfers(transfers bchain.TokenTransfers) []T
if erc20c == nil {
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{
Type: TokenTypeMap[t.Type],
Token: t.Contract,
From: t.From,
To: t.To,
+ Value: value,
+ Values: values,
Decimals: erc20c.Decimals,
- Value: (*Amount)(&t.Value),
Name: erc20c.Name,
Symbol: erc20c.Symbol,
}
@@ -657,29 +693,30 @@ func (w *Worker) getEthereumToken(index int, addrDesc, contract bchain.AddressDe
}, 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 (
ba *db.AddrBalance
tokens []Token
ci *bchain.Erc20Contract
n uint64
nonContractTxs int
+ internalTxs int
)
// unknown number of results for paging
totalResults := -1
ca, err := w.db.GetAddrDescContracts(addrDesc)
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)
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
if filter.Contract != "" {
filterDesc, err = w.chainParser.GetAddrDescFromAddress(filter.Contract)
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 {
@@ -691,7 +728,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
}
n, err = w.chain.EthereumTypeGetNonce(addrDesc)
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 {
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))
if err != nil {
- return nil, nil, nil, 0, 0, 0, err
+ return nil, nil, nil, 0, 0, 0, 0, err
}
tokens[j] = *t
j++
@@ -716,7 +753,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
if len(filterDesc) > 0 && j == 0 && details >= AccountDetailsTokens {
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
if err != nil {
- return nil, nil, nil, 0, 0, 0, err
+ return nil, nil, nil, 0, 0, 0, 0, err
}
tokens = []Token{*t}
// 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)
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 {
// compute total results for paging
@@ -742,6 +779,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
}
}
nonContractTxs = int(ca.NonContractTxs)
+ internalTxs = int(ca.InternalTxs)
} else {
// addresses without any normal transactions can have internal transactions and therefore balance
if b != nil {
@@ -753,14 +791,14 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
if len(filterDesc) > 0 && details >= AccountDetailsTokens {
t, err := w.getEthereumToken(0, addrDesc, filterDesc, details, 0)
if err != nil {
- return nil, nil, nil, 0, 0, 0, err
+ return nil, nil, nil, 0, 0, 0, 0, err
}
tokens = []Token{*t}
// switch off query for transactions, there are no transactions
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) {
@@ -865,6 +903,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
nonce string
unconfirmedTxs int
nonTokenTxs int
+ internalTxs int
totalResults int
)
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 {
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 {
return nil, err
}
@@ -977,6 +1016,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
TotalSentSat: (*Amount)(totalSent),
Txs: int(ba.Txs),
NonTokenTxs: nonTokenTxs,
+ InternalTxs: internalTxs,
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
UnconfirmedTxs: unconfirmedTxs,
Transactions: txs,
diff --git a/bchain/coins/eth/ethrpc.go b/bchain/coins/eth/ethrpc.go
index 1b6f2803..8c4630c2 100644
--- a/bchain/coins/eth/ethrpc.go
+++ b/bchain/coins/eth/ethrpc.go
@@ -36,14 +36,15 @@ const (
// Configuration represents json config file
type Configuration struct {
- CoinName string `json:"coin_name"`
- CoinShortcut string `json:"coin_shortcut"`
- RPCURL string `json:"rpc_url"`
- RPCTimeout int `json:"rpc_timeout"`
- BlockAddressesToKeep int `json:"block_addresses_to_keep"`
- MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
- QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
- ProcessInternalTransactions bool `json:"processInternalTransactions"`
+ CoinName string `json:"coin_name"`
+ CoinShortcut string `json:"coin_shortcut"`
+ RPCURL string `json:"rpc_url"`
+ RPCTimeout int `json:"rpc_timeout"`
+ BlockAddressesToKeep int `json:"block_addresses_to_keep"`
+ MempoolTxTimeoutHours int `json:"mempoolTxTimeoutHours"`
+ QueryBackendOnMempoolResync bool `json:"queryBackendOnMempoolResync"`
+ ProcessInternalTransactions bool `json:"processInternalTransactions"`
+ ProcessZeroInternalTransactions bool `json:"processZeroInternalTransactions"`
}
// EthereumRPC is an interface to JSON-RPC eth service.
@@ -65,6 +66,9 @@ type EthereumRPC struct {
ChainConfig *Configuration
}
+// ProcessInternalTransactions specifies if internal transactions are processed
+var ProcessInternalTransactions bool
+
// NewEthereumRPC returns new EthRPC instance.
func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.NotificationType)) (bchain.BlockChain, error) {
var err error
@@ -90,6 +94,8 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
ChainConfig: &c,
}
+ ProcessInternalTransactions = c.ProcessInternalTransactions
+
// always create parser
s.Parser = NewEthereumParser(c.BlockAddressesToKeep)
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{}{
"fromBlock": blockNumber,
"toBlock": blockNumber,
- "topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature},
+ // "topics": []string{tokenTransferEventSignature, tokenERC1155TransferSingleEventSignature, tokenERC1155TransferBatchEventSignature},
})
if err != nil {
return nil, errors.Annotatef(err, "blockNumber %v", blockNumber)
@@ -543,7 +549,7 @@ func (b *EthereumRPC) processCallTrace(call *rpcCallTrace, d *bchain.EthereumInt
From: call.From,
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{
Value: *value,
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
func (b *EthereumRPC) getInternalDataForBlock(blockHash string, transactions []bchain.RpcTransaction) ([]bchain.EthereumInternalData, error) {
data := make([]bchain.EthereumInternalData, len(transactions))
- if b.ChainConfig.ProcessInternalTransactions {
+ if ProcessInternalTransactions {
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
defer cancel()
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)
if err != nil {
blockSpecificData = &bchain.EthereumBlockSpecificData{InternalDataError: err.Error()}
+ glog.Info("InternalDataError ", bbh.Height, ": ", err.Error())
}
btxs := make([]bchain.Tx, len(body.Transactions))
diff --git a/server/public.go b/server/public.go
index 125c273a..e778306b 100644
--- a/server/public.go
+++ b/server/public.go
@@ -456,6 +456,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
"setTxToTemplateData": setTxToTemplateData,
"isOwnAddress": isOwnAddress,
"toJSON": toJSON,
+ "tokenTransfersCount": tokenTransfersCount,
}
var createTemplate func(filenames ...string) *template.Template
if s.debug {
@@ -558,6 +559,17 @@ func isOwnAddress(td *TemplateData, a string) bool {
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) {
var tx *api.Tx
var err error
diff --git a/server/public_ethereumtype_test.go b/server/public_ethereumtype_test.go
index 4c9c4878..55ed9dc5 100644
--- a/server/public_ethereumtype_test.go
+++ b/server/public_ethereumtype_test.go
@@ -34,7 +34,7 @@ func httpTestsEthereumType(t *testing.T, ts *httptest.Server) {
status: http.StatusOK,
contentType: "application/json; charset=utf-8",
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}}`,
},
},
}
diff --git a/static/templates/address.html b/static/templates/address.html
index 208a2b44..b5b67d3d 100644
--- a/static/templates/address.html
+++ b/static/templates/address.html
@@ -22,6 +22,10 @@
Non-contract Transactions |
{{$addr.NonTokenTxs}} |
+
+ | Internal Transactions |
+ {{$addr.InternalTxs}} |
+
| Nonce |
{{$addr.Nonce}} |
diff --git a/static/templates/txdetail_ethereumtype.html b/static/templates/txdetail_ethereumtype.html
index c8c9f8bf..36aed548 100644
--- a/static/templates/txdetail_ethereumtype.html
+++ b/static/templates/txdetail_ethereumtype.html
@@ -6,6 +6,7 @@
{{if eq $tx.EthereumSpecific.Status 1}} ✔{{end}}{{if eq $tx.EthereumSpecific.Status 0}} ✘{{end}}
{{- if $tx.Blocktime}}{{if $tx.Confirmations}}mined{{else}}first seen{{end}} {{formatUnixTime $tx.Blocktime}}
{{end -}}
+ {{if $tx.EthereumSpecific.Error}}Error: {{$tx.EthereumSpecific.Error}}
{{end}}
@@ -67,19 +68,20 @@
{{formatAmount $tx.ValueOutSat}} {{$cs}}
- {{- if $tx.TokenTransfers -}}
+
+ {{if $tx.EthereumSpecific.InternalTransfers}}
- ERC20 Token Transfers
+ Internal Transactions
- {{- range $erc20 := $tx.TokenTransfers -}}
+ {{- range $tt := $tx.EthereumSpecific.InternalTransfers -}}
-
+
|
- {{if ne $erc20.From $addr}}{{$erc20.From}}{{else}}{{$erc20.From}}{{end}}
+ {{if ne $tt.From $addr}}{{$tt.From}}{{else}}{{$tt.From}}{{end}}
|
@@ -95,20 +97,160 @@
-
+
|
- {{if ne $erc20.To $addr}}{{$erc20.To}}{{else}}{{$erc20.To}}{{end}}
+ {{if ne $tt.To $addr}}{{$tt.To}}{{else}}{{$tt.To}}{{end}}
|
- {{formatAmountWithDecimals $erc20.Value $erc20.Decimals}} {{$erc20.Symbol}}
+ {{formatAmount $tt.Value}} {{$cs}}
{{- end -}}
{{- end -}}
+
+ {{- if tokenTransfersCount $tx "ERC20" -}}
+
+ ERC20 Token Transfers
+
+ {{- range $tt := $tx.TokenTransfers -}}
+ {{if eq $tt.Type "ERC20"}}
+
+
+
+
+
+
+ |
+ {{if ne $tt.From $addr}}{{$tt.From}}{{else}}{{$tt.From}}{{end}}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{if ne $tt.To $addr}}{{$tt.To}}{{else}}{{$tt.To}}{{end}}
+ |
+
+
+
+
+
+
{{formatAmountWithDecimals $tt.Value $tt.Decimals}} {{$tt.Symbol}}
+
+ {{- end -}}
+ {{- end -}}
+
+ {{- end -}}
+
+ {{- if tokenTransfersCount $tx "ERC721" -}}
+
+ ERC721 Token Transfers
+
+ {{- range $tt := $tx.TokenTransfers -}}
+ {{if eq $tt.Type "ERC721"}}
+
+
+
+
+
+
+ |
+ {{if ne $tt.From $addr}}{{$tt.From}}{{else}}{{$tt.From}}{{end}}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{if ne $tt.To $addr}}{{$tt.To}}{{else}}{{$tt.To}}{{end}}
+ |
+
+
+
+
+
+
ID {{formatAmountWithDecimals $tt.Value 0}} {{$tt.Symbol}}
+
+ {{- end -}}
+ {{- end -}}
+
+ {{- end -}}
+
+ {{- if tokenTransfersCount $tx "ERC1155" -}}
+
+ ERC1155 Token Transfers
+
+ {{- range $tt := $tx.TokenTransfers -}}
+ {{if eq $tt.Type "ERC1155"}}
+
+
+
+
+
+
+ |
+ {{if ne $tt.From $addr}}{{$tt.From}}{{else}}{{$tt.From}}{{end}}
+ |
+
+
+
+
+
+
+
+
+
+
+
+ |
+ {{if ne $tt.To $addr}}{{$tt.To}}{{else}}{{$tt.To}}{{end}}
+ |
+
+
+
+
+
+
+ {{- range $iv := $tt.Values -}}
+ {{formatAmountWithDecimals $iv.Id 0}}:{{formatAmountWithDecimals $iv.Value $tt.Decimals}} {{$tt.Symbol}}
+ {{- end -}}
+
+
+ {{- end -}}
+ {{- end -}}
+
+ {{- end -}}
+