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
11 changes: 11 additions & 0 deletions api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -10638,6 +10638,7 @@
}
},
{
"description": "Deprecated: served from a cache, so results may lag behind real-time. Use `last_account_id` for live, real-time pagination instead.",
"in": "query",
"name": "offset",
"schema": {
Expand All @@ -10647,6 +10648,16 @@
"minimum": 0,
"type": "integer"
}
},
{
"description": "Cursor for pagination, always resolved live. Pass the `metadata.address` of the last jetton master from the previous page to get the next page. Preferred over `offset`.",
"in": "query",
"name": "last_account_id",
"schema": {
"example": "0:97264395BD65A255A429B11326C84128B7D70FFED7949ABAE3036D506BA38621",
"format": "address",
"type": "string"
}
}
],
"responses": {
Expand Down
12 changes: 12 additions & 0 deletions api/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1587,12 +1587,24 @@ paths:
minimum: 1
- name: offset
in: query
description: >-
Deprecated: served from a cache, so results may lag behind real-time. Use `last_account_id` for
live, real-time pagination instead.
schema:
type: integer
format: int32
default: 0
example: 10
minimum: 0
- name: last_account_id
in: query
description: >-
Cursor for pagination, always resolved live. Pass the `metadata.address` of the last jetton master
from the previous page to get the next page. Preferred over `offset`.
schema:
type: string
format: address
example: 0:97264395BD65A255A429B11326C84128B7D70FFED7949ABAE3036D506BA38621
responses:
'200':
description: a list of jettons
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/sourcegraph/conc v0.3.0
github.com/stretchr/testify v1.11.1
github.com/tonkeeper/scam_backoffice_rules v0.0.12
github.com/tonkeeper/tongo v1.22.47
github.com/tonkeeper/tongo v1.22.48
go.opentelemetry.io/otel v1.43.0
go.opentelemetry.io/otel/metric v1.43.0
go.opentelemetry.io/otel/trace v1.43.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ github.com/tonkeeper/scam_backoffice_rules v0.0.12 h1:vCxE77SEddtPTWCaTH1E3tZ9Ss
github.com/tonkeeper/scam_backoffice_rules v0.0.12/go.mod h1:SqZXYO9vbID8ku+xnnaKXeNGmehxigODGrk5V1KqbRA=
github.com/tonkeeper/tongo v1.22.47 h1:srweJ0PlupSw7AW+/zNCjHzA8uZuQA4kALayplqM2R0=
github.com/tonkeeper/tongo v1.22.47/go.mod h1:nHmdEXPfT0/EvkEaBzPiY599/0OYjQSW4dWR7aX+OII=
github.com/tonkeeper/tongo v1.22.48 h1:yqjBXKOIfax7hb3e2NCEOyoPq0pU+eC+aLNpiqnKhJA=
github.com/tonkeeper/tongo v1.22.48/go.mod h1:nHmdEXPfT0/EvkEaBzPiY599/0OYjQSW4dWR7aX+OII=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
Expand Down
6 changes: 4 additions & 2 deletions pkg/api/event_converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ func (h *Handler) convertRisk(ctx context.Context, risk wallet.Risk, walletAddre
var curPrice float64
var todayRates map[string]float64
var err error
currencyKnown := currency != nil
if currency != nil {
todayRates, _, _, _, err = h.getRates()
if err != nil {
Expand All @@ -111,7 +112,8 @@ func (h *Handler) convertRisk(ctx context.Context, risk wallet.Risk, walletAddre
var prs bool
curPrice, prs = todayRates[strings.ToUpper(*currency)]
if !prs {
return oas.Risk{}, fmt.Errorf("can't calculate risk: unknown currency")
h.logger.Warn("can't calculate risk: unknown currency", zap.String("currency", *currency))
currencyKnown = false
}
}
if risk.TransferAllRemainingBalance {
Expand Down Expand Up @@ -155,7 +157,7 @@ func (h *Handler) convertRisk(ctx context.Context, risk wallet.Risk, walletAddre
}
oasRisk.Jettons = append(oasRisk.Jettons, jettonQuantity)
}
if currency != nil {
if currencyKnown {
oasRisk.TotalEquivalent = oas.NewOptFloat32(float32(total / curPrice))
}
if len(risk.Nfts) > 0 {
Expand Down
15 changes: 15 additions & 0 deletions pkg/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ type Handler struct {
mu sync.Mutex
dns *dns.DNS // todo: update when blockchain config changes
configPool *sync.Pool

// jettonMasters serves legacy offset-based /v2/jettons requests. May be nil, in which case
// offset-based requests fail rather than being silently served as page one.
jettonMasters jettonMastersOffsetSource
}

func (h *Handler) NewError(ctx context.Context, err error) *oas.ErrorStatusCode {
Expand Down Expand Up @@ -108,6 +112,8 @@ type Options struct {
parallelTraceProcessing bool
archiveLiteServers []config.LiteServer
publicAPIURL string

jettonMastersOffsetSource jettonMastersOffsetSource
}

type Option func(o *Options)
Expand Down Expand Up @@ -216,6 +222,14 @@ func WithPublicAPIURL(publicAPIURL string) Option {
}
}

// WithJettonMastersOffsetSource registers the source used to serve legacy offset-based
// /v2/jettons requests. When not set, offset-based requests fail with an error.
func WithJettonMastersOffsetSource(source jettonMastersOffsetSource) Option {
return func(o *Options) {
o.jettonMastersOffsetSource = source
}
}

func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
options := &Options{}
for _, o := range opts {
Expand Down Expand Up @@ -319,6 +333,7 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
configPool: configPool,
rewards: rwd,
stats: rewards.NewStats(liteapi.WithLiteServers(options.archiveLiteServers)),
jettonMasters: options.jettonMastersOffsetSource,
}, nil
}

Expand Down
7 changes: 6 additions & 1 deletion pkg/api/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ type storage interface {
GetWalletAddressesByPubkeys(ctx context.Context, pubKeys []ed25519.PublicKey) (map[string]map[ton.AccountID]abi.ContractInterface, error)
GetSubscriptionsV2(ctx context.Context, address tongo.AccountID) ([]core.SubscriptionV2, error)
GetSubscriptionsV1(ctx context.Context, address tongo.AccountID) ([]core.SubscriptionV1, error)
GetJettonMasters(ctx context.Context, limit, offset int) ([]core.JettonMaster, error)
GetJettonMasters(ctx context.Context, limit int, lastAccountID *tongo.AccountID) ([]core.JettonMaster, error)
GetJettonMastersByAddresses(ctx context.Context, addresses []ton.AccountID) ([]core.JettonMaster, error)

GetLastConfig(ctx context.Context) (ton.BlockchainConfig, error)
Expand Down Expand Up @@ -153,6 +153,11 @@ type liteStorageRaw interface {
GetOutMsgQueueSizes(ctx context.Context) (liteclient.LiteServerOutMsgQueueSizesC, error)
}

// jettonMastersOffsetSource serves legacy offset-based /v2/jettons pagination without ever running an OFFSET query against storage
type jettonMastersOffsetSource interface {
Slice(limit, offset int) []core.JettonMaster
}

// chainState provides current blockchain state which change very rarely or slow like staking APY income
type chainState interface {
GetAPY() float64
Expand Down
24 changes: 20 additions & 4 deletions pkg/api/jetton_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,11 +198,27 @@ func (h *Handler) GetJettons(ctx context.Context, params oas.GetJettonsParams) (
limit = 1000
}
}
offset := 0
if params.Offset.IsSet() {
offset = max(int(params.Offset.Value), 0)
var jettons []core.JettonMaster
var err error
switch {
case params.LastAccountID.IsSet():
var account ton.Address
account, err = tongo.ParseAddress(params.LastAccountID.Value)
if err != nil {
return nil, toError(http.StatusBadRequest, fmt.Errorf("last_account_id: %w", err))
}
jettons, err = h.storage.GetJettonMasters(ctx, limit, &account.ID)
case params.Offset.IsSet() && params.Offset.Value > 0:
if h.jettonMasters != nil {
offset := max(int(params.Offset.Value), 0)
jettons = h.jettonMasters.Slice(limit, offset)
} else {
h.logger.Error("received an offset-based /v2/jettons request, but no jettonMastersOffsetSource is configured")
return nil, toError(http.StatusInternalServerError, fmt.Errorf("offset-based pagination is currently not available"))
}
default:
jettons, err = h.storage.GetJettonMasters(ctx, limit, nil)
}
jettons, err := h.storage.GetJettonMasters(ctx, limit, offset)
if err != nil {
return nil, toError(http.StatusInternalServerError, err)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/litestorage/jetton.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func (s *LiteStorage) JettonMastersForWallets(ctx context.Context, wallets []ton
return masters, nil
}

func (s *LiteStorage) GetJettonMasters(ctx context.Context, limit, offset int) ([]core.JettonMaster, error) {
func (s *LiteStorage) GetJettonMasters(ctx context.Context, limit int, lastAccountID *tongo.AccountID) ([]core.JettonMaster, error) {
// TODO: implement
return []core.JettonMaster{}, nil
}
Expand Down
17 changes: 17 additions & 0 deletions pkg/oas/oas_client_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions pkg/oas/oas_handlers_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 56 additions & 1 deletion pkg/oas/oas_parameters_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading