diff --git a/api/openapi.json b/api/openapi.json index e3edc0d2..b6bb2f1f 100644 --- a/api/openapi.json +++ b/api/openapi.json @@ -3331,12 +3331,16 @@ "preview": { "example": "https://cache.tonapi.io/images/media.jpg", "type": "string" + }, + "trust": { + "$ref": "#/components/schemas/TrustType" } }, "required": [ "address", "name", - "preview" + "preview", + "trust" ], "type": "object" }, diff --git a/api/openapi.yml b/api/openapi.yml index 053d5ea3..fc509ed3 100644 --- a/api/openapi.yml +++ b/api/openapi.yml @@ -7290,6 +7290,7 @@ components: - address - name - preview + - trust properties: address: type: string @@ -7300,6 +7301,8 @@ components: preview: type: string example: "https://cache.tonapi.io/images/media.jpg" + trust: + $ref: '#/components/schemas/TrustType' DnsExpiring: type: object required: diff --git a/pkg/addressbook/addressbook.go b/pkg/addressbook/addressbook.go index 176b366b..b64d0bc1 100644 --- a/pkg/addressbook/addressbook.go +++ b/pkg/addressbook/addressbook.go @@ -12,14 +12,11 @@ import ( "sync" "time" + "github.com/shopspring/decimal" "github.com/tonkeeper/opentonapi/pkg/core" - imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" + "github.com/tonkeeper/tongo" "github.com/tonkeeper/tongo/abi" "github.com/tonkeeper/tongo/tlb" - "github.com/tonkeeper/tongo/ton" - - "github.com/shopspring/decimal" - "github.com/tonkeeper/tongo" "go.uber.org/zap" "golang.org/x/exp/maps" "golang.org/x/exp/slices" @@ -28,6 +25,7 @@ import ( "github.com/tonkeeper/opentonapi/pkg/oas" ) +// NormalizeReg is a regular expression to remove all non-letter and non-number characters var NormalizeReg = regexp.MustCompile("[^\\p{L}\\p{N}]") // KnownAddress represents additional manually crafted information about a particular account in the blockchain @@ -52,19 +50,6 @@ const ( JettonNameAccountType AttachedAccountType = "jetton_name" ) -// AttachedAccount represents domains, nft collections for quick search by name are presented -type AttachedAccount struct { - Name string `json:"name"` - Preview string `json:"preview"` - Wallet ton.AccountID `json:"wallet"` - Slug string `json:"-"` - Symbol string `json:"-"` - Type AttachedAccountType `json:"-"` - Weight int32 `json:"-"` - Popular int32 `json:"-"` - Normalized string `json:"-"` -} - // KnownJetton represents additional manually crafted information about a particular jetton in the blockchain type KnownJetton struct { Name string `json:"name"` @@ -138,29 +123,43 @@ func (b *Book) GetAddressInfoByAddress(a tongo.AccountID) (KnownAddress, bool) { // SearchAttachedAccountsByPrefix searches for accounts by prefix func (b *Book) SearchAttachedAccountsByPrefix(prefix string) []AttachedAccount { + if prefix == "" { + return []AttachedAccount{} + } prefix = strings.ToLower(NormalizeReg.ReplaceAllString(prefix, "")) - var accounts []AttachedAccount + exclusiveAccounts := make(map[string]AttachedAccount) for i := range b.addressers { foundAccounts := b.addressers[i].SearchAttachedAccounts(prefix) - if len(foundAccounts) > 0 { - accounts = append(accounts, foundAccounts...) + for _, account := range foundAccounts { + key := fmt.Sprintf("%v:%v", account.Wallet.ToRaw(), account.Normalized) + existing, ok := exclusiveAccounts[key] + // Ensure only one account per wallet is included + if !ok || (existing.Trust != core.TrustWhitelist && account.Trust == core.TrustWhitelist) { + exclusiveAccounts[key] = account + } } } + accounts := maps.Values(exclusiveAccounts) tonDomainPrefix := prefix + "ton" tgDomainPrefix := prefix + "tme" - // Adjust weight for full matches + // Boost weight for accounts that match the prefix for i := range accounts { - if accounts[i].Normalized == prefix || accounts[i].Normalized == tonDomainPrefix || accounts[i].Normalized == tgDomainPrefix { - accounts[i].Weight *= 100 + normalized := accounts[i].Normalized + if normalized == prefix || normalized == tonDomainPrefix || normalized == tgDomainPrefix { + accounts[i].Weight *= BoostForFullMatch + } + if accounts[i].Trust == core.TrustWhitelist { + accounts[i].Weight *= BoostForVerified } } - // Sort and limit the result + // Sort accounts by weight and name length sort.Slice(accounts, func(i, j int) bool { if accounts[i].Weight == accounts[j].Weight { return len(accounts[i].Name) < len(accounts[j].Name) } return accounts[i].Weight > accounts[j].Weight }) + // Limit the result to 50 accounts if len(accounts) > 50 { accounts = accounts[:50] } @@ -288,94 +287,44 @@ func (m *manualAddresser) refreshAddresses(addressPath string) error { if err != nil { return err } - newAddresses := make(map[tongo.AccountID]KnownAddress, len(addresses)) - var newSorted []AttachedAccount + knownAccounts := make(map[tongo.AccountID]KnownAddress, len(addresses)) + attachedAccounts := make([]AttachedAccount, 0, len(addresses)*3) for _, item := range addresses { account, err := tongo.ParseAddress(item.Address) if err != nil { continue } item.Address = account.ID.ToRaw() - newAddresses[account.ID] = item - var preview string - if item.Image != "" { - preview = imgGenerator.DefaultGenerator.GenerateImageUrl(item.Image, 200, 200) - } + knownAccounts[account.ID] = item + // Generate name variants for the account names := GenerateNameVariants(item.Name) for idx, name := range names { - // Assign initial weight, give extra weight to the first name for priority - weight := int32(1000) - if idx == 0 { - weight *= 10 // Boost weight for the first name + weight := KnownAccountWeight + if idx == 0 { // Boost weight for the first name + weight *= BoostForOriginalName } - newSorted = append(newSorted, AttachedAccount{ - Name: item.Name, - Preview: preview, - Wallet: account.ID, - Type: ManualAccountType, - Weight: weight, - Popular: 1, - Normalized: strings.ToLower(NormalizeReg.ReplaceAllString(name, "")), - }) + // Convert known account to attached account + attachedAccount, err := ConvertAttachedAccount(name, item.Image, account.ID, weight, core.TrustWhitelist, ManualAccountType) + if err != nil { + continue + } + attachedAccounts = append(attachedAccounts, attachedAccount) } } - sort.Slice(newSorted, func(i, j int) bool { - return newSorted[i].Normalized < newSorted[j].Normalized - }) m.mu.Lock() - m.addresses = newAddresses - m.sorted = newSorted - m.mu.Unlock() + defer m.mu.Unlock() + m.addresses = knownAccounts + sorted := m.sorted + sorted = append(sorted, attachedAccounts...) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Normalized < sorted[j].Normalized + }) + m.sorted = sorted return nil } -func GenerateNameVariants(name string) []string { - words := strings.Fields(name) // Split the name into words - var variants []string - // Generate up to 3 variants by rotating the words - for i := 0; i < len(words) && i < 3; i++ { - variant := append(words[i:], words[:i]...) // Rotate the words - variants = append(variants, strings.Join(variant, " ")) - } - return variants -} - -func FindIndexes(sortedList []AttachedAccount, prefix string) (int, int) { - low, high := 0, len(sortedList)-1 - startIdx := -1 - // Find starting index for the prefix - for low <= high { - med := (low + high) / 2 - if strings.HasPrefix(sortedList[med].Normalized, prefix) { - startIdx = med - high = med - 1 - } else if sortedList[med].Normalized < prefix { - low = med + 1 - } else { - high = med - 1 - } - } - if startIdx == -1 { // No prefix match - return -1, -1 - } - low, high = startIdx, len(sortedList)-1 - endIdx := -1 - // Find ending index for the prefix - for low <= high { - med := (low + high) / 2 - if strings.HasPrefix(sortedList[med].Normalized, prefix) { - endIdx = med - low = med + 1 - } else { - high = med - 1 - } - } - - return startIdx, endIdx -} - // NewAddressBook initializes a Book and starts background refreshers tasks func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath string, storage accountsStatesSource, opts ...Option) *Book { var manual = &manualAddresser{ @@ -401,7 +350,7 @@ func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath // Start background refreshers go Refresher("gg whitelist", time.Hour, 5*time.Minute, logger, book.getGGWhitelist) go Refresher("addresses", time.Minute*15, 5*time.Minute, logger, func() error { return manual.refreshAddresses(addressPath) }) - go Refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(jettonPath) }) + go Refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(manual, jettonPath) }) go Refresher("collections", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshCollections(collectionPath) }) book.refreshTfPools(logger) // Refresh tfPools once on initialization as it doesn't need periodic updates @@ -423,22 +372,50 @@ func Refresher(name string, interval, errorInterval time.Duration, logger *zap.L } // refreshJettons fetches and updates the jetton data from the provided URL -func (b *Book) refreshJettons(jettonPath string) error { +func (b *Book) refreshJettons(addresser *manualAddresser, jettonPath string) error { + // Download jettons data jettons, err := downloadJson[KnownJetton](jettonPath) if err != nil { return err } - b.mu.Lock() - defer b.mu.Unlock() - // Update jettons map with the fetched data + knownJettons := make(map[tongo.AccountID]KnownJetton, len(jettons)) + var attachedAccounts []AttachedAccount for _, item := range jettons { account, err := tongo.ParseAddress(item.Address) if err != nil { continue } item.Address = account.ID.ToRaw() - b.jettons[account.ID] = item + knownJettons[account.ID] = item + // Generate name variants for the jetton + names := GenerateNameVariants(item.Name) + for idx, name := range names { + weight := KnownAccountWeight + if idx == 0 { // Boost weight for the first name + weight *= BoostForOriginalName + } + // Convert known account to attached account + attachedAccount, err := ConvertAttachedAccount(name, item.Image, account.ID, weight, core.TrustWhitelist, JettonNameAccountType) + if err != nil { + continue + } + attachedAccounts = append(attachedAccounts, attachedAccount) + } } + + b.mu.Lock() + b.jettons = knownJettons + b.mu.Unlock() + + addresser.mu.Lock() + defer addresser.mu.Unlock() + sorted := addresser.sorted + sorted = append(sorted, attachedAccounts...) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Normalized < sorted[j].Normalized + }) + addresser.sorted = sorted + return nil } diff --git a/pkg/addressbook/addressbook_test.go b/pkg/addressbook/addressbook_test.go index 61634db5..32169ac0 100644 --- a/pkg/addressbook/addressbook_test.go +++ b/pkg/addressbook/addressbook_test.go @@ -1,6 +1,8 @@ package addressbook import ( + "reflect" + "sort" "strings" "testing" "time" @@ -158,3 +160,240 @@ func TestFetchGetGemsVerifiedCollections(t *testing.T) { require.Equal(t, len(m), len(accountIDs)) require.True(t, len(m) > 100) } + +func TestGenerateNameVariants(t *testing.T) { + tests := []struct { + name string + input string + expected []string + }{ + { + name: "Single word", + input: "TON", + expected: []string{"TON"}, + }, + { + name: "Two words", + input: "TON Foundation", + expected: []string{"TON Foundation", "Foundation TON"}, + }, + { + name: "Three words", + input: "TON Believers Fund", + expected: []string{"TON Believers Fund", "Believers Fund TON", "Fund TON Believers"}, + }, + { + name: "Four words", + input: "Notcoin Foundation of Believers", + expected: []string{"Notcoin Foundation of Believers", "Foundation of Believers Notcoin", "of Believers Notcoin Foundation"}, + }, + { + name: "Five words", + input: "The Open Network Blockchain Project", + expected: []string{"The Open Network Blockchain Project", "Open Network Blockchain Project The", "Network Blockchain Project The Open"}, + }, + { + name: "Six words", + input: "The First Decentralized Cryptocurrency of TON", + expected: []string{"The First Decentralized Cryptocurrency of TON", "First Decentralized Cryptocurrency of TON The", "Decentralized Cryptocurrency of TON The First"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := GenerateNameVariants(tt.input) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("Expected %v, but got %v", tt.expected, result) + } + }) + } +} + +func TestFindIndexes(t *testing.T) { + tests := []struct { + name string + accounts []AttachedAccount + prefix string + startIndex int + endIndex int + }{ + { + name: "Prefix found", + accounts: []AttachedAccount{ + {Normalized: "toncoin"}, + {Normalized: "tonstarter"}, + {Normalized: "tonstarter"}, + {Normalized: "tonswap"}, + {Normalized: "uniswap"}, + }, + prefix: "tonst", + startIndex: 1, + endIndex: 2, + }, + { + name: "Prefix not found", + accounts: []AttachedAccount{ + {Normalized: "toncoin"}, + {Normalized: "tonstarter"}, + {Normalized: "tonswap"}, + {Normalized: "uniswap"}, + }, + prefix: "xyz", + startIndex: -1, + endIndex: -1, + }, + { + name: "Prefix at start", + accounts: []AttachedAccount{ + {Normalized: "toncoin"}, + {Normalized: "tonstarter"}, + {Normalized: "tonswap"}, + {Normalized: "uniswap"}, + }, + prefix: "tonco", + startIndex: 0, + endIndex: 0, + }, + { + name: "Prefix at end", + accounts: []AttachedAccount{ + {Normalized: "toncoin"}, + {Normalized: "tonstarter"}, + {Normalized: "tonswap"}, + {Normalized: "uniswap"}, + }, + prefix: "uniswap", + startIndex: 3, + endIndex: 3, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sort.Slice(tt.accounts, func(i, j int) bool { + return tt.accounts[i].Normalized < tt.accounts[j].Normalized + }) + startIdx, endIdx := FindIndexes(tt.accounts, tt.prefix) + if startIdx != tt.startIndex || endIdx != tt.endIndex { + t.Errorf("Expected (%d, %d), but got (%d, %d)", tt.startIndex, tt.endIndex, startIdx, endIdx) + } + }) + } +} + +func TestSearchAccountByName(t *testing.T) { + type testCase struct { + name string + book *Book + query string + result []string + } + for _, test := range []testCase{ + { + name: "Search by first letters with one found account", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "elon-musk.ton", + Normalized: "elonmuskton", + }, + }, + }}}, + query: "elon", + result: []string{"elonmuskton"}, + }, + { + name: "Search by full name with one found account", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "elon-musk.ton", + Normalized: "elonmuskton", + }, + }, + }}}, + query: "elon-musk.ton", + result: []string{"elonmuskton"}, + }, + { + name: "Search by first letters with multiple accounts found", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "elongate-blah.ton", + Wallet: ton.MustParseAccountID("Ef9VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVbxn"), + Normalized: "elongateblahton", + }, + { + Name: "elongate.ton", + Wallet: ton.MustParseAccountID("Ef8zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM0vF"), + Normalized: "elongateton", + }, + { + Name: "elon-musk.ton", + Wallet: ton.MustParseAccountID("Ef_lZ1T4NCb2mwkme9h2rJfESCE0W34ma9lWp7-_uY3zXDvq"), + Normalized: "elonmuskton", + }, + }, + }}}, + query: "elon", + result: []string{"elongateblahton", "elongateton", "elonmuskton"}, + }, + { + name: "Search through an empty list", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{}}, + }}, + query: "blah", + result: []string{}, + }, + { + name: "Search for a word that doesn't exist", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "elon-musk.ton", + Normalized: "elonmuskton", + }, + }, + }}}, + query: "blah", + result: []string{}, + }, + { + name: "Search with case insensitive query", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "Elon-Musk.ton", + Normalized: "elonmuskton", + }, + }, + }}}, + query: "ELON", + result: []string{"elonmuskton"}, + }, + { + name: "Search with empty query string", + book: &Book{addressers: []addresser{&manualAddresser{ + sorted: []AttachedAccount{ + { + Name: "elon-musk.ton", + Normalized: "elonmuskton", + }, + }, + }}}, + query: "", + result: []string{}, + }, + } { + t.Run(test.name, func(t *testing.T) { + var foundAccounts []string + attachedAccounts := test.book.SearchAttachedAccountsByPrefix(test.query) + for _, account := range attachedAccounts { + foundAccounts = append(foundAccounts, account.Normalized) + } + require.ElementsMatch(t, foundAccounts, test.result) + }) + } +} diff --git a/pkg/addressbook/attached_accounts.go b/pkg/addressbook/attached_accounts.go new file mode 100644 index 00000000..c4d4b944 --- /dev/null +++ b/pkg/addressbook/attached_accounts.go @@ -0,0 +1,129 @@ +package addressbook + +import ( + "fmt" + "strings" + + "github.com/tonkeeper/opentonapi/pkg/core" + imgGenerator "github.com/tonkeeper/opentonapi/pkg/image" + "github.com/tonkeeper/opentonapi/pkg/references" + rules "github.com/tonkeeper/scam_backoffice_rules" + "github.com/tonkeeper/tongo/ton" +) + +// AttachedAccountType represents the type of the attached account +const ( + KnownAccountWeight = 1000 + BoostForFullMatch = 100 + BoostForOriginalName = 50 + BoostForVerified = 5 +) + +// AttachedAccount represents domains, nft collections for quick search by name are presented +type AttachedAccount struct { + Name string `json:"name"` + Preview string `json:"preview"` + Wallet ton.AccountID `json:"wallet"` + Slug string `json:"-"` + Symbol string `json:"-"` + Type AttachedAccountType `json:"-"` + Weight int64 `json:"-"` + Popular int64 `json:"-"` + Trust core.TrustType `json:"-"` + Normalized string `json:"-"` +} + +// ConvertAttachedAccount converts a known account to an attached account +func ConvertAttachedAccount(slug, image string, account ton.AccountID, weight int, trust core.TrustType, accountType AttachedAccountType) (AttachedAccount, error) { + var name string + // Handle different account types and assign appropriate values + switch accountType { + case TonDomainAccountType, TgDomainAccountType: + weight = 1000 + name = fmt.Sprintf("%v · account", slug) + // Generate image URL for "t.me" subdomains + if strings.HasSuffix(slug, "t.me") && strings.Count(slug, ".") == 2 { + image = fmt.Sprintf("https://t.me/i/userpic/320/%v.jpg", strings.TrimSuffix(slug, ".t.me")) + } else { + image = references.PlugAutoCompleteDomain + } + case JettonSymbolAccountType, JettonNameAccountType: + name = fmt.Sprintf("%v · jetton", slug) + if image == "" { + image = references.PlugAutoCompleteJetton + } + case NftCollectionAccountType: + name = fmt.Sprintf("%v · collection", slug) + if image == "" { + image = references.PlugAutoCompleteCollection + } + case ManualAccountType: + name = fmt.Sprintf("%v · account", slug) + if image == "" { + image = references.PlugAutoCompleteAccount + } + default: + return AttachedAccount{}, fmt.Errorf("unknown account type") + } + if len(image) > 0 { // Generate a preview image + image = imgGenerator.DefaultGenerator.GenerateImageUrl(image, 200, 200) + } + return AttachedAccount{ + Name: name, + Slug: slug, + Preview: image, + Wallet: account, + Type: accountType, + Weight: int64(weight), + Popular: int64(weight), + Trust: trust, + Normalized: rules.NormalizeJettonSymbol(slug), + }, nil +} + +// GenerateNameVariants generates name variants by rotating the words +func GenerateNameVariants(name string) []string { + words := strings.Fields(name) // Split the name into words + var variants []string + // Generate up to 3 variants by rotating the words + for i := 0; i < len(words) && i < 3; i++ { + variant := append(words[i:], words[:i]...) // Rotate the words + variants = append(variants, strings.Join(variant, " ")) + } + return variants +} + +// FindIndexes finds the start and end indexes of the prefix in the sorted list +func FindIndexes(sortedList []AttachedAccount, prefix string) (int, int) { + low, high := 0, len(sortedList)-1 + startIdx := -1 + // Find starting index for the prefix + for low <= high { + med := (low + high) / 2 + if strings.HasPrefix(sortedList[med].Normalized, prefix) { + startIdx = med + high = med - 1 + } else if sortedList[med].Normalized < prefix { + low = med + 1 + } else { + high = med - 1 + } + } + if startIdx == -1 { // No prefix match + return -1, -1 + } + low, high = startIdx, len(sortedList)-1 + endIdx := -1 + // Find ending index for the prefix + for low <= high { + med := (low + high) / 2 + if strings.HasPrefix(sortedList[med].Normalized, prefix) { + endIdx = med + low = med + 1 + } else { + high = med - 1 + } + } + + return startIdx, endIdx +} diff --git a/pkg/api/account_handlers.go b/pkg/api/account_handlers.go index e3d10ee1..0026b724 100644 --- a/pkg/api/account_handlers.go +++ b/pkg/api/account_handlers.go @@ -13,12 +13,12 @@ import ( "strings" "time" + "github.com/tonkeeper/opentonapi/pkg/addressbook" "golang.org/x/exp/slices" "github.com/cespare/xxhash/v2" "github.com/go-faster/jx" "github.com/tonkeeper/opentonapi/internal/g" - "github.com/tonkeeper/opentonapi/pkg/addressbook" "github.com/tonkeeper/opentonapi/pkg/core" "github.com/tonkeeper/opentonapi/pkg/oas" "github.com/tonkeeper/tongo" @@ -29,7 +29,6 @@ import ( "github.com/tonkeeper/tongo/ton" "github.com/tonkeeper/tongo/utils" walletTongo "github.com/tonkeeper/tongo/wallet" - "golang.org/x/exp/maps" ) func (h *Handler) GetBlockchainRawAccount(ctx context.Context, params oas.GetBlockchainRawAccountParams) (*oas.BlockchainRawAccount, error) { @@ -267,7 +266,7 @@ func (h *Handler) ExecGetMethodForBlockchainAccount(ctx context.Context, params func (h *Handler) SearchAccounts(ctx context.Context, params oas.SearchAccountsParams) (*oas.FoundAccounts, error) { attachedAccounts := h.addressBook.SearchAttachedAccountsByPrefix(params.Name) - parsedAccounts := make(map[tongo.AccountID]addressbook.AttachedAccount) + accounts := make([]addressbook.AttachedAccount, 0, len(attachedAccounts)) for _, account := range attachedAccounts { if account.Symbol != "" { trust := h.spamFilter.JettonTrust(account.Wallet, account.Symbol, account.Name, account.Preview) @@ -275,21 +274,15 @@ func (h *Handler) SearchAccounts(ctx context.Context, params oas.SearchAccountsP continue } } - parsedAccounts[account.Wallet] = account + accounts = append(accounts, account) } - accounts := maps.Values(parsedAccounts) - sort.Slice(accounts, func(i, j int) bool { - if accounts[i].Weight == accounts[j].Weight { - return len(accounts[i].Name) < len(accounts[j].Name) - } - return accounts[i].Weight > accounts[j].Weight - }) converted := make([]oas.FoundAccountsAddressesItem, len(accounts)) for idx, account := range accounts { converted[idx] = oas.FoundAccountsAddressesItem{ Address: account.Wallet.ToRaw(), Name: account.Name, Preview: account.Preview, + Trust: oas.TrustType(account.Trust), } } return &oas.FoundAccounts{Addresses: converted}, nil diff --git a/pkg/oas/oas_json_gen.go b/pkg/oas/oas_json_gen.go index e6c33009..0039469a 100644 --- a/pkg/oas/oas_json_gen.go +++ b/pkg/oas/oas_json_gen.go @@ -16103,12 +16103,17 @@ func (s *FoundAccountsAddressesItem) encodeFields(e *jx.Encoder) { e.FieldStart("preview") e.Str(s.Preview) } + { + e.FieldStart("trust") + s.Trust.Encode(e) + } } -var jsonFieldsNameOfFoundAccountsAddressesItem = [3]string{ +var jsonFieldsNameOfFoundAccountsAddressesItem = [4]string{ 0: "address", 1: "name", 2: "preview", + 3: "trust", } // Decode decodes FoundAccountsAddressesItem from json. @@ -16156,6 +16161,16 @@ func (s *FoundAccountsAddressesItem) Decode(d *jx.Decoder) error { }(); err != nil { return errors.Wrap(err, "decode field \"preview\"") } + case "trust": + requiredBitSet[0] |= 1 << 3 + if err := func() error { + if err := s.Trust.Decode(d); err != nil { + return err + } + return nil + }(); err != nil { + return errors.Wrap(err, "decode field \"trust\"") + } default: return d.Skip() } @@ -16166,7 +16181,7 @@ func (s *FoundAccountsAddressesItem) Decode(d *jx.Decoder) error { // Validate required fields. var failures []validate.FieldError for i, mask := range [1]uint8{ - 0b00000111, + 0b00001111, } { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { // Mask only required fields and check equality to mask using XOR. diff --git a/pkg/oas/oas_schemas_gen.go b/pkg/oas/oas_schemas_gen.go index c04c5d97..b6e67706 100644 --- a/pkg/oas/oas_schemas_gen.go +++ b/pkg/oas/oas_schemas_gen.go @@ -5589,9 +5589,10 @@ func (s *FoundAccounts) SetAddresses(val []FoundAccountsAddressesItem) { } type FoundAccountsAddressesItem struct { - Address string `json:"address"` - Name string `json:"name"` - Preview string `json:"preview"` + Address string `json:"address"` + Name string `json:"name"` + Preview string `json:"preview"` + Trust TrustType `json:"trust"` } // GetAddress returns the value of Address. @@ -5609,6 +5610,11 @@ func (s *FoundAccountsAddressesItem) GetPreview() string { return s.Preview } +// GetTrust returns the value of Trust. +func (s *FoundAccountsAddressesItem) GetTrust() TrustType { + return s.Trust +} + // SetAddress sets the value of Address. func (s *FoundAccountsAddressesItem) SetAddress(val string) { s.Address = val @@ -5624,6 +5630,11 @@ func (s *FoundAccountsAddressesItem) SetPreview(val string) { s.Preview = val } +// SetTrust sets the value of Trust. +func (s *FoundAccountsAddressesItem) SetTrust(val TrustType) { + s.Trust = val +} + // Ref: #/components/schemas/GasLimitPrices type GasLimitPrices struct { SpecialGasLimit OptInt64 `json:"special_gas_limit"` diff --git a/pkg/oas/oas_validators_gen.go b/pkg/oas/oas_validators_gen.go index 667446e5..40157635 100644 --- a/pkg/oas/oas_validators_gen.go +++ b/pkg/oas/oas_validators_gen.go @@ -2388,6 +2388,23 @@ func (s *FoundAccounts) Validate() error { if s.Addresses == nil { return errors.New("nil is invalid value") } + var failures []validate.FieldError + for i, elem := range s.Addresses { + if err := func() error { + if err := elem.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: fmt.Sprintf("[%d]", i), + Error: err, + }) + } + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } return nil }(); err != nil { failures = append(failures, validate.FieldError{ @@ -2401,6 +2418,29 @@ func (s *FoundAccounts) Validate() error { return nil } +func (s *FoundAccountsAddressesItem) Validate() error { + if s == nil { + return validate.ErrNilPointer + } + + var failures []validate.FieldError + if err := func() error { + if err := s.Trust.Validate(); err != nil { + return err + } + return nil + }(); err != nil { + failures = append(failures, validate.FieldError{ + Name: "trust", + Error: err, + }) + } + if len(failures) > 0 { + return &validate.Error{Fields: failures} + } + return nil +} + func (s *GaslessConfig) Validate() error { if s == nil { return validate.ErrNilPointer diff --git a/pkg/references/media.go b/pkg/references/media.go index be00ea64..10c9a8ad 100644 --- a/pkg/references/media.go +++ b/pkg/references/media.go @@ -4,6 +4,7 @@ const ( PlugAutoCompleteWallet = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/wallet_plug.png" PlugAutoCompleteCollection = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/collection_plug.png" PlugAutoCompleteDomain = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/domain_plug.png" + PlugAutoCompleteAccount = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/domain_plug.png" PlugAutoCompleteJetton = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/jetton_plug.png" Placeholder = "https://raw.githubusercontent.com/tonkeeper/opentonapi/master/pkg/references/media/token_placeholder.png" )