@@ -12,14 +12,11 @@ import (
12
12
"sync"
13
13
"time"
14
14
15
+ "github.com/shopspring/decimal"
15
16
"github.com/tonkeeper/opentonapi/pkg/core"
16
- imgGenerator "github.com/tonkeeper/opentonapi/pkg/image "
17
+ "github.com/tonkeeper/tongo "
17
18
"github.com/tonkeeper/tongo/abi"
18
19
"github.com/tonkeeper/tongo/tlb"
19
- "github.com/tonkeeper/tongo/ton"
20
-
21
- "github.com/shopspring/decimal"
22
- "github.com/tonkeeper/tongo"
23
20
"go.uber.org/zap"
24
21
"golang.org/x/exp/maps"
25
22
"golang.org/x/exp/slices"
@@ -28,6 +25,7 @@ import (
28
25
"github.com/tonkeeper/opentonapi/pkg/oas"
29
26
)
30
27
28
+ // NormalizeReg is a regular expression to remove all non-letter and non-number characters
31
29
var NormalizeReg = regexp .MustCompile ("[^\\ p{L}\\ p{N}]" )
32
30
33
31
// KnownAddress represents additional manually crafted information about a particular account in the blockchain
@@ -52,19 +50,6 @@ const (
52
50
JettonNameAccountType AttachedAccountType = "jetton_name"
53
51
)
54
52
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
-
68
53
// KnownJetton represents additional manually crafted information about a particular jetton in the blockchain
69
54
type KnownJetton struct {
70
55
Name string `json:"name"`
@@ -138,29 +123,43 @@ func (b *Book) GetAddressInfoByAddress(a tongo.AccountID) (KnownAddress, bool) {
138
123
139
124
// SearchAttachedAccountsByPrefix searches for accounts by prefix
140
125
func (b * Book ) SearchAttachedAccountsByPrefix (prefix string ) []AttachedAccount {
126
+ if prefix == "" {
127
+ return []AttachedAccount {}
128
+ }
141
129
prefix = strings .ToLower (NormalizeReg .ReplaceAllString (prefix , "" ))
142
- var accounts [ ]AttachedAccount
130
+ exclusiveAccounts := make ( map [ string ]AttachedAccount )
143
131
for i := range b .addressers {
144
132
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
+ }
147
140
}
148
141
}
142
+ accounts := maps .Values (exclusiveAccounts )
149
143
tonDomainPrefix := prefix + "ton"
150
144
tgDomainPrefix := prefix + "tme"
151
- // Adjust weight for full matches
145
+ // Boost weight for accounts that match the prefix
152
146
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
155
153
}
156
154
}
157
- // Sort and limit the result
155
+ // Sort accounts by weight and name length
158
156
sort .Slice (accounts , func (i , j int ) bool {
159
157
if accounts [i ].Weight == accounts [j ].Weight {
160
158
return len (accounts [i ].Name ) < len (accounts [j ].Name )
161
159
}
162
160
return accounts [i ].Weight > accounts [j ].Weight
163
161
})
162
+ // Limit the result to 50 accounts
164
163
if len (accounts ) > 50 {
165
164
accounts = accounts [:50 ]
166
165
}
@@ -288,94 +287,44 @@ func (m *manualAddresser) refreshAddresses(addressPath string) error {
288
287
if err != nil {
289
288
return err
290
289
}
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 )
293
292
for _ , item := range addresses {
294
293
account , err := tongo .ParseAddress (item .Address )
295
294
if err != nil {
296
295
continue
297
296
}
298
297
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
304
300
names := GenerateNameVariants (item .Name )
305
301
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
310
305
}
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 )
320
312
}
321
313
}
322
- sort .Slice (newSorted , func (i , j int ) bool {
323
- return newSorted [i ].Normalized < newSorted [j ].Normalized
324
- })
325
314
326
315
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
330
324
331
325
return nil
332
326
}
333
327
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
-
379
328
// NewAddressBook initializes a Book and starts background refreshers tasks
380
329
func NewAddressBook (logger * zap.Logger , addressPath , jettonPath , collectionPath string , storage accountsStatesSource , opts ... Option ) * Book {
381
330
var manual = & manualAddresser {
@@ -401,7 +350,7 @@ func NewAddressBook(logger *zap.Logger, addressPath, jettonPath, collectionPath
401
350
// Start background refreshers
402
351
go Refresher ("gg whitelist" , time .Hour , 5 * time .Minute , logger , book .getGGWhitelist )
403
352
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 ) })
405
354
go Refresher ("collections" , time .Minute * 15 , 5 * time .Minute , logger , func () error { return book .refreshCollections (collectionPath ) })
406
355
book .refreshTfPools (logger ) // Refresh tfPools once on initialization as it doesn't need periodic updates
407
356
@@ -423,22 +372,50 @@ func Refresher(name string, interval, errorInterval time.Duration, logger *zap.L
423
372
}
424
373
425
374
// 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
427
377
jettons , err := downloadJson [KnownJetton ](jettonPath )
428
378
if err != nil {
429
379
return err
430
380
}
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
434
383
for _ , item := range jettons {
435
384
account , err := tongo .ParseAddress (item .Address )
436
385
if err != nil {
437
386
continue
438
387
}
439
388
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
+ }
441
404
}
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
+
442
419
return nil
443
420
}
444
421
0 commit comments