@talismn/chaindata-provider 0.7.0 → 0.8.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/dist/declarations/src/ChaindataProvider.d.ts +90 -17
- package/dist/declarations/src/TalismanChaindataDatabase.d.ts +9 -0
- package/dist/declarations/src/constants.d.ts +26 -0
- package/dist/declarations/src/index.d.ts +5 -2
- package/dist/declarations/src/init/chains.d.ts +243 -0
- package/dist/declarations/src/init/evm-networks.d.ts +61 -0
- package/dist/declarations/src/init/index.d.ts +5 -0
- package/dist/declarations/src/init/mini-metadatas.d.ts +21 -0
- package/dist/declarations/src/init/tokens.d.ts +103 -0
- package/dist/declarations/src/log.d.ts +2 -0
- package/dist/declarations/src/net.d.ts +8 -0
- package/dist/declarations/src/types/Chain.d.ts +42 -5
- package/dist/declarations/src/types/ChaindataProviderInterface.d.ts +42 -0
- package/dist/declarations/src/types/EvmNetwork.d.ts +17 -4
- package/dist/declarations/src/types/Token/EvmErc20Token.d.ts +15 -0
- package/dist/declarations/src/types/Token/EvmNativeToken.d.ts +10 -0
- package/dist/declarations/src/types/Token/EvmUniswapV2Token.d.ts +22 -0
- package/dist/declarations/src/types/Token/SubstrateAssetsToken.d.ts +12 -0
- package/dist/declarations/src/types/Token/SubstrateEquilibriumToken.d.ts +11 -0
- package/dist/declarations/src/types/Token/SubstrateForeignAssetsToken.d.ts +12 -0
- package/dist/declarations/src/types/Token/SubstrateNativeToken.d.ts +13 -0
- package/dist/declarations/src/types/Token/SubstratePsp22Token.d.ts +11 -0
- package/dist/declarations/src/types/Token/SubstrateTokensToken.d.ts +11 -0
- package/dist/declarations/src/types/Token/index.d.ts +26 -0
- package/dist/declarations/src/types/Token/types.d.ts +27 -0
- package/dist/declarations/src/types/index.d.ts +1 -0
- package/dist/declarations/src/upgrades/2024-01-25-upgradeAddIsDefaultToExistingChains.d.ts +2 -0
- package/dist/declarations/src/upgrades/2024-01-25-upgradeRemoveSymbolFromNativeTokenId.d.ts +2 -0
- package/dist/declarations/src/upgrades/index.d.ts +1 -0
- package/dist/declarations/src/util.d.ts +27 -0
- package/dist/net-BE0MfrDv.cjs.dev.js +89 -0
- package/dist/net-BE0MfrDv.cjs.prod.js +89 -0
- package/dist/net-il4EYhJN.esm.js +55 -0
- package/dist/talismn-chaindata-provider.cjs.dev.js +912 -23
- package/dist/talismn-chaindata-provider.cjs.prod.js +912 -23
- package/dist/talismn-chaindata-provider.esm.js +858 -12
- package/init/chains/dist/talismn-chaindata-provider-init-chains.cjs.d.ts +1 -0
- package/init/chains/dist/talismn-chaindata-provider-init-chains.cjs.dev.js +934 -0
- package/init/chains/dist/talismn-chaindata-provider-init-chains.cjs.js +7 -0
- package/init/chains/dist/talismn-chaindata-provider-init-chains.cjs.prod.js +934 -0
- package/init/chains/dist/talismn-chaindata-provider-init-chains.esm.js +932 -0
- package/init/chains/package.json +4 -0
- package/init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.cjs.d.ts +1 -0
- package/init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.cjs.dev.js +69 -0
- package/init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.cjs.js +7 -0
- package/init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.cjs.prod.js +69 -0
- package/init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.esm.js +67 -0
- package/init/evm-networks/package.json +4 -0
- package/init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.cjs.d.ts +1 -0
- package/init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.cjs.dev.js +311 -0
- package/init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.cjs.js +7 -0
- package/init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.cjs.prod.js +311 -0
- package/init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.esm.js +309 -0
- package/init/mini-metadatas/package.json +4 -0
- package/init/tokens/dist/talismn-chaindata-provider-init-tokens.cjs.d.ts +1 -0
- package/init/tokens/dist/talismn-chaindata-provider-init-tokens.cjs.dev.js +406 -0
- package/init/tokens/dist/talismn-chaindata-provider-init-tokens.cjs.js +7 -0
- package/init/tokens/dist/talismn-chaindata-provider-init-tokens.cjs.prod.js +406 -0
- package/init/tokens/dist/talismn-chaindata-provider-init-tokens.esm.js +404 -0
- package/init/tokens/package.json +4 -0
- package/net/dist/talismn-chaindata-provider-net.cjs.d.ts +1 -0
- package/net/dist/talismn-chaindata-provider-net.cjs.dev.js +14 -0
- package/net/dist/talismn-chaindata-provider-net.cjs.js +7 -0
- package/net/dist/talismn-chaindata-provider-net.cjs.prod.js +14 -0
- package/net/dist/talismn-chaindata-provider-net.esm.js +1 -0
- package/net/package.json +4 -0
- package/package.json +27 -14
- package/CHANGELOG.md +0 -104
- package/dist/declarations/src/github.d.ts +0 -11
- package/dist/declarations/src/plugins.d.ts +0 -17
- package/dist/declarations/src/types/Token.d.ts +0 -48
- package/plugins/dist/talismn-chaindata-provider-plugins.cjs.d.ts +0 -1
- package/plugins/dist/talismn-chaindata-provider-plugins.cjs.dev.js +0 -2
- package/plugins/dist/talismn-chaindata-provider-plugins.cjs.js +0 -7
- package/plugins/dist/talismn-chaindata-provider-plugins.cjs.prod.js +0 -2
- package/plugins/dist/talismn-chaindata-provider-plugins.esm.js +0 -1
- package/plugins/package.json +0 -4
|
@@ -1,12 +1,858 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
import { f as fetchChain, a as fetchSubstrateToken, b as fetchEvmNetwork, c as fetchChains, d as fetchEvmNetworks, g as githubTokenLogoUrl, e as githubUnknownTokenLogoUrl, h as fetchSubstrateTokens } from './net-il4EYhJN.esm.js';
|
|
2
|
+
export { G as availableTokenLogoFilenames, y as chaindataChainByGenesisHashUrl, x as chaindataChainByIdUrl, v as chaindataChainsAllUrl, w as chaindataChainsSummaryUrl, B as chaindataEvmNetworkByIdUrl, z as chaindataEvmNetworksAllUrl, A as chaindataEvmNetworksSummaryUrl, E as chaindataMiniMetadatasAllUrl, D as chaindataTokenByIdUrl, C as chaindataTokensAllUrl, F as fetchMiniMetadatas, i as githubApi, j as githubCdn, s as githubChainLogoUrl, o as githubChaindataBaseUrl, m as githubChaindataBranch, q as githubChaindataChainsAssetsDir, n as githubChaindataDistDir, p as githubChaindataDistUrl, k as githubChaindataOrg, l as githubChaindataRepo, r as githubChaindataTokensAssetsDir, t as githubEvmNetworkLogoUrl, u as githubUnknownChainLogoUrl } from './net-il4EYhJN.esm.js';
|
|
3
|
+
import { chains } from '../init/chains/dist/talismn-chaindata-provider-init-chains.esm.js';
|
|
4
|
+
import { evmNetworks } from '../init/evm-networks/dist/talismn-chaindata-provider-init-evm-networks.esm.js';
|
|
5
|
+
import { miniMetadatas } from '../init/mini-metadatas/dist/talismn-chaindata-provider-init-mini-metadatas.esm.js';
|
|
6
|
+
import { tokens } from '../init/tokens/dist/talismn-chaindata-provider-init-tokens.esm.js';
|
|
7
|
+
import { firstValueFrom, ReplaySubject, map } from 'rxjs';
|
|
8
|
+
import { Dexie, liveQuery } from 'dexie';
|
|
9
|
+
import anylogger from 'anylogger';
|
|
10
|
+
|
|
11
|
+
// The init files imported in this module are generated by a script.
|
|
12
|
+
//
|
|
13
|
+
// They are used in two places:
|
|
14
|
+
// 1. As fallbacks for fresh installations who are unable to pull an initial set of data from the upstream chaindata repo.
|
|
15
|
+
// 2. As mock data for tests.
|
|
16
|
+
//
|
|
17
|
+
// They should be periodically updated with the latest state of chaindata.
|
|
18
|
+
// You can update them by running the following command:
|
|
19
|
+
// `pnpm chore:generate-init-data`
|
|
20
|
+
|
|
21
|
+
const fetchInitChains = async () => chains;
|
|
22
|
+
const fetchInitEvmNetworks = async () => evmNetworks;
|
|
23
|
+
const fetchInitSubstrateTokens = async () => tokens;
|
|
24
|
+
|
|
25
|
+
// TODO: Move `fetchMiniMetadatas` into `@talismn/balances`,
|
|
26
|
+
// so that we don't have a circular import between `@talismn/balances` and `@talismn/chaindata-provider`.
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
28
|
+
const fetchInitMiniMetadatas = async () => miniMetadatas;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Util to add our onfinality api key to any public onfinality RPC urls in an array of chains.
|
|
32
|
+
*/
|
|
33
|
+
const addCustomChainRpcs = (chains, onfinalityApiKey) => chains.map(chain => {
|
|
34
|
+
// copy chain instead of mutating
|
|
35
|
+
const chainWithCustomRpcs = {
|
|
36
|
+
...chain
|
|
37
|
+
};
|
|
38
|
+
if (typeof onfinalityApiKey !== "string" || !onfinalityApiKey) return chainWithCustomRpcs;
|
|
39
|
+
|
|
40
|
+
// add rpcs
|
|
41
|
+
chainWithCustomRpcs.rpcs = (chainWithCustomRpcs.rpcs || []
|
|
42
|
+
// convert public onfinality rpc endpoints to private onfinality rpc endpoints
|
|
43
|
+
).map(rpc => {
|
|
44
|
+
rpc.url = rpc.url.replace(/^wss:\/\/([A-z-]+)\.api\.onfinality\.io\/public-ws\/?$/, `wss://$1.api.onfinality.io/ws?apikey=${onfinalityApiKey}`);
|
|
45
|
+
return rpc;
|
|
46
|
+
})
|
|
47
|
+
// prioritise onfinality rpcs
|
|
48
|
+
.sort((a, b) => {
|
|
49
|
+
if (a.url.includes("api.onfinality.io")) return -1;
|
|
50
|
+
if (b.url.includes("api.onfinality.io")) return 1;
|
|
51
|
+
return 0;
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// return copy
|
|
55
|
+
return chainWithCustomRpcs;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
//
|
|
59
|
+
// Utils for parsing chaindata tokens.json
|
|
60
|
+
//
|
|
61
|
+
|
|
62
|
+
const parseTokensResponse = tokens => tokens.filter(isTokenPartial).filter(isToken);
|
|
63
|
+
const isTokenPartial = token => typeof token === "object" && token !== null;
|
|
64
|
+
const isToken = token => {
|
|
65
|
+
const id = token.id;
|
|
66
|
+
if (typeof id !== "string") return false;
|
|
67
|
+
const type = token.type;
|
|
68
|
+
if (typeof type !== "string") return false;
|
|
69
|
+
const isTestnet = token.isTestnet;
|
|
70
|
+
if (typeof isTestnet !== "boolean") return false;
|
|
71
|
+
const symbol = token.symbol;
|
|
72
|
+
if (typeof symbol !== "string") return false;
|
|
73
|
+
const decimals = token.decimals;
|
|
74
|
+
if (typeof decimals !== "number") return false;
|
|
75
|
+
const logo = token.logo;
|
|
76
|
+
if (typeof logo !== "string") return false;
|
|
77
|
+
|
|
78
|
+
// coingeckoId can be undefined
|
|
79
|
+
// const coingeckoId = token.coingeckoId
|
|
80
|
+
// if (typeof coingeckoId !== "string") return false
|
|
81
|
+
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
//
|
|
86
|
+
// map from Item[] to another type
|
|
87
|
+
//
|
|
88
|
+
|
|
89
|
+
const itemsToIds = items => items.map(({
|
|
90
|
+
id
|
|
91
|
+
}) => id);
|
|
92
|
+
const itemsToMapById = items => Object.fromEntries(items.map(item => [item.id, item]));
|
|
93
|
+
const itemsToMapByGenesisHash = items => Object.fromEntries(items.flatMap(item => item.genesisHash ? [[item.genesisHash, item]] : []));
|
|
94
|
+
|
|
95
|
+
//
|
|
96
|
+
// filters for Item[] where Item.isCustom == true
|
|
97
|
+
//
|
|
98
|
+
|
|
99
|
+
const customChainsFilter = chains => chains.filter(chain => "isCustom" in chain && chain.isCustom);
|
|
100
|
+
const customEvmNetworksFilter = evmNetworks => evmNetworks.filter(evmNetwork => "isCustom" in evmNetwork && evmNetwork.isCustom);
|
|
101
|
+
const customTokensFilter = tokens => tokens.filter(token => "isCustom" in token && token.isCustom);
|
|
102
|
+
|
|
103
|
+
//
|
|
104
|
+
// Utils to wrap Observable methods with one-shot Promise methods
|
|
105
|
+
//
|
|
106
|
+
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
const wrapObservableWithGetter = async (errorReason, observable) => {
|
|
109
|
+
return await withErrorReason(errorReason, () => firstValueFrom(observable));
|
|
110
|
+
};
|
|
111
|
+
const withErrorReason = async (reason, task) => {
|
|
112
|
+
try {
|
|
113
|
+
return await task();
|
|
114
|
+
} catch (cause) {
|
|
115
|
+
throw new Error(reason, {
|
|
116
|
+
cause
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
//
|
|
122
|
+
// Utils which aren't used by this package, but are helpful for other packages
|
|
123
|
+
//
|
|
124
|
+
|
|
125
|
+
const isCustomChain = chain => {
|
|
126
|
+
return "isCustom" in chain && chain.isCustom === true;
|
|
127
|
+
};
|
|
128
|
+
const isCustomEvmNetwork = evmNetwork => {
|
|
129
|
+
return "isCustom" in evmNetwork && evmNetwork.isCustom === true;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
var packageJson = {
|
|
133
|
+
name: "@talismn/chaindata-provider",
|
|
134
|
+
version: "0.8.1",
|
|
135
|
+
author: "Talisman",
|
|
136
|
+
homepage: "https://talisman.xyz",
|
|
137
|
+
license: "GPL-3.0-or-later",
|
|
138
|
+
publishConfig: {
|
|
139
|
+
access: "public"
|
|
140
|
+
},
|
|
141
|
+
repository: {
|
|
142
|
+
directory: "packages/chaindata-provider",
|
|
143
|
+
type: "git",
|
|
144
|
+
url: "https://github.com/talismansociety/talisman.git"
|
|
145
|
+
},
|
|
146
|
+
main: "dist/talismn-chaindata-provider.cjs.js",
|
|
147
|
+
module: "dist/talismn-chaindata-provider.esm.js",
|
|
148
|
+
files: [
|
|
149
|
+
"/dist",
|
|
150
|
+
"/init",
|
|
151
|
+
"/net"
|
|
152
|
+
],
|
|
153
|
+
engines: {
|
|
154
|
+
node: ">=18"
|
|
155
|
+
},
|
|
156
|
+
scripts: {
|
|
157
|
+
test: "jest",
|
|
158
|
+
lint: "eslint src --max-warnings 0",
|
|
159
|
+
"chore:generate-init-data": "ts-node scripts/generateInitData.ts",
|
|
160
|
+
clean: "rm -rf dist init/*/dist net/dist .turbo node_modules"
|
|
161
|
+
},
|
|
162
|
+
dependencies: {
|
|
163
|
+
anylogger: "^1.0.11",
|
|
164
|
+
dexie: "^4.0.9",
|
|
165
|
+
rxjs: "^7.8.1"
|
|
166
|
+
},
|
|
167
|
+
devDependencies: {
|
|
168
|
+
"@talismn/eslint-config": "workspace:*",
|
|
169
|
+
"@talismn/tsconfig": "workspace:*",
|
|
170
|
+
"@types/jest": "^29.5.14",
|
|
171
|
+
eslint: "^8.57.1",
|
|
172
|
+
jest: "^29.7.0",
|
|
173
|
+
prettier: "^3.3.3",
|
|
174
|
+
"ts-jest": "^29.2.5",
|
|
175
|
+
"ts-node": "^10.9.2",
|
|
176
|
+
typescript: "^5.6.3"
|
|
177
|
+
},
|
|
178
|
+
preconstruct: {
|
|
179
|
+
entrypoints: [
|
|
180
|
+
"index.ts",
|
|
181
|
+
"init/chains.ts",
|
|
182
|
+
"init/evm-networks.ts",
|
|
183
|
+
"init/mini-metadatas.ts",
|
|
184
|
+
"init/tokens.ts",
|
|
185
|
+
"net.ts"
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
eslintConfig: {
|
|
189
|
+
root: true,
|
|
190
|
+
"extends": [
|
|
191
|
+
"@talismn/eslint-config/base"
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
var log = anylogger(packageJson.name);
|
|
197
|
+
|
|
198
|
+
const subNativeTokenId = chainId => `${chainId}-substrate-native`.toLowerCase().replace(/ /g, "-");
|
|
199
|
+
const evmNativeTokenId = chainId => `${chainId}-evm-native`.toLowerCase().replace(/ /g, "-");
|
|
200
|
+
|
|
201
|
+
// for DB version 2, Wallet version 1.21.0
|
|
202
|
+
const upgradeRemoveSymbolFromNativeTokenId = async tx => {
|
|
203
|
+
const tokensTable = tx.table("tokens");
|
|
204
|
+
const chainsTable = tx.table("chains");
|
|
205
|
+
const evmNetworksTable = tx.table("evmNetworks");
|
|
206
|
+
const nativeTokens = (await tokensTable.toArray()).filter(t => ["substrate-native", "evm-native"].includes(t.type));
|
|
207
|
+
const chains = await chainsTable.toArray();
|
|
208
|
+
const evmNetworks = await evmNetworksTable.toArray();
|
|
209
|
+
const tokenIdsToDelete = [];
|
|
210
|
+
const tokensToUpsert = [];
|
|
211
|
+
const chainsToUpsert = [];
|
|
212
|
+
const evmNetworksToUpsert = [];
|
|
213
|
+
for (const nativeToken of nativeTokens) {
|
|
214
|
+
const networkId = nativeToken.chain?.id || nativeToken.evmNetwork?.id;
|
|
215
|
+
if (!networkId) continue;
|
|
216
|
+
const id = nativeToken.type === "substrate-native" ? subNativeTokenId(networkId) : nativeToken.type === "evm-native" ? evmNativeTokenId(networkId) : undefined;
|
|
217
|
+
if (!id) continue;
|
|
218
|
+
const chain = chains.find(({
|
|
219
|
+
id
|
|
220
|
+
}) => id === networkId);
|
|
221
|
+
const evmNetwork = evmNetworks.find(({
|
|
222
|
+
id
|
|
223
|
+
}) => id === networkId);
|
|
224
|
+
tokenIdsToDelete.push(nativeToken.id);
|
|
225
|
+
tokensToUpsert.push({
|
|
226
|
+
...nativeToken,
|
|
227
|
+
id
|
|
228
|
+
});
|
|
229
|
+
if (chain) chainsToUpsert.push({
|
|
230
|
+
...chain,
|
|
231
|
+
nativeToken: {
|
|
232
|
+
id
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
if (evmNetwork) evmNetworksToUpsert.push({
|
|
236
|
+
...evmNetwork,
|
|
237
|
+
nativeToken: {
|
|
238
|
+
id
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
await tokensTable.bulkPut(tokensToUpsert);
|
|
243
|
+
await chainsTable.bulkPut(chainsToUpsert);
|
|
244
|
+
await evmNetworksTable.bulkPut(evmNetworksToUpsert);
|
|
245
|
+
await tokensTable.bulkDelete(tokenIdsToDelete);
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// for DB version 2, Wallet version 1.21.0
|
|
249
|
+
const upgradeAddIsDefaultToExistingChains = async tx => {
|
|
250
|
+
const chainsTable = tx.table("chains");
|
|
251
|
+
const evmNetworksTable = tx.table("evmNetworks");
|
|
252
|
+
const tokensTable = tx.table("tokens");
|
|
253
|
+
await chainsTable.toCollection().modify(chain => {
|
|
254
|
+
if ("isCustom" in chain && chain.isCustom) return;
|
|
255
|
+
chain.isDefault = true;
|
|
256
|
+
});
|
|
257
|
+
await evmNetworksTable.toCollection().modify(evmNetwork => {
|
|
258
|
+
if ("isCustom" in evmNetwork && evmNetwork.isCustom) return;
|
|
259
|
+
evmNetwork.isDefault = true;
|
|
260
|
+
});
|
|
261
|
+
await tokensTable.toCollection().modify(token => {
|
|
262
|
+
if ("isCustom" in token && token.isCustom) return;
|
|
263
|
+
token.isDefault = true;
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
class TalismanChaindataDatabase extends Dexie {
|
|
268
|
+
constructor() {
|
|
269
|
+
super("TalismanChaindata");
|
|
270
|
+
|
|
271
|
+
// https://dexie.org/docs/Tutorial/Design#database-versioning
|
|
272
|
+
this.version(2).stores({
|
|
273
|
+
// You only need to specify properties that you wish to index.
|
|
274
|
+
// The object store will allow any properties on your stored objects but you can only query them by indexed properties
|
|
275
|
+
// https://dexie.org/docs/API-Reference#declare-database
|
|
276
|
+
//
|
|
277
|
+
// Never index properties containing images, movies or large (huge) strings. Store them in IndexedDB, yes! but just don’t index them!
|
|
278
|
+
// https://dexie.org/docs/Version/Version.stores()#warning
|
|
279
|
+
chains: "id, genesisHash, name",
|
|
280
|
+
evmNetworks: "id, name",
|
|
281
|
+
tokens: "id, type, symbol, coingeckoId, dcentName, contractAddress"
|
|
282
|
+
}).upgrade(upgradeRemoveSymbolFromNativeTokenId).upgrade(upgradeAddIsDefaultToExistingChains);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
new TalismanChaindataDatabase();
|
|
286
|
+
|
|
287
|
+
// removes the need to reference @talismn/balances in this package. should we ?
|
|
288
|
+
const getNativeTokenId = (chainId, moduleType) => `${chainId}-${moduleType}`.toLowerCase().replace(/ /g, "-");
|
|
289
|
+
const minimumHydrationInterval = 300_000; // 300_000ms = 300s = 5 minutes
|
|
290
|
+
|
|
291
|
+
class ChaindataProvider {
|
|
292
|
+
#db;
|
|
293
|
+
#onfinalityApiKey;
|
|
294
|
+
#liveQueries;
|
|
295
|
+
#lastHydratedChainsAt = 0;
|
|
296
|
+
#lastHydratedEvmNetworksAt = 0;
|
|
297
|
+
#lastHydratedTokensAt = 0;
|
|
298
|
+
constructor(options) {
|
|
299
|
+
this.#db = new TalismanChaindataDatabase();
|
|
300
|
+
this.#onfinalityApiKey = options?.onfinalityApiKey ?? undefined;
|
|
301
|
+
this.#liveQueries = [liveQuery(() => this.#db.chains.toArray()).subscribe(this.chainsObservable), liveQuery(() => this.#db.evmNetworks.toArray()).subscribe(this.evmNetworksObservable), liveQuery(() => this.#db.tokens.toArray()).subscribe(this.tokensObservable)];
|
|
302
|
+
}
|
|
303
|
+
destroy() {
|
|
304
|
+
this.#liveQueries.forEach(subscription => subscription.unsubscribe());
|
|
305
|
+
}
|
|
306
|
+
setOnfinalityApiKey(apiKey) {
|
|
307
|
+
this.#onfinalityApiKey = apiKey;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
//
|
|
311
|
+
// base items
|
|
312
|
+
//
|
|
313
|
+
|
|
314
|
+
chainsObservable = new ReplaySubject(1);
|
|
315
|
+
async chains() {
|
|
316
|
+
return await wrapObservableWithGetter("Failed to get chains", this.chainsObservable);
|
|
317
|
+
}
|
|
318
|
+
evmNetworksObservable = new ReplaySubject(1);
|
|
319
|
+
async evmNetworks() {
|
|
320
|
+
return await wrapObservableWithGetter("Failed to get evmNetworks", this.evmNetworksObservable);
|
|
321
|
+
}
|
|
322
|
+
tokensObservable = new ReplaySubject(1);
|
|
323
|
+
async tokens() {
|
|
324
|
+
return await wrapObservableWithGetter("Failed to get tokens", this.tokensObservable);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
//
|
|
328
|
+
// custom item observables
|
|
329
|
+
//
|
|
330
|
+
|
|
331
|
+
get customChainsObservable() {
|
|
332
|
+
return this.chainsObservable.pipe(map(customChainsFilter));
|
|
333
|
+
}
|
|
334
|
+
async customChains() {
|
|
335
|
+
return await wrapObservableWithGetter("Failed to get custom chains", this.customChainsObservable);
|
|
336
|
+
}
|
|
337
|
+
get customEvmNetworksObservable() {
|
|
338
|
+
return this.evmNetworksObservable.pipe(map(customEvmNetworksFilter));
|
|
339
|
+
}
|
|
340
|
+
async customEvmNetworks() {
|
|
341
|
+
return await wrapObservableWithGetter("Failed to get custom evmNetworks", this.customEvmNetworksObservable);
|
|
342
|
+
}
|
|
343
|
+
get customTokensObservable() {
|
|
344
|
+
return this.tokensObservable.pipe(map(customTokensFilter));
|
|
345
|
+
}
|
|
346
|
+
async customTokens() {
|
|
347
|
+
return await wrapObservableWithGetter("Failed to get custom tokens", this.customTokensObservable);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
//
|
|
351
|
+
// item ids
|
|
352
|
+
//
|
|
353
|
+
|
|
354
|
+
get chainIdsObservable() {
|
|
355
|
+
return this.chainsObservable.pipe(map(itemsToIds));
|
|
356
|
+
}
|
|
357
|
+
async chainIds() {
|
|
358
|
+
return await wrapObservableWithGetter("Failed to get chainIds", this.chainIdsObservable);
|
|
359
|
+
}
|
|
360
|
+
get evmNetworkIdsObservable() {
|
|
361
|
+
return this.evmNetworksObservable.pipe(map(itemsToIds));
|
|
362
|
+
}
|
|
363
|
+
async evmNetworkIds() {
|
|
364
|
+
return await wrapObservableWithGetter("Failed to get evmNetworkIds", this.evmNetworkIdsObservable);
|
|
365
|
+
}
|
|
366
|
+
get tokenIdsObservable() {
|
|
367
|
+
return this.tokensObservable.pipe(map(itemsToIds));
|
|
368
|
+
}
|
|
369
|
+
async tokenIds() {
|
|
370
|
+
return await wrapObservableWithGetter("Failed to get tokenIds", this.tokenIdsObservable);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
//
|
|
374
|
+
// items by id
|
|
375
|
+
//
|
|
376
|
+
|
|
377
|
+
get chainsByIdObservable() {
|
|
378
|
+
return this.chainsObservable.pipe(map(itemsToMapById));
|
|
379
|
+
}
|
|
380
|
+
async chainsById() {
|
|
381
|
+
return await wrapObservableWithGetter("Failed to get chains by id", this.chainsByIdObservable);
|
|
382
|
+
}
|
|
383
|
+
get evmNetworksByIdObservable() {
|
|
384
|
+
return this.evmNetworksObservable.pipe(map(itemsToMapById));
|
|
385
|
+
}
|
|
386
|
+
async evmNetworksById() {
|
|
387
|
+
return await wrapObservableWithGetter("Failed to get evmNetworks by id", this.evmNetworksByIdObservable);
|
|
388
|
+
}
|
|
389
|
+
get tokensByIdObservable() {
|
|
390
|
+
return this.tokensObservable.pipe(map(itemsToMapById));
|
|
391
|
+
}
|
|
392
|
+
async tokensById() {
|
|
393
|
+
return await wrapObservableWithGetter("Failed to get tokens by id", this.tokensByIdObservable);
|
|
394
|
+
}
|
|
395
|
+
async tokensByIdForType(type) {
|
|
396
|
+
const tokensByIdForTypeObservable = this.tokensObservable.pipe(map(tokens => tokens.filter(token => token.type === type))).pipe(map(itemsToMapById));
|
|
397
|
+
return await wrapObservableWithGetter("Failed to get tokenIds", tokensByIdForTypeObservable);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
//
|
|
401
|
+
// items by genesisHash
|
|
402
|
+
//
|
|
403
|
+
|
|
404
|
+
get chainsByGenesisHashObservable() {
|
|
405
|
+
return this.chainsObservable.pipe(map(itemsToMapByGenesisHash));
|
|
406
|
+
}
|
|
407
|
+
async chainsByGenesisHash() {
|
|
408
|
+
return await wrapObservableWithGetter("Failed to get chains by genesisHash", this.chainsByGenesisHashObservable);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
//
|
|
412
|
+
// filters for a single item
|
|
413
|
+
//
|
|
414
|
+
|
|
415
|
+
async chainById(chainId) {
|
|
416
|
+
return await withErrorReason("Failed to get chain by id", async () => (await this.chainsById())[chainId] ?? null);
|
|
417
|
+
}
|
|
418
|
+
async chainByGenesisHash(genesisHash) {
|
|
419
|
+
return await withErrorReason("Failed to get chain by genesisHash", async () => (await this.chainsByGenesisHash())[genesisHash] ?? null);
|
|
420
|
+
}
|
|
421
|
+
async evmNetworkById(evmNetworkId) {
|
|
422
|
+
return await withErrorReason("Failed to get evmNetwork by id", async () => (await this.evmNetworksById())[evmNetworkId] ?? null);
|
|
423
|
+
}
|
|
424
|
+
async tokenById(tokenId) {
|
|
425
|
+
return await withErrorReason("Failed to get token by id", async () => (await this.tokensById())[tokenId] ?? null);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
//
|
|
429
|
+
// mutations / methods with side-effects
|
|
430
|
+
//
|
|
431
|
+
|
|
432
|
+
async addCustomChain(customChain) {
|
|
433
|
+
try {
|
|
434
|
+
if (!("isCustom" in customChain && customChain.isCustom)) return;
|
|
435
|
+
return await this.#db.chains.put(customChain);
|
|
436
|
+
} catch (cause) {
|
|
437
|
+
throw new Error("Failed to add custom chain", {
|
|
438
|
+
cause
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async removeCustomChain(chainId) {
|
|
443
|
+
try {
|
|
444
|
+
return await this.#db.chains
|
|
445
|
+
// only affect custom chains
|
|
446
|
+
.filter(chain => "isCustom" in chain && chain.isCustom)
|
|
447
|
+
// only affect the provided chainId
|
|
448
|
+
.filter(chain => chain.id === chainId)
|
|
449
|
+
// delete the chain (if exists)
|
|
450
|
+
.delete();
|
|
451
|
+
} catch (cause) {
|
|
452
|
+
throw new Error("Failed to remove custom chain", {
|
|
453
|
+
cause
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async setCustomChains(chains) {
|
|
458
|
+
return await this.#db.transaction("rw", this.#db.chains, async () => {
|
|
459
|
+
const keys = await this.#db.chains.filter(chain => "isCustom" in chain && chain.isCustom).primaryKeys();
|
|
460
|
+
await this.#db.chains.bulkDelete(keys);
|
|
461
|
+
await this.#db.chains.bulkPut(chains.filter(chain => chain.isCustom));
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
async resetChain(chainId) {
|
|
465
|
+
const builtInChain = await fetchChain(chainId);
|
|
466
|
+
if (!builtInChain) throw new Error("Cannot reset non-built-in chain");
|
|
467
|
+
if (!builtInChain.nativeToken?.id) throw new Error("Failed to lookup native token (no token exists for chain)");
|
|
468
|
+
const builtInNativeToken = await fetchSubstrateToken(builtInChain?.nativeToken?.id);
|
|
469
|
+
if (!isTokenPartial(builtInNativeToken)) throw new Error("Failed to lookup native token");
|
|
470
|
+
if (!isToken(builtInNativeToken)) throw new Error("Failed to lookup native token (isToken test failed)");
|
|
471
|
+
try {
|
|
472
|
+
return await this.#db.transaction("rw", this.#db.chains, this.#db.tokens, async () => {
|
|
473
|
+
// delete chain and its native tokens (ensures cleanup of tokens with legacy ids)
|
|
474
|
+
await this.#db.tokens.filter(token => token.type === "substrate-native" && token.chain?.id === chainId).delete();
|
|
475
|
+
await this.#db.chains.delete(chainId);
|
|
476
|
+
|
|
477
|
+
// reprovision them from subsquid data
|
|
478
|
+
await this.#db.chains.put(builtInChain);
|
|
479
|
+
await this.#db.tokens.put(builtInNativeToken);
|
|
480
|
+
});
|
|
481
|
+
} catch (cause) {
|
|
482
|
+
throw new Error("Failed to reset chain", {
|
|
483
|
+
cause
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
async addCustomEvmNetwork(customEvmNetwork) {
|
|
488
|
+
try {
|
|
489
|
+
if (!("isCustom" in customEvmNetwork && customEvmNetwork.isCustom)) return Promise.resolve();
|
|
490
|
+
return await this.#db.evmNetworks.put(customEvmNetwork);
|
|
491
|
+
} catch (cause) {
|
|
492
|
+
throw new Error("Failed to add custom evm network", {
|
|
493
|
+
cause
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
async removeCustomEvmNetwork(evmNetworkId) {
|
|
498
|
+
if (await this.getIsBuiltInEvmNetwork(evmNetworkId)) throw new Error("Cannot remove built-in EVM network");
|
|
499
|
+
try {
|
|
500
|
+
return this.#db.transaction("rw", [this.#db.evmNetworks, this.#db.tokens], async () => {
|
|
501
|
+
await this.#db.evmNetworks.delete(evmNetworkId);
|
|
502
|
+
await this.#db.tokens.filter(token => token.evmNetwork?.id === evmNetworkId).delete();
|
|
503
|
+
});
|
|
504
|
+
} catch (cause) {
|
|
505
|
+
throw new Error("Failed to remove custom evm network", {
|
|
506
|
+
cause
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async setCustomEvmNetworks(networks) {
|
|
511
|
+
return await this.#db.transaction("rw", this.#db.evmNetworks, async () => {
|
|
512
|
+
const keys = await this.#db.evmNetworks.filter(network => "isCustom" in network && network.isCustom).primaryKeys();
|
|
513
|
+
await this.#db.evmNetworks.bulkDelete(keys);
|
|
514
|
+
await this.#db.evmNetworks.bulkPut(networks.filter(network => network.isCustom));
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
async resetEvmNetwork(evmNetworkId) {
|
|
518
|
+
const builtInEvmNetwork = await fetchEvmNetwork(evmNetworkId);
|
|
519
|
+
if (!builtInEvmNetwork) throw new Error("Cannot reset non-built-in EVM network");
|
|
520
|
+
const nativeModule = builtInEvmNetwork.balancesConfig.find(c => c.moduleType === "evm-native");
|
|
521
|
+
if (!nativeModule?.moduleConfig) throw new Error("Failed to lookup native token (no token exists for network)");
|
|
522
|
+
const {
|
|
523
|
+
symbol,
|
|
524
|
+
decimals,
|
|
525
|
+
coingeckoId,
|
|
526
|
+
logo,
|
|
527
|
+
mirrorOf,
|
|
528
|
+
dcentName,
|
|
529
|
+
noDiscovery
|
|
530
|
+
} = nativeModule.moduleConfig;
|
|
531
|
+
if (!symbol) throw new Error("Missing native token symbol");
|
|
532
|
+
if (!decimals) throw new Error("Missing native token decimals");
|
|
533
|
+
const builtInNativeToken = {
|
|
534
|
+
id: getNativeTokenId(evmNetworkId, "evm-native"),
|
|
535
|
+
type: "evm-native",
|
|
536
|
+
evmNetwork: {
|
|
537
|
+
id: evmNetworkId
|
|
538
|
+
},
|
|
539
|
+
isTestnet: builtInEvmNetwork.isTestnet ?? false,
|
|
540
|
+
isDefault: true,
|
|
541
|
+
symbol,
|
|
542
|
+
decimals,
|
|
543
|
+
coingeckoId,
|
|
544
|
+
logo
|
|
545
|
+
};
|
|
546
|
+
if (mirrorOf) builtInNativeToken.mirrorOf = mirrorOf;
|
|
547
|
+
if (dcentName) builtInNativeToken.dcentName = dcentName;
|
|
548
|
+
if (noDiscovery) builtInNativeToken.noDiscovery = noDiscovery;
|
|
549
|
+
builtInEvmNetwork.nativeToken = {
|
|
550
|
+
id: builtInNativeToken.id
|
|
551
|
+
};
|
|
552
|
+
try {
|
|
553
|
+
return await this.#db.transaction("rw", this.#db.evmNetworks, this.#db.tokens, async () => {
|
|
554
|
+
// delete chain and its native tokens (ensures cleanup of tokens with legacy ids)
|
|
555
|
+
await this.#db.tokens.filter(token => token.type === "evm-native" && token.evmNetwork?.id === evmNetworkId).delete();
|
|
556
|
+
const networkToDelete = await this.#db.evmNetworks.get(evmNetworkId);
|
|
557
|
+
if (networkToDelete?.nativeToken?.id) await this.#db.tokens.delete(networkToDelete.nativeToken.id);
|
|
558
|
+
await this.#db.evmNetworks.delete(evmNetworkId);
|
|
559
|
+
|
|
560
|
+
// reprovision them from chaindata
|
|
561
|
+
await this.#db.evmNetworks.put(builtInEvmNetwork);
|
|
562
|
+
await this.#db.tokens.put(builtInNativeToken);
|
|
563
|
+
});
|
|
564
|
+
} catch (cause) {
|
|
565
|
+
throw new Error("Failed to reset evm network", {
|
|
566
|
+
cause
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
async addCustomToken(customToken) {
|
|
571
|
+
try {
|
|
572
|
+
if (!("isCustom" in customToken && customToken.isCustom)) return Promise.resolve();
|
|
573
|
+
return await this.#db.tokens.put(customToken);
|
|
574
|
+
} catch (cause) {
|
|
575
|
+
throw new Error("Failed to add custom token", {
|
|
576
|
+
cause
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async removeCustomToken(tokenId) {
|
|
581
|
+
try {
|
|
582
|
+
return await this.#db.tokens
|
|
583
|
+
// only affect custom tokens
|
|
584
|
+
.filter(token => "isCustom" in token && Boolean(token.isCustom))
|
|
585
|
+
// only affect the provided token
|
|
586
|
+
.filter(token => token.id === tokenId)
|
|
587
|
+
// delete the token (if exists)
|
|
588
|
+
.delete();
|
|
589
|
+
} catch (cause) {
|
|
590
|
+
throw new Error("Failed to remove custom token", {
|
|
591
|
+
cause
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async setCustomTokens(tokens) {
|
|
596
|
+
// TODO custom tokens have to go into localstorage
|
|
597
|
+
return await this.#db.transaction("rw", this.#db.tokens, async () => {
|
|
598
|
+
const keys = await this.#db.tokens.filter(token => "isCustom" in token && Boolean(token.isCustom)).primaryKeys();
|
|
599
|
+
await this.#db.tokens.bulkDelete(keys);
|
|
600
|
+
await this.#db.tokens.bulkPut(tokens.filter(token => "isCustom" in token && token.isCustom));
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
async removeToken(tokenId) {
|
|
604
|
+
try {
|
|
605
|
+
return await this.#db.tokens.delete(tokenId);
|
|
606
|
+
} catch (cause) {
|
|
607
|
+
throw new Error("Failed to remove token", {
|
|
608
|
+
cause
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Hydrate the db with the latest chaindata from subsquid.
|
|
615
|
+
*
|
|
616
|
+
* @returns A promise which resolves to true if any db table has been hydrated, or false if all hydration has been skipped.
|
|
617
|
+
*/
|
|
618
|
+
async hydrate({
|
|
619
|
+
// chainsArgs, // hydrateChains has no args
|
|
620
|
+
// evmNetworksArgs, // hydrateEvmNetworks has no args
|
|
621
|
+
tokensArgs
|
|
622
|
+
} = {}) {
|
|
623
|
+
return (await Promise.all([
|
|
624
|
+
// call inner hydration methods
|
|
625
|
+
this.hydrateChains(), this.hydrateEvmNetworks(), this.hydrateSubstrateTokens(...(tokensArgs ? tokensArgs : []))])
|
|
626
|
+
|
|
627
|
+
// return true if any hydration occurred
|
|
628
|
+
).some(Boolean);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/**
|
|
632
|
+
* Hydrate the db with the latest chains from subsquid.
|
|
633
|
+
* Hydration is skipped when the last successful hydration was less than minimumHydrationInterval ms ago.
|
|
634
|
+
*
|
|
635
|
+
* @returns A promise which resolves to true if the db has been hydrated, or false if the hydration was skipped.
|
|
636
|
+
*/
|
|
637
|
+
async hydrateChains() {
|
|
638
|
+
const now = Date.now();
|
|
639
|
+
if (now - this.#lastHydratedChainsAt < minimumHydrationInterval) return false;
|
|
640
|
+
const dbHasChains = (await this.#db.chains.count()) > 0;
|
|
641
|
+
try {
|
|
642
|
+
try {
|
|
643
|
+
var chains = addCustomChainRpcs(await fetchChains(), this.#onfinalityApiKey); // eslint-disable-line no-var
|
|
644
|
+
if (chains.length <= 0) throw new Error("Ignoring empty chaindata chains response");
|
|
645
|
+
} catch (error) {
|
|
646
|
+
if (dbHasChains) throw error;
|
|
647
|
+
|
|
648
|
+
// On first start-up (db is empty), if we fail to fetch chains then we should
|
|
649
|
+
// initialize the DB with the list of chains inside our init/chains.json file.
|
|
650
|
+
// This data will represent a relatively recent copy of what's in the squid,
|
|
651
|
+
// which will be better for our users than to have nothing at all.
|
|
652
|
+
var chains = addCustomChainRpcs(await fetchInitChains(), this.#onfinalityApiKey); // eslint-disable-line no-var
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// TODO check if alec is this the right way to set native token
|
|
656
|
+
// note : many chains don't have a native module provisionned from chaindata => breaks edit network screen and probably send funds and tx screens
|
|
657
|
+
for (const chain of chains) {
|
|
658
|
+
const nativeTokenModule = chain.balancesConfig.find(c => c.moduleType === "substrate-native");
|
|
659
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
660
|
+
const symbol = nativeTokenModule?.moduleConfig?.symbol;
|
|
661
|
+
if (!symbol) continue;
|
|
662
|
+
chain.nativeToken = {
|
|
663
|
+
id: getNativeTokenId(chain.id, "substrate-native")
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
await this.#db.transaction("rw", this.#db.chains, async () => {
|
|
667
|
+
await this.#db.chains.filter(chain => !("isCustom" in chain && chain.isCustom)).delete();
|
|
668
|
+
|
|
669
|
+
// add all except ones matching custom existing ones (user may customize built-in chains)
|
|
670
|
+
const customChainIds = (await this.#db.chains.toArray()).map(chain => chain.id);
|
|
671
|
+
const newChains = chains.filter(chain => !customChainIds.includes(chain.id));
|
|
672
|
+
await this.#db.chains.bulkPut(newChains);
|
|
673
|
+
});
|
|
674
|
+
this.#lastHydratedChainsAt = now;
|
|
675
|
+
return true;
|
|
676
|
+
} catch (error) {
|
|
677
|
+
log.warn(`Failed to hydrate chains from chaindata`, error);
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Hydrate the db with the latest evmNetworks from subsquid.
|
|
684
|
+
* Hydration is skipped when the last successful hydration was less than minimumHydrationInterval ms ago.
|
|
685
|
+
*
|
|
686
|
+
* @returns A promise which resolves to true if the db has been hydrated, or false if the hydration was skipped.
|
|
687
|
+
*/
|
|
688
|
+
async hydrateEvmNetworks() {
|
|
689
|
+
const now = Date.now();
|
|
690
|
+
if (now - this.#lastHydratedEvmNetworksAt < minimumHydrationInterval) return false;
|
|
691
|
+
const dbHasEvmNetworks = (await this.#db.evmNetworks.count()) > 0;
|
|
692
|
+
try {
|
|
693
|
+
try {
|
|
694
|
+
var evmNetworks = await fetchEvmNetworks(); // eslint-disable-line no-var
|
|
695
|
+
if (evmNetworks.length <= 0) throw new Error("Ignoring empty chaindata evmNetworks response");
|
|
696
|
+
} catch (error) {
|
|
697
|
+
if (dbHasEvmNetworks) throw error;
|
|
698
|
+
|
|
699
|
+
// On first start-up (db is empty), if we fail to fetch evmNetworks then we should
|
|
700
|
+
// initialize the DB with the list of evmNetworks inside our init/evm-networks.json file.
|
|
701
|
+
// This data will represent a relatively recent copy of what's in the squid,
|
|
702
|
+
// which will be better for our users than to have nothing at all.
|
|
703
|
+
var evmNetworks = await fetchInitEvmNetworks(); // eslint-disable-line no-var
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// set native token
|
|
707
|
+
for (const evmNetwork of evmNetworks) {
|
|
708
|
+
evmNetwork.nativeToken = {
|
|
709
|
+
id: getNativeTokenId(evmNetwork.id, "evm-native")
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
await this.#db.transaction("rw", this.#db.evmNetworks, async () => {
|
|
713
|
+
await this.#db.evmNetworks.filter(network => !("isCustom" in network && network.isCustom)).delete();
|
|
714
|
+
|
|
715
|
+
// remaining entries are custom networks
|
|
716
|
+
const existingCustomNetworks = await this.#db.evmNetworks.toArray();
|
|
717
|
+
const existingCustomNetworksById = Object.fromEntries(existingCustomNetworks.map(network => [network.id, network]));
|
|
718
|
+
|
|
719
|
+
// dont override custom networks, except for the balancesConfig property
|
|
720
|
+
const newNetworks = evmNetworks.map(network => {
|
|
721
|
+
const existing = existingCustomNetworksById[network.id];
|
|
722
|
+
return existing ? Object.assign({}, existing, {
|
|
723
|
+
balancesConfig: network.balancesConfig
|
|
724
|
+
}) : network;
|
|
725
|
+
});
|
|
726
|
+
await this.#db.evmNetworks.bulkPut(newNetworks);
|
|
727
|
+
});
|
|
728
|
+
this.#lastHydratedEvmNetworksAt = now;
|
|
729
|
+
return true;
|
|
730
|
+
} catch (error) {
|
|
731
|
+
log.warn(`Failed to hydrate evmNetworks from chaindata`, error);
|
|
732
|
+
return false;
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
async updateChainTokens(chainId, source, newTokens, availableTokenLogoFilenames) {
|
|
736
|
+
// TODO: Test logos and fall back to unknown token logo url
|
|
737
|
+
// (Maybe put the test into each balance module itself)
|
|
738
|
+
|
|
739
|
+
const existingChainTokens = await this.#db.tokens.filter(token => token.chain?.id === chainId && token.type === source).toArray();
|
|
740
|
+
newTokens.forEach(token => {
|
|
741
|
+
if (token.logo) return;
|
|
742
|
+
const symbolLogo = token.symbol.toLowerCase().replace(/ /g, "_");
|
|
743
|
+
if (availableTokenLogoFilenames.includes(`${symbolLogo}.svg`)) {
|
|
744
|
+
return token.logo = githubTokenLogoUrl(symbolLogo);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// TODO: Use coingeckoId logo if exists
|
|
748
|
+
|
|
749
|
+
return token.logo = githubUnknownTokenLogoUrl;
|
|
750
|
+
});
|
|
751
|
+
const notCustomTokenIds = existingChainTokens.filter(token => !("isCustom" in token && token.isCustom)).map(token => token.id);
|
|
752
|
+
const customTokenIds = existingChainTokens.filter(token => "isCustom" in token && token.isCustom).map(token => token.id);
|
|
753
|
+
await this.#db.transaction("rw", this.#db.tokens, this.#db.chains, async () => {
|
|
754
|
+
await this.#db.tokens.bulkDelete(notCustomTokenIds);
|
|
755
|
+
await this.#db.tokens.bulkPut(newTokens.filter(token => !customTokenIds.includes(token.id)));
|
|
756
|
+
//if (chain && shouldUpdateChain) await this.#db.chains.put(chain)
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
async updateEvmNetworkTokens(newTokens) {
|
|
760
|
+
const existingEvmNetworkTokens = await this.#db.tokens.filter(t => t.type.startsWith("evm-")).toArray();
|
|
761
|
+
const isCustomToken = token => "isCustom" in token && token.isCustom;
|
|
762
|
+
|
|
763
|
+
// don't override custom tokens
|
|
764
|
+
const customTokenIds = new Set();
|
|
765
|
+
|
|
766
|
+
// delete non-custom tokens which aren't in `newTokens`
|
|
767
|
+
const deleteTokenIds = new Set();
|
|
768
|
+
for (const token of existingEvmNetworkTokens) {
|
|
769
|
+
if (isCustomToken(token)) customTokenIds.add(token.id);else deleteTokenIds.add(token.id);
|
|
770
|
+
}
|
|
771
|
+
const tokensToUpdate = newTokens.filter(token => {
|
|
772
|
+
deleteTokenIds.delete(token.id);
|
|
773
|
+
return !customTokenIds.has(token.id);
|
|
774
|
+
});
|
|
775
|
+
this.#db.transaction("rw", this.#db.tokens, async () => {
|
|
776
|
+
// delete all existing non custom tokens
|
|
777
|
+
await this.#db.tokens.bulkDelete([...deleteTokenIds]);
|
|
778
|
+
|
|
779
|
+
// force update on all non custom tokens
|
|
780
|
+
await this.#db.tokens.bulkPut(tokensToUpdate);
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* Hydrate the db with the latest tokens from subsquid.
|
|
786
|
+
* Hydration is skipped when the last successful hydration was less than minimumHydrationInterval ms ago.
|
|
787
|
+
*
|
|
788
|
+
* @returns A promise which resolves to true if the db has been hydrated, or false if the hydration was skipped.
|
|
789
|
+
*/
|
|
790
|
+
async hydrateSubstrateTokens(chainIdFilter) {
|
|
791
|
+
const now = Date.now();
|
|
792
|
+
if (now - this.#lastHydratedTokensAt < minimumHydrationInterval) return false;
|
|
793
|
+
const dbHasTokens = (await this.#db.tokens.count()) > 0;
|
|
794
|
+
try {
|
|
795
|
+
try {
|
|
796
|
+
var tokens = parseTokensResponse(await fetchSubstrateTokens()); // eslint-disable-line no-var
|
|
797
|
+
if (tokens.length <= 0) throw new Error("Ignoring empty chaindata tokens response");
|
|
798
|
+
} catch (error) {
|
|
799
|
+
if (dbHasTokens) throw error;
|
|
800
|
+
|
|
801
|
+
// On first start-up (db is empty), if we fail to fetch tokens then we should
|
|
802
|
+
// initialize the DB with the list of tokens inside our init/tokens.json file.
|
|
803
|
+
// This data will represent a relatively recent copy of what's in the squid,
|
|
804
|
+
// which will be better for our users than to have nothing at all.
|
|
805
|
+
var tokens = parseTokensResponse(await fetchInitSubstrateTokens()); // eslint-disable-line no-var
|
|
806
|
+
}
|
|
807
|
+
await this.#db.transaction("rw", this.#db.tokens, async () => {
|
|
808
|
+
const deleteChains = chainIdFilter ? new Set(chainIdFilter) : undefined;
|
|
809
|
+
const tokensToDelete = (await this.#db.tokens.toArray()).filter(token => {
|
|
810
|
+
// don't delete custom tokens
|
|
811
|
+
if ("isCustom" in token && token.isCustom) return false;
|
|
812
|
+
|
|
813
|
+
// delete all other tokens if chainIdFilter is not specified
|
|
814
|
+
if (deleteChains === undefined) return true;
|
|
815
|
+
|
|
816
|
+
// delete tokens on chainIdFilter chains is it is specified
|
|
817
|
+
if (token.chain?.id && deleteChains.has(token.chain.id)) return true;
|
|
818
|
+
return false;
|
|
819
|
+
}).map(token => token.id);
|
|
820
|
+
if (tokensToDelete.length) await this.#db.tokens.bulkDelete(tokensToDelete);
|
|
821
|
+
|
|
822
|
+
// add all except ones matching custom existing ones (user may customize built-in tokens)
|
|
823
|
+
const customTokenIds = (await this.#db.tokens.toArray()).map(token => token.id);
|
|
824
|
+
const newTokens = tokens.filter(token => {
|
|
825
|
+
// don't replace custom tokens
|
|
826
|
+
if (customTokenIds.includes(token.id)) return false;
|
|
827
|
+
if (deleteChains === undefined) return true;
|
|
828
|
+
if (!token.chain?.id) return true;
|
|
829
|
+
if (deleteChains.has(token.chain.id)) return true;
|
|
830
|
+
return false;
|
|
831
|
+
});
|
|
832
|
+
await this.#db.tokens.bulkPut(newTokens);
|
|
833
|
+
});
|
|
834
|
+
this.#lastHydratedTokensAt = now;
|
|
835
|
+
return true;
|
|
836
|
+
} catch (error) {
|
|
837
|
+
log.warn(`Failed to hydrate tokens from chaindata`, error);
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async getIsBuiltInChain(chainId) {
|
|
842
|
+
const chain = await fetchChain(chainId);
|
|
843
|
+
return !!chain;
|
|
844
|
+
}
|
|
845
|
+
async getIsBuiltInEvmNetwork(evmNetworkId) {
|
|
846
|
+
try {
|
|
847
|
+
const evmNetwork = await fetchEvmNetwork(evmNetworkId);
|
|
848
|
+
return !!evmNetwork;
|
|
849
|
+
} catch (e) {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
async transaction(mode, tables, scope) {
|
|
854
|
+
return await this.#db.transaction(mode, tables, scope);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export { ChaindataProvider, addCustomChainRpcs, customChainsFilter, customEvmNetworksFilter, customTokensFilter, fetchChain, fetchChains, fetchEvmNetwork, fetchEvmNetworks, fetchInitChains, fetchInitEvmNetworks, fetchInitMiniMetadatas, fetchInitSubstrateTokens, fetchSubstrateToken, fetchSubstrateTokens, githubTokenLogoUrl, githubUnknownTokenLogoUrl, isCustomChain, isCustomEvmNetwork, isToken, isTokenPartial, itemsToIds, itemsToMapByGenesisHash, itemsToMapById, parseTokensResponse, withErrorReason, wrapObservableWithGetter };
|