Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5836,6 +5836,37 @@
],
"type": "object"
},
"RewardsStats": {
"properties": {
"apy": {
"description": "Time series of APY values as [timestamp_ms, apy] pairs",
"items": {
"items": {
"format": "double",
"type": "number"
},
"type": "array"
},
"type": "array"
},
"total_stake": {
"description": "Time series of total stake in TON as [timestamp_ms, stake] pairs",
"items": {
"items": {
"format": "double",
"type": "number"
},
"type": "array"
},
"type": "array"
}
},
"required": [
"apy",
"total_stake"
],
"type": "object"
},
"Risk": {
"description": "Conservative upper bound on assets this wallet may lose if the emulated message is sent and the counterparty behaves maliciously. Values may exceed current balances (e.g. already-authorized future receipts). For UI display only.\n",
"properties": {
Expand Down Expand Up @@ -11852,6 +11883,31 @@
]
}
},
"/v2/rewards/stats": {
"get": {
"description": "Returns time series of APY and total stake from past validation rounds.",
"operationId": "getRewardsStats",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RewardsStats"
}
}
},
"description": "APY and stake statistics"
},
"default": {
"$ref": "#/components/responses/Error"
}
},
"summary": "Get historical APY and stake statistics",
"tags": [
"Rewards"
]
}
},
"/v2/rewards/validation-rounds": {
"get": {
"description": "Returns past and current validation rounds with boundaries, stakes, and bonuses. Always uses the latest masterchain block.\n",
Expand Down
36 changes: 36 additions & 0 deletions api/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3131,6 +3131,22 @@ paths:
$ref: "#/components/schemas/RoundRewardsResponse"
'default':
$ref: '#/components/responses/Error'
/v2/rewards/stats:
get:
operationId: getRewardsStats
tags:
- Rewards
summary: Get historical APY and stake statistics
description: Returns time series of APY and total stake from past validation rounds.
responses:
'200':
description: APY and stake statistics
content:
application/json:
schema:
$ref: '#/components/schemas/RewardsStats'
'default':
$ref: '#/components/responses/Error'
components:
parameters:
masterchainSeqno:
Expand Down Expand Up @@ -8847,6 +8863,26 @@ components:
$ref: "#/components/schemas/ValidatorRewardEntry"
error:
type: string
RewardsStats:
type: object
required: [apy, total_stake]
properties:
apy:
type: array
description: Time series of APY values as [timestamp_ms, apy] pairs
items:
type: array
items:
type: number
format: double
total_stake:
type: array
description: Time series of total stake in TON as [timestamp_ms, stake] pairs
items:
type: array
items:
type: number
format: double
responses:
Error:
description: Some error during request processing
Expand Down
10 changes: 5 additions & 5 deletions cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ func main() {
if err != nil {
log.Fatal("failed to create liteapi client", zap.Error(err))
}
var rewardsLiteServers []ton.LiteServer
var archiveLiteServers []ton.LiteServer
if len(cfg.App.ArchiveLiteServers) != 0 {
rewardsLiteServers = cfg.App.ArchiveLiteServers
archiveLiteServers = cfg.App.ArchiveLiteServers
} else if len(cfg.App.LiteServers) != 0 {
rewardsLiteServers = cfg.App.LiteServers
archiveLiteServers = cfg.App.LiteServers
} else {
var opt liteapi.Options
liteapi.Mainnet()(&opt)
rewardsLiteServers = opt.LiteServers
archiveLiteServers = opt.LiteServers
}

storage, err := litestorage.NewLiteStorage(
Expand Down Expand Up @@ -81,7 +81,7 @@ func main() {
api.WithMessageSender(msgSender),
api.WithSpamFilter(spamFilter),
api.WithTonConnectSecret(cfg.TonConnect.Secret),
api.WithRewards(rewardsLiteServers),
api.WithArchiveLiteServers(archiveLiteServers),
)
if err != nil {
log.Fatal("failed to create api handler", zap.Error(err))
Expand Down
23 changes: 17 additions & 6 deletions pkg/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/tonkeeper/tongo"
"github.com/tonkeeper/tongo/config"
"github.com/tonkeeper/tongo/contract/dns"
"github.com/tonkeeper/tongo/liteapi"
"github.com/tonkeeper/tongo/tep64"
"github.com/tonkeeper/tongo/ton"
"github.com/tonkeeper/tongo/tonconnect"
Expand Down Expand Up @@ -52,6 +53,7 @@ type Handler struct {
tonConnect *tonconnect.Server
verifierSource verifierSource
rewards *rewards.Service
rewardsStats *rewards.Stats

// parallelTraceProcessing enables parallel trace-to-action conversion.
parallelTraceProcessing bool
Expand Down Expand Up @@ -96,7 +98,7 @@ type Options struct {
verifier verifierSource
score scoreSource
parallelTraceProcessing bool
rewardsLiteServers []config.LiteServer
archiveLiteServers []config.LiteServer
}

type Option func(o *Options)
Expand Down Expand Up @@ -184,9 +186,9 @@ func WithParallelTraceProcessing(enabled bool) Option {
}
}

func WithRewards(s []config.LiteServer) Option {
func WithArchiveLiteServers(s []config.LiteServer) Option {
return func(o *Options) {
o.rewardsLiteServers = s
o.archiveLiteServers = s
}
}

Expand Down Expand Up @@ -246,14 +248,22 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
slog.Warn("unable to detect tongo version", "err", err)
}
var rwd *rewards.Service
if len(options.rewardsLiteServers) != 0 {
cli, err := rewards.NewClient(options.rewardsLiteServers)
var stats *rewards.Stats
if len(options.archiveLiteServers) != 0 {
cli, err := rewards.NewClient(options.archiveLiteServers)
if err == nil {
rwd = rewards.New(cli, options.rewardsLiteServers)
rwd = rewards.New(cli, options.archiveLiteServers)
log.Println("rewards service initialized")
} else {
log.Println("rewards service unavailable:", err)
}
statsCli, err := liteapi.NewClient(liteapi.WithLiteServers(options.archiveLiteServers))
if err == nil {
stats = rewards.NewStats(statsCli)
log.Println("rewards stats service initialized")
} else {
log.Println("rewards stats service unavailable:", err)
}
}
return &Handler{
logger: logger,
Expand Down Expand Up @@ -287,6 +297,7 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
tonConnect: tonConnect,
configPool: configPool,
rewards: rwd,
rewardsStats: stats,
}, nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/rewards_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,11 @@ func (h *Handler) GetRoundRewards(ctx context.Context, params oas.GetRoundReward
res.ResponseTimeMs = time.Since(timeStart).Milliseconds()
return res, nil
}

func (h *Handler) GetRewardsStats(ctx context.Context) (*oas.RewardsStats, error) {
if h.rewardsStats == nil {
return nil, toError(http.StatusServiceUnavailable, errServiceUnavailable)
}
res := h.rewardsStats.GetStats()
return &res, nil
}
Loading
Loading