Return internal data and ERC721 and ERC1155 tokens from API and explorer
This commit is contained in:
parent
ae2d0e3958
commit
72e0ac23bc
14
api/types.go
14
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"`
|
||||
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 []bchain.EthereumInternalTransfer `json:"internalTransfers,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"`
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -44,6 +44,7 @@ type Configuration struct {
|
||||
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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -22,6 +22,10 @@
|
||||
<td>Non-contract Transactions</td>
|
||||
<td class="data">{{$addr.NonTokenTxs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Internal Transactions</td>
|
||||
<td class="data">{{$addr.InternalTxs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>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}}
|
||||
</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.EthereumSpecific.Error}}<div class="col-12">Error: {{$tx.EthereumSpecific.Error}}</div>{{end}}
|
||||
</div>
|
||||
<div class="row line-mid">
|
||||
<div class="col-md-4">
|
||||
@ -67,19 +68,20 @@
|
||||
{{formatAmount $tx.ValueOutSat}} {{$cs}}
|
||||
</div>
|
||||
</div>
|
||||
{{- if $tx.TokenTransfers -}}
|
||||
|
||||
{{if $tx.EthereumSpecific.InternalTransfers}}
|
||||
<div class="row line-top" style="padding: 15px 0 6px 15px;font-weight: bold;">
|
||||
ERC20 Token Transfers
|
||||
Internal Transactions
|
||||
</div>
|
||||
{{- range $erc20 := $tx.TokenTransfers -}}
|
||||
{{- range $tt := $tx.EthereumSpecific.InternalTransfers -}}
|
||||
<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 $erc20.From}} class="tx-own"{{end}}>
|
||||
<tr{{if isOwnAddress $data $tt.From}} class="tx-own"{{end}}>
|
||||
<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>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -95,20 +97,160 @@
|
||||
<div class="row tx-out">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr{{if isOwnAddress $data $erc20.To}} class="tx-own"{{end}}>
|
||||
<tr{{if isOwnAddress $data $tt.To}} class="tx-own"{{end}}>
|
||||
<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>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
{{- end -}}
|
||||
<div class="row" style="padding: 6px 15px;"></div>
|
||||
{{- 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="col-xs-6 col-sm-4 col-md-4">
|
||||
{{- if $tx.FeesSat -}}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user