@keplr-wallet/background 0.12.308-rc.1 → 0.12.309-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/keyring-cosmos/service.js +2 -1
- package/build/keyring-cosmos/service.js.map +1 -1
- package/build/keyring-starknet/service.js +1 -1
- package/build/token-scan/handler.js +22 -2
- package/build/token-scan/handler.js.map +1 -1
- package/build/token-scan/init.js +1 -0
- package/build/token-scan/init.js.map +1 -1
- package/build/token-scan/messages.d.ts +18 -1
- package/build/token-scan/messages.js +22 -1
- package/build/token-scan/messages.js.map +1 -1
- package/build/token-scan/service.d.ts +22 -14
- package/build/token-scan/service.js +209 -66
- package/build/token-scan/service.js.map +1 -1
- package/build/tx/service.js +1 -1
- package/package.json +13 -13
- package/src/keyring-cosmos/service.ts +2 -1
- package/src/keyring-starknet/service.ts +1 -1
- package/src/token-scan/handler.ts +44 -3
- package/src/token-scan/init.ts +6 -1
- package/src/token-scan/messages.ts +34 -1
- package/src/token-scan/service.ts +299 -99
- package/src/tx/service.ts +1 -1
|
@@ -2,7 +2,11 @@ import { Message } from "@keplr-wallet/router";
|
|
|
2
2
|
import { TokenScan } from "./service";
|
|
3
3
|
import { ROUTE } from "./constants";
|
|
4
4
|
|
|
5
|
-
export class GetTokenScansMsg extends Message<
|
|
5
|
+
export class GetTokenScansMsg extends Message<{
|
|
6
|
+
vaultId: string;
|
|
7
|
+
tokenScans: TokenScan[];
|
|
8
|
+
tokenScansWithoutDismissed: TokenScan[];
|
|
9
|
+
}> {
|
|
6
10
|
public static type() {
|
|
7
11
|
return "get-token-scans";
|
|
8
12
|
}
|
|
@@ -29,6 +33,7 @@ export class GetTokenScansMsg extends Message<TokenScan[]> {
|
|
|
29
33
|
export class RevalidateTokenScansMsg extends Message<{
|
|
30
34
|
vaultId: string;
|
|
31
35
|
tokenScans: TokenScan[];
|
|
36
|
+
tokenScansWithoutDismissed: TokenScan[];
|
|
32
37
|
}> {
|
|
33
38
|
public static type() {
|
|
34
39
|
return "revalidate-token-scans";
|
|
@@ -52,3 +57,31 @@ export class RevalidateTokenScansMsg extends Message<{
|
|
|
52
57
|
return RevalidateTokenScansMsg.type();
|
|
53
58
|
}
|
|
54
59
|
}
|
|
60
|
+
|
|
61
|
+
export class DismissNewTokenFoundInMainMsg extends Message<{
|
|
62
|
+
vaultId: string;
|
|
63
|
+
tokenScans: TokenScan[];
|
|
64
|
+
tokenScansWithoutDismissed: TokenScan[];
|
|
65
|
+
}> {
|
|
66
|
+
public static type() {
|
|
67
|
+
return "dismiss-new-token-found-in-main";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
constructor(public readonly vaultId: string) {
|
|
71
|
+
super();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
validateBasic(): void {
|
|
75
|
+
if (!this.vaultId) {
|
|
76
|
+
throw new Error("Empty vault id");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
route(): string {
|
|
81
|
+
return ROUTE;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
type(): string {
|
|
85
|
+
return DismissNewTokenFoundInMainMsg.type();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -2,35 +2,56 @@ import { ChainsService } from "../chains";
|
|
|
2
2
|
import { KeyRingCosmosService } from "../keyring-cosmos";
|
|
3
3
|
import { KeyRingService } from "../keyring";
|
|
4
4
|
import { ChainsUIForegroundService, ChainsUIService } from "../chains-ui";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
action,
|
|
7
|
+
autorun,
|
|
8
|
+
makeObservable,
|
|
9
|
+
observable,
|
|
10
|
+
runInAction,
|
|
11
|
+
toJS,
|
|
12
|
+
} from "mobx";
|
|
6
13
|
import { AppCurrency, SupportedPaymentType } from "@keplr-wallet/types";
|
|
7
14
|
import { simpleFetch } from "@keplr-wallet/simple-fetch";
|
|
8
15
|
import { Dec } from "@keplr-wallet/unit";
|
|
9
16
|
import { ChainIdHelper } from "@keplr-wallet/cosmos";
|
|
10
17
|
import { VaultService } from "../vault";
|
|
11
|
-
import { KVStore } from "@keplr-wallet/common";
|
|
18
|
+
import { DenomHelper, KVStore } from "@keplr-wallet/common";
|
|
12
19
|
import { KeyRingStarknetService } from "../keyring-starknet";
|
|
13
20
|
import { CairoUint256 } from "starknet";
|
|
14
21
|
import { KeyRingBitcoinService } from "../keyring-bitcoin";
|
|
15
22
|
import { MessageRequester } from "@keplr-wallet/router";
|
|
16
23
|
|
|
24
|
+
const thirdpartySupportedChainIdMap: Record<string, string> = {
|
|
25
|
+
"eip155:1": "eth",
|
|
26
|
+
"eip155:10": "opt",
|
|
27
|
+
"eip155:137": "polygon",
|
|
28
|
+
"eip155:8453": "base",
|
|
29
|
+
"eip155:42161": "arb",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
type Asset = {
|
|
33
|
+
currency?: AppCurrency;
|
|
34
|
+
coinMinimalDenom?: string;
|
|
35
|
+
amount: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type TokenScanInfo = {
|
|
39
|
+
bech32Address?: string;
|
|
40
|
+
ethereumHexAddress?: string;
|
|
41
|
+
starknetHexAddress?: string;
|
|
42
|
+
bitcoinAddress?: {
|
|
43
|
+
bech32Address: string;
|
|
44
|
+
paymentType: SupportedPaymentType;
|
|
45
|
+
};
|
|
46
|
+
coinType?: number;
|
|
47
|
+
assets: Asset[];
|
|
48
|
+
};
|
|
49
|
+
|
|
17
50
|
export type TokenScan = {
|
|
18
51
|
chainId: string;
|
|
19
|
-
infos:
|
|
20
|
-
bech32Address?: string;
|
|
21
|
-
ethereumHexAddress?: string;
|
|
22
|
-
starknetHexAddress?: string;
|
|
23
|
-
bitcoinAddress?: {
|
|
24
|
-
bech32Address: string;
|
|
25
|
-
paymentType: SupportedPaymentType;
|
|
26
|
-
};
|
|
27
|
-
coinType?: number;
|
|
28
|
-
assets: {
|
|
29
|
-
currency: AppCurrency;
|
|
30
|
-
amount: string;
|
|
31
|
-
}[];
|
|
32
|
-
}[];
|
|
52
|
+
infos: TokenScanInfo[];
|
|
33
53
|
linkedChainKey?: string;
|
|
54
|
+
dismissedInfos?: TokenScanInfo[];
|
|
34
55
|
};
|
|
35
56
|
|
|
36
57
|
export class TokenScanService {
|
|
@@ -127,29 +148,28 @@ export class TokenScanService {
|
|
|
127
148
|
});
|
|
128
149
|
}
|
|
129
150
|
);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
);
|
|
151
|
+
|
|
152
|
+
this.chainsService.addChainRemovedHandler((chainInfo) => {
|
|
153
|
+
runInAction(() => {
|
|
154
|
+
for (const [vaultId, tokenScans] of this.vaultToMap.entries()) {
|
|
155
|
+
let prevTokenScans = tokenScans;
|
|
156
|
+
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
157
|
+
return scan.chainId !== chainInfo.chainId;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
145
164
|
}
|
|
146
165
|
|
|
147
166
|
getTokenScans(vaultId: string): TokenScan[] {
|
|
148
167
|
return (this.vaultToMap.get(vaultId) ?? [])
|
|
149
168
|
.filter((tokenScan) => {
|
|
150
169
|
return (
|
|
151
|
-
this.chainsService.hasChainInfo(tokenScan.chainId) ||
|
|
152
|
-
|
|
170
|
+
(this.chainsService.hasChainInfo(tokenScan.chainId) ||
|
|
171
|
+
this.chainsService.hasModularChainInfo(tokenScan.chainId)) &&
|
|
172
|
+
!this.chainsUIService.isEnabled(vaultId, tokenScan.chainId)
|
|
153
173
|
);
|
|
154
174
|
})
|
|
155
175
|
.sort((a, b) => {
|
|
@@ -225,6 +245,13 @@ export class TokenScanService {
|
|
|
225
245
|
const chainIdentifier = ChainIdHelper.parse(
|
|
226
246
|
tokenScan.chainId
|
|
227
247
|
).identifier;
|
|
248
|
+
|
|
249
|
+
const prevTokenScan = prevTokenScans.find((scan) => {
|
|
250
|
+
return (
|
|
251
|
+
ChainIdHelper.parse(scan.chainId).identifier === chainIdentifier
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
|
|
228
255
|
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
229
256
|
const prevChainIdentifier = ChainIdHelper.parse(
|
|
230
257
|
scan.chainId
|
|
@@ -232,7 +259,10 @@ export class TokenScanService {
|
|
|
232
259
|
return chainIdentifier !== prevChainIdentifier;
|
|
233
260
|
});
|
|
234
261
|
|
|
235
|
-
prevTokenScans.push(
|
|
262
|
+
prevTokenScans.push({
|
|
263
|
+
...prevTokenScan,
|
|
264
|
+
...tokenScan,
|
|
265
|
+
});
|
|
236
266
|
|
|
237
267
|
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
238
268
|
});
|
|
@@ -254,6 +284,7 @@ export class TokenScanService {
|
|
|
254
284
|
const tokenScans: TokenScan[] = [];
|
|
255
285
|
const processedLinkedChainKeys = new Set<string>();
|
|
256
286
|
const promises: Promise<void>[] = [];
|
|
287
|
+
const logChains: string[] = [];
|
|
257
288
|
|
|
258
289
|
for (const modularChainInfo of modularChainInfos) {
|
|
259
290
|
if ("linkedChainKey" in modularChainInfo) {
|
|
@@ -275,10 +306,18 @@ export class TokenScanService {
|
|
|
275
306
|
}
|
|
276
307
|
})()
|
|
277
308
|
);
|
|
309
|
+
logChains.push(modularChainInfo.chainId);
|
|
278
310
|
}
|
|
279
311
|
|
|
280
312
|
// ignore error
|
|
281
|
-
await Promise.allSettled(promises);
|
|
313
|
+
const settled = await Promise.allSettled(promises);
|
|
314
|
+
for (let i = 0; i < settled.length; i++) {
|
|
315
|
+
const s = settled[i];
|
|
316
|
+
if (s.status === "rejected") {
|
|
317
|
+
console.error("failed to calculateTokenScan", logChains[i]);
|
|
318
|
+
console.error(s.reason);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
282
321
|
|
|
283
322
|
if (tokenScans.length > 0) {
|
|
284
323
|
runInAction(() => {
|
|
@@ -288,6 +327,13 @@ export class TokenScanService {
|
|
|
288
327
|
const chainIdentifier = ChainIdHelper.parse(
|
|
289
328
|
tokenScan.chainId
|
|
290
329
|
).identifier;
|
|
330
|
+
|
|
331
|
+
const prevTokenScan = prevTokenScans.find((scan) => {
|
|
332
|
+
return (
|
|
333
|
+
ChainIdHelper.parse(scan.chainId).identifier === chainIdentifier
|
|
334
|
+
);
|
|
335
|
+
});
|
|
336
|
+
|
|
291
337
|
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
292
338
|
const prevChainIdentifier = ChainIdHelper.parse(
|
|
293
339
|
scan.chainId
|
|
@@ -295,13 +341,12 @@ export class TokenScanService {
|
|
|
295
341
|
return chainIdentifier !== prevChainIdentifier;
|
|
296
342
|
});
|
|
297
343
|
|
|
298
|
-
prevTokenScans.push(
|
|
344
|
+
prevTokenScans.push({
|
|
345
|
+
...prevTokenScan,
|
|
346
|
+
...tokenScan,
|
|
347
|
+
});
|
|
299
348
|
}
|
|
300
349
|
|
|
301
|
-
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
302
|
-
return !this.chainsUIService.isEnabled(vaultId, scan.chainId);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
350
|
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
306
351
|
});
|
|
307
352
|
}
|
|
@@ -346,6 +391,8 @@ export class TokenScanService {
|
|
|
346
391
|
pubkey.getEthAddress()
|
|
347
392
|
).toString("hex")}`;
|
|
348
393
|
|
|
394
|
+
const assets: Asset[] = [];
|
|
395
|
+
|
|
349
396
|
const res = await simpleFetch<{
|
|
350
397
|
result: string;
|
|
351
398
|
}>(evmInfo.rpc, {
|
|
@@ -373,16 +420,76 @@ export class TokenScanService {
|
|
|
373
420
|
res.status === 200 &&
|
|
374
421
|
BigInt(res.data.result).toString(10) !== "0"
|
|
375
422
|
) {
|
|
423
|
+
assets.push({
|
|
424
|
+
currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0],
|
|
425
|
+
amount: BigInt(res.data.result).toString(10),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (thirdpartySupportedChainIdMap[chainId]) {
|
|
430
|
+
const tokenAPIURL = `https://evm-${chainId.replace(
|
|
431
|
+
"eip155:",
|
|
432
|
+
""
|
|
433
|
+
)}.keplr.app/api`;
|
|
434
|
+
|
|
435
|
+
const res = await simpleFetch<{
|
|
436
|
+
jsonrpc: string;
|
|
437
|
+
id: number;
|
|
438
|
+
result: {
|
|
439
|
+
address: string;
|
|
440
|
+
tokenBalances: {
|
|
441
|
+
contractAddress: string;
|
|
442
|
+
tokenBalance: string | null;
|
|
443
|
+
error: {
|
|
444
|
+
code: number;
|
|
445
|
+
message: string;
|
|
446
|
+
} | null;
|
|
447
|
+
}[];
|
|
448
|
+
// TODO: Support pagination.
|
|
449
|
+
pageKey: string;
|
|
450
|
+
};
|
|
451
|
+
}>(tokenAPIURL, {
|
|
452
|
+
method: "POST",
|
|
453
|
+
headers: {
|
|
454
|
+
"content-type": "application/json",
|
|
455
|
+
...(() => {
|
|
456
|
+
if (typeof browser !== "undefined") {
|
|
457
|
+
return {
|
|
458
|
+
"request-source": new URL(browser.runtime.getURL("/"))
|
|
459
|
+
.origin,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
return undefined;
|
|
463
|
+
})(),
|
|
464
|
+
},
|
|
465
|
+
body: JSON.stringify({
|
|
466
|
+
jsonrpc: "2.0",
|
|
467
|
+
method: "alchemy_getTokenBalances",
|
|
468
|
+
params: [ethereumHexAddress, "erc20"],
|
|
469
|
+
id: 1,
|
|
470
|
+
}),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
if (res.status === 200) {
|
|
474
|
+
for (const tokenBalance of res.data.result?.tokenBalances ?? []) {
|
|
475
|
+
if (tokenBalance.tokenBalance && tokenBalance.error == null) {
|
|
476
|
+
assets.push({
|
|
477
|
+
coinMinimalDenom: DenomHelper.normalizeDenom(
|
|
478
|
+
`erc20:${tokenBalance.contractAddress}`
|
|
479
|
+
),
|
|
480
|
+
amount: BigInt(tokenBalance.tokenBalance).toString(10),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (assets.length > 0) {
|
|
376
488
|
tokenScan.infos.push({
|
|
377
489
|
bech32Address: "",
|
|
378
490
|
ethereumHexAddress,
|
|
379
491
|
coinType: 60,
|
|
380
|
-
assets
|
|
381
|
-
{
|
|
382
|
-
currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0],
|
|
383
|
-
amount: BigInt(res.data.result).toString(10),
|
|
384
|
-
},
|
|
385
|
-
],
|
|
492
|
+
assets,
|
|
386
493
|
});
|
|
387
494
|
}
|
|
388
495
|
} else {
|
|
@@ -422,26 +529,26 @@ export class TokenScanService {
|
|
|
422
529
|
);
|
|
423
530
|
|
|
424
531
|
if (res.status === 200) {
|
|
425
|
-
const assets:
|
|
532
|
+
const assets: TokenScanInfo["assets"] = [];
|
|
426
533
|
|
|
427
534
|
const balances = res.data?.balances ?? [];
|
|
428
535
|
for (const bal of balances) {
|
|
429
536
|
const currency = chainInfo.currencies.find(
|
|
430
537
|
(cur) => cur.coinMinimalDenom === bal.denom
|
|
431
538
|
);
|
|
432
|
-
if (currency) {
|
|
433
|
-
// validate
|
|
434
|
-
if (typeof bal.amount !== "string") {
|
|
435
|
-
throw new Error("Invalid amount");
|
|
436
|
-
}
|
|
437
539
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
540
|
+
// validate
|
|
541
|
+
if (typeof bal.amount !== "string") {
|
|
542
|
+
throw new Error("Invalid amount");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const dec = new Dec(bal.amount);
|
|
546
|
+
if (dec.gte(new Dec(0))) {
|
|
547
|
+
assets.push({
|
|
548
|
+
currency,
|
|
549
|
+
coinMinimalDenom: bal.denom,
|
|
550
|
+
amount: bal.amount,
|
|
551
|
+
});
|
|
445
552
|
}
|
|
446
553
|
}
|
|
447
554
|
|
|
@@ -502,28 +609,26 @@ export class TokenScanService {
|
|
|
502
609
|
.toBigInt()
|
|
503
610
|
.toString(10);
|
|
504
611
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
{
|
|
513
|
-
currency,
|
|
514
|
-
amount,
|
|
515
|
-
},
|
|
516
|
-
],
|
|
517
|
-
});
|
|
518
|
-
} else {
|
|
519
|
-
if (
|
|
520
|
-
tokenScan.infos[0].starknetHexAddress === starknetHexAddress
|
|
521
|
-
) {
|
|
522
|
-
tokenScan.infos[0].assets.push({
|
|
612
|
+
// XXX: Starknet의 경우는 여러 주소가 나올수가 없으므로
|
|
613
|
+
// starknetHexAddress는 같은 값으로 나온다고 생각하고 처리한다.
|
|
614
|
+
if (tokenScan.infos.length === 0) {
|
|
615
|
+
tokenScan.infos.push({
|
|
616
|
+
starknetHexAddress,
|
|
617
|
+
assets: [
|
|
618
|
+
{
|
|
523
619
|
currency,
|
|
524
620
|
amount,
|
|
525
|
-
}
|
|
526
|
-
|
|
621
|
+
},
|
|
622
|
+
],
|
|
623
|
+
});
|
|
624
|
+
} else {
|
|
625
|
+
if (
|
|
626
|
+
tokenScan.infos[0].starknetHexAddress === starknetHexAddress
|
|
627
|
+
) {
|
|
628
|
+
tokenScan.infos[0].assets.push({
|
|
629
|
+
currency,
|
|
630
|
+
amount,
|
|
631
|
+
});
|
|
527
632
|
}
|
|
528
633
|
}
|
|
529
634
|
}
|
|
@@ -613,40 +718,135 @@ export class TokenScanService {
|
|
|
613
718
|
);
|
|
614
719
|
}
|
|
615
720
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
for (const bitcoinScanInfo of bitcoinScanInfos) {
|
|
619
|
-
// 우선 main currency만 처리한다.
|
|
620
|
-
if (
|
|
621
|
-
bitcoinScanInfo.assets.length > 0 &&
|
|
622
|
-
bitcoinScanInfo.assets[0].amount !== "0"
|
|
623
|
-
) {
|
|
624
|
-
hasNonZeroAmount = true;
|
|
625
|
-
break;
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
// 하나라도 0이 아닌 값이 있으면 연결된 모든 체인에 대해 토큰 스캔 정보를 추가한다.
|
|
630
|
-
if (hasNonZeroAmount) {
|
|
631
|
-
tokenScan.infos.push(...bitcoinScanInfos);
|
|
632
|
-
}
|
|
721
|
+
tokenScan.infos.push(...bitcoinScanInfos);
|
|
633
722
|
} else {
|
|
634
723
|
const bitcoinScanInfo = await getBitcoinScanInfo(
|
|
635
724
|
vaultId,
|
|
636
725
|
chainId,
|
|
637
|
-
|
|
726
|
+
true
|
|
638
727
|
);
|
|
639
|
-
|
|
640
728
|
if (bitcoinScanInfo) {
|
|
641
729
|
tokenScan.infos.push(bitcoinScanInfo);
|
|
642
730
|
}
|
|
643
731
|
}
|
|
644
732
|
}
|
|
645
|
-
|
|
646
733
|
if (tokenScan.infos.length > 0) {
|
|
647
734
|
return tokenScan;
|
|
648
735
|
}
|
|
649
736
|
|
|
650
737
|
return undefined;
|
|
651
738
|
}
|
|
739
|
+
|
|
740
|
+
@action
|
|
741
|
+
dismissNewTokenFoundInHome(vaultId: string) {
|
|
742
|
+
const prevTokenScans = this.vaultToMap.get(vaultId) ?? [];
|
|
743
|
+
for (const prevTokenScan of prevTokenScans) {
|
|
744
|
+
prevTokenScan.dismissedInfos = prevTokenScan.infos;
|
|
745
|
+
}
|
|
746
|
+
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
@action
|
|
750
|
+
resetDismiss(vaultId: string) {
|
|
751
|
+
const prevTokenScans = this.vaultToMap.get(vaultId) ?? [];
|
|
752
|
+
for (const prevTokenScan of prevTokenScans) {
|
|
753
|
+
prevTokenScan.dismissedInfos = undefined;
|
|
754
|
+
}
|
|
755
|
+
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
public isMeaningfulTokenScanChangeBetweenDismissed(
|
|
759
|
+
tokenScan: TokenScan
|
|
760
|
+
): boolean {
|
|
761
|
+
if (!tokenScan.dismissedInfos || tokenScan.dismissedInfos.length === 0) {
|
|
762
|
+
return tokenScan.infos.length > 0;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
const makeKey = (info: TokenScanInfo): string | undefined => {
|
|
766
|
+
if (info.bech32Address) return `bech32:${info.bech32Address}`;
|
|
767
|
+
if (info.ethereumHexAddress) return `eth:${info.ethereumHexAddress}`;
|
|
768
|
+
if (info.starknetHexAddress) return `stark:${info.starknetHexAddress}`;
|
|
769
|
+
if (info.bitcoinAddress?.bech32Address)
|
|
770
|
+
return `btc:${info.bitcoinAddress.bech32Address}`;
|
|
771
|
+
if (info.coinType != null) return `coin:${info.coinType}`;
|
|
772
|
+
return undefined;
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
const toBigIntSafe = (v: string): bigint | undefined => {
|
|
776
|
+
try {
|
|
777
|
+
return BigInt(v);
|
|
778
|
+
} catch {
|
|
779
|
+
return undefined;
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
|
|
783
|
+
const dismissedTokenInfosMap = new Map<string, TokenScanInfo>();
|
|
784
|
+
for (const info of tokenScan.dismissedInfos ?? []) {
|
|
785
|
+
const key = makeKey(info);
|
|
786
|
+
if (key) {
|
|
787
|
+
dismissedTokenInfosMap.set(key, info);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
for (const info of tokenScan.infos) {
|
|
792
|
+
const key = makeKey(info);
|
|
793
|
+
if (!key) {
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const dismissedTokenInfo = dismissedTokenInfosMap.get(key);
|
|
798
|
+
|
|
799
|
+
if (!dismissedTokenInfo) {
|
|
800
|
+
if (info.assets.length > 0) {
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const dismissedAssetMap = new Map<string, Asset>();
|
|
807
|
+
for (const asset of dismissedTokenInfo.assets) {
|
|
808
|
+
const coinMinimalDenom =
|
|
809
|
+
asset.currency?.coinMinimalDenom || asset.coinMinimalDenom;
|
|
810
|
+
if (!coinMinimalDenom) {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
dismissedAssetMap.set(coinMinimalDenom, asset);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
for (const asset of info.assets) {
|
|
817
|
+
const coinMinimalDenom =
|
|
818
|
+
asset.currency?.coinMinimalDenom || asset.coinMinimalDenom;
|
|
819
|
+
if (!coinMinimalDenom) {
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const prevAsset = dismissedAssetMap.get(coinMinimalDenom);
|
|
823
|
+
|
|
824
|
+
// 없던 토큰이 생긴경우
|
|
825
|
+
if (!prevAsset) {
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const prevAmount = toBigIntSafe(prevAsset.amount);
|
|
830
|
+
const curAmount = toBigIntSafe(asset.amount);
|
|
831
|
+
if (prevAmount == null || curAmount == null) {
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// 이전에 0이였다가 밸런스가 생긴경우.
|
|
836
|
+
if (prevAmount === BigInt(0) && curAmount > BigInt(0)) {
|
|
837
|
+
return true;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// 이전 밸런스에 배해서 10% 밸런스가 증가한 경우
|
|
841
|
+
if (
|
|
842
|
+
prevAmount > BigInt(0) &&
|
|
843
|
+
curAmount * BigInt(10) >= prevAmount * BigInt(11)
|
|
844
|
+
) {
|
|
845
|
+
return true;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
652
852
|
}
|
package/src/tx/service.ts
CHANGED