Add support of staking pools

This commit is contained in:
Martin Boehm 2024-01-19 01:26:46 +01:00
parent f03c625def
commit ac46385f49
12 changed files with 346 additions and 25 deletions

View File

@ -316,6 +316,20 @@ type AddressFilter struct {
OnlyConfirmed bool OnlyConfirmed bool
} }
// StakingPool holds data about address participation in a staking pool contract
type StakingPool struct {
Contract string `json:"contract"`
Name string `json:"name"`
PendingBalance *Amount `json:"pendingBalance"`
PendingDepositedBalance *Amount `json:"pendingDepositedBalance"`
DepositedBalance *Amount `json:"depositedBalance"`
WithdrawTotalAmount *Amount `json:"withdrawTotalAmount"`
ClaimableAmount *Amount `json:"claimableAmount"`
PendingRestakedReward *Amount `json:"pendingRestakedReward"`
RestakedReward *Amount `json:"restakedReward"`
AutocompoundBalance *Amount `json:"autocompoundBalance"`
}
// Address holds information about address and its transactions // Address holds information about address and its transactions
type Address struct { type Address struct {
Paging Paging
@ -342,6 +356,7 @@ type Address struct {
ContractInfo *bchain.ContractInfo `json:"contractInfo,omitempty"` ContractInfo *bchain.ContractInfo `json:"contractInfo,omitempty"`
Erc20Contract *bchain.ContractInfo `json:"erc20Contract,omitempty"` // deprecated Erc20Contract *bchain.ContractInfo `json:"erc20Contract,omitempty"` // deprecated
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"` AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`
StakingPools []StakingPool `json:"stakingPools,omitempty"`
// helpers for explorer // helpers for explorer
Filter string `json:"-"` Filter string `json:"-"`
XPubAddresses map[string]struct{} `json:"-"` XPubAddresses map[string]struct{} `json:"-"`
@ -504,6 +519,7 @@ type BlockbookInfo struct {
CurrentFiatRatesTime *time.Time `json:"currentFiatRatesTime,omitempty"` CurrentFiatRatesTime *time.Time `json:"currentFiatRatesTime,omitempty"`
HistoricalFiatRatesTime *time.Time `json:"historicalFiatRatesTime,omitempty"` HistoricalFiatRatesTime *time.Time `json:"historicalFiatRatesTime,omitempty"`
HistoricalTokenFiatRatesTime *time.Time `json:"historicalTokenFiatRatesTime,omitempty"` HistoricalTokenFiatRatesTime *time.Time `json:"historicalTokenFiatRatesTime,omitempty"`
SupportedStakingPools []string `json:"supportedStakingPools,omitempty"`
DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"` DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"`
DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"` DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"`
About string `json:"about"` About string `json:"about"`

View File

@ -1058,6 +1058,7 @@ type ethereumTypeAddressData struct {
totalResults int totalResults int
tokensBaseValue float64 tokensBaseValue float64
tokensSecondaryValue float64 tokensSecondaryValue float64
stakingPools []StakingPool
} }
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter, secondaryCoin string) (*db.AddrBalance, *ethereumTypeAddressData, error) { func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter, secondaryCoin string) (*db.AddrBalance, *ethereumTypeAddressData, error) {
@ -1157,9 +1158,42 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
filter.Vout = AddressFilterVoutQueryNotNecessary filter.Vout = AddressFilterVoutQueryNotNecessary
d.totalResults = -1 d.totalResults = -1
} }
// if staking pool enabled, fetch the staking pool details
if details >= AccountDetailsTokenBalances {
d.stakingPools, err = w.getStakingPoolsData(addrDesc)
if err != nil {
return nil, nil, err
}
}
return ba, &d, nil return ba, &d, nil
} }
func (w *Worker) getStakingPoolsData(addrDesc bchain.AddressDescriptor) ([]StakingPool, error) {
var pools []StakingPool
if len(w.chain.EthereumTypeGetSupportedStakingPools()) > 0 {
sp, err := w.chain.EthereumTypeGetStakingPoolsData(addrDesc)
if err != nil {
return nil, err
}
for i := range sp {
p := &sp[i]
pools = append(pools, StakingPool{
Contract: p.Contract,
Name: p.Name,
PendingBalance: (*Amount)(&p.PendingBalance),
PendingDepositedBalance: (*Amount)(&p.PendingDepositedBalance),
DepositedBalance: (*Amount)(&p.DepositedBalance),
WithdrawTotalAmount: (*Amount)(&p.WithdrawTotalAmount),
ClaimableAmount: (*Amount)(&p.ClaimableAmount),
PendingRestakedReward: (*Amount)(&p.PendingRestakedReward),
RestakedReward: (*Amount)(&p.RestakedReward),
AutocompoundBalance: (*Amount)(&p.AutocompoundBalance),
})
}
}
return pools, nil
}
func (w *Worker) txFromTxid(txid string, bestHeight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) { func (w *Worker) txFromTxid(txid string, bestHeight uint32, option AccountDetails, blockInfo *db.BlockInfo, addresses map[string]struct{}) (*Tx, error) {
var tx *Tx var tx *Tx
var err error var err error
@ -1401,6 +1435,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option Acco
ContractInfo: ed.contractInfo, ContractInfo: ed.contractInfo,
Nonce: ed.nonce, Nonce: ed.nonce,
AddressAliases: w.getAddressAliases(addresses), AddressAliases: w.getAddressAliases(addresses),
StakingPools: ed.stakingPools,
} }
// keep address backward compatible, set deprecated Erc20Contract value if ERC20 token // keep address backward compatible, set deprecated Erc20Contract value if ERC20 token
if ed.contractInfo != nil && ed.contractInfo.Type == bchain.ERC20TokenType { if ed.contractInfo != nil && ed.contractInfo.Type == bchain.ERC20TokenType {
@ -2379,6 +2414,7 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
CurrentFiatRatesTime: nonZeroTime(currentFiatRatesTime), CurrentFiatRatesTime: nonZeroTime(currentFiatRatesTime),
HistoricalFiatRatesTime: nonZeroTime(w.is.HistoricalFiatRatesTime), HistoricalFiatRatesTime: nonZeroTime(w.is.HistoricalFiatRatesTime),
HistoricalTokenFiatRatesTime: nonZeroTime(w.is.HistoricalTokenFiatRatesTime), HistoricalTokenFiatRatesTime: nonZeroTime(w.is.HistoricalTokenFiatRatesTime),
SupportedStakingPools: w.chain.EthereumTypeGetSupportedStakingPools(),
DbSize: w.db.DatabaseSizeOnDisk(), DbSize: w.db.DatabaseSizeOnDisk(),
DbSizeFromColumns: internalDBSize, DbSizeFromColumns: internalDBSize,
DbColumns: columnStats, DbColumns: columnStats,

View File

@ -41,30 +41,38 @@ func (b *BaseChain) GetMempoolEntry(txid string) (*MempoolEntry, error) {
// EthereumTypeGetBalance is not supported // EthereumTypeGetBalance is not supported
func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) { func (b *BaseChain) EthereumTypeGetBalance(addrDesc AddressDescriptor) (*big.Int, error) {
return nil, errors.New("Not supported") return nil, errors.New("not supported")
} }
// EthereumTypeGetNonce is not supported // EthereumTypeGetNonce is not supported
func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) { func (b *BaseChain) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) {
return 0, errors.New("Not supported") return 0, errors.New("not supported")
} }
// EthereumTypeEstimateGas is not supported // EthereumTypeEstimateGas is not supported
func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) { func (b *BaseChain) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) {
return 0, errors.New("Not supported") return 0, errors.New("not supported")
} }
// GetContractInfo is not supported // GetContractInfo is not supported
func (b *BaseChain) GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) { func (b *BaseChain) GetContractInfo(contractDesc AddressDescriptor) (*ContractInfo, error) {
return nil, errors.New("Not supported") return nil, errors.New("not supported")
} }
// EthereumTypeGetErc20ContractBalance is not supported // EthereumTypeGetErc20ContractBalance is not supported
func (b *BaseChain) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) { func (b *BaseChain) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) {
return nil, errors.New("Not supported") return nil, errors.New("not supported")
} }
// GetContractInfo returns URI of non fungible or multi token defined by token id // GetContractInfo returns URI of non fungible or multi token defined by token id
func (p *BaseChain) GetTokenURI(contractDesc AddressDescriptor, tokenID *big.Int) (string, error) { func (p *BaseChain) GetTokenURI(contractDesc AddressDescriptor, tokenID *big.Int) (string, error) {
return "", errors.New("Not supported") return "", errors.New("not supported")
}
func (b *BaseChain) EthereumTypeGetSupportedStakingPools() []string {
return nil
}
func (b *BaseChain) EthereumTypeGetStakingPoolsData(addrDesc AddressDescriptor) ([]StakingPoolData, error) {
return nil, errors.New("not supported")
} }

View File

@ -332,6 +332,15 @@ func (c *blockChainWithMetrics) GetTokenURI(contractDesc bchain.AddressDescripto
return c.b.GetTokenURI(contractDesc, tokenID) return c.b.GetTokenURI(contractDesc, tokenID)
} }
func (c *blockChainWithMetrics) EthereumTypeGetSupportedStakingPools() []string {
return c.b.EthereumTypeGetSupportedStakingPools()
}
func (c *blockChainWithMetrics) EthereumTypeGetStakingPoolsData(addrDesc bchain.AddressDescriptor) (v []bchain.StakingPoolData, err error) {
defer func(s time.Time) { c.observeRPCLatency("EthereumTypeStakingPoolsData", s, err) }(time.Now())
return c.b.EthereumTypeGetStakingPoolsData(addrDesc)
}
type mempoolWithMetrics struct { type mempoolWithMetrics struct {
mempool bchain.Mempool mempool bchain.Mempool
m *common.Metrics m *common.Metrics

View File

@ -337,9 +337,9 @@ func (b *EthereumRPC) GetContractInfo(contractDesc bchain.AddressDescriptor) (*b
// EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address // EthereumTypeGetErc20ContractBalance returns balance of ERC20 contract for given address
func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) { func (b *EthereumRPC) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc bchain.AddressDescriptor) (*big.Int, error) {
addr := hexutil.Encode(addrDesc) addr := hexutil.Encode(addrDesc)[2:]
contract := hexutil.Encode(contractDesc) contract := hexutil.Encode(contractDesc)
req := contractBalanceOfSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr)-2:] + addr[2:] req := contractBalanceOfSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr):] + addr
data, err := b.ethCall(req, contract) data, err := b.ethCall(req, contract)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -56,23 +56,26 @@ type Configuration struct {
// EthereumRPC is an interface to JSON-RPC eth service. // EthereumRPC is an interface to JSON-RPC eth service.
type EthereumRPC struct { type EthereumRPC struct {
*bchain.BaseChain *bchain.BaseChain
Client bchain.EVMClient Client bchain.EVMClient
RPC bchain.EVMRPCClient RPC bchain.EVMRPCClient
MainNetChainID Network MainNetChainID Network
Timeout time.Duration Timeout time.Duration
Parser *EthereumParser Parser *EthereumParser
PushHandler func(bchain.NotificationType) PushHandler func(bchain.NotificationType)
OpenRPC func(string) (bchain.EVMRPCClient, bchain.EVMClient, error) OpenRPC func(string) (bchain.EVMRPCClient, bchain.EVMClient, error)
Mempool *bchain.MempoolEthereumType Mempool *bchain.MempoolEthereumType
mempoolInitialized bool mempoolInitialized bool
bestHeaderLock sync.Mutex bestHeaderLock sync.Mutex
bestHeader bchain.EVMHeader bestHeader bchain.EVMHeader
bestHeaderTime time.Time bestHeaderTime time.Time
NewBlock bchain.EVMNewBlockSubscriber NewBlock bchain.EVMNewBlockSubscriber
newBlockSubscription bchain.EVMClientSubscription newBlockSubscription bchain.EVMClientSubscription
NewTx bchain.EVMNewTxSubscriber NewTx bchain.EVMNewTxSubscriber
newTxSubscription bchain.EVMClientSubscription newTxSubscription bchain.EVMClientSubscription
ChainConfig *Configuration ChainConfig *Configuration
supportedStakingPools []string
stakingPoolNames []string
stakingPoolContracts []string
} }
// ProcessInternalTransactions specifies if internal transactions are processed // ProcessInternalTransactions specifies if internal transactions are processed
@ -155,6 +158,12 @@ func (b *EthereumRPC) Initialize() error {
default: default:
return errors.Errorf("Unknown network id %v", id) return errors.Errorf("Unknown network id %v", id)
} }
err = b.initStakingPools(b.ChainConfig.CoinShortcut)
if err != nil {
return err
}
glog.Info("rpc: block chain ", b.Network) glog.Info("rpc: block chain ", b.Network)
return nil return nil

View File

@ -0,0 +1,150 @@
package eth
import (
"math/big"
"os"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/golang/glog"
"github.com/juju/errors"
"github.com/trezor/blockbook/bchain"
)
func (b *EthereumRPC) initStakingPools(coinShortcut string) error {
// for now only single staking pool
envVar := strings.ToUpper(coinShortcut) + "_STAKING_POOL_CONTRACT"
envValue := os.Getenv(envVar)
if envValue != "" {
parts := strings.Split(envValue, "/")
if len(parts) != 2 {
glog.Errorf("Wrong format of environment variable %s=%s, expecting value '<pool name>/<pool contract>', staking pools not enabled", envVar, envValue)
return nil
}
b.supportedStakingPools = []string{envValue}
b.stakingPoolNames = []string{parts[0]}
b.stakingPoolContracts = []string{parts[1]}
glog.Info("Support of staking pools enabled with these pools: ", b.supportedStakingPools)
}
return nil
}
func (b *EthereumRPC) EthereumTypeGetSupportedStakingPools() []string {
return b.supportedStakingPools
}
func (b *EthereumRPC) EthereumTypeGetStakingPoolsData(addrDesc bchain.AddressDescriptor) ([]bchain.StakingPoolData, error) {
// for now only single staking pool - Everstake
addr := hexutil.Encode(addrDesc)[2:]
if len(b.supportedStakingPools) == 1 {
data, err := b.everstakePoolData(addr, b.stakingPoolContracts[0], b.stakingPoolNames[0])
if err != nil {
return nil, err
}
if data != nil {
return []bchain.StakingPoolData{*data}, nil
}
}
return nil, nil
}
const everstakePendingBalanceOfMethodSignature = "0x59b8c763" // pendingBalanceOf(address)
const everstakePendingDepositedBalanceOfMethodSignature = "0x80f14ecc" // pendingDepositedBalanceOf(address)
const everstakeDepositedBalanceOfMethodSignature = "0x68b48254" // depositedBalanceOf(address)
const everstakeWithdrawRequestMethodSignature = "0x14cbc46a" // withdrawRequest(address)
const everstakePendingRestakedRewardOfMethodSignature = "0x376d1884" // pendingRestakedRewardOf(address)
const everstakeRestakedRewardOfMethodSignature = "0x0c98929a" // restakedRewardOf(address)
const everstakeAutocompoundBalanceOfMethodSignature = "0x2fec7966" // autocompoundBalanceOf(address)
func isZeroBigInt(b *big.Int) bool {
return len(b.Bits()) == 0
}
func (b *EthereumRPC) everstakeBalanceTypeContractCall(signature, addr, contract string) (string, error) {
req := signature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addr):] + addr
return b.ethCall(req, contract)
}
func (b *EthereumRPC) everstakeContractCallSimpleNumeric(signature, addr, contract string) (*big.Int, error) {
data, err := b.everstakeBalanceTypeContractCall(signature, addr, contract)
if err != nil {
return nil, err
}
r := parseSimpleNumericProperty(data)
if r == nil {
return nil, errors.New("Invalid balance")
}
return r, nil
}
func (b *EthereumRPC) everstakePoolData(addr, contract, name string) (*bchain.StakingPoolData, error) {
poolData := bchain.StakingPoolData{
Contract: contract,
Name: name,
}
allZeros := true
value, err := b.everstakeContractCallSimpleNumeric(everstakePendingBalanceOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.PendingBalance = *value
allZeros = allZeros && isZeroBigInt(value)
value, err = b.everstakeContractCallSimpleNumeric(everstakePendingDepositedBalanceOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.PendingDepositedBalance = *value
allZeros = allZeros && isZeroBigInt(value)
value, err = b.everstakeContractCallSimpleNumeric(everstakeDepositedBalanceOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.DepositedBalance = *value
allZeros = allZeros && isZeroBigInt(value)
data, err := b.everstakeBalanceTypeContractCall(everstakeWithdrawRequestMethodSignature, addr, contract)
if err != nil {
return nil, err
}
value = parseSimpleNumericProperty(data)
if value == nil {
return nil, errors.New("Invalid balance")
}
poolData.WithdrawTotalAmount = *value
allZeros = allZeros && isZeroBigInt(value)
value = parseSimpleNumericProperty(data[64+2:])
if value == nil {
return nil, errors.New("Invalid balance")
}
poolData.ClaimableAmount = *value
allZeros = allZeros && isZeroBigInt(value)
value, err = b.everstakeContractCallSimpleNumeric(everstakePendingRestakedRewardOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.PendingRestakedReward = *value
allZeros = allZeros && isZeroBigInt(value)
value, err = b.everstakeContractCallSimpleNumeric(everstakeRestakedRewardOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.RestakedReward = *value
allZeros = allZeros && isZeroBigInt(value)
value, err = b.everstakeContractCallSimpleNumeric(everstakeAutocompoundBalanceOfMethodSignature, addr, contract)
if err != nil {
return nil, err
}
poolData.AutocompoundBalance = *value
allZeros = allZeros && isZeroBigInt(value)
if allZeros {
return nil, nil
}
return &poolData, nil
}

View File

@ -333,6 +333,8 @@ type BlockChain interface {
EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error) EthereumTypeGetNonce(addrDesc AddressDescriptor) (uint64, error)
EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error) EthereumTypeEstimateGas(params map[string]interface{}) (uint64, error)
EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error) EthereumTypeGetErc20ContractBalance(addrDesc, contractDesc AddressDescriptor) (*big.Int, error)
EthereumTypeGetSupportedStakingPools() []string
EthereumTypeGetStakingPoolsData(addrDesc AddressDescriptor) ([]StakingPoolData, error)
GetTokenURI(contractDesc AddressDescriptor, tokenID *big.Int) (string, error) GetTokenURI(contractDesc AddressDescriptor, tokenID *big.Int) (string, error)
} }

View File

@ -146,3 +146,17 @@ type EthereumBlockSpecificData struct {
AddressAliasRecords []AddressAliasRecord AddressAliasRecords []AddressAliasRecord
Contracts []ContractInfo Contracts []ContractInfo
} }
// StakingPool holds data about address participation in a staking pool contract
type StakingPoolData struct {
Contract string `json:"contract"`
Name string `json:"name"`
PendingBalance big.Int `json:"pendingBalance"` // pendingBalanceOf method
PendingDepositedBalance big.Int `json:"pendingDepositedBalance"` // pendingDepositedBalanceOf method
DepositedBalance big.Int `json:"depositedBalance"` // depositedBalanceOf method
WithdrawTotalAmount big.Int `json:"withdrawTotalAmount"` // withdrawRequest method, return value [0]
ClaimableAmount big.Int `json:"claimableAmount"` // withdrawRequest method, return value [1]
PendingRestakedReward big.Int `json:"pendingRestakedReward"` // pendingRestakedRewardOf method
RestakedReward big.Int `json:"restakedReward"` // restakedRewardOf method
AutocompoundBalance big.Int `json:"autocompoundBalance"` // autocompoundBalanceOf method
}

View File

@ -109,6 +109,17 @@ export interface FeeStats {
averageFeePerKb: number; averageFeePerKb: number;
decilesFeePerKb: number[]; decilesFeePerKb: number[];
} }
export interface StakingPool {
contract: string;
pendingBalance: string;
pendingDepositedBalance: string;
depositedBalance: string;
withdrawTotalAmount: string;
claimableAmount: string;
pendingRestakedReward: string;
restakedReward: string;
autocompoundBalance: string;
}
export interface ContractInfo { export interface ContractInfo {
type: string; type: string;
contract: string; contract: string;
@ -161,6 +172,7 @@ export interface Address {
contractInfo?: ContractInfo; contractInfo?: ContractInfo;
erc20Contract?: ContractInfo; erc20Contract?: ContractInfo;
addressAliases?: { [key: string]: AddressAlias }; addressAliases?: { [key: string]: AddressAlias };
stakingPools?: StakingPool[];
} }
export interface Utxo { export interface Utxo {
txid: string; txid: string;
@ -264,6 +276,7 @@ export interface BlockbookInfo {
currentFiatRatesTime?: string; currentFiatRatesTime?: string;
historicalFiatRatesTime?: string; historicalFiatRatesTime?: string;
historicalTokenFiatRatesTime?: string; historicalTokenFiatRatesTime?: string;
stakingPoolContracts?: string[];
dbSizeFromColumns?: number; dbSizeFromColumns?: number;
dbColumns?: InternalStateColumn[]; dbColumns?: InternalStateColumn[];
about: string; about: string;

View File

@ -221,6 +221,64 @@
</div> </div>
</div> </div>
{{end}} {{end}}
{{if $addr.StakingPools }}
<div class="accordion mt-2 mb-2" id="stakingPools">
<div class="accordion-item">
<div class="accordion-header" id="stakingPoolsHeading">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#stakingPoolsBody" aria-expanded="false" aria-controls="stakingPoolsBody">
<div class="row g-0 w-100">
<h5 class="col-12 mb-md-0">Staking Pools <span class="badge bg-secondary">{{len $addr.StakingPools}}</span></span></h5>
</div>
</button>
</div>
<div id="stakingPoolsBody" class="accordion-collapse collapse" aria-labelledby="stakingPoolsHeading" data-bs-parent="#stakingPools">
<div class="accordion-body">
{{range $sp := $addr.StakingPools}}
<table class="table data-table info-table mt-0 mb-2 ml-0">
<tbody>
<tr>
<td colspan="2" style="white-space: nowrap;"><span class="h5" style="color: var(--bs-body-color);">{{$sp.Name}}</span> {{$sp.Contract}}</td>
</tr>
<tr>
<td style="width: 25%;">Pending Balance</td>
<td>{{amountSpan $sp.PendingBalance $data "copyable"}}</td>
</tr>
<tr>
<td>Pending Deposited Balance</td>
<td>{{amountSpan $sp.PendingDepositedBalance $data "copyable"}}</td>
</tr>
<tr>
<td style="width: 25%;">Deposited Balance</td>
<td>{{amountSpan $sp.DepositedBalance $data "copyable"}}</td>
</tr>
<tr>
<td>Withdrawal Total Amount</td>
<td>{{amountSpan $sp.WithdrawTotalAmount $data "copyable"}}</td>
</tr>
<tr>
<td style="width: 25%;">Claimable Amount</td>
<td>{{amountSpan $sp.ClaimableAmount $data "copyable"}}</td>
</tr>
<tr>
<td>Pending Restaked Reward</td>
<td>{{amountSpan $sp.PendingRestakedReward $data "copyable"}}</td>
</tr>
<tr>
<td>Restaked Reward</td>
<td>{{amountSpan $sp.RestakedReward $data "copyable"}}</td>
</tr>
<tr>
<td>Autocompound Balance</td>
<td>{{amountSpan $sp.AutocompoundBalance $data "copyable"}}</td>
</tr>
</tbody>
</table>
{{end}}
</div>
</div>
</div>
</div>
{{end}}
{{end}} {{end}}
{{if or $addr.Transactions $addr.Filter}} {{if or $addr.Transactions $addr.Filter}}
<div class="row pt-3 pb-1"> <div class="row pt-3 pb-1">

View File

@ -64,6 +64,12 @@
<td>Size On Disk</td> <td>Size On Disk</td>
<td>{{formatInt64 $bb.DbSize}}</td> <td>{{formatInt64 $bb.DbSize}}</td>
</tr> </tr>
{{if $bb.SupportedStakingPools}}
<tr>
<td>Supported Staking Pools</td>
<td>{{$bb.SupportedStakingPools}}</td>
</tr>
{{end}}
</tbody> </tbody>
</table> </table>
</div> </div>