@otoplo/wallet-common 0.2.2 → 0.4.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.
Files changed (90) hide show
  1. package/dist/index.js +1798 -1986
  2. package/dist/index.js.map +1 -1
  3. package/dist/types/index.d.ts +0 -1
  4. package/dist/types/index.d.ts.map +1 -1
  5. package/dist/types/persistence/index.d.ts +1 -1
  6. package/dist/types/persistence/index.d.ts.map +1 -1
  7. package/dist/types/persistence/{datastore/db.d.ts → repository.d.ts} +8 -5
  8. package/dist/types/persistence/repository.d.ts.map +1 -0
  9. package/dist/types/persistence/wallet-db.d.ts +17 -25
  10. package/dist/types/persistence/wallet-db.d.ts.map +1 -1
  11. package/dist/types/services/asset.d.ts +6 -0
  12. package/dist/types/services/asset.d.ts.map +1 -1
  13. package/dist/types/services/cache.d.ts +0 -2
  14. package/dist/types/services/cache.d.ts.map +1 -1
  15. package/dist/types/services/discovery.d.ts +3 -3
  16. package/dist/types/services/discovery.d.ts.map +1 -1
  17. package/dist/types/services/index.d.ts +1 -0
  18. package/dist/types/services/index.d.ts.map +1 -1
  19. package/dist/types/services/key.d.ts +3 -0
  20. package/dist/types/services/key.d.ts.map +1 -1
  21. package/dist/types/services/market.d.ts +33 -0
  22. package/dist/types/services/market.d.ts.map +1 -0
  23. package/dist/types/services/rostrum.d.ts +5 -3
  24. package/dist/types/services/rostrum.d.ts.map +1 -1
  25. package/dist/types/services/transaction.d.ts.map +1 -1
  26. package/dist/types/services/wallet.d.ts +13 -6
  27. package/dist/types/services/wallet.d.ts.map +1 -1
  28. package/dist/types/types/db.types.d.ts +4 -0
  29. package/dist/types/types/db.types.d.ts.map +1 -1
  30. package/dist/types/utils/asset.d.ts +1 -2
  31. package/dist/types/utils/asset.d.ts.map +1 -1
  32. package/dist/types/utils/market.d.ts +0 -3
  33. package/dist/types/utils/market.d.ts.map +1 -1
  34. package/package.json +13 -17
  35. package/src/index.ts +0 -1
  36. package/src/persistence/index.ts +1 -1
  37. package/src/persistence/{datastore/db.ts → repository.ts} +9 -5
  38. package/src/persistence/wallet-db.ts +95 -91
  39. package/src/services/asset.ts +60 -26
  40. package/src/services/cache.ts +4 -14
  41. package/src/services/discovery.ts +54 -42
  42. package/src/services/index.ts +2 -1
  43. package/src/services/key.ts +8 -2
  44. package/src/services/market.ts +143 -0
  45. package/src/services/rostrum.ts +14 -6
  46. package/src/services/transaction.ts +3 -10
  47. package/src/services/wallet.ts +66 -42
  48. package/src/types/db.types.ts +6 -1
  49. package/src/utils/asset.ts +2 -21
  50. package/src/utils/market.ts +0 -54
  51. package/dist/types/persistence/datastore/db.d.ts.map +0 -1
  52. package/dist/types/persistence/datastore/index.d.ts +0 -3
  53. package/dist/types/persistence/datastore/index.d.ts.map +0 -1
  54. package/dist/types/persistence/datastore/kv.d.ts +0 -34
  55. package/dist/types/persistence/datastore/kv.d.ts.map +0 -1
  56. package/dist/types/state/hooks.d.ts +0 -17
  57. package/dist/types/state/hooks.d.ts.map +0 -1
  58. package/dist/types/state/index.d.ts +0 -4
  59. package/dist/types/state/index.d.ts.map +0 -1
  60. package/dist/types/state/slices/auth.d.ts +0 -17
  61. package/dist/types/state/slices/auth.d.ts.map +0 -1
  62. package/dist/types/state/slices/dapp.d.ts +0 -29
  63. package/dist/types/state/slices/dapp.d.ts.map +0 -1
  64. package/dist/types/state/slices/index.d.ts +0 -8
  65. package/dist/types/state/slices/index.d.ts.map +0 -1
  66. package/dist/types/state/slices/loader.d.ts +0 -15
  67. package/dist/types/state/slices/loader.d.ts.map +0 -1
  68. package/dist/types/state/slices/market.d.ts +0 -54
  69. package/dist/types/state/slices/market.d.ts.map +0 -1
  70. package/dist/types/state/slices/notifications.d.ts +0 -142
  71. package/dist/types/state/slices/notifications.d.ts.map +0 -1
  72. package/dist/types/state/slices/status.d.ts +0 -35
  73. package/dist/types/state/slices/status.d.ts.map +0 -1
  74. package/dist/types/state/slices/wallet.d.ts +0 -833
  75. package/dist/types/state/slices/wallet.d.ts.map +0 -1
  76. package/dist/types/state/store.d.ts +0 -26
  77. package/dist/types/state/store.d.ts.map +0 -1
  78. package/src/persistence/datastore/index.ts +0 -2
  79. package/src/persistence/datastore/kv.ts +0 -129
  80. package/src/state/hooks.ts +0 -47
  81. package/src/state/index.ts +0 -3
  82. package/src/state/slices/auth.ts +0 -28
  83. package/src/state/slices/dapp.ts +0 -32
  84. package/src/state/slices/index.ts +0 -7
  85. package/src/state/slices/loader.ts +0 -31
  86. package/src/state/slices/market.ts +0 -47
  87. package/src/state/slices/notifications.ts +0 -44
  88. package/src/state/slices/status.ts +0 -41
  89. package/src/state/slices/wallet.ts +0 -107
  90. package/src/state/store.ts +0 -27
@@ -0,0 +1,143 @@
1
+ import type { WalletDB } from "../persistence/wallet-db";
2
+ import type { MarketData } from "../types/wallet.types";
3
+ import { currencyCodes, type CurrencyCode } from "../utils/market";
4
+
5
+ interface MarketDataResponse {
6
+ id: string;
7
+ circulating_supply?: number | null;
8
+ current_price?: number | null;
9
+ fully_diluted_valuation?: number | null;
10
+ image?: string;
11
+ last_updated?: string;
12
+ market_cap?: number | null;
13
+ market_cap_change_24h?: number | null;
14
+ market_cap_change_percentage_24h?: number | null;
15
+ max_supply?: number | null;
16
+ name?: string;
17
+ price_change_24h?: number | null;
18
+ price_change_percentage_24h?: number | null;
19
+ symbol?: string;
20
+ total_supply?: number | null;
21
+ total_volume?: number | null;
22
+ }
23
+
24
+ export type MarketEvent =
25
+ | { type: 'market_data_updated'; currency: CurrencyCode; data: Record<string, MarketData> }
26
+ | { type: 'currency_updated'; currency: CurrencyCode };
27
+
28
+ export type MarketUpdateCallback = (event: MarketEvent) => void;
29
+
30
+ export class MarketService {
31
+
32
+ private static readonly POLL_INTERVAL_MS = 2 * 60 * 1000;
33
+ private static readonly CURRENCY_SETTING = "market-currency";
34
+ private static readonly SUPPORTED_TOKENS: Record<string, string> = {
35
+ nexacoin: 'nexa'
36
+ };
37
+
38
+ private readonly walletDb: WalletDB;
39
+
40
+ private currency: CurrencyCode = 'usd';
41
+ private running = false;
42
+ private pollTimer?: ReturnType<typeof setTimeout>;
43
+
44
+ private updateCallback?: MarketUpdateCallback;
45
+
46
+ public constructor(walletDb: WalletDB) {
47
+ this.walletDb = walletDb;
48
+ }
49
+
50
+ public onUpdate(callback: MarketUpdateCallback): void {
51
+ this.updateCallback = callback;
52
+ }
53
+
54
+ private notify(event: MarketEvent): void {
55
+ this.updateCallback?.(event);
56
+ }
57
+
58
+ public async init(): Promise<void> {
59
+ this.stop();
60
+
61
+ const stored = await this.walletDb.getSetting(MarketService.CURRENCY_SETTING);
62
+ this.currency = currencyCodes.includes(stored as CurrencyCode) ? stored as CurrencyCode : 'usd';
63
+ this.notify({ type: 'currency_updated', currency: this.currency });
64
+
65
+ this.running = true;
66
+ this.poll();
67
+ }
68
+
69
+ public stop(): void {
70
+ this.running = false;
71
+ clearTimeout(this.pollTimer);
72
+ this.pollTimer = undefined;
73
+ }
74
+
75
+ public getCurrency(): CurrencyCode {
76
+ return this.currency;
77
+ }
78
+
79
+ public async setCurrency(currency: CurrencyCode): Promise<void> {
80
+ if (currency === this.currency) {
81
+ return;
82
+ }
83
+
84
+ await this.walletDb.saveSetting(MarketService.CURRENCY_SETTING, currency);
85
+ this.currency = currency;
86
+ this.notify({ type: 'currency_updated', currency });
87
+
88
+ this.poll();
89
+ }
90
+
91
+ private async poll(): Promise<void> {
92
+ clearTimeout(this.pollTimer);
93
+ const currency = this.currency;
94
+ try {
95
+ const data = await this.fetchMarketData(currency);
96
+ if (currency === this.currency) {
97
+ this.notify({ type: 'market_data_updated', currency, data });
98
+ }
99
+ } catch (e) {
100
+ console.error(e);
101
+ } finally {
102
+ this.scheduleNext();
103
+ }
104
+ }
105
+
106
+ private scheduleNext(): void {
107
+ clearTimeout(this.pollTimer);
108
+ if (!this.running) {
109
+ return;
110
+ }
111
+ this.pollTimer = setTimeout(() => this.poll(), MarketService.POLL_INTERVAL_MS);
112
+ }
113
+
114
+ private async fetchMarketData(currency: CurrencyCode): Promise<Record<string, MarketData>> {
115
+ const ids = Object.keys(MarketService.SUPPORTED_TOKENS).join(",");
116
+ const res = await fetch(`https://api.coingecko.com/api/v3/coins/markets?ids=${ids}&vs_currency=${currency}`);
117
+ if (!res.ok) {
118
+ throw new Error("Failed to fetch market data");
119
+ }
120
+
121
+ const payload: MarketDataResponse[] = await res.json();
122
+ const data: Record<string, MarketData> = {};
123
+ payload.forEach(entry => {
124
+ data[MarketService.SUPPORTED_TOKENS[entry.id]] = {
125
+ id: entry.id,
126
+ name: entry.name ?? '',
127
+ symbol: entry.symbol ?? '',
128
+ image: entry.image ?? '',
129
+ price: entry.current_price ?? 0,
130
+ priceChange: entry.price_change_24h,
131
+ priceChangePercentage: entry.price_change_percentage_24h,
132
+ marketCap: entry.market_cap,
133
+ fdv: entry.fully_diluted_valuation,
134
+ circSupply: entry.circulating_supply,
135
+ totalSupply: entry.total_supply,
136
+ maxSupply: entry.max_supply,
137
+ totalVolume: entry.total_volume
138
+ }
139
+ });
140
+
141
+ return data;
142
+ }
143
+ }
@@ -3,18 +3,18 @@ import { ConnectionStatus, ElectrumClient, TransportScheme } from "@otoplo/elect
3
3
  import type { BlockTip, IFirstUse, IListUnspentRecord, ITokenGenesis, ITokenListUnspent, ITokensBalance, ITokenUtxo, ITransaction, ITXHistory, IUtxo, RostrumParams, ServerFeatures } from "../types/rostrum.types";
4
4
  import type { Balance } from "../types/wallet.types";
5
5
  import { isTestnet } from "../utils/common";
6
- import type { KVStore } from "../persistence/datastore/kv";
6
+ import type { WalletDB } from "../persistence";
7
7
 
8
8
  type RPCParameter = object | string | number | boolean | null;
9
9
 
10
10
  export class RostrumService {
11
11
 
12
- private readonly kvStore: KVStore;
12
+ private readonly walletDb: WalletDB;
13
13
 
14
14
  private client?: ElectrumClient;
15
15
 
16
- public constructor(kvStore: KVStore) {
17
- this.kvStore = kvStore;
16
+ public constructor(walletDb: WalletDB) {
17
+ this.walletDb = walletDb;
18
18
  }
19
19
 
20
20
  public getFeatures(): Promise<ServerFeatures> {
@@ -176,10 +176,18 @@ export class RostrumService {
176
176
  });
177
177
  }
178
178
 
179
+ public setCurrentInstance(params: RostrumParams): Promise<void> {
180
+ return this.walletDb.saveSetting("rostrum-params", JSON.stringify(params));
181
+ }
182
+
183
+ public resetCurrentInstance(): Promise<void> {
184
+ return this.walletDb.removeSetting("rostrum-params");
185
+ }
186
+
179
187
  public async getCurrentInstance(): Promise<RostrumParams> {
180
- const params = await this.kvStore.getRostrumParams();
188
+ const params = await this.walletDb.getSetting("rostrum-params");
181
189
  if (params) {
182
- return params;
190
+ return JSON.parse(params);
183
191
  }
184
192
  return RostrumService.getPredefinedInstances()[0];
185
193
  }
@@ -2,7 +2,7 @@ import type { PrivateKey, PublicKey, Script, UTXO } from "libnexa-ts";
2
2
  import { Address, AddressType, Transaction, TransactionBuilder, UnitUtils } from "libnexa-ts";
3
3
  import type { KeyManager } from "./key";
4
4
  import type { RostrumService } from "./rostrum";
5
- import type { AssetMovement, TransactionDTO, TransactionEntity, TransactionType } from "../types/db.types";
5
+ import type { AssetMovement, TransactionDTO, TransactionType } from "../types/db.types";
6
6
  import { currentTimestamp, isNullOrEmpty, isValidNexaAddress, MAX_INT64, tokenAmountToAssetAmount, tokenIdToHex, VAULT_FIRST_BLOCK } from "../utils/common";
7
7
  import type { WalletDB } from "../persistence/wallet-db";
8
8
  import type { ITXHistory } from "../types/rostrum.types";
@@ -68,26 +68,19 @@ export class TransactionService {
68
68
  const txDto = await this.classifyTransaction(txHash, myAddresses);
69
69
  txDto.accountId = accountId;
70
70
 
71
- const txEntity: TransactionEntity = {
72
- ...txDto,
73
- othersOutputs: JSON.stringify(txDto.othersOutputs),
74
- myOutputs: JSON.stringify(txDto.myOutputs),
75
- myInputs: JSON.stringify(txDto.myInputs)
76
- };
77
-
78
71
  const involvedAssets = new Set<string>();
79
72
  txDto.myInputs.forEach(m => m.assetId && involvedAssets.add(m.assetId));
80
73
  txDto.myOutputs.forEach(m => m.assetId && involvedAssets.add(m.assetId));
81
74
 
82
75
  for (const assetId of involvedAssets) {
83
- await this.walletDb.addAssetTransaction({
76
+ await this.walletDb.upsertAssetTransaction({
84
77
  accountId: accountId,
85
78
  assetId: assetId,
86
79
  txIdem: txDto.txIdem,
87
80
  time: txDto.time
88
81
  });
89
82
  }
90
- await this.walletDb.addLocalTransaction(txEntity);
83
+ await this.walletDb.upsertTransaction(txDto);
91
84
  }
92
85
 
93
86
  private async classifyTransaction(txHash: string, myAddresses: string[]): Promise<TransactionDTO> {
@@ -1,16 +1,16 @@
1
- import type { KVStore, WalletDB } from "../persistence";
1
+ import type { WalletDB } from "../persistence";
2
2
  import type { Account, AccountDTO, AddressDTO, AddressKey, Balance, ITXHistory, RostrumParams, VaultDTO, VaultInfo } from "../types";
3
3
  import { AccountType, keyPathToString, KeySpace, MAIN_WALLET_ID, sumBalance, sumTokensBalance } from "../utils";
4
4
  import type { AssetService } from "./asset";
5
5
  import { WalletDiscoveryService } from "./discovery";
6
- import type { KeyManager } from "./key";
6
+ import { KeyManager } from "./key";
7
7
  import type { RostrumService } from "./rostrum";
8
8
  import type { SessionManager } from "./session";
9
9
  import type { TransactionService } from "./transaction";
10
10
 
11
11
  type WalletAddress = AccountDTO | AddressDTO | VaultDTO;
12
12
 
13
- interface AddressHistory {
13
+ export interface AddressHistory {
14
14
  address: WalletAddress;
15
15
  txs: ITXHistory[];
16
16
  }
@@ -35,10 +35,10 @@ export type WalletUpdateCallback = (event: WalletEvent) => void;
35
35
 
36
36
  export class WalletManager {
37
37
 
38
- private static readonly GAP_LIMIT = 20;
39
- private static readonly DEBOUNCE_MS = 1000;
38
+ private static readonly DEBOUNCE_MS = 2000;
40
39
 
41
40
  private addressResolvers: Map<string, () => void>;
41
+ private discoveryResolver?: () => void;
42
42
 
43
43
  public accounts: Map<number, AccountDTO>;
44
44
  public accountsAddressToId: Map<string, number>;
@@ -57,7 +57,6 @@ export class WalletManager {
57
57
 
58
58
  public constructor(
59
59
  private readonly keyManager: KeyManager,
60
- private readonly kvStore: KVStore,
61
60
  private readonly walletDb: WalletDB,
62
61
  private readonly rostrumService: RostrumService,
63
62
  private readonly assetService: AssetService,
@@ -121,7 +120,7 @@ export class WalletManager {
121
120
  }
122
121
 
123
122
  public async reloadRostrum(params: RostrumParams): Promise<void> {
124
- await this.kvStore.saveRostrumParams(params);
123
+ await this.rostrumService.setCurrentInstance(params);
125
124
  await this.rostrumService.disconnect(true);
126
125
  await this.initRostrum();
127
126
  await this.subscribeAddresses(this.getAllAddresses())
@@ -152,9 +151,9 @@ export class WalletManager {
152
151
 
153
152
  const [rIndex, cIndex, dappIndex] = await Promise.all([rIndexPromise, cIndexPromise, dappIndexPromis]);
154
153
 
155
- this.receiveAddresses = this.deriveAddresses(KeySpace.RECEIVE, 0, rIndex + WalletManager.GAP_LIMIT);
156
- this.changeAddresses = this.deriveAddresses(KeySpace.CHANGE, 0, cIndex + WalletManager.GAP_LIMIT);
157
- this.accounts = this.deriveAccounts(0, dappIndex + 1);
154
+ this.receiveAddresses = this.deriveAddresses(KeySpace.RECEIVE, 0, rIndex + KeyManager.GAP_LIMIT + 1);
155
+ this.changeAddresses = this.deriveAddresses(KeySpace.CHANGE, 0, cIndex + KeyManager.GAP_LIMIT + 1);
156
+ this.accounts = this.deriveAccounts(0, dappIndex < 0 ? 1 : dappIndex + 1);
158
157
  this.accounts.forEach(entry => this.accountsAddressToId.set(entry.address, entry.id));
159
158
 
160
159
  await this.saveAddresses(this.receiveAddresses);
@@ -167,8 +166,6 @@ export class WalletManager {
167
166
 
168
167
  this.notify({ type: 'discover_wallet', loading: false });
169
168
  this.notify({ type: 'load_wallet', loading: false });
170
-
171
- await this.rescanVaults();
172
169
  }
173
170
 
174
171
  private async loadWallet(): Promise<void> {
@@ -191,13 +188,6 @@ export class WalletManager {
191
188
 
192
189
  private async initialSync(): Promise<void> {
193
190
  await this.subscribeAddresses(this.getAllAddresses(), true);
194
-
195
- const tokensLoadPromises: Promise<void>[] = [];
196
- tokensLoadPromises.push(this.assetService.syncTokens(MAIN_WALLET_ID, sumTokensBalance(this.getMainAddresses().map(a => a.tokensBalance))));
197
- for (const account of this.accounts.values()) {
198
- tokensLoadPromises.push(this.assetService.syncTokens(account.id, account.tokensBalance));
199
- }
200
- await Promise.all(tokensLoadPromises);
201
191
  }
202
192
 
203
193
  public async reconnectSync(): Promise<void> {
@@ -214,7 +204,7 @@ export class WalletManager {
214
204
  type: 'new_account',
215
205
  account: {
216
206
  id: MAIN_WALLET_ID,
217
- name: 'Main Wallet',
207
+ name: 'HD Wallet',
218
208
  address: this.getReceiveAddress(),
219
209
  balance: sumBalance(walletAddresses.map(a => a.balance)),
220
210
  tokensBalance: sumTokensBalance(walletAddresses.map(a => a.tokensBalance)),
@@ -252,11 +242,8 @@ export class WalletManager {
252
242
  private deriveAddresses(space: KeySpace, startIndex: number, count: number): AddressDTO[] {
253
243
  const newAddresses: AddressDTO[] = [];
254
244
  for (let i = startIndex; i < startIndex + count; i++) {
255
- const path = keyPathToString(AccountType.MAIN, space, i);
256
- const address = this.keyManager.getKey(path).privateKey.toAddress().toString();
257
-
258
245
  const addressDTO: AddressDTO = {
259
- address: address,
246
+ address: this.keyManager.deriveAddress(AccountType.MAIN, space, i),
260
247
  space: space,
261
248
  idx: i,
262
249
  used: false,
@@ -276,13 +263,10 @@ export class WalletManager {
276
263
  private deriveAccounts(startIndex: number, count: number): Map<number, AccountDTO> {
277
264
  const newAccounts = new Map<number, AccountDTO>();
278
265
  for (let i = startIndex; i < startIndex + count; i++) {
279
- const path = keyPathToString(AccountType.DAPP, 0, i);
280
- const address = this.keyManager.getKey(path).privateKey.toAddress().toString();
281
-
282
266
  const account: AccountDTO = {
283
267
  id: i,
284
268
  name: `Account ${i + 1}`,
285
- address: address,
269
+ address: this.keyManager.deriveAddress(AccountType.DAPP, KeySpace.RECEIVE, i),
286
270
  height: 0,
287
271
  hidden: 0,
288
272
  statusHash: '',
@@ -325,6 +309,13 @@ export class WalletManager {
325
309
  this.addressResolvers.set(addr.address, resolve);
326
310
  });
327
311
  initPromises.push(p);
312
+
313
+ if (!this.discoveryResolver) {
314
+ const discoveryPromise = new Promise<void>((resolve) => {
315
+ this.discoveryResolver = resolve;
316
+ });
317
+ initPromises.push(discoveryPromise);
318
+ }
328
319
  }
329
320
  this.registerUpdate({address: addr, result});
330
321
  }
@@ -384,10 +375,7 @@ export class WalletManager {
384
375
  }
385
376
  }
386
377
 
387
- const accountsTxs = await Promise.all(accountsHistoryPromises);
388
- await Promise.all(vaultsHistoryPromises);
389
- const walletTxs = await Promise.all(walletHistoryPromises);
390
-
378
+ let initResolved = false;
391
379
  if (this.addressResolvers.size > 0) {
392
380
  for (const [address,] of updates) {
393
381
  const resolver = this.addressResolvers.get(address);
@@ -396,9 +384,23 @@ export class WalletManager {
396
384
  this.addressResolvers.delete(address);
397
385
  }
398
386
  }
387
+ initResolved = this.addressResolvers.size === 0;
388
+ }
389
+
390
+ const accountsTxs = await Promise.all(accountsHistoryPromises);
391
+ await Promise.all(vaultsHistoryPromises);
392
+ const walletTxs = await Promise.all(walletHistoryPromises);
393
+
394
+ const postPromises: Promise<void>[] = [this.postProcessWalletUpdate(walletTxs, initResolved), this.postProcessAccountsUpdate(accountsTxs)];
395
+ if (initResolved) {
396
+ postPromises.push(this.recoverTokens());
399
397
  }
398
+ await Promise.all(postPromises);
400
399
 
401
- await Promise.all([this.postProcessWalletUpdate(walletTxs), this.postProcessAccountsUpdate(accountsTxs)]);
400
+ if (initResolved && this.discoveryResolver) {
401
+ this.discoveryResolver();
402
+ this.discoveryResolver = undefined;
403
+ }
402
404
  } catch (e) {
403
405
  console.error('Error processing pending updates:', e);
404
406
  } finally {
@@ -406,7 +408,7 @@ export class WalletManager {
406
408
  }
407
409
  }
408
410
 
409
- private async postProcessWalletUpdate(history: AddressHistory[]): Promise<void> {
411
+ private async postProcessWalletUpdate(history: AddressHistory[], processVaults: boolean): Promise<void> {
410
412
  if (history.length === 0) {
411
413
  return;
412
414
  }
@@ -417,6 +419,11 @@ export class WalletManager {
417
419
  txHistoryMap.set(tx.tx_hash, tx);
418
420
  }
419
421
  }
422
+ const txHistory = Array.from(txHistoryMap.values());
423
+
424
+ if (processVaults) {
425
+ this.recoverVaults(txHistory);
426
+ }
420
427
 
421
428
  await this.checkGapLimit(KeySpace.RECEIVE);
422
429
  await this.checkGapLimit(KeySpace.CHANGE);
@@ -428,7 +435,7 @@ export class WalletManager {
428
435
  this.notify({ type: 'account_balance_updated', accountId: MAIN_WALLET_ID, balance: walletBalance, tokensBalance: walletTokenBalances });
429
436
  this.notify({ type: 'main_address_updated', address: this.getReceiveAddress() });
430
437
 
431
- await this.postProcessActivity(MAIN_WALLET_ID, Array.from(txHistoryMap.values()), walletAddresses.map(a => a.address), walletTokenBalances);
438
+ await this.postProcessActivity(MAIN_WALLET_ID, txHistory, walletAddresses.map(a => a.address), walletTokenBalances);
432
439
  }
433
440
 
434
441
  private async postProcessAccountsUpdate(history: AddressHistory[]): Promise<void> {
@@ -509,8 +516,8 @@ export class WalletManager {
509
516
  const spaceName = keySpace === KeySpace.RECEIVE ? 'Receive' : 'Change';
510
517
  const unused = addresses.filter(a => !a.used).length;
511
518
 
512
- if (unused < WalletManager.GAP_LIMIT) {
513
- const needed = WalletManager.GAP_LIMIT - unused;
519
+ if (unused < KeyManager.GAP_LIMIT) {
520
+ const needed = KeyManager.GAP_LIMIT - unused;
514
521
  const lastIndex = addresses[addresses.length - 1].idx;
515
522
 
516
523
  console.log(`Deriving ${needed} more ${spaceName} addresses...`);
@@ -529,8 +536,7 @@ export class WalletManager {
529
536
  }
530
537
 
531
538
  public async addNewAccount(id: number, name: string): Promise<void> {
532
- const path = keyPathToString(AccountType.DAPP, 0, id);
533
- const address = this.keyManager.getKey(path).privateKey.toAddress().toString();
539
+ const address = this.keyManager.deriveAddress(AccountType.DAPP, KeySpace.RECEIVE, id);
534
540
 
535
541
  const account: AccountDTO = {
536
542
  id: id,
@@ -587,6 +593,15 @@ export class WalletManager {
587
593
  }
588
594
  }
589
595
 
596
+ public rescanVaults(): Promise<AddressHistory[]> {
597
+ const vaultsPromises: Promise<AddressHistory>[] = [];
598
+ for (const vault of this.vaults.values()) {
599
+ const p = this.fetchAndUpdateVault(vault, vault.statusHash);
600
+ vaultsPromises.push(p);
601
+ }
602
+ return Promise.all(vaultsPromises);
603
+ }
604
+
590
605
  public getVaultNextIndex(): number {
591
606
  if (this.vaults.size == 0) {
592
607
  return 0;
@@ -624,9 +639,9 @@ export class WalletManager {
624
639
  await this.subscribeAddresses([vault]);
625
640
  }
626
641
 
627
- public async rescanVaults(): Promise<boolean> {
642
+ public async recoverVaults(txs: ITXHistory[]): Promise<boolean> {
628
643
  let found = false;
629
- const vaults = await this.discoveryService.discoverVaults(this.getMainAddresses().map(a => a.address));
644
+ const vaults = await this.discoveryService.discoverVaults(txs);
630
645
  const promises: Promise<void>[] = [];
631
646
  for (const [address, vault] of vaults) {
632
647
  if (!this.vaults.has(address)) {
@@ -638,6 +653,15 @@ export class WalletManager {
638
653
  return found;
639
654
  }
640
655
 
656
+ private async recoverTokens(): Promise<void> {
657
+ const tokensLoadPromises: Promise<void>[] = [];
658
+ tokensLoadPromises.push(this.assetService.syncTokens(MAIN_WALLET_ID, sumTokensBalance(this.getMainAddresses().map(a => a.tokensBalance))));
659
+ for (const account of this.accounts.values()) {
660
+ tokensLoadPromises.push(this.assetService.syncTokens(account.id, account.tokensBalance));
661
+ }
662
+ await Promise.all(tokensLoadPromises);
663
+ }
664
+
641
665
  private isVaultAddress(address: AccountDTO | AddressDTO | VaultDTO): address is VaultDTO {
642
666
  return address.type == AccountType.VAULT;
643
667
  }
@@ -649,4 +673,4 @@ export class WalletManager {
649
673
  private isMainAddress(address: AccountDTO | AddressDTO | VaultDTO): address is AddressDTO {
650
674
  return address.type == AccountType.MAIN;
651
675
  }
652
- }
676
+ }
@@ -144,4 +144,9 @@ export interface VaultEntity {
144
144
  statusHash: string;
145
145
  balance: string;
146
146
  tokensBalance: string;
147
- }
147
+ }
148
+
149
+ export interface SettingEntity {
150
+ name: string;
151
+ value: string;
152
+ }
@@ -1,5 +1,4 @@
1
1
  import { isTestnet } from "./common";
2
- import type { AssetInfo } from "../types";
3
2
 
4
3
  export function transformTokenIconUrl(icon: string, documentUrl: string): string {
5
4
  if (icon && typeof icon === 'string') {
@@ -32,8 +31,8 @@ function translateIfIpfsUrl(url: string, gateway = "https://ipfs.nebula.markets/
32
31
  return url;
33
32
  }
34
33
 
35
- export function getAssetMetadata(token: string): Promise<AssetInfo> {
36
- return performGet(`${getAssetsEndpoint()}/metadata/${token}`, 'json');
34
+ export function getAssetMetadataUrl(token: string): string {
35
+ return `${getAssetsEndpoint()}/metadata/${token}`;
37
36
  }
38
37
 
39
38
  export function getAssetFileUrl(token:string, asset: string): string {
@@ -43,21 +42,3 @@ export function getAssetFileUrl(token:string, asset: string): string {
43
42
  function getAssetsEndpoint(): string {
44
43
  return `https://${isTestnet() ? 'testapi' : 'api'}.otoplo.com`;
45
44
  }
46
-
47
- async function performGet(url: string, responseType?: 'json' | 'raw'): Promise<any> {
48
- try {
49
- const response = await fetch(url);
50
- if (!response.ok) {
51
- throw new Error(`Failed to perform GET. status: ${response.status}`);
52
- }
53
-
54
- if (responseType === 'raw') {
55
- const arrayBuffer = await response.arrayBuffer();
56
- return new Uint8Array(arrayBuffer);
57
- } else {
58
- return await response.json();
59
- }
60
- } catch (e) {
61
- throw new Error(`Unexpected Error: ${e}`);
62
- }
63
- }
@@ -1,24 +1,4 @@
1
1
  import bigDecimal from "js-big-decimal";
2
- import type { MarketData } from "../types/wallet.types";
3
-
4
- interface MarketDataResponse {
5
- id: string;
6
- circulating_supply?: number | null;
7
- current_price?: number | null;
8
- fully_diluted_valuation?: number | null;
9
- image?: string;
10
- last_updated?: string;
11
- market_cap?: number | null;
12
- market_cap_change_24h?: number | null;
13
- market_cap_change_percentage_24h?: number | null;
14
- max_supply?: number | null;
15
- name?: string;
16
- price_change_24h?: number | null;
17
- price_change_percentage_24h?: number | null;
18
- symbol?: string;
19
- total_supply?: number | null;
20
- total_volume?: number | null;
21
- }
22
2
 
23
3
  export const CURRENCIES = [
24
4
  { code: 'usd', symbol: '$', name: 'US Dollar' },
@@ -42,40 +22,6 @@ export const currencySymbols = Object.fromEntries(
42
22
 
43
23
  export const currencyCodes = CURRENCIES.map(c => c.code);
44
24
 
45
- export const SUPPORTED_TOKENS: Record<string, string> = {
46
- nexacoin: 'nexa'
47
- };
48
-
49
- export async function getMarketData(currency: CurrencyCode): Promise<Record<string, MarketData>> {
50
- const ids = Object.keys(SUPPORTED_TOKENS).join(",");
51
- const res = await fetch(`https://api.coingecko.com/api/v3/coins/markets?ids=${ids}&vs_currency=${currency}`);
52
- if (!res.ok) {
53
- throw new Error("Failed to fetch market data");
54
- }
55
-
56
- const payload: MarketDataResponse[] = await res.json();
57
- const data: Record<string, MarketData> = {};
58
- payload.forEach(entry => {
59
- data[SUPPORTED_TOKENS[entry.id]] = {
60
- id: entry.id,
61
- name: entry.name ?? '',
62
- symbol: entry.symbol ?? '',
63
- image: entry.image ?? '',
64
- price: entry.current_price ?? 0,
65
- priceChange: entry.price_change_24h,
66
- priceChangePercentage: entry.price_change_percentage_24h,
67
- marketCap: entry.market_cap,
68
- fdv: entry.fully_diluted_valuation,
69
- circSupply: entry.circulating_supply,
70
- totalSupply: entry.total_supply,
71
- maxSupply: entry.max_supply,
72
- totalVolume: entry.total_volume
73
- }
74
- });
75
-
76
- return data;
77
- }
78
-
79
25
  export function getCurrencySymbol(currency: CurrencyCode): string {
80
26
  return currencySymbols[currency] || currency;
81
27
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../../src/persistence/datastore/db.ts"],"names":[],"mappings":"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"}
@@ -1,3 +0,0 @@
1
- export * from './db';
2
- export * from './kv';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/persistence/datastore/index.ts"],"names":[],"mappings":"AAAA,cAAc,MAAM,CAAC;AACrB,cAAc,MAAM,CAAC"}
@@ -1,34 +0,0 @@
1
- import { RostrumParams } from '../../types/rostrum.types';
2
- export declare abstract class KVStore {
3
- abstract getValue(key: string): Promise<string | null>;
4
- abstract setValue(key: string, value: string): Promise<void>;
5
- abstract removeValue(key: string): Promise<void>;
6
- abstract clearData(): Promise<void>;
7
- protected getPrefix(): string;
8
- getEncryptedSeed(): Promise<string | null>;
9
- saveEncryptedSeed(seed: string): Promise<void>;
10
- getVersionCode(): Promise<string | null>;
11
- setVersionCode(code: string): Promise<void>;
12
- getReleaseNumber(): Promise<string | null>;
13
- setReleaseNumber(release: string): Promise<void>;
14
- getRostrumParams(): Promise<RostrumParams | undefined>;
15
- saveRostrumParams(params: RostrumParams): Promise<void>;
16
- removeRostrumParams(): Promise<void>;
17
- setHideZeroTokenConfig(hide: boolean): Promise<void>;
18
- getHideZeroTokenConfig(): Promise<boolean>;
19
- setHideZeroVaultsConfig(hide: boolean): Promise<void>;
20
- getHideZeroVaultsConfig(): Promise<boolean>;
21
- getSelectedCurrency(): Promise<string>;
22
- setSelectedCurrency(currency: string): Promise<void>;
23
- getUseBiometric(): Promise<boolean>;
24
- setUseBiometric(useBiometric: boolean): Promise<void>;
25
- getRequireAuth(): Promise<boolean>;
26
- setRequireAuth(requireAuth: boolean): Promise<void>;
27
- getReleaseNotesTime(): Promise<number | undefined>;
28
- setReleaseNotesTime(time: string): Promise<void>;
29
- getIsTestnet(): Promise<boolean>;
30
- setIsTestnet(isTestnet: boolean): Promise<void>;
31
- getAutoLockSeconds(): Promise<number>;
32
- setAutoLockSeconds(autoLock: number): Promise<void>;
33
- }
34
- //# sourceMappingURL=kv.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"kv.d.ts","sourceRoot":"","sources":["../../../../src/persistence/datastore/kv.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,8BAAsB,OAAO;aAEX,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;aAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;aACnD,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;aACvC,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAE1C,SAAS,CAAC,SAAS,IAAI,MAAM;IAIhB,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAI1C,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAIxC,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,gBAAgB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAI1C,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,gBAAgB,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;IAOtD,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,sBAAsB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,sBAAsB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK1C,uBAAuB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK3C,mBAAmB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKtC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpD,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC;IAKnC,eAAe,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAKlC,cAAc,CAAC,WAAW,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAKlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC;IAKhC,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;IAKrC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGjE"}