Skip to content

Commit 08f8d92

Browse files
Merge branch 'search_accounts'
2 parents 03313c0 + 2d0e96d commit 08f8d92

10 files changed

+531
-119
lines changed

api/openapi.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -3331,12 +3331,16 @@
33313331
"preview": {
33323332
"example": "https://cache.tonapi.io/images/media.jpg",
33333333
"type": "string"
3334+
},
3335+
"trust": {
3336+
"$ref": "#/components/schemas/TrustType"
33343337
}
33353338
},
33363339
"required": [
33373340
"address",
33383341
"name",
3339-
"preview"
3342+
"preview",
3343+
"trust"
33403344
],
33413345
"type": "object"
33423346
},

api/openapi.yml

+3
Original file line numberDiff line numberDiff line change
@@ -7288,6 +7288,7 @@ components:
72887288
- address
72897289
- name
72907290
- preview
7291+
- trust
72917292
properties:
72927293
address:
72937294
type: string
@@ -7298,6 +7299,8 @@ components:
72987299
preview:
72997300
type: string
73007301
example: "https://cache.tonapi.io/images/media.jpg"
7302+
trust:
7303+
$ref: '#/components/schemas/TrustType'
73017304
DnsExpiring:
73027305
type: object
73037306
required:

pkg/addressbook/addressbook.go

+79-102
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ import (
1212
"sync"
1313
"time"
1414

15+
"github.com/shopspring/decimal"
1516
"github.com/tonkeeper/opentonapi/pkg/core"
16-
imgGenerator "github.com/tonkeeper/opentonapi/pkg/image"
17+
"github.com/tonkeeper/tongo"
1718
"github.com/tonkeeper/tongo/abi"
1819
"github.com/tonkeeper/tongo/tlb"
19-
"github.com/tonkeeper/tongo/ton"
20-
21-
"github.com/shopspring/decimal"
22-
"github.com/tonkeeper/tongo"
2320
"go.uber.org/zap"
2421
"golang.org/x/exp/maps"
2522
"golang.org/x/exp/slices"
@@ -28,6 +25,7 @@ import (
2825
"github.com/tonkeeper/opentonapi/pkg/oas"
2926
)
3027

28+
// NormalizeReg is a regular expression to remove all non-letter and non-number characters
3129
var NormalizeReg = regexp.MustCompile("[^\\p{L}\\p{N}]")
3230

3331
// KnownAddress represents additional manually crafted information about a particular account in the blockchain
@@ -52,19 +50,6 @@ const (
5250
JettonNameAccountType AttachedAccountType = "jetton_name"
5351
)
5452

55-
// AttachedAccount represents domains, nft collections for quick search by name are presented
56-
type AttachedAccount struct {
57-
Name string `json:"name"`
58-
Preview string `json:"preview"`
59-
Wallet ton.AccountID `json:"wallet"`
60-
Slug string `json:"-"`
61-
Symbol string `json:"-"`
62-
Type AttachedAccountType `json:"-"`
63-
Weight int32 `json:"-"`
64-
Popular int32 `json:"-"`
65-
Normalized string `json:"-"`
66-
}
67-
6853
// KnownJetton represents additional manually crafted information about a particular jetton in the blockchain
6954
type KnownJetton struct {
7055
Name string `json:"name"`
@@ -138,29 +123,43 @@ func (b *Book) GetAddressInfoByAddress(a tongo.AccountID) (KnownAddress, bool) {
138123

139124
// SearchAttachedAccountsByPrefix searches for accounts by prefix
140125
func (b *Book) SearchAttachedAccountsByPrefix(prefix string) []AttachedAccount {
126+
if prefix == "" {
127+
return []AttachedAccount{}
128+
}
141129
prefix = strings.ToLower(NormalizeReg.ReplaceAllString(prefix, ""))
142-
var accounts []AttachedAccount
130+
exclusiveAccounts := make(map[string]AttachedAccount)
143131
for i := range b.addressers {
144132
foundAccounts := b.addressers[i].SearchAttachedAccounts(prefix)
145-
if len(foundAccounts) > 0 {
146-
accounts = append(accounts, foundAccounts...)
133+
for _, account := range foundAccounts {
134+
key := fmt.Sprintf("%v:%v", account.Wallet.ToRaw(), account.Normalized)
135+
existing, ok := exclusiveAccounts[key]
136+
// Ensure only one account per wallet is included
137+
if !ok || (existing.Trust != core.TrustWhitelist && account.Trust == core.TrustWhitelist) {
138+
exclusiveAccounts[key] = account
139+
}
147140
}
148141
}
142+
accounts := maps.Values(exclusiveAccounts)
149143
tonDomainPrefix := prefix + "ton"
150144
tgDomainPrefix := prefix + "tme"
151-
// Adjust weight for full matches
145+
// Boost weight for accounts that match the prefix
152146
for i := range accounts {
153-
if accounts[i].Normalized == prefix || accounts[i].Normalized == tonDomainPrefix || accounts[i].Normalized == tgDomainPrefix {
154-
accounts[i].Weight *= 100
147+
normalized := accounts[i].Normalized
148+
if normalized == prefix || normalized == tonDomainPrefix || normalized == tgDomainPrefix {
149+
accounts[i].Weight *= BoostForFullMatch
150+
}
151+
if accounts[i].Trust == core.TrustWhitelist {
152+
accounts[i].Weight *= BoostForVerified
155153
}
156154
}
157-
// Sort and limit the result
155+
// Sort accounts by weight and name length
158156
sort.Slice(accounts, func(i, j int) bool {
159157
if accounts[i].Weight == accounts[j].Weight {
160158
return len(accounts[i].Name) < len(accounts[j].Name)
161159
}
162160
return accounts[i].Weight > accounts[j].Weight
163161
})
162+
// Limit the result to 50 accounts
164163
if len(accounts) > 50 {
165164
accounts = accounts[:50]
166165
}
@@ -288,94 +287,44 @@ func (m *manualAddresser) refreshAddresses(addressPath string) error {
288287
if err != nil {
289288
return err
290289
}
291-
newAddresses := make(map[tongo.AccountID]KnownAddress, len(addresses))
292-
var newSorted []AttachedAccount
290+
knownAccounts := make(map[tongo.AccountID]KnownAddress, len(addresses))
291+
attachedAccounts := make([]AttachedAccount, 0, len(addresses)*3)
293292
for _, item := range addresses {
294293
account, err := tongo.ParseAddress(item.Address)
295294
if err != nil {
296295
continue
297296
}
298297
item.Address = account.ID.ToRaw()
299-
newAddresses[account.ID] = item
300-
var preview string
301-
if item.Image != "" {
302-
preview = imgGenerator.DefaultGenerator.GenerateImageUrl(item.Image, 200, 200)
303-
}
298+
knownAccounts[account.ID] = item
299+
// Generate name variants for the account
304300
names := GenerateNameVariants(item.Name)
305301
for idx, name := range names {
306-
// Assign initial weight, give extra weight to the first name for priority
307-
weight := int32(1000)
308-
if idx == 0 {
309-
weight *= 10 // Boost weight for the first name
302+
weight := KnownAccountWeight
303+
if idx == 0 { // Boost weight for the first name
304+
weight *= BoostForOriginalName
310305
}
311-
newSorted = append(newSorted, AttachedAccount{
312-
Name: item.Name,
313-
Preview: preview,
314-
Wallet: account.ID,
315-
Type: ManualAccountType,
316-
Weight: weight,
317-
Popular: 1,
318-
Normalized: strings.ToLower(NormalizeReg.ReplaceAllString(name, "")),
319-
})
306+
// Convert known account to attached account
307+
attachedAccount, err := ConvertAttachedAccount(name, item.Image, account.ID, weight, core.TrustWhitelist, ManualAccountType)
308+
if err != nil {
309+
continue
310+
}
311+
attachedAccounts = append(attachedAccounts, attachedAccount)
320312
}
321313
}
322-
sort.Slice(newSorted, func(i, j int) bool {
323-
return newSorted[i].Normalized < newSorted[j].Normalized
324-
})
325314

326315
m.mu.Lock()
327-
m.addresses = newAddresses
328-
m.sorted = newSorted
329-
m.mu.Unlock()
316+
defer m.mu.Unlock()
317+
m.addresses = knownAccounts
318+
sorted := m.sorted
319+
sorted = append(sorted, attachedAccounts...)
320+
sort.Slice(sorted, func(i, j int) bool {
321+
return sorted[i].Normalized < sorted[j].Normalized
322+
})
323+
m.sorted = sorted
330324

331325
return nil
332326
}
333327

334-
func GenerateNameVariants(name string) []string {
335-
words := strings.Fields(name) // Split the name into words
336-
var variants []string
337-
// Generate up to 3 variants by rotating the words
338-
for i := 0; i < len(words) && i < 3; i++ {
339-
variant := append(words[i:], words[:i]...) // Rotate the words
340-
variants = append(variants, strings.Join(variant, " "))
341-
}
342-
return variants
343-
}
344-
345-
func FindIndexes(sortedList []AttachedAccount, prefix string) (int, int) {
346-
low, high := 0, len(sortedList)-1
347-
startIdx := -1
348-
// Find starting index for the prefix
349-
for low <= high {
350-
med := (low + high) / 2
351-
if strings.HasPrefix(sortedList[med].Normalized, prefix) {
352-
startIdx = med
353-
high = med - 1
354-
} else if sortedList[med].Normalized < prefix {
355-
low = med + 1
356-
} else {
357-
high = med - 1
358-
}
359-
}
360-
if startIdx == -1 { // No prefix match
361-
return -1, -1
362-
}
363-
low, high = startIdx, len(sortedList)-1
364-
endIdx := -1
365-
// Find ending index for the prefix
366-
for low <= high {
367-
med := (low + high) / 2
368-
if strings.HasPrefix(sortedList[med].Normalized, prefix) {
369-
endIdx = med
370-
low = med + 1
371-
} else {
372-
high = med - 1
373-
}
374-
}
375-
376-
return startIdx, endIdx
377-
}
378-
379328
// NewAddressBook initializes a Book and starts background refreshers tasks
380329
func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath string, storage accountsStatesSource, opts ...Option) *Book {
381330
var manual = &manualAddresser{
@@ -401,7 +350,7 @@ func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath
401350
// Start background refreshers
402351
go Refresher("gg whitelist", time.Hour, 5*time.Minute, logger, book.getGGWhitelist)
403352
go Refresher("addresses", time.Minute*15, 5*time.Minute, logger, func() error { return manual.refreshAddresses(addressPath) })
404-
go Refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(jettonPath) })
353+
go Refresher("jettons", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshJettons(manual, jettonPath) })
405354
go Refresher("collections", time.Minute*15, 5*time.Minute, logger, func() error { return book.refreshCollections(collectionPath) })
406355
book.refreshTfPools(logger) // Refresh tfPools once on initialization as it doesn't need periodic updates
407356

@@ -423,22 +372,50 @@ func Refresher(name string, interval, errorInterval time.Duration, logger *zap.L
423372
}
424373

425374
// refreshJettons fetches and updates the jetton data from the provided URL
426-
func (b *Book) refreshJettons(jettonPath string) error {
375+
func (b *Book) refreshJettons(addresser *manualAddresser, jettonPath string) error {
376+
// Download jettons data
427377
jettons, err := downloadJson[KnownJetton](jettonPath)
428378
if err != nil {
429379
return err
430380
}
431-
b.mu.Lock()
432-
defer b.mu.Unlock()
433-
// Update jettons map with the fetched data
381+
knownJettons := make(map[tongo.AccountID]KnownJetton, len(jettons))
382+
var attachedAccounts []AttachedAccount
434383
for _, item := range jettons {
435384
account, err := tongo.ParseAddress(item.Address)
436385
if err != nil {
437386
continue
438387
}
439388
item.Address = account.ID.ToRaw()
440-
b.jettons[account.ID] = item
389+
knownJettons[account.ID] = item
390+
// Generate name variants for the jetton
391+
names := GenerateNameVariants(item.Name)
392+
for idx, name := range names {
393+
weight := KnownAccountWeight
394+
if idx == 0 { // Boost weight for the first name
395+
weight *= BoostForOriginalName
396+
}
397+
// Convert known account to attached account
398+
attachedAccount, err := ConvertAttachedAccount(name, item.Image, account.ID, weight, core.TrustWhitelist, JettonNameAccountType)
399+
if err != nil {
400+
continue
401+
}
402+
attachedAccounts = append(attachedAccounts, attachedAccount)
403+
}
441404
}
405+
406+
b.mu.Lock()
407+
b.jettons = knownJettons
408+
b.mu.Unlock()
409+
410+
addresser.mu.Lock()
411+
defer addresser.mu.Unlock()
412+
sorted := addresser.sorted
413+
sorted = append(sorted, attachedAccounts...)
414+
sort.Slice(sorted, func(i, j int) bool {
415+
return sorted[i].Normalized < sorted[j].Normalized
416+
})
417+
addresser.sorted = sorted
418+
442419
return nil
443420
}
444421

0 commit comments

Comments
 (0)