@keplr-wallet/background 0.12.308 → 0.12.309-rc.1
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 +205 -33
- 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 +294 -61
- package/src/tx/service.ts +1 -1
|
@@ -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,31 +148,42 @@ 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
|
})
|
|
175
|
+
.filter((tokenScan) => {
|
|
176
|
+
let hasAmount = false;
|
|
177
|
+
for (const info of tokenScan.infos) {
|
|
178
|
+
for (const asset of info.assets) {
|
|
179
|
+
if (asset.amount && asset.amount !== "0") {
|
|
180
|
+
hasAmount = true;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return hasAmount;
|
|
186
|
+
})
|
|
155
187
|
.sort((a, b) => {
|
|
156
188
|
// Sort by chain name
|
|
157
189
|
const aChainInfo = this.chainsService.hasChainInfo(a.chainId)
|
|
@@ -225,6 +257,13 @@ export class TokenScanService {
|
|
|
225
257
|
const chainIdentifier = ChainIdHelper.parse(
|
|
226
258
|
tokenScan.chainId
|
|
227
259
|
).identifier;
|
|
260
|
+
|
|
261
|
+
const prevTokenScan = prevTokenScans.find((scan) => {
|
|
262
|
+
return (
|
|
263
|
+
ChainIdHelper.parse(scan.chainId).identifier === chainIdentifier
|
|
264
|
+
);
|
|
265
|
+
});
|
|
266
|
+
|
|
228
267
|
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
229
268
|
const prevChainIdentifier = ChainIdHelper.parse(
|
|
230
269
|
scan.chainId
|
|
@@ -232,7 +271,10 @@ export class TokenScanService {
|
|
|
232
271
|
return chainIdentifier !== prevChainIdentifier;
|
|
233
272
|
});
|
|
234
273
|
|
|
235
|
-
prevTokenScans.push(
|
|
274
|
+
prevTokenScans.push({
|
|
275
|
+
...prevTokenScan,
|
|
276
|
+
...tokenScan,
|
|
277
|
+
});
|
|
236
278
|
|
|
237
279
|
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
238
280
|
});
|
|
@@ -254,6 +296,7 @@ export class TokenScanService {
|
|
|
254
296
|
const tokenScans: TokenScan[] = [];
|
|
255
297
|
const processedLinkedChainKeys = new Set<string>();
|
|
256
298
|
const promises: Promise<void>[] = [];
|
|
299
|
+
const logChains: string[] = [];
|
|
257
300
|
|
|
258
301
|
for (const modularChainInfo of modularChainInfos) {
|
|
259
302
|
if ("linkedChainKey" in modularChainInfo) {
|
|
@@ -275,10 +318,18 @@ export class TokenScanService {
|
|
|
275
318
|
}
|
|
276
319
|
})()
|
|
277
320
|
);
|
|
321
|
+
logChains.push(modularChainInfo.chainId);
|
|
278
322
|
}
|
|
279
323
|
|
|
280
324
|
// ignore error
|
|
281
|
-
await Promise.allSettled(promises);
|
|
325
|
+
const settled = await Promise.allSettled(promises);
|
|
326
|
+
for (let i = 0; i < settled.length; i++) {
|
|
327
|
+
const s = settled[i];
|
|
328
|
+
if (s.status === "rejected") {
|
|
329
|
+
console.error("failed to calculateTokenScan", logChains[i]);
|
|
330
|
+
console.error(s.reason);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
282
333
|
|
|
283
334
|
if (tokenScans.length > 0) {
|
|
284
335
|
runInAction(() => {
|
|
@@ -288,6 +339,13 @@ export class TokenScanService {
|
|
|
288
339
|
const chainIdentifier = ChainIdHelper.parse(
|
|
289
340
|
tokenScan.chainId
|
|
290
341
|
).identifier;
|
|
342
|
+
|
|
343
|
+
const prevTokenScan = prevTokenScans.find((scan) => {
|
|
344
|
+
return (
|
|
345
|
+
ChainIdHelper.parse(scan.chainId).identifier === chainIdentifier
|
|
346
|
+
);
|
|
347
|
+
});
|
|
348
|
+
|
|
291
349
|
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
292
350
|
const prevChainIdentifier = ChainIdHelper.parse(
|
|
293
351
|
scan.chainId
|
|
@@ -295,13 +353,12 @@ export class TokenScanService {
|
|
|
295
353
|
return chainIdentifier !== prevChainIdentifier;
|
|
296
354
|
});
|
|
297
355
|
|
|
298
|
-
prevTokenScans.push(
|
|
356
|
+
prevTokenScans.push({
|
|
357
|
+
...prevTokenScan,
|
|
358
|
+
...tokenScan,
|
|
359
|
+
});
|
|
299
360
|
}
|
|
300
361
|
|
|
301
|
-
prevTokenScans = prevTokenScans.filter((scan) => {
|
|
302
|
-
return !this.chainsUIService.isEnabled(vaultId, scan.chainId);
|
|
303
|
-
});
|
|
304
|
-
|
|
305
362
|
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
306
363
|
});
|
|
307
364
|
}
|
|
@@ -346,6 +403,8 @@ export class TokenScanService {
|
|
|
346
403
|
pubkey.getEthAddress()
|
|
347
404
|
).toString("hex")}`;
|
|
348
405
|
|
|
406
|
+
const assets: Asset[] = [];
|
|
407
|
+
|
|
349
408
|
const res = await simpleFetch<{
|
|
350
409
|
result: string;
|
|
351
410
|
}>(evmInfo.rpc, {
|
|
@@ -373,16 +432,79 @@ export class TokenScanService {
|
|
|
373
432
|
res.status === 200 &&
|
|
374
433
|
BigInt(res.data.result).toString(10) !== "0"
|
|
375
434
|
) {
|
|
435
|
+
assets.push({
|
|
436
|
+
currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0],
|
|
437
|
+
amount: BigInt(res.data.result).toString(10),
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (thirdpartySupportedChainIdMap[chainId]) {
|
|
442
|
+
const tokenAPIURL = `https://evm-${chainId.replace(
|
|
443
|
+
"eip155:",
|
|
444
|
+
""
|
|
445
|
+
)}.keplr.app/api`;
|
|
446
|
+
|
|
447
|
+
const res = await simpleFetch<{
|
|
448
|
+
jsonrpc: string;
|
|
449
|
+
id: number;
|
|
450
|
+
result: {
|
|
451
|
+
address: string;
|
|
452
|
+
tokenBalances: {
|
|
453
|
+
contractAddress: string;
|
|
454
|
+
tokenBalance: string | null;
|
|
455
|
+
error: {
|
|
456
|
+
code: number;
|
|
457
|
+
message: string;
|
|
458
|
+
} | null;
|
|
459
|
+
}[];
|
|
460
|
+
// TODO: Support pagination.
|
|
461
|
+
pageKey: string;
|
|
462
|
+
};
|
|
463
|
+
}>(tokenAPIURL, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: {
|
|
466
|
+
"content-type": "application/json",
|
|
467
|
+
...(() => {
|
|
468
|
+
if (typeof browser !== "undefined") {
|
|
469
|
+
return {
|
|
470
|
+
"request-source": new URL(browser.runtime.getURL("/"))
|
|
471
|
+
.origin,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return undefined;
|
|
475
|
+
})(),
|
|
476
|
+
},
|
|
477
|
+
body: JSON.stringify({
|
|
478
|
+
jsonrpc: "2.0",
|
|
479
|
+
method: "alchemy_getTokenBalances",
|
|
480
|
+
params: [ethereumHexAddress, "erc20"],
|
|
481
|
+
id: 1,
|
|
482
|
+
}),
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (res.status === 200) {
|
|
486
|
+
for (const tokenBalance of res.data.result?.tokenBalances ?? []) {
|
|
487
|
+
if (tokenBalance.tokenBalance && tokenBalance.error == null) {
|
|
488
|
+
const amount = BigInt(tokenBalance.tokenBalance).toString(10);
|
|
489
|
+
if (amount !== "0") {
|
|
490
|
+
assets.push({
|
|
491
|
+
coinMinimalDenom: DenomHelper.normalizeDenom(
|
|
492
|
+
`erc20:${tokenBalance.contractAddress}`
|
|
493
|
+
),
|
|
494
|
+
amount,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (assets.length > 0) {
|
|
376
503
|
tokenScan.infos.push({
|
|
377
504
|
bech32Address: "",
|
|
378
505
|
ethereumHexAddress,
|
|
379
506
|
coinType: 60,
|
|
380
|
-
assets
|
|
381
|
-
{
|
|
382
|
-
currency: chainInfo.stakeCurrency ?? chainInfo.currencies[0],
|
|
383
|
-
amount: BigInt(res.data.result).toString(10),
|
|
384
|
-
},
|
|
385
|
-
],
|
|
507
|
+
assets,
|
|
386
508
|
});
|
|
387
509
|
}
|
|
388
510
|
} else {
|
|
@@ -422,26 +544,26 @@ export class TokenScanService {
|
|
|
422
544
|
);
|
|
423
545
|
|
|
424
546
|
if (res.status === 200) {
|
|
425
|
-
const assets:
|
|
547
|
+
const assets: TokenScanInfo["assets"] = [];
|
|
426
548
|
|
|
427
549
|
const balances = res.data?.balances ?? [];
|
|
428
550
|
for (const bal of balances) {
|
|
429
551
|
const currency = chainInfo.currencies.find(
|
|
430
552
|
(cur) => cur.coinMinimalDenom === bal.denom
|
|
431
553
|
);
|
|
432
|
-
if (currency) {
|
|
433
|
-
// validate
|
|
434
|
-
if (typeof bal.amount !== "string") {
|
|
435
|
-
throw new Error("Invalid amount");
|
|
436
|
-
}
|
|
437
554
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
555
|
+
// validate
|
|
556
|
+
if (typeof bal.amount !== "string") {
|
|
557
|
+
throw new Error("Invalid amount");
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const dec = new Dec(bal.amount);
|
|
561
|
+
if (dec.gt(new Dec(0))) {
|
|
562
|
+
assets.push({
|
|
563
|
+
currency,
|
|
564
|
+
coinMinimalDenom: bal.denom,
|
|
565
|
+
amount: bal.amount,
|
|
566
|
+
});
|
|
445
567
|
}
|
|
446
568
|
}
|
|
447
569
|
|
|
@@ -636,17 +758,128 @@ export class TokenScanService {
|
|
|
636
758
|
chainId,
|
|
637
759
|
false
|
|
638
760
|
);
|
|
639
|
-
|
|
640
761
|
if (bitcoinScanInfo) {
|
|
641
762
|
tokenScan.infos.push(bitcoinScanInfo);
|
|
642
763
|
}
|
|
643
764
|
}
|
|
644
765
|
}
|
|
645
|
-
|
|
646
766
|
if (tokenScan.infos.length > 0) {
|
|
647
767
|
return tokenScan;
|
|
648
768
|
}
|
|
649
769
|
|
|
650
770
|
return undefined;
|
|
651
771
|
}
|
|
772
|
+
|
|
773
|
+
@action
|
|
774
|
+
dismissNewTokenFoundInHome(vaultId: string) {
|
|
775
|
+
const prevTokenScans = this.vaultToMap.get(vaultId) ?? [];
|
|
776
|
+
for (const prevTokenScan of prevTokenScans) {
|
|
777
|
+
prevTokenScan.dismissedInfos = prevTokenScan.infos;
|
|
778
|
+
}
|
|
779
|
+
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
@action
|
|
783
|
+
resetDismiss(vaultId: string) {
|
|
784
|
+
const prevTokenScans = this.vaultToMap.get(vaultId) ?? [];
|
|
785
|
+
for (const prevTokenScan of prevTokenScans) {
|
|
786
|
+
prevTokenScan.dismissedInfos = undefined;
|
|
787
|
+
}
|
|
788
|
+
this.vaultToMap.set(vaultId, prevTokenScans);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
public isMeaningfulTokenScanChangeBetweenDismissed(
|
|
792
|
+
tokenScan: TokenScan
|
|
793
|
+
): boolean {
|
|
794
|
+
if (!tokenScan.dismissedInfos || tokenScan.dismissedInfos.length === 0) {
|
|
795
|
+
return tokenScan.infos.length > 0;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const makeKey = (info: TokenScanInfo): string | undefined => {
|
|
799
|
+
if (info.bech32Address) return `bech32:${info.bech32Address}`;
|
|
800
|
+
if (info.ethereumHexAddress) return `eth:${info.ethereumHexAddress}`;
|
|
801
|
+
if (info.starknetHexAddress) return `stark:${info.starknetHexAddress}`;
|
|
802
|
+
if (info.bitcoinAddress?.bech32Address)
|
|
803
|
+
return `btc:${info.bitcoinAddress.bech32Address}`;
|
|
804
|
+
if (info.coinType != null) return `coin:${info.coinType}`;
|
|
805
|
+
return undefined;
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const toBigIntSafe = (v: string): bigint | undefined => {
|
|
809
|
+
try {
|
|
810
|
+
return BigInt(v);
|
|
811
|
+
} catch {
|
|
812
|
+
return undefined;
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
|
|
816
|
+
const dismissedTokenInfosMap = new Map<string, TokenScanInfo>();
|
|
817
|
+
for (const info of tokenScan.dismissedInfos ?? []) {
|
|
818
|
+
const key = makeKey(info);
|
|
819
|
+
if (key) {
|
|
820
|
+
dismissedTokenInfosMap.set(key, info);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
for (const info of tokenScan.infos) {
|
|
825
|
+
const key = makeKey(info);
|
|
826
|
+
if (!key) {
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const dismissedTokenInfo = dismissedTokenInfosMap.get(key);
|
|
831
|
+
|
|
832
|
+
if (!dismissedTokenInfo) {
|
|
833
|
+
if (info.assets.length > 0) {
|
|
834
|
+
return true;
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const dismissedAssetMap = new Map<string, Asset>();
|
|
840
|
+
for (const asset of dismissedTokenInfo.assets) {
|
|
841
|
+
const coinMinimalDenom =
|
|
842
|
+
asset.currency?.coinMinimalDenom || asset.coinMinimalDenom;
|
|
843
|
+
if (!coinMinimalDenom) {
|
|
844
|
+
continue;
|
|
845
|
+
}
|
|
846
|
+
dismissedAssetMap.set(coinMinimalDenom, asset);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
for (const asset of info.assets) {
|
|
850
|
+
const coinMinimalDenom =
|
|
851
|
+
asset.currency?.coinMinimalDenom || asset.coinMinimalDenom;
|
|
852
|
+
if (!coinMinimalDenom) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const prevAsset = dismissedAssetMap.get(coinMinimalDenom);
|
|
856
|
+
|
|
857
|
+
// 없던 토큰이 생긴경우
|
|
858
|
+
if (!prevAsset) {
|
|
859
|
+
return true;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const prevAmount = toBigIntSafe(prevAsset.amount);
|
|
863
|
+
const curAmount = toBigIntSafe(asset.amount);
|
|
864
|
+
if (prevAmount == null || curAmount == null) {
|
|
865
|
+
continue;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// 이전에 0이였다가 밸런스가 생긴경우.
|
|
869
|
+
if (prevAmount === BigInt(0) && curAmount > BigInt(0)) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// 이전 밸런스에 배해서 10% 밸런스가 증가한 경우
|
|
874
|
+
if (
|
|
875
|
+
prevAmount > BigInt(0) &&
|
|
876
|
+
curAmount * BigInt(10) >= prevAmount * BigInt(11)
|
|
877
|
+
) {
|
|
878
|
+
return true;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return false;
|
|
884
|
+
}
|
|
652
885
|
}
|
package/src/tx/service.ts
CHANGED