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
6 changes: 1 addition & 5 deletions api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -5225,7 +5225,7 @@
},
"sponsored": {
"default": false,
"description": "true — the Battery relay pays gas for this transaction; submit it via /v2/gasless/send. false — self-paid; sign and broadcast via /v2/blockchain/message as usual. The final TON sweep is always self-paid, whatever gas_payer says: the relay does not sponsor a TON-only batch, and gasless has no jetton balance left to bill a commission against.\n",
"description": "true — the Battery relay pays gas for this transaction; submit it via /v2/gasless/send. false — self-paid; sign and broadcast via /v2/blockchain/message as usual (e.g. the final TON sweep).\n",
"type": "boolean"
},
"state_init": {
Expand Down Expand Up @@ -5787,10 +5787,6 @@
"trust": {
"$ref": "#/components/schemas/TrustType"
},
"trust_v2": {
"$ref": "#/components/schemas/TrustType",
"description": "Corrected trust classification: an item that is neither whitelisted, graylisted, nor blacklisted is TrustNone here (trust still reports TrustBlacklist for that case, for backward compatibility with older clients)."
},
"verified": {
"description": "Collection master contract confirmed that this item is part of collection",
"example": true,
Expand Down
6 changes: 0 additions & 6 deletions api/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6628,12 +6628,6 @@ components:
example: false
trust:
$ref: '#/components/schemas/TrustType'
trust_v2:
$ref: '#/components/schemas/TrustType'
description: >-
Corrected trust classification: an item that is neither whitelisted, graylisted, nor
blacklisted is TrustNone here (trust still reports TrustBlacklist for that case, for
backward compatibility with older clients).
code_hash:
type: string
description: Hash of the NFT item account code cell (hex)
Expand Down
10 changes: 10 additions & 0 deletions pkg/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type Handler struct {

// parallelTraceProcessing enables parallel trace-to-action conversion.
parallelTraceProcessing bool
// nftTrustNoneEnabled reports an unreviewed NFT item as TrustNone instead of TrustBlacklist.
nftTrustNoneEnabled bool
// mempoolEmulate contains results of emulation of messages that are in the mempool.
mempoolEmulate mempoolEmulate
// ctxToDetails converts a request context to a details instance.
Expand Down Expand Up @@ -106,6 +108,7 @@ type Options struct {
defiAssets defiAssetsSource
score scoreSource
parallelTraceProcessing bool
nftTrustNoneEnabled bool
archiveLiteServers []config.LiteServer
archiveClient rewards.LiteClient
publicAPIURL string
Expand Down Expand Up @@ -205,6 +208,12 @@ func WithParallelTraceProcessing(enabled bool) Option {
}
}

func WithNftTrustNoneEnabled(enabled bool) Option {
return func(o *Options) {
o.nftTrustNoneEnabled = enabled
}
}

func WithArchiveLiteServers(s []config.LiteServer) Option {
return func(o *Options) {
o.archiveLiteServers = s
Expand Down Expand Up @@ -336,6 +345,7 @@ func NewHandler(logger *zap.Logger, opts ...Option) (*Handler, error) {
tongo.MustParseAddress("0:0000000000000000000000000000000000000000000000000000000000000000").ID: {},
},
parallelTraceProcessing: options.parallelTraceProcessing,
nftTrustNoneEnabled: options.nftTrustNoneEnabled,
tongoVersion: tongoVersion,
blacklistedBocCache: cache.NewLRUCache[[32]byte, struct{}](100000, "blacklisted_boc_cache"),
getMethodsCache: cache.NewLRUCache[string, *oas.MethodExecutionResult](100000, "get_methods_cache"),
Expand Down
11 changes: 4 additions & 7 deletions pkg/api/nft_converters.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,19 @@ func (h *Handler) convertNFT(ctx context.Context, item core.NftItem, book addres
switch {
case len(nftItem.ApprovedBy) > 0 && nftItem.Verified:
nftItem.Trust = oas.TrustType(core.TrustWhitelist)
nftItem.TrustV2.SetTo(oas.TrustType(core.TrustWhitelist))
case trustType == core.TrustWhitelist || trustType == core.TrustGraylist:
// The item has been reviewed and cleared (support graylisted it, for instance). That
// verdict wins over whatever the spam filter's heuristics would otherwise return.
nftItem.Trust = oas.TrustType(trustType)
nftItem.TrustV2.SetTo(oas.TrustType(trustType))
default:
nftTrust := h.spamFilter.NftTrust(item.Address, item.CollectionAddress, item.OwnerAddress, collectionTrust, name, description, image)
if nftTrust == core.TrustNone && trustType != "" {
nftTrust = trustType
}
nftItem.TrustV2.SetTo(oas.TrustType(nftTrust))
// trust keeps the historical contract for clients that can't update instantly: an item
// nothing vouches for is blacklisted so they keep blurring it. trust_v2 carries the
// corrected TrustNone value above.
if nftTrust == core.TrustNone {
if nftTrust == core.TrustNone && !h.nftTrustNoneEnabled {
// Deployments that can't update their client instantly (e.g. mobile) keep the old
// contract: an item nothing vouches for is blacklisted so they keep blurring it.
// Flip NFT_TRUST_NONE_ENABLED once the client handles TrustNone correctly.
nftTrust = core.TrustBlacklist
}
nftItem.Trust = oas.TrustType(nftTrust)
Expand Down
85 changes: 44 additions & 41 deletions pkg/api/nft_converters_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ func newTestMetaCache(collections map[ton.AccountID]collectionMeta) metadataCach
}

// TestConvertNFTTrust locks in how the NFT trust sources are combined: an item nothing vouches
// for is blacklisted in the legacy trust field (so older clients keep blurring it) but TrustNone
// in trust_v2 (the corrected value, for clients that can read it); an item inherits the trust of
// its collection; and a review — support graylisting the item, or the address book approving it —
// keeps it visible in both fields.
// for resolves to TrustBlacklist by default (nftTrustNoneEnabled off, e.g. mobile) or TrustNone
// once nftTrustNoneEnabled is on (e.g. web); an item inherits the trust of its collection; and a
// review — support graylisting the item, or the address book approving it — keeps it visible
// regardless of the flag.
func TestConvertNFTTrust(t *testing.T) {
nftID := ton.MustParseAccountID("EQCNmNR28mDfkwn4bwAlwJ1uhEFnjSQTZ3REz9d7IGZXU9EZ")
collectionID := ton.MustParseAccountID("EQDaaxtmY6Dk0YzIV0zNnbUpbjZ92TJHBvO72esc0srwv8K2")
Expand All @@ -39,75 +39,78 @@ func TestConvertNFTTrust(t *testing.T) {
})

tests := []struct {
name string
collection *ton.AccountID
collectionTrust core.TrustType
trustType core.TrustType
expectedTrust oas.TrustType
expectedTrustV2 oas.TrustType
name string
collection *ton.AccountID
collectionTrust core.TrustType
trustType core.TrustType
nftTrustNoneEnabled bool
expectedTrust oas.TrustType
}{
{
name: "NFT without a collection is unreviewed",
collection: nil,
expectedTrust: oas.TrustType(core.TrustBlacklist),
expectedTrustV2: oas.TrustType(core.TrustNone),
name: "NFT without a collection is unreviewed",
collection: nil,
expectedTrust: oas.TrustType(core.TrustBlacklist),
},
{
name: "NFT in an unreviewed collection is unreviewed",
collection: &collectionID,
expectedTrust: oas.TrustType(core.TrustBlacklist),
expectedTrustV2: oas.TrustType(core.TrustNone),
name: "NFT in an unreviewed collection is unreviewed",
collection: &collectionID,
expectedTrust: oas.TrustType(core.TrustBlacklist),
},
{
name: "NFT without a collection is TrustNone once nftTrustNoneEnabled is on",
collection: nil,
nftTrustNoneEnabled: true,
expectedTrust: oas.TrustType(core.TrustNone),
},
{
name: "NFT in an unreviewed collection is TrustNone once nftTrustNoneEnabled is on",
collection: &collectionID,
nftTrustNoneEnabled: true,
expectedTrust: oas.TrustType(core.TrustNone),
},
{
name: "NFT inherits a blacklisted collection",
collection: &collectionID,
collectionTrust: core.TrustBlacklist,
expectedTrust: oas.TrustType(core.TrustBlacklist),
expectedTrustV2: oas.TrustType(core.TrustBlacklist),
},
{
name: "NFT inherits a graylisted collection",
collection: &collectionID,
collectionTrust: core.TrustGraylist,
expectedTrust: oas.TrustType(core.TrustGraylist),
expectedTrustV2: oas.TrustType(core.TrustGraylist),
},
{
name: "a blacklisted item stays blacklisted",
collection: &collectionID,
trustType: core.TrustBlacklist,
expectedTrust: oas.TrustType(core.TrustBlacklist),
expectedTrustV2: oas.TrustType(core.TrustBlacklist),
name: "a blacklisted item stays blacklisted",
collection: &collectionID,
trustType: core.TrustBlacklist,
expectedTrust: oas.TrustType(core.TrustBlacklist),
},
{
name: "a graylisted item stays visible even though nothing else vouches for it",
collection: &collectionID,
trustType: core.TrustGraylist,
expectedTrust: oas.TrustType(core.TrustGraylist),
expectedTrustV2: oas.TrustType(core.TrustGraylist),
name: "a graylisted item stays visible even though nothing else vouches for it",
collection: &collectionID,
trustType: core.TrustGraylist,
expectedTrust: oas.TrustType(core.TrustGraylist),
},
{
name: "a whitelisted item stays visible even without a collection",
collection: nil,
trustType: core.TrustWhitelist,
expectedTrust: oas.TrustType(core.TrustWhitelist),
expectedTrustV2: oas.TrustType(core.TrustWhitelist),
name: "a whitelisted item stays visible even without a collection",
collection: nil,
trustType: core.TrustWhitelist,
expectedTrust: oas.TrustType(core.TrustWhitelist),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
h := &Handler{
addressBook: mockAddressBook{},
spamFilter: mockSpamFilter{collectionTrust: tt.collectionTrust},
metaCache: metaCache,
addressBook: mockAddressBook{},
spamFilter: mockSpamFilter{collectionTrust: tt.collectionTrust},
metaCache: metaCache,
nftTrustNoneEnabled: tt.nftTrustNoneEnabled,
}
item := core.NftItem{Address: nftID, CollectionAddress: tt.collection}
got := h.convertNFT(context.Background(), item, h.addressBook, h.metaCache, tt.trustType)
assert.Equal(t, tt.expectedTrust, got.Trust)
gotTrustV2, ok := got.TrustV2.Get()
assert.True(t, ok, "trust_v2 should always be set")
assert.Equal(t, tt.expectedTrustV2, gotTrustV2)
})
}
}
Expand Down
56 changes: 3 additions & 53 deletions pkg/oas/oas_json_gen.go

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

Loading
Loading