diff --git a/api/openapi.json b/api/openapi.json index 8c800c00..d55d05e0 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -3710,6 +3710,10 @@ "example": true, "type": "boolean" }, + "score": { + "format": "int32", + "type": "integer" + }, "total_supply": { "example": "5887105890579978", "type": "string", @@ -3848,6 +3852,10 @@ "example": "Wrapped TON", "type": "string" }, + "score": { + "format": "int32", + "type": "integer" + }, "symbol": { "example": "WTON", "type": "string" diff --git a/api/openapi.yml b/api/openapi.yml index e343936e..4753c0b6 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -5417,6 +5417,9 @@ components: $ref: '#/components/schemas/JettonVerificationType' custom_payload_api_uri: # todo: maybe remove type: string + score: + type: integer + format: int32 JettonBalance: type: object required: @@ -6969,6 +6972,9 @@ components: type: integer format: int32 example: 2000 + score: + type: integer + format: int32 JettonHolders: type: object required: diff --git a/pkg/api/event_converters.go b/pkg/api/event_converters.go index 44e78291..a1cc847f 100644 --- a/pkg/api/event_converters.go +++ b/pkg/api/event_converters.go @@ -74,7 +74,8 @@ func (h *Handler) convertRisk(ctx context.Context, risk wallet.Risk, walletAddre } jettonWallet := jettonWallets[0] meta := h.GetJettonNormalizedMetadata(ctx, jettonWallet.JettonAddress) - preview := jettonPreview(jettonWallet.JettonAddress, meta) + score, _ := h.score.GetJettonScore(jettonWallet.JettonAddress) + preview := jettonPreview(jettonWallet.JettonAddress, meta, score) jettonQuantity := oas.JettonQuantity{ Quantity: quantity.String(), WalletAddress: convertAccountAddress(jettonWallet.Address, h.addressBook), @@ -176,7 +177,8 @@ func (h *Handler) convertActionNftTransfer(t *bath.NftTransferAction, acceptLang func (h *Handler) convertActionJettonTransfer(ctx context.Context, t *bath.JettonTransferAction, acceptLanguage string, viewer *tongo.AccountID) (oas.OptJettonTransferAction, oas.ActionSimplePreview) { meta := h.GetJettonNormalizedMetadata(ctx, t.Jetton) - preview := jettonPreview(t.Jetton, meta) + score, _ := h.score.GetJettonScore(t.Jetton) + preview := jettonPreview(t.Jetton, meta, score) var action oas.OptJettonTransferAction action.SetTo(oas.JettonTransferAction{ Amount: g.Pointer(big.Int(t.Amount)).String(), @@ -214,7 +216,8 @@ func (h *Handler) convertActionJettonTransfer(ctx context.Context, t *bath.Jetto func (h *Handler) convertActionJettonMint(ctx context.Context, m *bath.JettonMintAction, acceptLanguage string, viewer *tongo.AccountID) (oas.OptJettonMintAction, oas.ActionSimplePreview) { meta := h.GetJettonNormalizedMetadata(ctx, m.Jetton) - preview := jettonPreview(m.Jetton, meta) + score, _ := h.score.GetJettonScore(m.Jetton) + preview := jettonPreview(m.Jetton, meta, score) var action oas.OptJettonMintAction action.SetTo(oas.JettonMintAction{ Amount: g.Pointer(big.Int(m.Amount)).String(), @@ -436,7 +439,8 @@ func (h *Handler) convertAction(ctx context.Context, viewer *tongo.AccountID, a action.JettonMint, action.SimplePreview = h.convertActionJettonMint(ctx, a.JettonMint, acceptLanguage.Value, viewer) case bath.JettonBurn: meta := h.GetJettonNormalizedMetadata(ctx, a.JettonBurn.Jetton) - preview := jettonPreview(a.JettonBurn.Jetton, meta) + score, _ := h.score.GetJettonScore(a.JettonBurn.Jetton) + preview := jettonPreview(a.JettonBurn.Jetton, meta, score) action.JettonBurn.SetTo(oas.JettonBurnAction{ Amount: g.Pointer(big.Int(a.JettonBurn.Amount)).String(), Sender: convertAccountAddress(a.JettonBurn.Sender, h.addressBook), @@ -611,7 +615,8 @@ func (h *Handler) convertAction(ctx context.Context, viewer *tongo.AccountID, a } else { swapAction.AmountIn = a.JettonSwap.In.Amount.String() jettonInMeta := h.GetJettonNormalizedMetadata(ctx, a.JettonSwap.In.JettonMaster) - preview := jettonPreview(a.JettonSwap.In.JettonMaster, jettonInMeta) + score, _ := h.score.GetJettonScore(a.JettonSwap.In.JettonMaster) + preview := jettonPreview(a.JettonSwap.In.JettonMaster, jettonInMeta, score) swapAction.JettonMasterIn.SetTo(preview) simplePreviewData["JettonIn"] = preview.GetSymbol() simplePreviewData["AmountIn"] = ScaleJettons(a.JettonSwap.In.Amount, jettonInMeta.Decimals).String() @@ -623,7 +628,8 @@ func (h *Handler) convertAction(ctx context.Context, viewer *tongo.AccountID, a } else { swapAction.AmountOut = a.JettonSwap.Out.Amount.String() jettonOutMeta := h.GetJettonNormalizedMetadata(ctx, a.JettonSwap.Out.JettonMaster) - preview := jettonPreview(a.JettonSwap.Out.JettonMaster, jettonOutMeta) + score, _ := h.score.GetJettonScore(a.JettonSwap.Out.JettonMaster) + preview := jettonPreview(a.JettonSwap.Out.JettonMaster, jettonOutMeta, score) swapAction.JettonMasterOut.SetTo(preview) simplePreviewData["JettonOut"] = preview.GetSymbol() simplePreviewData["AmountOut"] = ScaleJettons(a.JettonSwap.Out.Amount, jettonOutMeta.Decimals).String() @@ -777,7 +783,8 @@ func (h *Handler) toEvent(ctx context.Context, trace *core.Trace, result *bath.A continue } meta := h.GetJettonNormalizedMetadata(ctx, jettonMaster) - previews[jettonMaster] = jettonPreview(jettonMaster, meta) + score, _ := h.score.GetJettonScore(jettonMaster) + previews[jettonMaster] = jettonPreview(jettonMaster, meta, score) } } for accountID, flow := range result.ValueFlow.Accounts { diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 382d3f79..ff313436 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -9,6 +9,7 @@ import ( "github.com/tonkeeper/opentonapi/pkg/chainstate" "github.com/tonkeeper/opentonapi/pkg/core" "github.com/tonkeeper/opentonapi/pkg/rates" + "github.com/tonkeeper/opentonapi/pkg/score" "github.com/tonkeeper/tongo" "github.com/tonkeeper/tongo/contract/dns" "github.com/tonkeeper/tongo/tep64" @@ -40,6 +41,7 @@ type Handler struct { limits Limits spamFilter SpamFilter ratesSource ratesSource + score scoreSource metaCache metadataCache tonConnect *tonconnect.Server @@ -80,6 +82,7 @@ type Options struct { tonConnectSecret string ctxToDetails ctxToDetails gasless Gasless + score scoreSource } type Option func(o *Options) @@ -149,6 +152,12 @@ func WithGasless(gasless Gasless) Option { } } +func WithScore(score scoreSource) Option { + return func(o *Options) { + o.score = score + } +} + func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) { options := &Options{} for _, o := range opts { @@ -194,6 +203,9 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) { if err != nil { return nil, fmt.Errorf("failed to init tonconnect") } + if options.score == nil { + options.score = score.NewScore() + } return &Handler{ logger: logger, storage: options.storage, @@ -205,6 +217,7 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) { spamFilter: options.spamFilter, ctxToDetails: options.ctxToDetails, gasless: options.gasless, + score: options.score, ratesSource: rates.InitCalculator(options.ratesSource), metaCache: metadataCache{ collectionsCache: cache.NewLRUCache[tongo.AccountID, tep64.Metadata](10000, "nft_metadata_cache"), diff --git a/pkg/api/interfaces.go b/pkg/api/interfaces.go index ed4a7ba4..16b26a3a 100644 --- a/pkg/api/interfaces.go +++ b/pkg/api/interfaces.go @@ -178,6 +178,10 @@ type ratesSource interface { GetMarketsTonPrice() ([]rates.Market, error) } +type scoreSource interface { + GetJettonScore(masterID ton.AccountID) (int32, error) +} + type SpamFilter interface { CheckActions(actions []oas.Action, viewer *ton.AccountID) bool JettonTrust(address tongo.AccountID, symbol, name, image string) core.TrustType diff --git a/pkg/api/jetton_converters.go b/pkg/api/jetton_converters.go index a2e9d7c0..863a52b8 100644 --- a/pkg/api/jetton_converters.go +++ b/pkg/api/jetton_converters.go @@ -15,7 +15,7 @@ import ( "github.com/tonkeeper/tongo/ton" ) -func jettonPreview(master ton.AccountID, meta NormalizedMetadata) oas.JettonPreview { +func jettonPreview(master ton.AccountID, meta NormalizedMetadata, score int32) oas.JettonPreview { preview := oas.JettonPreview{ Address: master.ToRaw(), Name: meta.Name, @@ -27,6 +27,9 @@ func jettonPreview(master ton.AccountID, meta NormalizedMetadata) oas.JettonPrev if meta.CustomPayloadApiUri != "" { preview.CustomPayloadAPIURI = oas.NewOptString(meta.CustomPayloadApiUri) } + if score != 0 { + preview.Score = oas.NewOptInt32(score) + } return preview } @@ -163,15 +166,16 @@ func (h *Handler) convertJettonBalance(ctx context.Context, wallet core.JettonWa } normalizedMetadata = NormalizeMetadata(meta, nil, trust) } - jettonBalance.Jetton = jettonPreview(wallet.JettonAddress, normalizedMetadata) + score, _ := h.score.GetJettonScore(wallet.JettonAddress) + jettonBalance.Jetton = jettonPreview(wallet.JettonAddress, normalizedMetadata, score) return jettonBalance, nil } -func (h *Handler) convertJettonInfo(ctx context.Context, master core.JettonMaster, holders map[tongo.AccountID]int32) oas.JettonInfo { +func (h *Handler) convertJettonInfo(ctx context.Context, master core.JettonMaster, holders map[tongo.AccountID]int32, score int32) oas.JettonInfo { meta := h.GetJettonNormalizedMetadata(ctx, master.Address) metadata := jettonMetadata(master.Address, meta) - return oas.JettonInfo{ + info := oas.JettonInfo{ Mintable: master.Mintable, TotalSupply: master.TotalSupply.String(), Metadata: metadata, @@ -179,4 +183,8 @@ func (h *Handler) convertJettonInfo(ctx context.Context, master core.JettonMaste HoldersCount: holders[master.Address], Admin: convertOptAccountAddress(master.Admin, h.addressBook), } + if score != 0 { + info.Score = oas.NewOptInt32(score) + } + return info } diff --git a/pkg/api/jetton_handlers.go b/pkg/api/jetton_handlers.go index 3490bd45..2db807ae 100644 --- a/pkg/api/jetton_handlers.go +++ b/pkg/api/jetton_handlers.go @@ -162,7 +162,8 @@ func (h *Handler) GetJettons(ctx context.Context, params oas.GetJettonsParams) ( } results := make([]oas.JettonInfo, len(jettons)) for idx, master := range jettons { - results[idx] = h.convertJettonInfo(ctx, master, holders) + score, _ := h.score.GetJettonScore(master.Address) + results[idx] = h.convertJettonInfo(ctx, master, holders, score) } return &oas.Jettons{Jettons: results}, nil } @@ -293,7 +294,8 @@ func (h *Handler) GetJettonInfosByAddresses(ctx context.Context, request oas.Opt } results := make([]oas.JettonInfo, len(jettons)) for idx, master := range jettons { - results[idx] = h.convertJettonInfo(ctx, master, jettonsHolders) + score, _ := h.score.GetJettonScore(master.Address) + results[idx] = h.convertJettonInfo(ctx, master, jettonsHolders, score) } return &oas.Jettons{Jettons: results}, nil diff --git a/pkg/oas/oas_json_gen.go b/pkg/oas/oas_json_gen.go index 6c309f0b..4e7cdee3 100644 --- a/pkg/oas/oas_json_gen.go +++ b/pkg/oas/oas_json_gen.go @@ -23509,15 +23509,22 @@ func (s *JettonInfo) encodeFields(e *jx.Encoder) { e.FieldStart("holders_count") e.Int32(s.HoldersCount) } + { + if s.Score.Set { + e.FieldStart("score") + s.Score.Encode(e) + } + } } -var jsonFieldsNameOfJettonInfo = [6]string{ +var jsonFieldsNameOfJettonInfo = [7]string{ 0: "mintable", 1: "total_supply", 2: "admin", 3: "metadata", 4: "verification", 5: "holders_count", + 6: "score", } // Decode decodes JettonInfo from json. @@ -23595,6 +23602,16 @@ func (s *JettonInfo) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"holders_count\"") } + case "score": + if err := func() error { + s.Score.Reset() + if err := s.Score.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"score\"") + } default: return d.Skip() } @@ -24122,9 +24139,15 @@ func (s *JettonPreview) encodeFields(e *jx.Encoder) { s.CustomPayloadAPIURI.Encode(e) } } + { + if s.Score.Set { + e.FieldStart("score") + s.Score.Encode(e) + } + } } -var jsonFieldsNameOfJettonPreview = [7]string{ +var jsonFieldsNameOfJettonPreview = [8]string{ 0: "address", 1: "name", 2: "symbol", @@ -24132,6 +24155,7 @@ var jsonFieldsNameOfJettonPreview = [7]string{ 4: "image", 5: "verification", 6: "custom_payload_api_uri", + 7: "score", } // Decode decodes JettonPreview from json. @@ -24223,6 +24247,16 @@ func (s *JettonPreview) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"custom_payload_api_uri\"") } + case "score": + if err := func() error { + s.Score.Reset() + if err := s.Score.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"score\"") + } default: return d.Skip() } diff --git a/pkg/oas/oas_schemas_gen.go b/pkg/oas/oas_schemas_gen.go index 5f4880dc..4e862ca1 100644 --- a/pkg/oas/oas_schemas_gen.go +++ b/pkg/oas/oas_schemas_gen.go @@ -7717,6 +7717,7 @@ type JettonInfo struct { Metadata JettonMetadata `json:"metadata"` Verification JettonVerificationType `json:"verification"` HoldersCount int32 `json:"holders_count"` + Score OptInt32 `json:"score"` } // GetMintable returns the value of Mintable. @@ -7749,6 +7750,11 @@ func (s *JettonInfo) GetHoldersCount() int32 { return s.HoldersCount } +// GetScore returns the value of Score. +func (s *JettonInfo) GetScore() OptInt32 { + return s.Score +} + // SetMintable sets the value of Mintable. func (s *JettonInfo) SetMintable(val bool) { s.Mintable = val @@ -7779,6 +7785,11 @@ func (s *JettonInfo) SetHoldersCount(val int32) { s.HoldersCount = val } +// SetScore sets the value of Score. +func (s *JettonInfo) SetScore(val OptInt32) { + s.Score = val +} + // Ref: #/components/schemas/JettonMetadata type JettonMetadata struct { Address string `json:"address"` @@ -7951,6 +7962,7 @@ type JettonPreview struct { Image string `json:"image"` Verification JettonVerificationType `json:"verification"` CustomPayloadAPIURI OptString `json:"custom_payload_api_uri"` + Score OptInt32 `json:"score"` } // GetAddress returns the value of Address. @@ -7988,6 +8000,11 @@ func (s *JettonPreview) GetCustomPayloadAPIURI() OptString { return s.CustomPayloadAPIURI } +// GetScore returns the value of Score. +func (s *JettonPreview) GetScore() OptInt32 { + return s.Score +} + // SetAddress sets the value of Address. func (s *JettonPreview) SetAddress(val string) { s.Address = val @@ -8023,6 +8040,11 @@ func (s *JettonPreview) SetCustomPayloadAPIURI(val OptString) { s.CustomPayloadAPIURI = val } +// SetScore sets the value of Score. +func (s *JettonPreview) SetScore(val OptInt32) { + s.Score = val +} + // Ref: #/components/schemas/JettonQuantity type JettonQuantity struct { Quantity string `json:"quantity"` diff --git a/pkg/score/score.go b/pkg/score/score.go new file mode 100644 index 00000000..0fd8d7bb --- /dev/null +++ b/pkg/score/score.go @@ -0,0 +1,15 @@ +package score + +import ( + "github.com/tonkeeper/tongo/ton" +) + +type Score struct{} + +func NewScore() *Score { + return &Score{} +} + +func (s *Score) GetJettonScore(masterID ton.AccountID) (int32, error) { + return 0, nil +}