@otoplo/wallet-common 0.1.15 → 0.2.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/dist/index.js +555 -579
- package/dist/index.js.map +1 -1
- package/dist/types/persistence/datastore/db.d.ts +5 -3
- package/dist/types/persistence/datastore/db.d.ts.map +1 -1
- package/dist/types/persistence/wallet-db.d.ts +10 -18
- package/dist/types/persistence/wallet-db.d.ts.map +1 -1
- package/dist/types/services/asset.d.ts +5 -9
- package/dist/types/services/asset.d.ts.map +1 -1
- package/dist/types/services/cache.d.ts +4 -2
- package/dist/types/services/cache.d.ts.map +1 -1
- package/dist/types/services/wallet.d.ts +2 -1
- package/dist/types/services/wallet.d.ts.map +1 -1
- package/dist/types/state/hooks.d.ts +2 -1
- package/dist/types/state/hooks.d.ts.map +1 -1
- package/dist/types/state/slices/market.d.ts +54 -0
- package/dist/types/state/slices/market.d.ts.map +1 -0
- package/dist/types/state/slices/status.d.ts +0 -27
- package/dist/types/state/slices/status.d.ts.map +1 -1
- package/dist/types/state/slices/wallet.d.ts +21 -220
- package/dist/types/state/slices/wallet.d.ts.map +1 -1
- package/dist/types/state/store.d.ts +3 -0
- package/dist/types/state/store.d.ts.map +1 -1
- package/dist/types/types/db.types.d.ts +16 -11
- package/dist/types/types/db.types.d.ts.map +1 -1
- package/dist/types/types/wallet.types.d.ts +39 -14
- package/dist/types/types/wallet.types.d.ts.map +1 -1
- package/dist/types/utils/asset.d.ts +3 -6
- package/dist/types/utils/asset.d.ts.map +1 -1
- package/dist/types/utils/common.d.ts +0 -1
- package/dist/types/utils/common.d.ts.map +1 -1
- package/dist/types/utils/enums.d.ts +0 -4
- package/dist/types/utils/enums.d.ts.map +1 -1
- package/dist/types/utils/index.d.ts +1 -1
- package/dist/types/utils/index.d.ts.map +1 -1
- package/dist/types/utils/{price.d.ts → market.d.ts} +19 -13
- package/dist/types/utils/market.d.ts.map +1 -0
- package/package.json +4 -5
- package/src/persistence/datastore/db.ts +5 -3
- package/src/persistence/wallet-db.ts +28 -28
- package/src/services/asset.ts +69 -162
- package/src/services/cache.ts +12 -2
- package/src/services/wallet.ts +23 -21
- package/src/state/hooks.ts +3 -1
- package/src/state/slices/market.ts +47 -0
- package/src/state/slices/status.ts +2 -31
- package/src/state/slices/wallet.ts +8 -13
- package/src/state/store.ts +3 -0
- package/src/types/db.types.ts +17 -12
- package/src/types/wallet.types.ts +40 -16
- package/src/utils/asset.ts +12 -52
- package/src/utils/common.ts +0 -6
- package/src/utils/enums.ts +0 -5
- package/src/utils/index.ts +1 -1
- package/src/utils/market.ts +97 -0
- package/dist/types/utils/price.d.ts.map +0 -1
- package/src/utils/price.ts +0 -46
package/src/services/wallet.ts
CHANGED
|
@@ -193,11 +193,10 @@ export class WalletManager {
|
|
|
193
193
|
await this.subscribeAddresses(this.getAllAddresses(), true);
|
|
194
194
|
|
|
195
195
|
const tokensLoadPromises: Promise<void>[] = [];
|
|
196
|
-
tokensLoadPromises.push(this.assetService.
|
|
196
|
+
tokensLoadPromises.push(this.assetService.syncTokens(MAIN_WALLET_ID, sumTokensBalance(this.getMainAddresses().map(a => a.tokensBalance))));
|
|
197
197
|
for (const account of this.accounts.values()) {
|
|
198
|
-
tokensLoadPromises.push(this.assetService.
|
|
198
|
+
tokensLoadPromises.push(this.assetService.syncTokens(account.id, account.tokensBalance));
|
|
199
199
|
}
|
|
200
|
-
|
|
201
200
|
await Promise.all(tokensLoadPromises);
|
|
202
201
|
}
|
|
203
202
|
|
|
@@ -219,7 +218,6 @@ export class WalletManager {
|
|
|
219
218
|
address: this.getReceiveAddress(),
|
|
220
219
|
balance: sumBalance(walletAddresses.map(a => a.balance)),
|
|
221
220
|
tokensBalance: sumTokensBalance(walletAddresses.map(a => a.tokensBalance)),
|
|
222
|
-
tokens: [],
|
|
223
221
|
sessions: {}
|
|
224
222
|
}
|
|
225
223
|
});
|
|
@@ -233,7 +231,6 @@ export class WalletManager {
|
|
|
233
231
|
address: account.address,
|
|
234
232
|
balance: account.balance,
|
|
235
233
|
tokensBalance: account.tokensBalance,
|
|
236
|
-
tokens: [],
|
|
237
234
|
sessions: {}
|
|
238
235
|
}
|
|
239
236
|
});
|
|
@@ -401,7 +398,7 @@ export class WalletManager {
|
|
|
401
398
|
}
|
|
402
399
|
}
|
|
403
400
|
|
|
404
|
-
await Promise.all([this.postProcessWalletUpdate(walletTxs), this.
|
|
401
|
+
await Promise.all([this.postProcessWalletUpdate(walletTxs), this.postProcessAccountsUpdate(accountsTxs)]);
|
|
405
402
|
} catch (e) {
|
|
406
403
|
console.error('Error processing pending updates:', e);
|
|
407
404
|
} finally {
|
|
@@ -431,29 +428,35 @@ export class WalletManager {
|
|
|
431
428
|
this.notify({ type: 'account_balance_updated', accountId: MAIN_WALLET_ID, balance: walletBalance, tokensBalance: walletTokenBalances });
|
|
432
429
|
this.notify({ type: 'main_address_updated', address: this.getReceiveAddress() });
|
|
433
430
|
|
|
434
|
-
|
|
435
|
-
for (const tx of txHistoryMap.values()) {
|
|
436
|
-
await this.transactionService.classifyAndSaveTransaction(MAIN_WALLET_ID, tx.tx_hash, walletAddresses.map(a => a.address));
|
|
437
|
-
}
|
|
438
|
-
await nftPromise;
|
|
431
|
+
await this.postProcessActivity(MAIN_WALLET_ID, Array.from(txHistoryMap.values()), walletAddresses.map(a => a.address), walletTokenBalances);
|
|
439
432
|
}
|
|
440
433
|
|
|
441
|
-
private async
|
|
434
|
+
private async postProcessAccountsUpdate(history: AddressHistory[]): Promise<void> {
|
|
442
435
|
if (history.length === 0) {
|
|
443
436
|
return;
|
|
444
437
|
}
|
|
445
438
|
|
|
439
|
+
const activityPromises: Promise<void>[] = [];
|
|
446
440
|
for (const { address, txs } of history) {
|
|
447
441
|
const account = address as AccountDTO;
|
|
448
442
|
|
|
449
443
|
this.notify({ type: 'account_balance_updated', accountId: account.id, balance: account.balance, tokensBalance: account.tokensBalance });
|
|
450
444
|
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
445
|
+
const p = this.postProcessActivity(account.id, txs, [account.address], account.tokensBalance);
|
|
446
|
+
activityPromises.push(p);
|
|
447
|
+
}
|
|
448
|
+
await Promise.all(activityPromises);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private async postProcessActivity(accountId: number, txHistory: ITXHistory[], addresses: string[], tokensBalance: Record<string, Balance>): Promise<void> {
|
|
452
|
+
const postPromises: Promise<void>[] = [];
|
|
453
|
+
const nftPromise = this.assetService.syncNfts(accountId, tokensBalance);
|
|
454
|
+
for (const tx of txHistory) {
|
|
455
|
+
const p = this.transactionService.classifyAndSaveTransaction(accountId, tx.tx_hash, addresses);
|
|
456
|
+
postPromises.push(p);
|
|
456
457
|
}
|
|
458
|
+
postPromises.push(nftPromise);
|
|
459
|
+
await Promise.all(postPromises);
|
|
457
460
|
}
|
|
458
461
|
|
|
459
462
|
private async fetchAndUpdateAddress(addrDTO: AddressDTO, hash: string): Promise<AddressHistory> {
|
|
@@ -544,7 +547,6 @@ export class WalletManager {
|
|
|
544
547
|
this.accounts.set(id, account);
|
|
545
548
|
this.accountsAddressToId.set(address, id);
|
|
546
549
|
await this.walletDb.saveAccount(account);
|
|
547
|
-
await this.subscribeAddresses([account]);
|
|
548
550
|
|
|
549
551
|
this.notify({
|
|
550
552
|
type: 'new_account',
|
|
@@ -554,10 +556,10 @@ export class WalletManager {
|
|
|
554
556
|
address: account.address,
|
|
555
557
|
balance: account.balance,
|
|
556
558
|
tokensBalance: account.tokensBalance,
|
|
557
|
-
tokens: [],
|
|
558
559
|
sessions: {}
|
|
559
560
|
}
|
|
560
561
|
});
|
|
562
|
+
await this.subscribeAddresses([account]);
|
|
561
563
|
}
|
|
562
564
|
|
|
563
565
|
public async updateAccountName(id: number, name: string): Promise<void> {
|
|
@@ -609,8 +611,7 @@ export class WalletManager {
|
|
|
609
611
|
private async addVault(vault: VaultDTO): Promise<void> {
|
|
610
612
|
this.vaults.set(vault.address, vault);
|
|
611
613
|
await this.walletDb.saveVault(vault);
|
|
612
|
-
|
|
613
|
-
|
|
614
|
+
|
|
614
615
|
this.notify({
|
|
615
616
|
type: 'new_vault',
|
|
616
617
|
vault: {
|
|
@@ -620,6 +621,7 @@ export class WalletManager {
|
|
|
620
621
|
index: vault.idx
|
|
621
622
|
}
|
|
622
623
|
});
|
|
624
|
+
await this.subscribeAddresses([vault]);
|
|
623
625
|
}
|
|
624
626
|
|
|
625
627
|
public async rescanVaults(): Promise<boolean> {
|
package/src/state/hooks.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { useSelector } from "react-redux";
|
|
|
2
2
|
import type { SharedState } from "./store";
|
|
3
3
|
import { createSelector } from "@reduxjs/toolkit";
|
|
4
4
|
import type { AuthState } from "./slices";
|
|
5
|
-
import type { Account, AppNotification, Balance, SessionInfo, VaultInfo } from "../types";
|
|
5
|
+
import type { Account, AppNotification, Balance, MarketData, SessionInfo, VaultInfo } from "../types";
|
|
6
6
|
|
|
7
7
|
const useSharedSelector = useSelector.withTypes<SharedState>();
|
|
8
8
|
|
|
9
9
|
export const useAuth = (): AuthState => useSharedSelector(state => state.auth);
|
|
10
10
|
|
|
11
|
+
export const useMarketData = (tokenId: string): MarketData => useSharedSelector(state => state.market.data[tokenId]);
|
|
12
|
+
|
|
11
13
|
export const useBlockHeight = (): number => useSharedSelector(state => state.status.height);
|
|
12
14
|
|
|
13
15
|
export const useAccount = (id: number): Account => useSharedSelector(state => state.wallet.accounts[id]);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
|
+
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
|
3
|
+
import type { MarketData } from "../../types";
|
|
4
|
+
import type { CurrencyCode} from "../../utils";
|
|
5
|
+
import { getMarketData } from "../../utils";
|
|
6
|
+
import type { SharedState } from "../store";
|
|
7
|
+
|
|
8
|
+
export interface MarketState {
|
|
9
|
+
currency: CurrencyCode;
|
|
10
|
+
data: Record<string, MarketData>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const initialState: MarketState = {
|
|
14
|
+
currency: 'usd',
|
|
15
|
+
data: {}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const updateMarketData = createAsyncThunk('market/updateMarketData', async (_, thunkAPI) => {
|
|
19
|
+
const state = thunkAPI.getState() as SharedState;
|
|
20
|
+
return await getMarketData(state.market.currency);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const marketSlice = createSlice({
|
|
24
|
+
name: 'market',
|
|
25
|
+
initialState,
|
|
26
|
+
reducers: {
|
|
27
|
+
setCurrency: (state, action: PayloadAction<CurrencyCode>) => {
|
|
28
|
+
state.currency = action.payload;
|
|
29
|
+
},
|
|
30
|
+
setMarketData: (state, action: PayloadAction<Record<string, MarketData>>) => {
|
|
31
|
+
state.data = action.payload;
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
extraReducers: (builder) => {
|
|
35
|
+
builder
|
|
36
|
+
.addCase(updateMarketData.fulfilled, (state, action) => {
|
|
37
|
+
state.data = action.payload;
|
|
38
|
+
})
|
|
39
|
+
.addCase(updateMarketData.rejected, (_, action) => {
|
|
40
|
+
console.error(action.error.message);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const marketThunks = { updateMarketData };
|
|
46
|
+
export const marketActions = marketSlice.actions;
|
|
47
|
+
export const marketReducer = marketSlice.reducer;
|
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
|
-
import {
|
|
3
|
-
import type { Price } from "../../types";
|
|
4
|
-
import { getNexaPrices, initializePrices } from "../../utils";
|
|
1
|
+
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
|
+
import { createSlice } from "@reduxjs/toolkit";
|
|
5
3
|
|
|
6
4
|
export interface StatusState {
|
|
7
5
|
status: "Online" | "Offline";
|
|
8
6
|
height: number;
|
|
9
|
-
price: Record<string, Price>;
|
|
10
7
|
hasNetwork: boolean;
|
|
11
8
|
isSuspended: boolean;
|
|
12
9
|
}
|
|
@@ -14,27 +11,10 @@ export interface StatusState {
|
|
|
14
11
|
const initialState: StatusState = {
|
|
15
12
|
status: "Offline",
|
|
16
13
|
height: 0,
|
|
17
|
-
price: initializePrices(),
|
|
18
14
|
hasNetwork: true,
|
|
19
15
|
isSuspended: false,
|
|
20
16
|
};
|
|
21
17
|
|
|
22
|
-
export const fetchPrice = createAsyncThunk('status/fetchPrice', async () => {
|
|
23
|
-
const prices = initializePrices();
|
|
24
|
-
try {
|
|
25
|
-
const p = await getNexaPrices();
|
|
26
|
-
Object.keys(p).forEach(currency => {
|
|
27
|
-
prices[currency] = {
|
|
28
|
-
value: p[currency],
|
|
29
|
-
change: p[`${currency}_24h_change`]
|
|
30
|
-
};
|
|
31
|
-
});
|
|
32
|
-
} catch {
|
|
33
|
-
// prices remain at 0
|
|
34
|
-
}
|
|
35
|
-
return prices ;
|
|
36
|
-
});
|
|
37
|
-
|
|
38
18
|
const statusSlice = createSlice({
|
|
39
19
|
name: 'status',
|
|
40
20
|
initialState,
|
|
@@ -54,15 +34,6 @@ const statusSlice = createSlice({
|
|
|
54
34
|
setIsSuspended: (state, action: PayloadAction<boolean>) => {
|
|
55
35
|
state.isSuspended = action.payload;
|
|
56
36
|
}
|
|
57
|
-
},
|
|
58
|
-
extraReducers: (builder) => {
|
|
59
|
-
builder
|
|
60
|
-
.addCase(fetchPrice.fulfilled, (state, action) => {
|
|
61
|
-
state.price = action.payload;
|
|
62
|
-
})
|
|
63
|
-
.addCase(fetchPrice.rejected, (_, action) => {
|
|
64
|
-
console.error(action.error.message);
|
|
65
|
-
});
|
|
66
37
|
}
|
|
67
38
|
});
|
|
68
39
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
1
|
+
import type { PayloadAction } from "@reduxjs/toolkit";
|
|
2
2
|
import { createSlice } from "@reduxjs/toolkit";
|
|
3
|
-
import type { Account,
|
|
3
|
+
import type { Account, Balance, SessionInfo, VaultInfo } from "../../types";
|
|
4
4
|
import { MAIN_WALLET_ID } from "../../utils";
|
|
5
5
|
|
|
6
6
|
interface AddSessionPayload {
|
|
@@ -22,6 +22,7 @@ export interface WalletState {
|
|
|
22
22
|
initLoad: boolean;
|
|
23
23
|
txUpdateTrigger: number;
|
|
24
24
|
nftsUpdateTrigger: number;
|
|
25
|
+
tokensUpdateTrigger: number;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const initialState: WalletState = {
|
|
@@ -34,7 +35,6 @@ const initialState: WalletState = {
|
|
|
34
35
|
address: '',
|
|
35
36
|
balance: {confirmed: "0", unconfirmed: "0"},
|
|
36
37
|
tokensBalance: {},
|
|
37
|
-
tokens: [],
|
|
38
38
|
sessions: {}
|
|
39
39
|
}
|
|
40
40
|
},
|
|
@@ -42,7 +42,8 @@ const initialState: WalletState = {
|
|
|
42
42
|
sync: false,
|
|
43
43
|
initLoad: true,
|
|
44
44
|
txUpdateTrigger: 0,
|
|
45
|
-
nftsUpdateTrigger: 0
|
|
45
|
+
nftsUpdateTrigger: 0,
|
|
46
|
+
tokensUpdateTrigger: 0
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
const walletSlice = createSlice({
|
|
@@ -77,21 +78,15 @@ const walletSlice = createSlice({
|
|
|
77
78
|
setAccountName: (state, action: PayloadAction<{ id: number, name: string }>) => {
|
|
78
79
|
state.accounts[action.payload.id].name = action.payload.name;
|
|
79
80
|
},
|
|
80
|
-
setTokens: (state, action: PayloadAction<{ id: number, assets: AssetEntity[] }>) => {
|
|
81
|
-
state.accounts[action.payload.id].tokens = action.payload.assets;
|
|
82
|
-
},
|
|
83
|
-
addToken: (state, action: PayloadAction<{ id: number, asset: AssetEntity }>) => {
|
|
84
|
-
state.accounts[action.payload.id].tokens.unshift(action.payload.asset);
|
|
85
|
-
},
|
|
86
|
-
removeToken: (state, action: PayloadAction<{ id: number, tokenId: string }>) => {
|
|
87
|
-
state.accounts[action.payload.id].tokens = state.accounts[action.payload.id].tokens.filter(n => n.tokenIdHex !== action.payload.tokenId);
|
|
88
|
-
},
|
|
89
81
|
refreshTxs: (state) => {
|
|
90
82
|
state.txUpdateTrigger++;
|
|
91
83
|
},
|
|
92
84
|
refreshNfts: (state) => {
|
|
93
85
|
state.nftsUpdateTrigger++;
|
|
94
86
|
},
|
|
87
|
+
refreshTokens: (state) => {
|
|
88
|
+
state.tokensUpdateTrigger++;
|
|
89
|
+
},
|
|
95
90
|
addSession(state, action: PayloadAction<AddSessionPayload>) {
|
|
96
91
|
const { accountId, sessionInfo } = action.payload;
|
|
97
92
|
state.accounts[accountId].sessions[sessionInfo.details.sessionId] = sessionInfo;
|
package/src/state/store.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { authReducer } from "./slices/auth";
|
|
2
2
|
import { dappModalReducer } from "./slices/dapp";
|
|
3
3
|
import { loaderReducer } from "./slices/loader";
|
|
4
|
+
import { marketReducer } from "./slices/market";
|
|
4
5
|
import { notificationsReducer } from "./slices/notifications";
|
|
5
6
|
import { statusReducer } from "./slices/status";
|
|
6
7
|
import { walletReducer } from "./slices/wallet";
|
|
@@ -12,6 +13,7 @@ export const sharedReducers = {
|
|
|
12
13
|
wallet: walletReducer,
|
|
13
14
|
auth: authReducer,
|
|
14
15
|
notifications: notificationsReducer,
|
|
16
|
+
market: marketReducer,
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export type SharedState = {
|
|
@@ -21,4 +23,5 @@ export type SharedState = {
|
|
|
21
23
|
wallet: ReturnType<typeof walletReducer>;
|
|
22
24
|
auth: ReturnType<typeof authReducer>;
|
|
23
25
|
notifications: ReturnType<typeof notificationsReducer>;
|
|
26
|
+
market: ReturnType<typeof marketReducer>;
|
|
24
27
|
};
|
package/src/types/db.types.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { AccountType
|
|
2
|
-
import type { Balance } from "./wallet.types";
|
|
1
|
+
import type { AccountType } from "../utils/enums";
|
|
2
|
+
import type { Balance, AssetType } from "./wallet.types";
|
|
3
3
|
|
|
4
4
|
export interface AssetMovement {
|
|
5
5
|
address: string;
|
|
@@ -43,6 +43,13 @@ export interface AssetTransactionEntity {
|
|
|
43
43
|
time: number;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export interface AssetEntity {
|
|
47
|
+
accountId: number;
|
|
48
|
+
tokenIdHex: string;
|
|
49
|
+
type: AssetType;
|
|
50
|
+
addedTime: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
46
53
|
export interface TokenEntity {
|
|
47
54
|
token: string;
|
|
48
55
|
tokenIdHex: string;
|
|
@@ -50,15 +57,20 @@ export interface TokenEntity {
|
|
|
50
57
|
ticker: string;
|
|
51
58
|
iconUrl: string;
|
|
52
59
|
decimals: number;
|
|
53
|
-
|
|
60
|
+
parent: string;
|
|
54
61
|
}
|
|
55
62
|
|
|
56
63
|
export interface NftEntity {
|
|
57
64
|
token: string;
|
|
58
65
|
tokenIdHex: string;
|
|
59
|
-
|
|
60
|
-
|
|
66
|
+
name: string;
|
|
67
|
+
parent: string;
|
|
61
68
|
collection: string;
|
|
69
|
+
series: string;
|
|
70
|
+
author: string;
|
|
71
|
+
public: string;
|
|
72
|
+
front: string;
|
|
73
|
+
back: string;
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
export interface SessionEntity {
|
|
@@ -90,13 +102,6 @@ export interface AccountEntity {
|
|
|
90
102
|
tokensBalance: string;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
export interface AssetEntity {
|
|
94
|
-
accountId: number;
|
|
95
|
-
tokenIdHex: string;
|
|
96
|
-
type: AssetType;
|
|
97
|
-
addedTime: number;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
105
|
export interface AddressDTO {
|
|
101
106
|
address: string;
|
|
102
107
|
space: number;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { DAppInfo, SessionDetails } from "wallet-comms-sdk";
|
|
2
|
-
import type { AssetEntity } from "./db.types";
|
|
3
2
|
import type { AccountType, KeySpace } from "../utils/enums";
|
|
4
3
|
|
|
5
4
|
export interface KeyPath {
|
|
@@ -20,18 +19,12 @@ export interface Balance {
|
|
|
20
19
|
unconfirmed: string | number;
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
export interface Price {
|
|
24
|
-
value: number;
|
|
25
|
-
change: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
22
|
export interface Account {
|
|
29
23
|
id: number;
|
|
30
24
|
name: string;
|
|
31
25
|
address: string;
|
|
32
26
|
balance: Balance;
|
|
33
27
|
tokensBalance: Record<string, Balance>;
|
|
34
|
-
tokens: AssetEntity[];
|
|
35
28
|
sessions: Record<string, SessionInfo>
|
|
36
29
|
}
|
|
37
30
|
|
|
@@ -42,21 +35,52 @@ export interface VaultInfo {
|
|
|
42
35
|
index: number;
|
|
43
36
|
}
|
|
44
37
|
|
|
45
|
-
export interface NftPreview {
|
|
46
|
-
infoJson: string;
|
|
47
|
-
image: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
38
|
export interface SessionInfo {
|
|
51
39
|
details: SessionDetails;
|
|
52
40
|
appInfo: DAppInfo;
|
|
53
41
|
};
|
|
54
42
|
|
|
55
|
-
export interface
|
|
43
|
+
export interface AssetBase {
|
|
56
44
|
token: string;
|
|
57
45
|
tokenIdHex: string;
|
|
46
|
+
parent: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface TokenAsset {
|
|
50
|
+
name: string;
|
|
51
|
+
ticker: string;
|
|
52
|
+
iconUrl: string;
|
|
58
53
|
decimals: number;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface NFTAsset {
|
|
57
|
+
name: string;
|
|
58
|
+
series: string;
|
|
59
|
+
collection: string;
|
|
60
|
+
author: string;
|
|
61
|
+
public: string;
|
|
62
|
+
front: string;
|
|
63
|
+
back: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type AssetType = 'token' | 'nft';
|
|
67
|
+
|
|
68
|
+
export type AssetInfo =
|
|
69
|
+
| (AssetBase & { type: 'token'; data: TokenAsset })
|
|
70
|
+
| (AssetBase & { type: 'nft'; data: NFTAsset });
|
|
71
|
+
|
|
72
|
+
export interface MarketData {
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
symbol: string;
|
|
76
|
+
image: string;
|
|
77
|
+
price: number;
|
|
78
|
+
priceChange?: number | null;
|
|
79
|
+
priceChangePercentage?: number | null;
|
|
80
|
+
marketCap?: number | null;
|
|
81
|
+
fdv?: number | null;
|
|
82
|
+
circSupply?: number | null;
|
|
83
|
+
totalSupply?: number | null;
|
|
84
|
+
maxSupply?: number | null;
|
|
85
|
+
totalVolume?: number | null;
|
|
62
86
|
}
|
package/src/utils/asset.ts
CHANGED
|
@@ -1,53 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import { getAddressBuffer, isTestnet } from "./common";
|
|
4
|
-
|
|
5
|
-
export function getNiftyToken(): TokenEntity {
|
|
6
|
-
return {
|
|
7
|
-
name: 'NiftyArt',
|
|
8
|
-
ticker: 'NIFTY',
|
|
9
|
-
iconUrl: 'https://niftyart.cash/td/niftyicon.svg',
|
|
10
|
-
parentGroup: '',
|
|
11
|
-
token: isTestnet()
|
|
12
|
-
? 'nexatest:tq8r37lcjlqazz7vuvug84q2ev50573hesrnxkv9y6hvhhl5k5qqqnmyf79mx'
|
|
13
|
-
: 'nexa:tr9v70v4s9s6jfwz32ts60zqmmkp50lqv7t0ux620d50xa7dhyqqqcg6kdm6f',
|
|
14
|
-
tokenIdHex: isTestnet()
|
|
15
|
-
? '0e38fbf897c1d10bcce33883d40acb28fa7a37cc0733598526aecbdff4b50000'
|
|
16
|
-
: 'cacf3d958161a925c28a970d3c40deec1a3fe06796fe1b4a7b68f377cdb90000',
|
|
17
|
-
decimals: 0
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function fetchNiftyNFT(id: string): Promise<Uint8Array> {
|
|
22
|
-
return performGet(getNiftyEndpoint() + id, 'raw');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isNiftySubgroup(group: string): boolean {
|
|
26
|
-
try {
|
|
27
|
-
const addrBuf = getAddressBuffer(group);
|
|
28
|
-
if (!GroupToken.isSubgroup(addrBuf)) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return BufferUtils.bufferToHex(addrBuf.subarray(0, 32)) === getNiftyToken().tokenIdHex;
|
|
33
|
-
} catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function fetchAssetDoc(url: string): Promise<any> {
|
|
39
|
-
// it is possible that legacy token description is stored on IPFS, check for this
|
|
40
|
-
// example: ipfs://bafkvmicdm2vdjgieqvk3s5ykqlddtzd42yuy7voum4cifpknjvhv3uarwu
|
|
41
|
-
url = translateIfIpfsUrl(url);
|
|
42
|
-
return performGet(url, 'json');
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function fetchAssetBlob(url: string): Promise<Uint8Array> {
|
|
46
|
-
// it is possible that NRC (Token / NFT) description is stored on IPFS, check for this
|
|
47
|
-
// example: ipfs://bafkvmicdm2vdjgieqvk3s5ykqlddtzd42yuy7voum4cifpknjvhv3uarwu
|
|
48
|
-
url = translateIfIpfsUrl(url);
|
|
49
|
-
return performGet(url, 'raw');
|
|
50
|
-
}
|
|
1
|
+
import { isTestnet } from "./common";
|
|
2
|
+
import type { AssetInfo } from "../types";
|
|
51
3
|
|
|
52
4
|
export function transformTokenIconUrl(icon: string, documentUrl: string): string {
|
|
53
5
|
if (icon && typeof icon === 'string') {
|
|
@@ -80,8 +32,16 @@ function translateIfIpfsUrl(url: string, gateway = "https://ipfs.nebula.markets/
|
|
|
80
32
|
return url;
|
|
81
33
|
}
|
|
82
34
|
|
|
83
|
-
function
|
|
84
|
-
return
|
|
35
|
+
export function getAssetMetadata(token: string): Promise<AssetInfo> {
|
|
36
|
+
return performGet(`${getAssetsEndpoint()}/metadata/${token}`, 'json');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function getAssetFileUrl(token:string, asset: string): string {
|
|
40
|
+
return `${getAssetsEndpoint()}/assets/${token}/${asset}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getAssetsEndpoint(): string {
|
|
44
|
+
return `https://${isTestnet() ? 'testapi' : 'api'}.otoplo.com`;
|
|
85
45
|
}
|
|
86
46
|
|
|
87
47
|
async function performGet(url: string, responseType?: 'json' | 'raw'): Promise<any> {
|
package/src/utils/common.ts
CHANGED
|
@@ -170,9 +170,3 @@ export function prettifyAmount(amount: string | number): string {
|
|
|
170
170
|
}
|
|
171
171
|
return val;
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
export function calcAmountValue(amount: string | number, price: number, prettify = false): string {
|
|
175
|
-
const value = bigDecimal.multiply(amount, price);
|
|
176
|
-
const rounded = bigDecimal.round(value, 2, bigDecimal.RoundingModes.HALF_DOWN);
|
|
177
|
-
return prettify ? prettifyAmount(rounded) : rounded;
|
|
178
|
-
}
|
package/src/utils/enums.ts
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
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
|
+
|
|
23
|
+
export const CURRENCIES = [
|
|
24
|
+
{ code: 'usd', symbol: '$', name: 'US Dollar' },
|
|
25
|
+
{ code: 'eur', symbol: '€', name: 'Euro' },
|
|
26
|
+
{ code: 'gbp', symbol: '£', name: 'British Pound' },
|
|
27
|
+
{ code: 'aud', symbol: 'A$', name: 'Australian Dollar' },
|
|
28
|
+
{ code: 'cad', symbol: 'C$', name: 'Canadian Dollar' },
|
|
29
|
+
{ code: 'chf', symbol: 'Fr', name: 'Swiss Franc' },
|
|
30
|
+
{ code: 'inr', symbol: '₹', name: 'Indian Rupee' },
|
|
31
|
+
{ code: 'cny', symbol: '¥', name: 'Chinese Yuan' },
|
|
32
|
+
{ code: 'jpy', symbol: '¥', name: 'Japanese Yen' },
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export type CurrencyCode = typeof CURRENCIES[number]['code'];
|
|
36
|
+
|
|
37
|
+
export type CurrencySymbol = typeof CURRENCIES[number]['symbol'];
|
|
38
|
+
|
|
39
|
+
export const currencySymbols = Object.fromEntries(
|
|
40
|
+
CURRENCIES.map(c => [c.code, c.symbol])
|
|
41
|
+
) as Record<CurrencyCode, CurrencySymbol>;
|
|
42
|
+
|
|
43
|
+
export const currencyCodes = CURRENCIES.map(c => c.code);
|
|
44
|
+
|
|
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
|
+
export function getCurrencySymbol(currency: CurrencyCode): string {
|
|
80
|
+
return currencySymbols[currency] || currency;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
export function calculateFiatValue(amount: string | number, price: number, currency?: CurrencyCode): string {
|
|
85
|
+
const value = bigDecimal.multiply(amount, price);
|
|
86
|
+
const rounded = bigDecimal.round(value, 2, bigDecimal.RoundingModes.HALF_DOWN);
|
|
87
|
+
return currency ? formatMoney(rounded, currency) : rounded;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function formatMoney(value: string | number, currency: CurrencyCode, locale = 'en-US'): string {
|
|
91
|
+
return new Intl.NumberFormat(locale, {
|
|
92
|
+
style: 'currency',
|
|
93
|
+
currency,
|
|
94
|
+
maximumFractionDigits: 2,
|
|
95
|
+
minimumFractionDigits: 2
|
|
96
|
+
}).format(Number(value));
|
|
97
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"price.d.ts","sourceRoot":"","sources":["../../../src/utils/price.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EASb,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,MAAM,cAAc,GAAG,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC;AAEjE,eAAO,MAAM,eAAe,EAEvB,MAAM,CAAC,YAAY,EAAE,cAAc,CAAC,CAAC;AAE1C,eAAO,MAAM,aAAa,mEAA8B,CAAC;AAEzD,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAUrE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,CAEhE;AAED,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAMxD"}
|