From ff633d84a565842f40f4da93a0d25a2653326c70 Mon Sep 17 00:00:00 2001 From: Manuel <5877862+manuelsc@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:59:10 +0100 Subject: [PATCH] feat: added rocket pool dashboard list query endpoint perf: use new validator index for rocket pool mobile queries instead of pubkey --- backend/pkg/api/data_access/mobile.go | 26 +- .../pkg/api/data_access/vdb_rocket_pool.go | 241 +++++++++++++++++- .../api/enums/validator_dashboard_enums.go | 36 ++- backend/pkg/api/types/data_access.go | 10 + backend/pkg/api/types/rocketpool.go | 7 +- backend/pkg/api/types/validator_dashboard.go | 48 ++-- frontend/types/api/validator_dashboard.ts | 32 +-- 7 files changed, 314 insertions(+), 86 deletions(-) diff --git a/backend/pkg/api/data_access/mobile.go b/backend/pkg/api/data_access/mobile.go index 89b2b7b03..4979d8aad 100644 --- a/backend/pkg/api/data_access/mobile.go +++ b/backend/pkg/api/data_access/mobile.go @@ -7,14 +7,12 @@ import ( "time" "github.com/doug-martin/goqu/v9" - "github.com/ethereum/go-ethereum/common/hexutil" "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" "github.com/gobitfly/beaconchain/pkg/commons/cache" "github.com/gobitfly/beaconchain/pkg/commons/utils" constypes "github.com/gobitfly/beaconchain/pkg/consapi/types" "github.com/gobitfly/beaconchain/pkg/userservice" - "github.com/lib/pq" "github.com/pkg/errors" "github.com/shopspring/decimal" "golang.org/x/sync/errgroup" @@ -231,10 +229,9 @@ func (d *DataAccessService) GetValidatorDashboardMobileWidget(ctx context.Contex goqu.COALESCE(goqu.SUM("rpln.rpl_stake"), 0).As("rpl_stake")). From(goqu.L("rocketpool_nodes AS rpln")). LeftJoin(goqu.L("rocketpool_minipools AS m"), goqu.On(goqu.L("m.node_address = rpln.address"))). - LeftJoin(goqu.L("validators AS v"), goqu.On(goqu.L("m.pubkey = v.pubkey"))). Where(goqu.L("node_deposit_balance IS NOT NULL")). Where(goqu.L("user_deposit_balance IS NOT NULL")). - LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("uvdv.validator_index = v.validatorindex"))). + LeftJoin(goqu.L("users_val_dashboards_validators uvdv"), goqu.On(goqu.L("m.validator_index = uvdv.validator_index"))). Where(goqu.L("uvdv.dashboard_id = ?", dashboardId)) query, args, err := ds.Prepared(true).ToSQL() @@ -357,6 +354,7 @@ func (d *DataAccessService) getInternalRpNetworkStats(ctx context.Context) (*t.R EXTRACT(EPOCH FROM claim_interval_time) / 3600 AS claim_interval_hours, node_operator_rewards, effective_rpl_staked, + ts, rpl_price FROM rocketpool_network_stats ORDER BY ID @@ -372,17 +370,8 @@ func (d *DataAccessService) GetValidatorDashboardMobileValidators(ctx context.Co return nil, p, err } - // Get extra information for this result subset - validatorMapping, err := d.services.GetCurrentValidatorMapping() - if err != nil { - return nil, nil, errors.Wrap(err, "validator mapping error") - } - - pubKeys := make([][]byte, 0, len(result)) indices := make([]uint64, 0, len(result)) for _, row := range result { - metadata := validatorMapping.ValidatorMetadata[row.Index] - pubKeys = append(pubKeys, metadata.PublicKey) indices = append(indices, row.Index) } @@ -395,6 +384,7 @@ func (d *DataAccessService) GetValidatorDashboardMobileValidators(ctx context.Co DepositAmount decimal.Decimal `db:"node_deposit_balance"` Status string `db:"status"` IsInSmoothingPool bool `db:"smoothing_pool_opted_in"` + Index uint64 `db:"validator_index"` } var rocketPoolMap map[uint64]RocketPoolData @@ -408,20 +398,20 @@ func (d *DataAccessService) GetValidatorDashboardMobileValidators(ctx context.Co penalty_count, node_deposit_balance, status, - rn.smoothing_pool_opted_in + rn.smoothing_pool_opted_in, + validator_index FROM rocketpool_minipools LEFT JOIN rocketpool_nodes rn ON rocketpool_minipools.node_address = rn.address - WHERE pubkey = ANY($1) + WHERE validator_index = ANY($1) ` - err := d.alloyReader.SelectContext(ctx, &rocketPoolResults, validatorsQuery, pq.ByteaArray(pubKeys)) + err := d.alloyReader.SelectContext(ctx, &rocketPoolResults, validatorsQuery, indices) if err != nil { return errors.Wrap(err, "error retrieving rocketpool data") } rocketPoolMap = make(map[uint64]RocketPoolData, len(rocketPoolResults)) for _, row := range rocketPoolResults { - validatorIndex := validatorMapping.ValidatorIndices[string(t.PubKey(hexutil.Encode(row.PubKey)))] - rocketPoolMap[validatorIndex] = row + rocketPoolMap[row.Index] = row } return nil }) diff --git a/backend/pkg/api/data_access/vdb_rocket_pool.go b/backend/pkg/api/data_access/vdb_rocket_pool.go index 90a94d3af..9e6c91f7d 100644 --- a/backend/pkg/api/data_access/vdb_rocket_pool.go +++ b/backend/pkg/api/data_access/vdb_rocket_pool.go @@ -2,14 +2,251 @@ package dataaccess import ( "context" + "encoding/hex" + "fmt" + "slices" + "strings" + "github.com/doug-martin/goqu/v9" "github.com/gobitfly/beaconchain/pkg/api/enums" t "github.com/gobitfly/beaconchain/pkg/api/types" + "github.com/gobitfly/beaconchain/pkg/commons/utils" + "github.com/shopspring/decimal" + "golang.org/x/sync/errgroup" ) func (d *DataAccessService) GetValidatorDashboardRocketPool(ctx context.Context, dashboardId t.VDBId, cursor string, colSort t.Sort[enums.VDBRocketPoolColumn], search string, limit uint64) ([]t.VDBRocketPoolTableRow, *t.Paging, error) { - // TODO @DATA-ACCESS - return d.dummy.GetValidatorDashboardRocketPool(ctx, dashboardId, cursor, colSort, search, limit) + // Initialize the cursor + var currentCursor t.RocketPoolCursor + var err error + var paging t.Paging + + if cursor != "" { + currentCursor, err = utils.StringToCursor[t.RocketPoolCursor](cursor) + if err != nil { + return nil, &paging, fmt.Errorf("failed to parse passed cursor as ValidatorsCursor: %w", err) + } + } + + type RocketPoolData struct { + Node []byte `db:"naddress"` + StakedETH decimal.Decimal `db:"staked_eth"` + StakedRPL decimal.Decimal `db:"rpl_stake"` + MinipoolsTotal uint64 `db:"minipools_total"` + MinipoolsLeb16 uint64 `db:"minipools_leb16"` + MinipoolsLeb8 uint64 `db:"minipools_leb8"` + AvgCommission float64 `db:"avg_commission"` + RPLClaimed decimal.Decimal `db:"rpl_claimed"` + RPLUnclaimed decimal.Decimal `db:"rpl_unclaimed"` + EffectiveRPL decimal.Decimal `db:"effective_rpl"` + SmoothingPoolOptIn bool `db:"smoothing_pool_opted_in"` + SmoothingPoolClaimed decimal.Decimal `db:"claimed_smoothing_pool"` + SmoothingPoolUnclaimed decimal.Decimal `db:"unclaimed_smoothing_pool"` + Timezone string `db:"timezone_location"` + RefundBalance decimal.Decimal `db:"refund_balance"` + DepositCredit decimal.Decimal `db:"deposit_credit"` + RPLStakeMin decimal.Decimal `db:"min_rpl_stake"` + RPLStakeMax decimal.Decimal `db:"max_rpl_stake"` + } + + rocketPoolResults := []RocketPoolData{} + + ds := goqu.Dialect("postgres"). + From(goqu.T("rocketpool_minipools").As("mp")). + Select( + goqu.T("n").Col("address").As("naddress"), + goqu.L("SUM(mp.node_deposit_balance) AS staked_eth"), + goqu.T("n").Col("rpl_stake"), + goqu.L("COUNT(mp.address) as minipools_total"), + goqu.L("SUM(CASE WHEN mp.node_deposit_balance = 8e18 THEN 1 ELSE 0 END) AS minipools_leb8"), + goqu.L("SUM(CASE WHEN mp.node_deposit_balance = 16e18 THEN 1 ELSE 0 END) AS minipools_leb16"), + goqu.L("CASE WHEN COUNT(mp.address) > 0 THEN SUM(mp.node_fee) / COUNT(mp.address) ELSE 0 END AS avg_commission"), + goqu.T("n").Col("unclaimed_rpl_rewards").As("rpl_unclaimed"), + goqu.L("n.rpl_cumulative_rewards - n.unclaimed_rpl_rewards as rpl_claimed"), + goqu.T("n").Col("effective_rpl_stake").As("effective_rpl"), + goqu.T("n").Col("smoothing_pool_opted_in"), + goqu.T("n").Col("claimed_smoothing_pool"), + goqu.T("n").Col("unclaimed_smoothing_pool"), + goqu.T("n").Col("timezone_location"), + goqu.L("COALESCE(SUM(mp.node_refund_balance),0) AS refund_balance"), + goqu.T("n").Col("deposit_credit"), + goqu.L("min_rpl_stake"), + goqu.L("max_rpl_stake"), + ). + LeftJoin( + goqu.T("rocketpool_nodes").As("n"), + goqu.On(goqu.T("mp").Col("node_address").Eq(goqu.T("n").Col("address"))), + ) + + if len(dashboardId.Validators) > 0 { + ds = ds.Where(goqu.T("mp").Col("validator_index").In(dashboardId.Validators)) + } else { + ds = ds. + InnerJoin( + goqu.T("users_val_dashboards_validators").As("v"), + goqu.On(goqu.T("mp").Col("validator_index").Eq(goqu.T("v").Col("validator_index"))), + ). + Where(goqu.T("v").Col("dashboard_id").Eq(dashboardId.Id)) + } + + if search != "" { + bytes, err := hex.DecodeString(strings.TrimPrefix(search, "0x")) + if err == nil { + ds = ds.Where(goqu.L("n.address = ?", bytes)) + } + } + + ds = ds.GroupBy(goqu.L("n.address"), goqu.L("n.rpl_stake"), goqu.L("n.unclaimed_rpl_rewards"), goqu.L("n.rpl_cumulative_rewards"), goqu.L("n.effective_rpl_stake"), goqu.L("n.smoothing_pool_opted_in"), goqu.L("n.claimed_smoothing_pool"), goqu.L("n.unclaimed_smoothing_pool"), goqu.L("n.timezone_location"), goqu.L("n.deposit_credit"), goqu.L("min_rpl_stake"), goqu.L("max_rpl_stake")) + + // 3. Sorting and pagination + defaultColumns := []t.SortColumn{ + {Column: enums.VDBRocketPoolColumns.Node.ToExpr(), Desc: colSort.Desc, Offset: currentCursor.Address}, + } + var offset any + switch colSort.Column { + case enums.VDBRocketPoolNode: + offset = currentCursor.Address + case enums.VDBRocketPoolCollateral: + offset = currentCursor.StakedRpl + case enums.VDBRocketPoolEffectiveRpl: + offset = currentCursor.EffectiveRpl + case enums.VDBRocketPoolSmoothingPool: + offset = currentCursor.SmoothingpoolOptIn + case enums.VDBRocketPoolMinipools: + offset = currentCursor.MinipoolsTotal + } + + order, directions, err := applySortAndPagination(defaultColumns, t.SortColumn{Column: colSort.Column.ToExpr(), Desc: colSort.Desc, Offset: offset}, currentCursor.GenericCursor) + if err != nil { + return nil, nil, err + } + ds = ds.Order(order...) + if directions != nil { + if colSort.Column == enums.VDBRocketPoolMinipools { // minipools is an aggregate hence use having instead of where + ds = ds.Having(directions) + } else { + ds = ds.Where(directions) + } + } + + ds = ds.Limit(uint(limit + 1)) + + wg := errgroup.Group{} + + var rpNetworkStats *t.RPNetworkStats + + wg.Go(func() error { + query, args, err := ds.Prepared(true).ToSQL() + if err != nil { + return fmt.Errorf("error preparing query: %w", err) + } + + err = d.alloyReader.SelectContext(ctx, &rocketPoolResults, query, args...) + if err != nil { + return fmt.Errorf("error retrieving rocketpool data: %w", err) + } + return nil + }) + + wg.Go(func() error { + var err error + rpNetworkStats, err = d.getInternalRpNetworkStats(ctx) + if err != nil { + return fmt.Errorf("error retrieving rocketpool network stats: %w", err) + } + return nil + }) + + err = wg.Wait() + if err != nil { + return nil, &paging, err + } + + apr := func(node RocketPoolData) float64 { + if !rpNetworkStats.EffectiveRPLStaked.IsZero() && !node.EffectiveRPL.IsZero() && !rpNetworkStats.NodeOperatorRewards.IsZero() && rpNetworkStats.ClaimIntervalHours > 0 { + share := node.EffectiveRPL.Div(rpNetworkStats.EffectiveRPLStaked) + + periodsPerYear := decimal.NewFromFloat(365 / (rpNetworkStats.ClaimIntervalHours / 24)) + return rpNetworkStats.NodeOperatorRewards. + Mul(share). + Div(node.StakedRPL). + Mul(periodsPerYear). + Mul(decimal.NewFromInt(100)).InexactFloat64() + } + return 0 + } + + projectedRplClaim := func(node RocketPoolData) decimal.Decimal { + if !rpNetworkStats.EffectiveRPLStaked.IsZero() && !node.EffectiveRPL.IsZero() && !rpNetworkStats.NodeOperatorRewards.IsZero() { + share := node.EffectiveRPL.Div(rpNetworkStats.EffectiveRPLStaked) + return rpNetworkStats.NodeOperatorRewards.Mul(share).Floor() + } + return decimal.Zero + } + + collPercentage := func(node RocketPoolData) float64 { + if !node.StakedRPL.IsZero() && !node.RPLStakeMin.IsZero() { + rplPrice := rpNetworkStats.RPLPrice + currentETH := node.StakedRPL.Mul(rplPrice) + minETH := node.RPLStakeMin.Mul(rplPrice).Mul(decimal.NewFromInt(10)) + + return currentETH.Div(minETH).Mul(decimal.NewFromInt(100)).InexactFloat64() + } + return 0 + } + + var result []t.VDBRocketPoolTableRow + for _, row := range rocketPoolResults { + result = append(result, t.VDBRocketPoolTableRow{ + Address: row.Node, + Node: t.Address{Hash: t.Hash(fmt.Sprintf("0x%x", row.Node))}, + StakedEth: row.StakedETH, + StakedRpl: row.StakedRPL, + + MinipoolsTotal: row.MinipoolsTotal, + MinipoolsLeb16: row.MinipoolsLeb16, + MinipoolsLeb8: row.MinipoolsLeb8, + + Collateral: t.PercentageDetails[decimal.Decimal]{ + Percentage: collPercentage(row), + MinValue: row.RPLStakeMin, + MaxValue: row.RPLStakeMax, + }, + AvgCommission: row.AvgCommission, + RplClaimed: row.RPLClaimed, + RplUnclaimed: row.RPLUnclaimed, + EffectiveRpl: row.EffectiveRPL, + RplApr: apr(row), + RplAprUpdateTs: rpNetworkStats.Ts.Unix(), + RplEstimate: projectedRplClaim(row), + + SmoothingpoolOptIn: row.SmoothingPoolOptIn, + SmoothingpoolClaimed: row.SmoothingPoolClaimed, + SmoothingpoolUnclaimed: row.SmoothingPoolUnclaimed, + + Timezone: row.Timezone, + RefundBalance: row.RefundBalance, + DepositCredit: row.DepositCredit, + }) + } + + moreDataFlag := len(result) > int(limit) + if moreDataFlag { + result = result[:len(result)-1] + } + if currentCursor.IsReverse() { + slices.Reverse(result) + } + + if !moreDataFlag && !currentCursor.IsValid() { + // No paging required + return result, &t.Paging{}, nil + } + p, err := utils.GetPagingFromData(result, currentCursor, moreDataFlag) + if err != nil { + return nil, nil, err + } + return result, p, nil } func (d *DataAccessService) GetValidatorDashboardTotalRocketPool(ctx context.Context, dashboardId t.VDBId, search string) (*t.VDBRocketPoolTableRow, error) { diff --git a/backend/pkg/api/enums/validator_dashboard_enums.go b/backend/pkg/api/enums/validator_dashboard_enums.go index 6a4ab4945..280764658 100644 --- a/backend/pkg/api/enums/validator_dashboard_enums.go +++ b/backend/pkg/api/enums/validator_dashboard_enums.go @@ -396,12 +396,10 @@ type VDBRocketPoolColumn int var _ EnumFactory[VDBRocketPoolColumn] = VDBRocketPoolColumn(0) const ( - VDBRocketPoolNode VDBRocketPoolColumn = iota - VDBRocketPoolMinipools + VDBRocketPoolNode VDBRocketPoolColumn = iota + VDBRocketPoolMinipools // might be supported later VDBRocketPoolCollateral - VDBRocketPoolRpl VDBRocketPoolEffectiveRpl - VDBRocketPoolRplApr VDBRocketPoolSmoothingPool ) @@ -413,16 +411,12 @@ func (VDBRocketPoolColumn) NewFromString(s string) VDBRocketPoolColumn { switch s { case "node": return VDBRocketPoolNode - case "minipools": - return VDBRocketPoolMinipools case "collateral": return VDBRocketPoolCollateral - case "rpl": - return VDBRocketPoolRpl + case "minipools": + return VDBRocketPoolMinipools case "effective_rpl": return VDBRocketPoolEffectiveRpl - case "rpl_apr": - return VDBRocketPoolRplApr case "smoothing_pool": return VDBRocketPoolSmoothingPool default: @@ -430,21 +424,35 @@ func (VDBRocketPoolColumn) NewFromString(s string) VDBRocketPoolColumn { } } +func (c VDBRocketPoolColumn) ToExpr() OrderableSortable { + switch c { + case VDBRocketPoolNode: + return goqu.T("n").Col("address") + case VDBRocketPoolCollateral: + return goqu.C("rpl_stake") + case VDBRocketPoolEffectiveRpl: + return goqu.C("effective_rpl_stake") + case VDBRocketPoolSmoothingPool: + return goqu.C("smoothing_pool_opted_in") + case VDBRocketPoolMinipools: + return goqu.L("COUNT(mp.address)") + + default: + return nil + } +} + var VDBRocketPoolColumns = struct { Node VDBRocketPoolColumn Minipools VDBRocketPoolColumn Collateral VDBRocketPoolColumn - Rpl VDBRocketPoolColumn EffectiveRpl VDBRocketPoolColumn - RplApr VDBRocketPoolColumn SmoothingPool VDBRocketPoolColumn }{ VDBRocketPoolNode, VDBRocketPoolMinipools, VDBRocketPoolCollateral, - VDBRocketPoolRpl, VDBRocketPoolEffectiveRpl, - VDBRocketPoolRplApr, VDBRocketPoolSmoothingPool, } diff --git a/backend/pkg/api/types/data_access.go b/backend/pkg/api/types/data_access.go index 333c7b3e5..94cf9c21e 100644 --- a/backend/pkg/api/types/data_access.go +++ b/backend/pkg/api/types/data_access.go @@ -133,6 +133,16 @@ type NotificationMachinesCursor struct { Ts time.Time } +type RocketPoolCursor struct { + GenericCursor + + Address []byte + MinipoolsTotal uint64 + StakedRpl decimal.Decimal + EffectiveRpl decimal.Decimal + SmoothingpoolOptIn bool +} + type NotificationClientsCursor struct { GenericCursor diff --git a/backend/pkg/api/types/rocketpool.go b/backend/pkg/api/types/rocketpool.go index 1d173bce8..fa4b1829c 100644 --- a/backend/pkg/api/types/rocketpool.go +++ b/backend/pkg/api/types/rocketpool.go @@ -1,10 +1,15 @@ package types -import "github.com/shopspring/decimal" +import ( + "time" + + "github.com/shopspring/decimal" +) type RPNetworkStats struct { ClaimIntervalHours float64 `db:"claim_interval_hours"` NodeOperatorRewards decimal.Decimal `db:"node_operator_rewards"` EffectiveRPLStaked decimal.Decimal `db:"effective_rpl_staked"` RPLPrice decimal.Decimal `db:"rpl_price"` + Ts time.Time `db:"ts"` } diff --git a/backend/pkg/api/types/validator_dashboard.go b/backend/pkg/api/types/validator_dashboard.go index db3886072..c18e72bb7 100644 --- a/backend/pkg/api/types/validator_dashboard.go +++ b/backend/pkg/api/types/validator_dashboard.go @@ -285,40 +285,30 @@ type GetValidatorDashboardTotalWithdrawalsResponse ApiDataResponse[VDBTotalWithd // ------------------------------------------------------------ // Rocket Pool Tab type VDBRocketPoolTableRow struct { - Node Address `json:"node" extensions:"x-order=1"` - Staked struct { - Eth decimal.Decimal `json:"eth"` - Rpl decimal.Decimal `json:"rpl"` - } `json:"staked"` - Minipools struct { - Total uint64 `json:"total"` - Leb16 uint64 `json:"leb_16"` - Leb8 uint64 `json:"leb_8"` - } `json:"minipools"` - Collateral PercentageDetails[decimal.Decimal] `json:"collateral"` - AvgCommission float64 `json:"avg_commission"` - Rpl struct { - Claimed decimal.Decimal `json:"claimed"` - Unclaimed decimal.Decimal `json:"unclaimed"` - } `json:"rpl"` - EffectiveRpl decimal.Decimal `json:"effective_rpl"` - RplApr float64 `json:"rpl_apr"` - RplAprUpdateTs int64 `json:"rpl_apr_update_ts"` - RplEstimate decimal.Decimal `json:"rpl_estimate"` - SmoothingPool struct { - IsOptIn bool `json:"is_opt_in"` - Claimed decimal.Decimal `json:"claimed"` - Unclaimed decimal.Decimal `json:"unclaimed"` - } `json:"smoothing_pool"` + Address []byte `json:"-"` + Node Address `json:"node" extensions:"x-order=1"` + StakedEth decimal.Decimal `json:"staked_eth"` + StakedRpl decimal.Decimal `json:"staked_rpl"` + MinipoolsTotal uint64 `json:"minipools_count_total"` + MinipoolsLeb16 uint64 `json:"minipools_count_leb_16"` + MinipoolsLeb8 uint64 `json:"minipools_count_leb_8"` + Collateral PercentageDetails[decimal.Decimal] `json:"collateral"` + AvgCommission float64 `json:"avg_commission"` + RplClaimed decimal.Decimal `json:"rpl_claimed"` + RplUnclaimed decimal.Decimal `json:"rpl_unclaimed"` + EffectiveRpl decimal.Decimal `json:"effective_rpl"` + RplApr float64 `json:"rpl_apr"` + RplAprUpdateTs int64 `json:"rpl_apr_update_ts"` + RplEstimate decimal.Decimal `json:"rpl_estimate"` + SmoothingpoolOptIn bool `json:"smoothingpool_opt_in"` + SmoothingpoolClaimed decimal.Decimal `json:"smoothingpool_claimed"` + SmoothingpoolUnclaimed decimal.Decimal `json:"smoothingpool_unclaimed"` Timezone string `json:"timezone"` RefundBalance decimal.Decimal `json:"refund_balance"` DepositCredit decimal.Decimal `json:"deposit_credit"` - RplStake struct { - Min decimal.Decimal `json:"min"` - Max decimal.Decimal `json:"max"` - } `json:"rpl_stake"` } + type GetValidatorDashboardRocketPoolResponse ApiPagingResponse[VDBRocketPoolTableRow] type GetValidatorDashboardTotalRocketPoolResponse ApiDataResponse[VDBRocketPoolTableRow] diff --git a/frontend/types/api/validator_dashboard.ts b/frontend/types/api/validator_dashboard.ts index e85eac671..c520a7c6d 100644 --- a/frontend/types/api/validator_dashboard.ts +++ b/frontend/types/api/validator_dashboard.ts @@ -249,37 +249,25 @@ export type GetValidatorDashboardTotalWithdrawalsResponse = ApiDataResponse; avg_commission: number /* float64 */; - rpl: { - claimed: string /* decimal.Decimal */; - unclaimed: string /* decimal.Decimal */; - }; + rpl_claimed: string /* decimal.Decimal */; + rpl_unclaimed: string /* decimal.Decimal */; effective_rpl: string /* decimal.Decimal */; rpl_apr: number /* float64 */; rpl_apr_update_ts: number /* int64 */; rpl_estimate: string /* decimal.Decimal */; - smoothing_pool: { - is_opt_in: boolean; - claimed: string /* decimal.Decimal */; - unclaimed: string /* decimal.Decimal */; - }; + smoothingpool_opt_in: boolean; + smoothingpool_claimed: string /* decimal.Decimal */; + smoothingpool_unclaimed: string /* decimal.Decimal */; timezone: string; refund_balance: string /* decimal.Decimal */; deposit_credit: string /* decimal.Decimal */; - rpl_stake: { - min: string /* decimal.Decimal */; - max: string /* decimal.Decimal */; - }; } export type GetValidatorDashboardRocketPoolResponse = ApiPagingResponse; export type GetValidatorDashboardTotalRocketPoolResponse = ApiDataResponse;