@ocap/resolver 1.28.8 → 1.29.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/esm/api.d.mts +24 -0
- package/esm/api.mjs +53 -0
- package/esm/hooks.d.mts +153 -0
- package/esm/hooks.mjs +267 -0
- package/esm/index.d.mts +201 -0
- package/esm/index.mjs +1327 -0
- package/esm/migration-chain.d.mts +52 -0
- package/esm/migration-chain.mjs +97 -0
- package/esm/package.mjs +5 -0
- package/esm/token-cache.d.mts +20 -0
- package/esm/token-cache.mjs +26 -0
- package/esm/token-distribution.d.mts +166 -0
- package/esm/token-distribution.mjs +241 -0
- package/esm/token-flow.d.mts +139 -0
- package/esm/token-flow.mjs +330 -0
- package/esm/types.d.mts +115 -0
- package/esm/types.mjs +1 -0
- package/lib/_virtual/rolldown_runtime.cjs +29 -0
- package/lib/api.cjs +54 -0
- package/lib/api.d.cts +24 -0
- package/lib/hooks.cjs +274 -0
- package/lib/hooks.d.cts +153 -0
- package/lib/index.cjs +1343 -0
- package/lib/index.d.cts +201 -0
- package/lib/migration-chain.cjs +99 -0
- package/lib/migration-chain.d.cts +52 -0
- package/lib/package.cjs +11 -0
- package/lib/token-cache.cjs +27 -0
- package/lib/token-cache.d.cts +20 -0
- package/lib/token-distribution.cjs +243 -0
- package/lib/token-distribution.d.cts +166 -0
- package/lib/token-flow.cjs +336 -0
- package/lib/token-flow.d.cts +139 -0
- package/lib/types.cjs +0 -0
- package/lib/types.d.cts +115 -0
- package/package.json +49 -21
- package/lib/api.js +0 -71
- package/lib/hooks.js +0 -339
- package/lib/index.js +0 -1486
- package/lib/migration-chain.js +0 -144
- package/lib/token-cache.js +0 -40
- package/lib/token-distribution.js +0 -358
- package/lib/token-flow.js +0 -445
package/esm/index.mjs
ADDED
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
import { name } from "./package.mjs";
|
|
2
|
+
import { onCreateRollup, onCreateRollupBlock, onCreateTx, onUpdateToken } from "./hooks.mjs";
|
|
3
|
+
import { MigrationChainManager } from "./migration-chain.mjs";
|
|
4
|
+
import { getInstance } from "./token-cache.mjs";
|
|
5
|
+
import { TokenDistributionManager } from "./token-distribution.mjs";
|
|
6
|
+
import { listTokenFlows, verifyAccountRisk } from "./token-flow.mjs";
|
|
7
|
+
import { toStakeAddress } from "@arcblock/did-util";
|
|
8
|
+
import { BN, formatTxType, fromTokenToUnit, toAddress, toBN } from "@ocap/util";
|
|
9
|
+
import createDebug from "debug";
|
|
10
|
+
import pick from "lodash/pick.js";
|
|
11
|
+
import { fromPublicKey, isValid, toTypeInfo } from "@arcblock/did";
|
|
12
|
+
import { Joi, patterns } from "@arcblock/validator";
|
|
13
|
+
import * as Config from "@ocap/config";
|
|
14
|
+
import { types } from "@ocap/mcrypto";
|
|
15
|
+
import { fromTypeUrl } from "@ocap/message";
|
|
16
|
+
import * as states from "@ocap/state";
|
|
17
|
+
import { createExecutor } from "@ocap/tx-protocols";
|
|
18
|
+
import { decodeAnySafe } from "@ocap/tx-protocols/lib/util";
|
|
19
|
+
import { DEFAULT_TOKEN_DECIMAL } from "@ocap/util/lib/constant";
|
|
20
|
+
import { CustomError } from "@ocap/util/lib/error";
|
|
21
|
+
import { md5 } from "@ocap/util/lib/md5";
|
|
22
|
+
import get from "lodash/get.js";
|
|
23
|
+
import isEmpty from "lodash/isEmpty.js";
|
|
24
|
+
import isEqual from "lodash/isEqual.js";
|
|
25
|
+
import omit from "lodash/omit.js";
|
|
26
|
+
import set from "lodash/set.js";
|
|
27
|
+
import uniqBy from "lodash/unionBy.js";
|
|
28
|
+
import uniq from "lodash/uniq.js";
|
|
29
|
+
import Queue from "queue";
|
|
30
|
+
import { createIndexedAccount, createIndexedAsset, createIndexedDelegation, createIndexedFactory, createIndexedRollup, createIndexedRollupBlock, createIndexedStake, createIndexedToken, createIndexedTokenFactory, createIndexedTransaction, isDefaultTokenChanged } from "@ocap/indexdb/lib/util";
|
|
31
|
+
import { attachPaidTxGas, getTxReceipts, getTxReceiver, getTxSender, mergeTxReceipts } from "@ocap/state/lib/states/tx";
|
|
32
|
+
|
|
33
|
+
//#region src/index.ts
|
|
34
|
+
const debug = createDebug(name);
|
|
35
|
+
const noop = (x) => x;
|
|
36
|
+
const CHAIN_ADDR = md5("OCAP_CHAIN_ADDR");
|
|
37
|
+
const maxGasOps = {
|
|
38
|
+
"fg:t:account_migrate": {
|
|
39
|
+
create: 2,
|
|
40
|
+
update: 1
|
|
41
|
+
},
|
|
42
|
+
"fg:t:delegate": {
|
|
43
|
+
create: 3,
|
|
44
|
+
update: 1
|
|
45
|
+
},
|
|
46
|
+
"fg:t:revoke_delegate": {
|
|
47
|
+
create: 1,
|
|
48
|
+
update: 2
|
|
49
|
+
},
|
|
50
|
+
"fg:t:create_asset": {
|
|
51
|
+
create: 2,
|
|
52
|
+
update: 2
|
|
53
|
+
},
|
|
54
|
+
"fg:t:update_asset": {
|
|
55
|
+
create: 1,
|
|
56
|
+
update: 2
|
|
57
|
+
},
|
|
58
|
+
"fg:t:consume_asset": {
|
|
59
|
+
create: 1,
|
|
60
|
+
update: 2
|
|
61
|
+
},
|
|
62
|
+
"fg:t:create_factory": {
|
|
63
|
+
create: 2,
|
|
64
|
+
update: 2
|
|
65
|
+
},
|
|
66
|
+
"fg:t:acquire_asset_v2": {
|
|
67
|
+
create: 2,
|
|
68
|
+
update: 12
|
|
69
|
+
},
|
|
70
|
+
"fg:t:acquire_asset_v3": {
|
|
71
|
+
create: 3,
|
|
72
|
+
update: 12
|
|
73
|
+
},
|
|
74
|
+
"fg:t:mint_asset": {
|
|
75
|
+
create: 3,
|
|
76
|
+
update: 10
|
|
77
|
+
},
|
|
78
|
+
"fg:t:create_token": {
|
|
79
|
+
create: 2,
|
|
80
|
+
update: 3
|
|
81
|
+
},
|
|
82
|
+
"fg:t:deposit_token_v2": {
|
|
83
|
+
create: 3,
|
|
84
|
+
update: 3
|
|
85
|
+
},
|
|
86
|
+
"fg:t:withdraw_token_v2": {
|
|
87
|
+
create: 2,
|
|
88
|
+
update: 3
|
|
89
|
+
},
|
|
90
|
+
"fg:t:stake": {
|
|
91
|
+
create: 2,
|
|
92
|
+
update: 18
|
|
93
|
+
},
|
|
94
|
+
"fg:t:revoke_stake": {
|
|
95
|
+
create: 1,
|
|
96
|
+
update: 2
|
|
97
|
+
},
|
|
98
|
+
"fg:t:claim_stake": {
|
|
99
|
+
create: 2,
|
|
100
|
+
update: 18
|
|
101
|
+
},
|
|
102
|
+
"fg:t:slash_stake": { update: 18 },
|
|
103
|
+
"fg:t:return_stake": { update: 8 },
|
|
104
|
+
"fg:t:transfer": {
|
|
105
|
+
create: 2,
|
|
106
|
+
update: 20
|
|
107
|
+
},
|
|
108
|
+
"fg:t:transfer_v2": {
|
|
109
|
+
create: 2,
|
|
110
|
+
update: 20
|
|
111
|
+
},
|
|
112
|
+
"fg:t:transfer_v3": {
|
|
113
|
+
create: 9,
|
|
114
|
+
update: 16
|
|
115
|
+
},
|
|
116
|
+
"fg:t:exchange_v2": {
|
|
117
|
+
create: 1,
|
|
118
|
+
update: 18
|
|
119
|
+
},
|
|
120
|
+
"fg:t:create_token_factory": {
|
|
121
|
+
create: 2,
|
|
122
|
+
update: 3
|
|
123
|
+
},
|
|
124
|
+
"fg:t:update_token_factory": {
|
|
125
|
+
create: 0,
|
|
126
|
+
update: 3
|
|
127
|
+
},
|
|
128
|
+
"fg:t:mint_token": {
|
|
129
|
+
create: 2,
|
|
130
|
+
update: 13
|
|
131
|
+
},
|
|
132
|
+
"fg:t:burn_token": {
|
|
133
|
+
create: 2,
|
|
134
|
+
update: 13
|
|
135
|
+
},
|
|
136
|
+
"fg:t:create_rollup": {
|
|
137
|
+
create: 2,
|
|
138
|
+
update: 2
|
|
139
|
+
},
|
|
140
|
+
"fg:t:update_rollup": {
|
|
141
|
+
create: 1,
|
|
142
|
+
update: 2
|
|
143
|
+
},
|
|
144
|
+
"fg:t:pause_rollup": {
|
|
145
|
+
create: 1,
|
|
146
|
+
update: 2
|
|
147
|
+
},
|
|
148
|
+
"fg:t:close_rollup": {
|
|
149
|
+
create: 1,
|
|
150
|
+
update: 26
|
|
151
|
+
},
|
|
152
|
+
"fg:t:resume_rollup": {
|
|
153
|
+
create: 1,
|
|
154
|
+
update: 2
|
|
155
|
+
},
|
|
156
|
+
"fg:t:join_rollup": {
|
|
157
|
+
create: 2,
|
|
158
|
+
update: 3
|
|
159
|
+
},
|
|
160
|
+
"fg:t:leave_rollup": {
|
|
161
|
+
create: 2,
|
|
162
|
+
update: 3
|
|
163
|
+
},
|
|
164
|
+
"fg:t:migrate_rollup": {
|
|
165
|
+
create: 1,
|
|
166
|
+
update: 2
|
|
167
|
+
},
|
|
168
|
+
"fg:t:create_rollup_block": {
|
|
169
|
+
create: 2,
|
|
170
|
+
update: 38
|
|
171
|
+
},
|
|
172
|
+
"fg:t:claim_block_reward": {
|
|
173
|
+
create: 2,
|
|
174
|
+
update: 38
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const txRequestConfig = {
|
|
178
|
+
maxRequest: Number(process.env.TX_MAX_REQUEST) || 200,
|
|
179
|
+
concurrent: Number(process.env.TX_CONCURRENCY) || 10,
|
|
180
|
+
timeout: Number(process.env.TX_TIMEOUT) || 1e3 * 30
|
|
181
|
+
};
|
|
182
|
+
const formatData = (data) => {
|
|
183
|
+
if (!data) return data;
|
|
184
|
+
if (data.type_url) return data;
|
|
185
|
+
if (data.typeUrl && ["json", "vc"].includes(data.typeUrl)) try {
|
|
186
|
+
JSON.parse(data.value);
|
|
187
|
+
data.type_url = data.typeUrl;
|
|
188
|
+
return data;
|
|
189
|
+
} catch (_err) {
|
|
190
|
+
try {
|
|
191
|
+
JSON.parse(data.value.replace(/\\"/g, "\""));
|
|
192
|
+
data.type_url = data.typeUrl;
|
|
193
|
+
data.value = data.value.replace(/\\"/g, "\"");
|
|
194
|
+
return data;
|
|
195
|
+
} catch (_e) {}
|
|
196
|
+
}
|
|
197
|
+
const decoded = data.typeUrl ? decodeAnySafe(data) : data;
|
|
198
|
+
if (!decoded) return {
|
|
199
|
+
typeUrl: "",
|
|
200
|
+
type_url: "",
|
|
201
|
+
value: ""
|
|
202
|
+
};
|
|
203
|
+
if ([
|
|
204
|
+
"json",
|
|
205
|
+
"vc",
|
|
206
|
+
"AssetFactory"
|
|
207
|
+
].includes(decoded.type)) return {
|
|
208
|
+
typeUrl: decoded.type,
|
|
209
|
+
type_url: decoded.type,
|
|
210
|
+
value: JSON.stringify(decoded.value)
|
|
211
|
+
};
|
|
212
|
+
return {
|
|
213
|
+
typeUrl: decoded.type,
|
|
214
|
+
type_url: decoded.type,
|
|
215
|
+
value: decoded.value
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
const formatDelegationOps = (state) => {
|
|
219
|
+
if (state?.ops && typeof state.ops === "object" && !Array.isArray(state.ops)) {
|
|
220
|
+
const opsRecord = state.ops;
|
|
221
|
+
state.ops = Object.keys(opsRecord).map((x) => ({
|
|
222
|
+
key: x,
|
|
223
|
+
value: opsRecord[x]
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
return state;
|
|
227
|
+
};
|
|
228
|
+
const extractTokenMeta = (address, tokenStates) => {
|
|
229
|
+
const tokenState = tokenStates.filter(Boolean).find((t) => t.address === address);
|
|
230
|
+
if (!tokenState) return {};
|
|
231
|
+
return {
|
|
232
|
+
address,
|
|
233
|
+
decimal: typeof tokenState.decimal === "undefined" ? DEFAULT_TOKEN_DECIMAL : tokenState.decimal,
|
|
234
|
+
unit: tokenState.unit,
|
|
235
|
+
symbol: tokenState.symbol,
|
|
236
|
+
foreignToken: tokenState.foreignToken
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
var OCAPResolver = class {
|
|
240
|
+
/**
|
|
241
|
+
* Creates an instance of OCAPResolver.
|
|
242
|
+
* @param params Parameters to bootstrap the resolver
|
|
243
|
+
* @param params.statedb @ocap/statedb adapter
|
|
244
|
+
* @param params.indexdb @ocap/indexdb adapter
|
|
245
|
+
* @param params.config @ocap/config object
|
|
246
|
+
* @param params.filter bloom filter to do anti-replay check
|
|
247
|
+
* @param params.validateTokenConfig should we validate token supply and token-holder balance, should be disabled when starting an existing chain
|
|
248
|
+
*/
|
|
249
|
+
constructor({ statedb, indexdb, config, filter, validateTokenConfig = true, logger }) {
|
|
250
|
+
if (!statedb) throw new Error("OCAP Resolver requires a valid statedb implementation to work");
|
|
251
|
+
if (!indexdb) throw new Error("OCAP Resolver requires a valid indexdb implementation to work");
|
|
252
|
+
this.logger = logger || console;
|
|
253
|
+
this.statedb = statedb;
|
|
254
|
+
this.indexdb = indexdb;
|
|
255
|
+
this.filter = filter;
|
|
256
|
+
this.config = Object.freeze(Config.validate(config));
|
|
257
|
+
this.chainAddr = md5(config.chainId);
|
|
258
|
+
this.tokenItx = Config.genTokenItx(this.config);
|
|
259
|
+
this.consensus = `${statedb.name} v${statedb.version}`;
|
|
260
|
+
this.queue = new Queue({
|
|
261
|
+
autostart: true,
|
|
262
|
+
concurrency: txRequestConfig.concurrent,
|
|
263
|
+
timeout: txRequestConfig.timeout
|
|
264
|
+
});
|
|
265
|
+
if (indexdb) {
|
|
266
|
+
this.tokenCache = getInstance(indexdb.token);
|
|
267
|
+
this.tokenDistribution = new TokenDistributionManager(this);
|
|
268
|
+
}
|
|
269
|
+
if (this.tokenItx) {
|
|
270
|
+
this.config.token.address = this.tokenItx.address;
|
|
271
|
+
if (this.tokenCache) this.tokenCache.set(this.tokenItx.address, this.tokenItx);
|
|
272
|
+
}
|
|
273
|
+
this.executor = createExecutor({
|
|
274
|
+
filter,
|
|
275
|
+
runAsLambda: typeof statedb.runAsLambda === "function" ? statedb.runAsLambda.bind(statedb) : void 0
|
|
276
|
+
});
|
|
277
|
+
this.formatTx = this._formatTx.bind(this);
|
|
278
|
+
if (validateTokenConfig) this.validateTokenConfig();
|
|
279
|
+
this.connectIndexDB();
|
|
280
|
+
this.initializeStateDB().catch((err) => {
|
|
281
|
+
this.logger.error("Failed to initialize statedb:", err.message);
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
sendTx({ tx: txBase64, extra }, ctx = {}) {
|
|
286
|
+
debug("sendTx", {
|
|
287
|
+
txBase64,
|
|
288
|
+
request: ctx.request
|
|
289
|
+
});
|
|
290
|
+
if (process.env.CHAIN_MODE === "readonly") throw new CustomError("FORBIDDEN", "This chain node is running in readonly mode");
|
|
291
|
+
if (this.queue.length >= txRequestConfig.maxRequest) throw new CustomError("FORBIDDEN", "Chain is busy");
|
|
292
|
+
const context = {
|
|
293
|
+
txBase64,
|
|
294
|
+
statedb: this.statedb,
|
|
295
|
+
indexdb: this.indexdb,
|
|
296
|
+
config: this.config,
|
|
297
|
+
filter: this.filter,
|
|
298
|
+
logger: this.logger,
|
|
299
|
+
extra: {
|
|
300
|
+
...ctx,
|
|
301
|
+
txExtra: extra
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
const task = async () => {
|
|
306
|
+
try {
|
|
307
|
+
resolve((await this.executor.execute(context)).txHash);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
reject(err);
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
this.queue.push(task);
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
getTx({ hash }, ctx) {
|
|
316
|
+
if (patterns.txHash.test(hash)) return this._getState({
|
|
317
|
+
table: "tx",
|
|
318
|
+
id: hash.toUpperCase(),
|
|
319
|
+
dataKey: "tx.itxJson.data",
|
|
320
|
+
onRead: (tx) => this.formatTx(tx, ctx),
|
|
321
|
+
expandContext: false,
|
|
322
|
+
ctx
|
|
323
|
+
});
|
|
324
|
+
return Promise.resolve(null);
|
|
325
|
+
}
|
|
326
|
+
getBlock() {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
getBlocks() {
|
|
330
|
+
return [];
|
|
331
|
+
}
|
|
332
|
+
getUnconfirmedTxs() {
|
|
333
|
+
return [];
|
|
334
|
+
}
|
|
335
|
+
async getChainInfo() {
|
|
336
|
+
return {
|
|
337
|
+
id: this.chainAddr,
|
|
338
|
+
totalTxs: await this.indexdb.tx.count(),
|
|
339
|
+
blockHash: md5(""),
|
|
340
|
+
blockHeight: 0,
|
|
341
|
+
blockTime: "",
|
|
342
|
+
version: this.config.version,
|
|
343
|
+
address: this.chainAddr,
|
|
344
|
+
appHash: md5((/* @__PURE__ */ new Date()).toISOString()),
|
|
345
|
+
consensusVersion: this.consensus,
|
|
346
|
+
forgeAppsVersion: [],
|
|
347
|
+
moniker: this.config.chainId,
|
|
348
|
+
network: this.config.chainId,
|
|
349
|
+
supportedTxs: this.config.transaction.supportedTxs,
|
|
350
|
+
synced: true,
|
|
351
|
+
votingPower: "10"
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
getNodeInfo() {
|
|
355
|
+
return Promise.resolve({
|
|
356
|
+
address: this.chainAddr,
|
|
357
|
+
appHash: md5((/* @__PURE__ */ new Date()).toISOString()),
|
|
358
|
+
consensusVersion: this.consensus,
|
|
359
|
+
forgeAppsVersion: [],
|
|
360
|
+
geoInfo: {
|
|
361
|
+
city: "Unknown",
|
|
362
|
+
country: "Unknown",
|
|
363
|
+
latitude: 0,
|
|
364
|
+
longitude: 0
|
|
365
|
+
},
|
|
366
|
+
id: this.chainAddr,
|
|
367
|
+
ip: "",
|
|
368
|
+
moniker: this.config.chainId,
|
|
369
|
+
network: this.config.chainId,
|
|
370
|
+
p2pAddress: "",
|
|
371
|
+
supportedTxs: this.config.transaction.supportedTxs,
|
|
372
|
+
synced: true,
|
|
373
|
+
votingPower: "10"
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
getNetInfo() {
|
|
377
|
+
return Promise.resolve({
|
|
378
|
+
listeners: [],
|
|
379
|
+
listening: true,
|
|
380
|
+
nPeers: 0,
|
|
381
|
+
peers: []
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
getValidatorsInfo() {
|
|
385
|
+
return Promise.resolve({
|
|
386
|
+
blockHeight: 0,
|
|
387
|
+
validators: [{
|
|
388
|
+
address: fromPublicKey(this.chainAddr),
|
|
389
|
+
name: "",
|
|
390
|
+
proposerPriority: "0",
|
|
391
|
+
votingPower: "10"
|
|
392
|
+
}]
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
getConfig() {
|
|
396
|
+
return JSON.stringify(this.config);
|
|
397
|
+
}
|
|
398
|
+
getAccountState({ address, traceMigration = true, expandContext = true }, ctx) {
|
|
399
|
+
return this._getState({
|
|
400
|
+
table: "account",
|
|
401
|
+
id: address,
|
|
402
|
+
dataKey: "data",
|
|
403
|
+
extraParams: { traceMigration },
|
|
404
|
+
expandContext,
|
|
405
|
+
onRead: async (state) => {
|
|
406
|
+
if (state) {
|
|
407
|
+
if (state.tokens) state.tokens = await this.formatTokenMap(state.tokens);
|
|
408
|
+
}
|
|
409
|
+
return state;
|
|
410
|
+
},
|
|
411
|
+
ctx
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
getStakeState({ address }, ctx) {
|
|
415
|
+
return this._getState({
|
|
416
|
+
table: "stake",
|
|
417
|
+
id: address,
|
|
418
|
+
dataKey: "data",
|
|
419
|
+
onRead: async (state) => {
|
|
420
|
+
if (state) {
|
|
421
|
+
if (state.tokens) state.tokens = await this.formatTokenMap(state.tokens);
|
|
422
|
+
if (state.revokedTokens) state.revokedTokens = await this.formatTokenMap(state.revokedTokens);
|
|
423
|
+
}
|
|
424
|
+
return state;
|
|
425
|
+
},
|
|
426
|
+
ctx
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async getRollupState({ address }, ctx) {
|
|
430
|
+
const rollup = await this._getState({
|
|
431
|
+
table: "rollup",
|
|
432
|
+
id: address,
|
|
433
|
+
dataKey: "data",
|
|
434
|
+
onRead: async (state) => {
|
|
435
|
+
if (state) {
|
|
436
|
+
state.tokenInfo = (await this.formatTokenMap({ [state.tokenAddress]: "0" })).find((x) => x.address === state.tokenAddress);
|
|
437
|
+
state.foreignToken = state.tokenInfo?.foreignToken;
|
|
438
|
+
}
|
|
439
|
+
return state;
|
|
440
|
+
},
|
|
441
|
+
ctx
|
|
442
|
+
});
|
|
443
|
+
if (!rollup) return null;
|
|
444
|
+
const indexed = await this.indexdb.rollup.get(address);
|
|
445
|
+
if (indexed) {
|
|
446
|
+
rollup.totalDepositAmount = indexed.totalDepositAmount || "0";
|
|
447
|
+
rollup.totalWithdrawAmount = indexed.totalWithdrawAmount || "0";
|
|
448
|
+
}
|
|
449
|
+
return rollup;
|
|
450
|
+
}
|
|
451
|
+
async getRollupBlock({ hash, height, rollupAddress }, ctx) {
|
|
452
|
+
if (hash) return this._getState({
|
|
453
|
+
table: "rollupBlock",
|
|
454
|
+
id: hash,
|
|
455
|
+
dataKey: "data",
|
|
456
|
+
ctx
|
|
457
|
+
});
|
|
458
|
+
const blockHeight = Number(height || 0);
|
|
459
|
+
if (blockHeight > 0 && rollupAddress) {
|
|
460
|
+
const { blocks } = await this._doPaginatedSearch("listRollupBlocks", {
|
|
461
|
+
height: blockHeight,
|
|
462
|
+
rollupAddress
|
|
463
|
+
}, "blocks", "data", ctx);
|
|
464
|
+
if (blocks.length) return this._getState({
|
|
465
|
+
table: "rollupBlock",
|
|
466
|
+
id: blocks[0].hash,
|
|
467
|
+
dataKey: "data",
|
|
468
|
+
ctx
|
|
469
|
+
});
|
|
470
|
+
return null;
|
|
471
|
+
}
|
|
472
|
+
throw new Error("Can not get rollup block without hash or height + rollup");
|
|
473
|
+
}
|
|
474
|
+
getAssetState({ address }, ctx) {
|
|
475
|
+
return this._getState({
|
|
476
|
+
table: "asset",
|
|
477
|
+
id: address,
|
|
478
|
+
dataKey: "data",
|
|
479
|
+
onRead: (state) => {
|
|
480
|
+
if (state && Array.isArray(state.tagsList)) state.tags = state.tagsList;
|
|
481
|
+
return state;
|
|
482
|
+
},
|
|
483
|
+
ctx
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
getEvidenceState({ hash }, ctx) {
|
|
487
|
+
return this._getState({
|
|
488
|
+
table: "evidence",
|
|
489
|
+
id: hash,
|
|
490
|
+
dataKey: null,
|
|
491
|
+
ctx
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
getFactoryState({ address }, ctx) {
|
|
495
|
+
return this._getState({
|
|
496
|
+
table: "factory",
|
|
497
|
+
id: address,
|
|
498
|
+
dataKey: "data",
|
|
499
|
+
onRead: async (state) => {
|
|
500
|
+
if (state) {
|
|
501
|
+
state.tokens = await this.formatTokenMap(state.tokens || {});
|
|
502
|
+
const input = state.input;
|
|
503
|
+
if (Array.isArray(input?.tokens) && input.tokens.length > 0) input.tokens = await this.formatTokenArray(input.tokens || []);
|
|
504
|
+
const output = state.output;
|
|
505
|
+
if (output) output.data = formatData(output.data);
|
|
506
|
+
}
|
|
507
|
+
return state;
|
|
508
|
+
},
|
|
509
|
+
ctx
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
async getTokenState({ address }, ctx) {
|
|
513
|
+
const state = await this._getState({
|
|
514
|
+
table: "token",
|
|
515
|
+
id: address,
|
|
516
|
+
dataKey: "data",
|
|
517
|
+
ctx
|
|
518
|
+
});
|
|
519
|
+
if (state) {
|
|
520
|
+
if (typeof state.decimal === "undefined") state.decimal = DEFAULT_TOKEN_DECIMAL;
|
|
521
|
+
if (state.metadata) state.metadata = formatData(state.metadata);
|
|
522
|
+
}
|
|
523
|
+
return state;
|
|
524
|
+
}
|
|
525
|
+
getTokenFactoryState(args, ctx) {
|
|
526
|
+
return this._getState({
|
|
527
|
+
table: "tokenFactory",
|
|
528
|
+
id: args.address,
|
|
529
|
+
dataKey: "data",
|
|
530
|
+
ctx,
|
|
531
|
+
onRead: async (state) => {
|
|
532
|
+
if (state) {
|
|
533
|
+
const [token, reserveToken] = await Promise.all([this.getTokenState({ address: state.tokenAddress }), this.getTokenState({ address: state.reserveAddress })]);
|
|
534
|
+
state.token = {
|
|
535
|
+
...token,
|
|
536
|
+
metadata: formatData(token?.metadata)
|
|
537
|
+
};
|
|
538
|
+
state.reserveToken = {
|
|
539
|
+
...reserveToken,
|
|
540
|
+
metadata: formatData(reserveToken?.metadata)
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return state;
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
getDelegateState({ address }, ctx) {
|
|
548
|
+
return this._getState({
|
|
549
|
+
table: "delegation",
|
|
550
|
+
id: address,
|
|
551
|
+
dataKey: "data",
|
|
552
|
+
onRead: async (state) => {
|
|
553
|
+
if (state) {
|
|
554
|
+
formatDelegationOps(state);
|
|
555
|
+
await this.formatDelegateState(state);
|
|
556
|
+
}
|
|
557
|
+
return state;
|
|
558
|
+
},
|
|
559
|
+
ctx
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
async getAccountTokens({ address, token }, ctx) {
|
|
563
|
+
if (!address) return [];
|
|
564
|
+
let state = null;
|
|
565
|
+
if (toTypeInfo(address).role === types.RoleType.ROLE_ASSET) state = await this._getState({
|
|
566
|
+
table: "factory",
|
|
567
|
+
id: address,
|
|
568
|
+
dataKey: "data",
|
|
569
|
+
expandContext: false,
|
|
570
|
+
ctx
|
|
571
|
+
});
|
|
572
|
+
else state = await this._getState({
|
|
573
|
+
table: "account",
|
|
574
|
+
id: address,
|
|
575
|
+
dataKey: "data",
|
|
576
|
+
expandContext: false,
|
|
577
|
+
ctx
|
|
578
|
+
});
|
|
579
|
+
if (!state || !state.tokens) return [];
|
|
580
|
+
const allTokens = Object.keys(state.tokens);
|
|
581
|
+
const wantedTokens = token ? allTokens.filter((x) => x === token) : allTokens;
|
|
582
|
+
const tokensInfo = (await Promise.all(wantedTokens.map((x) => this._getState({
|
|
583
|
+
table: "token",
|
|
584
|
+
id: x,
|
|
585
|
+
dataKey: "data",
|
|
586
|
+
expandContext: false,
|
|
587
|
+
ctx
|
|
588
|
+
})))).reduce((acc, x) => {
|
|
589
|
+
acc[x.address] = {
|
|
590
|
+
symbol: x.symbol,
|
|
591
|
+
decimal: x.decimal || DEFAULT_TOKEN_DECIMAL,
|
|
592
|
+
unit: x.unit
|
|
593
|
+
};
|
|
594
|
+
return acc;
|
|
595
|
+
}, {});
|
|
596
|
+
return wantedTokens.map((x) => ({
|
|
597
|
+
address: x,
|
|
598
|
+
...tokensInfo[x],
|
|
599
|
+
balance: state.tokens[x]
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
getForgeState() {
|
|
603
|
+
const { accounts, token, transaction, vaults } = this.config;
|
|
604
|
+
return Promise.resolve({
|
|
605
|
+
accountConfig: accounts,
|
|
606
|
+
address: "forge_state",
|
|
607
|
+
consensus: {
|
|
608
|
+
maxBytes: "150000",
|
|
609
|
+
maxCandidates: 256,
|
|
610
|
+
maxGas: "-1",
|
|
611
|
+
maxValidators: 64,
|
|
612
|
+
paramChanged: false,
|
|
613
|
+
validatorChanged: false
|
|
614
|
+
},
|
|
615
|
+
data: null,
|
|
616
|
+
tasks: [],
|
|
617
|
+
token,
|
|
618
|
+
vaults,
|
|
619
|
+
txConfig: {
|
|
620
|
+
...transaction,
|
|
621
|
+
txGas: {
|
|
622
|
+
...transaction.txGas,
|
|
623
|
+
minStake: fromTokenToUnit(transaction.txGas.minStake, token.decimal).toString(10),
|
|
624
|
+
maxStake: fromTokenToUnit(transaction.txGas.maxStake, token.decimal).toString(10)
|
|
625
|
+
},
|
|
626
|
+
txStake: {
|
|
627
|
+
...transaction.txStake,
|
|
628
|
+
createToken: fromTokenToUnit(transaction.txStake.createToken, token.decimal).toString(10),
|
|
629
|
+
createCreditToken: fromTokenToUnit(transaction.txStake.createCreditToken, token.decimal).toString(10)
|
|
630
|
+
},
|
|
631
|
+
txFee: Object.keys(transaction.txFee).filter((x) => x !== "default").map((x) => ({
|
|
632
|
+
typeUrl: x,
|
|
633
|
+
fee: fromTokenToUnit(transaction.txFee[x], token.decimal).toString(10)
|
|
634
|
+
}))
|
|
635
|
+
},
|
|
636
|
+
upgradeInfo: null,
|
|
637
|
+
reservedSymbols: this.config.reservedSymbols
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
async getForgeStats() {
|
|
641
|
+
const [accountCount, assetCount, txCount] = await Promise.all([
|
|
642
|
+
this.indexdb.account.count(),
|
|
643
|
+
this.indexdb.asset.count(),
|
|
644
|
+
this.indexdb.tx.count()
|
|
645
|
+
]);
|
|
646
|
+
return {
|
|
647
|
+
avgBlockTime: 1,
|
|
648
|
+
avgTps: 0,
|
|
649
|
+
maxTps: 0,
|
|
650
|
+
numAccountMigrateTxs: ["0"],
|
|
651
|
+
numBlocks: [txCount],
|
|
652
|
+
numConsensusUpgradeTxs: [0],
|
|
653
|
+
numConsumeAssetTxs: ["0"],
|
|
654
|
+
numCreateAssetTxs: [assetCount],
|
|
655
|
+
numDeclareFileTxs: ["0"],
|
|
656
|
+
numDeclareTxs: [accountCount],
|
|
657
|
+
numExchangeTxs: ["0"],
|
|
658
|
+
numPokeTxs: ["0"],
|
|
659
|
+
numStakeTxs: ["0"],
|
|
660
|
+
numStakes: ["0"],
|
|
661
|
+
numSysUpgradeTxs: [0],
|
|
662
|
+
numTransferTxs: ["0"],
|
|
663
|
+
numTxs: [txCount],
|
|
664
|
+
numUpdateAssetTxs: ["0"],
|
|
665
|
+
numValidators: [1],
|
|
666
|
+
tps: [0]
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
async listTransactions(args = {}, ctx) {
|
|
670
|
+
return await this._doPaginatedSearch("listTransactions", args, "transactions", "tx.itxJson.data", ctx);
|
|
671
|
+
}
|
|
672
|
+
listAssets(args = {}, ctx) {
|
|
673
|
+
return this._doPaginatedSearch("listAssets", args, "assets", "data", ctx);
|
|
674
|
+
}
|
|
675
|
+
listAssetTransactions(args = {}, ctx = {}) {
|
|
676
|
+
if (!args.address) return Promise.resolve({ transactions: [] });
|
|
677
|
+
return this._doPaginatedSearch("listTransactions", {
|
|
678
|
+
...args,
|
|
679
|
+
assetFilter: { assets: [args.address] }
|
|
680
|
+
}, "transactions", "tx.itxJson.data", ctx);
|
|
681
|
+
}
|
|
682
|
+
async listFactories(args = {}, ctx) {
|
|
683
|
+
const result = await this._doPaginatedSearch("listFactories", args, "factories", "data", ctx);
|
|
684
|
+
result.factories = result.factories.map((x) => {
|
|
685
|
+
const output = x.output;
|
|
686
|
+
if (output) output.data = formatData(output.data);
|
|
687
|
+
return x;
|
|
688
|
+
});
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
listTopAccounts(args = {}) {
|
|
692
|
+
if (!args.tokenAddress) args.tokenAddress = this.tokenItx.address;
|
|
693
|
+
return this._doPaginatedSearch("listTopAccounts", args, "accounts");
|
|
694
|
+
}
|
|
695
|
+
listTokens(args = {}, ctx) {
|
|
696
|
+
return this._doPaginatedSearch("listTokens", args, "tokens", ["data", "metadata"], ctx);
|
|
697
|
+
}
|
|
698
|
+
listTokenFactories(args = {}, ctx) {
|
|
699
|
+
return this._doPaginatedSearch("listTokenFactories", args, "tokenFactories", "data", ctx);
|
|
700
|
+
}
|
|
701
|
+
listStakes(args = {}, ctx) {
|
|
702
|
+
return this._doPaginatedSearch("listStakes", args, "stakes", "data", ctx);
|
|
703
|
+
}
|
|
704
|
+
listRollups(args = {}, ctx) {
|
|
705
|
+
return this._doPaginatedSearch("listRollups", args, "rollups", "data", ctx);
|
|
706
|
+
}
|
|
707
|
+
listRollupBlocks(args = {}, ctx) {
|
|
708
|
+
return this._doPaginatedSearch("listRollupBlocks", args, "blocks", "data", ctx);
|
|
709
|
+
}
|
|
710
|
+
async listRollupValidators(args = {}, ctx) {
|
|
711
|
+
if (!args.rollupAddress) return { validators: [] };
|
|
712
|
+
const rollup = await this.statedb.rollup.get(args.rollupAddress);
|
|
713
|
+
if (!rollup) return { validators: [] };
|
|
714
|
+
const result = await this._doPaginatedSearch("listRollupValidators", args, "validators", null, ctx);
|
|
715
|
+
const stakes = (await Promise.all(result.validators.map((x) => {
|
|
716
|
+
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
717
|
+
return this.statedb.stake.get(stakeAddress);
|
|
718
|
+
}))).filter(Boolean);
|
|
719
|
+
result.validators.forEach((x) => {
|
|
720
|
+
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
721
|
+
const stake = stakes.find((s) => s.address === stakeAddress);
|
|
722
|
+
x.availableStake = stake ? stake.tokens[rollup.tokenAddress] : "0";
|
|
723
|
+
});
|
|
724
|
+
return result;
|
|
725
|
+
}
|
|
726
|
+
listDelegations(args = {}, ctx) {
|
|
727
|
+
return this._doPaginatedSearch("listDelegations", args, "delegations", "data", ctx);
|
|
728
|
+
}
|
|
729
|
+
verifyAccountRisk(args = {}, ctx) {
|
|
730
|
+
if (process.env.TOKEN_FLOW_ENABLED === "true") return verifyAccountRisk({
|
|
731
|
+
...args,
|
|
732
|
+
tokenAddress: toAddress(args.tokenAddress || this.config.token.address),
|
|
733
|
+
accountAddress: toAddress(args.accountAddress || ""),
|
|
734
|
+
accountLimit: Number(process.env.VERIFY_RISK_ACCOUNT_LIMIT) || void 0,
|
|
735
|
+
txLimit: Number(process.env.VERIFY_RISK_TX_LIMIT) || void 0,
|
|
736
|
+
tolerance: process.env.VERIFY_RISK_TOLERANCE || void 0
|
|
737
|
+
}, this, ctx);
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
listTokenFlows(args = {}, ctx) {
|
|
741
|
+
if (process.env.TOKEN_FLOW_ENABLED === "true") return listTokenFlows(args, this, ctx);
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
async search(args) {
|
|
745
|
+
if (!args.keyword) return { results: [] };
|
|
746
|
+
const doSearch = async (type, keyword) => {
|
|
747
|
+
return await this.indexdb[type].get(keyword) ? {
|
|
748
|
+
type,
|
|
749
|
+
id: keyword
|
|
750
|
+
} : null;
|
|
751
|
+
};
|
|
752
|
+
if (patterns.txHash.test(args.keyword)) return { results: (await Promise.all([doSearch("tx", args.keyword.toUpperCase()), doSearch("rollupBlock", args.keyword)])).filter(Boolean) };
|
|
753
|
+
const entitiesByDid = [
|
|
754
|
+
"account",
|
|
755
|
+
"asset",
|
|
756
|
+
"delegation",
|
|
757
|
+
"factory",
|
|
758
|
+
"token",
|
|
759
|
+
"stake",
|
|
760
|
+
"rollup",
|
|
761
|
+
"tokenFactory"
|
|
762
|
+
];
|
|
763
|
+
if (isValid(args.keyword)) {
|
|
764
|
+
const keyword = toAddress(args.keyword);
|
|
765
|
+
return { results: (await Promise.all(entitiesByDid.map((type) => doSearch(type, keyword)))).filter(Boolean) };
|
|
766
|
+
}
|
|
767
|
+
return { results: [] };
|
|
768
|
+
}
|
|
769
|
+
estimateGas(args = {}) {
|
|
770
|
+
const { typeUrl = "" } = args;
|
|
771
|
+
if (!typeUrl || !maxGasOps[typeUrl]) throw new CustomError("INVALID_REQUEST", "Unknown tx typeUrl");
|
|
772
|
+
const { txGas, maxTxSize } = this.config.transaction;
|
|
773
|
+
const estimate = maxGasOps[args.typeUrl];
|
|
774
|
+
let totalGas = new BN(txGas.price).mul(new BN(txGas.dataStorage)).mul(new BN(maxTxSize[args.typeUrl]));
|
|
775
|
+
totalGas = totalGas.add(new BN(txGas.price).mul(new BN(txGas.createState)).mul(new BN((estimate.create || 0) + 1)));
|
|
776
|
+
totalGas = totalGas.add(new BN(txGas.price).mul(new BN(txGas.updateState)).mul(new BN(estimate.update)));
|
|
777
|
+
return { estimate: { max: totalGas.toString(10) } };
|
|
778
|
+
}
|
|
779
|
+
listBlocks() {
|
|
780
|
+
return { blocks: [] };
|
|
781
|
+
}
|
|
782
|
+
validateTokenConfig() {
|
|
783
|
+
const { accounts, token } = this.config;
|
|
784
|
+
const sum = accounts.reduce((acc, x) => acc.add(toBN(x.balance ?? 0)), toBN(0));
|
|
785
|
+
const initialSupply = toBN(token.initialSupply);
|
|
786
|
+
if (!sum.eq(initialSupply)) throw new Error("Invalid config, account balance sum does not equal to token supply");
|
|
787
|
+
}
|
|
788
|
+
runAsLambda(fn, args) {
|
|
789
|
+
if (typeof this.statedb.runAsLambda === "function") return this.statedb.runAsLambda((txn) => fn(txn), args);
|
|
790
|
+
return Promise.resolve(fn());
|
|
791
|
+
}
|
|
792
|
+
async getChain() {
|
|
793
|
+
return this.statedb.chain.get(CHAIN_ADDR);
|
|
794
|
+
}
|
|
795
|
+
async getToken(id) {
|
|
796
|
+
return this.statedb.token.get(id);
|
|
797
|
+
}
|
|
798
|
+
async initializeStateDB() {
|
|
799
|
+
const { accounts, token } = this.config;
|
|
800
|
+
const { account: accountDB, chain: chainDB, token: tokenDB } = this.statedb;
|
|
801
|
+
const { account: accountState, chain: chainState, token: tokenState } = states;
|
|
802
|
+
const context = {
|
|
803
|
+
txTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
804
|
+
txHash: ""
|
|
805
|
+
};
|
|
806
|
+
await this.runAsLambda(async (txn) => {
|
|
807
|
+
const info = await this.statedb.chain.get(CHAIN_ADDR, { txn });
|
|
808
|
+
const config = omit(this.config, ["reservedSymbols"]);
|
|
809
|
+
if (!info) {
|
|
810
|
+
const state = chainState.create({
|
|
811
|
+
...config,
|
|
812
|
+
address: CHAIN_ADDR,
|
|
813
|
+
context
|
|
814
|
+
});
|
|
815
|
+
const result = await chainDB.create(CHAIN_ADDR, state, { txn });
|
|
816
|
+
this.logger.debug("create chain state", { result });
|
|
817
|
+
} else if (isEqual(pick(info, Object.keys(config)), config) === false) {
|
|
818
|
+
const state = chainState.update(info, {
|
|
819
|
+
...config,
|
|
820
|
+
context
|
|
821
|
+
});
|
|
822
|
+
const result = await chainDB.update(CHAIN_ADDR, state, { txn });
|
|
823
|
+
this.logger.debug("update chain state", { result });
|
|
824
|
+
}
|
|
825
|
+
}, { commitMessage: "initialize chain" });
|
|
826
|
+
if (this.tokenItx) {
|
|
827
|
+
await this.runAsLambda(async (txn) => {
|
|
828
|
+
const existToken = await this.statedb.token.get(this.tokenItx.address);
|
|
829
|
+
if (!existToken) {
|
|
830
|
+
const state = tokenState.create(this.tokenItx, context);
|
|
831
|
+
const result = await tokenDB.create(this.tokenItx.address, state, { txn });
|
|
832
|
+
tokenDB.emit("create", result, { txn });
|
|
833
|
+
this.logger.debug("create token state", { address: result?.address });
|
|
834
|
+
} else {
|
|
835
|
+
const changedKeys = [
|
|
836
|
+
"icon",
|
|
837
|
+
"website",
|
|
838
|
+
"metadata"
|
|
839
|
+
];
|
|
840
|
+
const pickStateToken = pick(existToken, changedKeys);
|
|
841
|
+
const pickConfigToken = pick(this.tokenItx, changedKeys);
|
|
842
|
+
if (!isEqual(pickStateToken, pickConfigToken)) {
|
|
843
|
+
const state = tokenState.update(existToken, pickConfigToken);
|
|
844
|
+
const result = await tokenDB.update(this.tokenItx.address, state, { txn });
|
|
845
|
+
tokenDB.emit("update", result, { txn });
|
|
846
|
+
this.logger.info("update token state", { result });
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}, { commitMessage: "initialize token" });
|
|
850
|
+
await this.runAsLambda(async (txn) => {
|
|
851
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
852
|
+
const { address, balance, moniker, pk } = accounts[i];
|
|
853
|
+
try {
|
|
854
|
+
if (!await accountDB.get(address, { txn })) {
|
|
855
|
+
const balanceStr = fromTokenToUnit(balance ?? 0, token.decimal ?? 18).toString(10);
|
|
856
|
+
const state = accountState.create({
|
|
857
|
+
address,
|
|
858
|
+
tokens: { [this.tokenItx.address]: balanceStr },
|
|
859
|
+
moniker: moniker || "token-holder",
|
|
860
|
+
pk
|
|
861
|
+
}, context);
|
|
862
|
+
const result = await accountDB.create(address, state, { txn });
|
|
863
|
+
accountDB.emit("create", result, { txn });
|
|
864
|
+
this.logger.info("create account done", { address });
|
|
865
|
+
}
|
|
866
|
+
} catch (err) {
|
|
867
|
+
this.logger.error("Failed to initialize initial token holders", err);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}, { commitMessage: "initialize accounts" });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
connectIndexDB() {
|
|
874
|
+
const getToken = this.getToken.bind(this);
|
|
875
|
+
this.statedb.tx.on("create", async (x, ctx) => {
|
|
876
|
+
if (!ctx) return;
|
|
877
|
+
try {
|
|
878
|
+
const tx = await createIndexedTransaction(x, ctx, this.indexdb);
|
|
879
|
+
await this.indexdb.tx.insert(tx);
|
|
880
|
+
if (typeof onCreateTx === "function") await onCreateTx(tx, ctx, this);
|
|
881
|
+
} catch (error) {
|
|
882
|
+
this.logger.error("create tx index failed", {
|
|
883
|
+
account: x,
|
|
884
|
+
error
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
this.statedb.account.on("create", async (x) => {
|
|
889
|
+
try {
|
|
890
|
+
await this.indexdb.account.insert(await createIndexedAccount(x, getToken));
|
|
891
|
+
} catch (error) {
|
|
892
|
+
this.logger.error("create account index failed", {
|
|
893
|
+
account: x,
|
|
894
|
+
error
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
this.statedb.account.on("update", async (x) => {
|
|
899
|
+
try {
|
|
900
|
+
await this.indexdb.account.update(x.address, await createIndexedAccount(x, getToken));
|
|
901
|
+
} catch (error) {
|
|
902
|
+
this.logger.error("update account index failed", {
|
|
903
|
+
account: x,
|
|
904
|
+
error
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
const mapping = {
|
|
909
|
+
asset: [
|
|
910
|
+
createIndexedAsset,
|
|
911
|
+
"address",
|
|
912
|
+
void 0,
|
|
913
|
+
void 0
|
|
914
|
+
],
|
|
915
|
+
delegation: [
|
|
916
|
+
createIndexedDelegation,
|
|
917
|
+
"address",
|
|
918
|
+
void 0,
|
|
919
|
+
void 0
|
|
920
|
+
],
|
|
921
|
+
token: [
|
|
922
|
+
createIndexedToken,
|
|
923
|
+
"address",
|
|
924
|
+
null,
|
|
925
|
+
onUpdateToken
|
|
926
|
+
],
|
|
927
|
+
factory: [
|
|
928
|
+
createIndexedFactory,
|
|
929
|
+
"address",
|
|
930
|
+
void 0,
|
|
931
|
+
void 0
|
|
932
|
+
],
|
|
933
|
+
stake: [
|
|
934
|
+
createIndexedStake,
|
|
935
|
+
"address",
|
|
936
|
+
void 0,
|
|
937
|
+
void 0
|
|
938
|
+
],
|
|
939
|
+
rollup: [
|
|
940
|
+
createIndexedRollup,
|
|
941
|
+
"address",
|
|
942
|
+
onCreateRollup,
|
|
943
|
+
void 0
|
|
944
|
+
],
|
|
945
|
+
rollupBlock: [
|
|
946
|
+
createIndexedRollupBlock,
|
|
947
|
+
"hash",
|
|
948
|
+
onCreateRollupBlock,
|
|
949
|
+
void 0
|
|
950
|
+
],
|
|
951
|
+
tokenFactory: [
|
|
952
|
+
createIndexedTokenFactory,
|
|
953
|
+
"address",
|
|
954
|
+
void 0,
|
|
955
|
+
void 0
|
|
956
|
+
]
|
|
957
|
+
};
|
|
958
|
+
Object.keys(mapping).forEach((table) => {
|
|
959
|
+
const [fn, key, onCreate, onUpdate] = mapping[table];
|
|
960
|
+
this.statedb[table].on("create", async (x, _ctx) => {
|
|
961
|
+
try {
|
|
962
|
+
const ctx = this.enrichIndexContext(_ctx);
|
|
963
|
+
const doc = await fn(x, ctx, this.indexdb);
|
|
964
|
+
await this.indexdb[table].insert(doc);
|
|
965
|
+
if (typeof onCreate === "function") await onCreate(doc, ctx, this.indexdb, this);
|
|
966
|
+
} catch (error) {
|
|
967
|
+
this.logger.error(`create ${table} index failed`, {
|
|
968
|
+
[table]: x,
|
|
969
|
+
error
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
this.statedb[table].on("update", async (x, _ctx) => {
|
|
974
|
+
try {
|
|
975
|
+
const ctx = this.enrichIndexContext(_ctx);
|
|
976
|
+
const doc = await fn(x, ctx, this.indexdb);
|
|
977
|
+
await this.indexdb[table].update(doc[key], doc);
|
|
978
|
+
if (typeof onUpdate === "function") onUpdate(doc, ctx, this.indexdb, this);
|
|
979
|
+
} catch (error) {
|
|
980
|
+
this.logger.error(`update ${table} index failed`, {
|
|
981
|
+
[table]: x,
|
|
982
|
+
error
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
async _getState({ table, id, dataKey, extraParams = {}, onRead = noop, expandContext = true, ctx }) {
|
|
989
|
+
if (!id) throw new CustomError("INVALID_REQUEST", `Missing required parameter to read ${table} state`);
|
|
990
|
+
const unified = isValid(id) ? toAddress(id) : id;
|
|
991
|
+
const state = await this.statedb[table].get(unified, { ...extraParams });
|
|
992
|
+
if (state) {
|
|
993
|
+
if (dataKey) set(state, dataKey, formatData(get(state, dataKey)));
|
|
994
|
+
if (expandContext && state.context) {
|
|
995
|
+
const { genesisTx, renaissanceTx } = state.context;
|
|
996
|
+
const txs = (await Promise.all(uniq([genesisTx, renaissanceTx]).filter((x) => Boolean(x)).map((x) => this.getTx({ hash: x }, ctx)))).filter(Boolean);
|
|
997
|
+
state.context.genesisTx = txs.find((x) => x.hash === genesisTx);
|
|
998
|
+
state.context.renaissanceTx = txs.find((x) => x.hash === renaissanceTx);
|
|
999
|
+
if (!state.context.genesisTx) state.context.genesisTx = { hash: genesisTx };
|
|
1000
|
+
if (!state.context.renaissanceTx) state.context.renaissanceTx = { hash: renaissanceTx };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return onRead(state);
|
|
1004
|
+
}
|
|
1005
|
+
async _doPaginatedSearch(fn, args, listKey, dataKey, ctx) {
|
|
1006
|
+
const { [listKey]: items = [], ...rest } = await this.indexdb[fn](args);
|
|
1007
|
+
let data = items;
|
|
1008
|
+
if (dataKey) data = await Promise.all(items.map(async (x) => {
|
|
1009
|
+
let tx = x;
|
|
1010
|
+
[].concat(dataKey).forEach((key) => {
|
|
1011
|
+
tx = set(tx, key, formatData(get(tx, key)));
|
|
1012
|
+
});
|
|
1013
|
+
if (listKey === "transactions") tx = await this.formatTx(tx, ctx);
|
|
1014
|
+
if (listKey === "delegations") tx = await this.formatDelegateState(tx);
|
|
1015
|
+
return tx;
|
|
1016
|
+
}));
|
|
1017
|
+
const final = {
|
|
1018
|
+
[listKey]: data,
|
|
1019
|
+
...rest
|
|
1020
|
+
};
|
|
1021
|
+
if (!final.paging) final.paging = {
|
|
1022
|
+
cursor: "0",
|
|
1023
|
+
next: false,
|
|
1024
|
+
total: 0
|
|
1025
|
+
};
|
|
1026
|
+
return final;
|
|
1027
|
+
}
|
|
1028
|
+
async _formatTx(tx, ctx) {
|
|
1029
|
+
if (!tx) return tx;
|
|
1030
|
+
if (!tx.sender && !tx.receiver) {
|
|
1031
|
+
const typeUrl$1 = fromTypeUrl(tx.tx.itx.typeUrl);
|
|
1032
|
+
tx.sender = getTxSender({
|
|
1033
|
+
tx: tx.tx,
|
|
1034
|
+
itx: tx.tx.itxJson,
|
|
1035
|
+
typeUrl: typeUrl$1
|
|
1036
|
+
});
|
|
1037
|
+
tx.receiver = getTxReceiver({
|
|
1038
|
+
tx: tx.tx,
|
|
1039
|
+
itx: tx.tx.itxJson,
|
|
1040
|
+
typeUrl: typeUrl$1
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
if (!tx.receipts) tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
1044
|
+
const typeUrl = get(tx, "tx.itxJson.type_url");
|
|
1045
|
+
if (typeUrl === "fg:t:revoke_withdraw" || typeUrl === "fg:t:approve_withdraw") {
|
|
1046
|
+
const withdrawHash = get(tx, "tx.itxJson.withdraw_tx_hash");
|
|
1047
|
+
if (withdrawHash && withdrawHash !== tx.hash) {
|
|
1048
|
+
const withdrawTx = await this.getTx({ hash: withdrawHash }, ctx);
|
|
1049
|
+
tx.receipts = getTxReceipts(tx, {
|
|
1050
|
+
config: this.config,
|
|
1051
|
+
withdrawTx
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
if (typeUrl && [
|
|
1056
|
+
"fg:t:deposit_token",
|
|
1057
|
+
"fg:t:withdraw_token",
|
|
1058
|
+
"fg:t:revoke_swap"
|
|
1059
|
+
].includes(typeUrl)) tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
1060
|
+
if (typeUrl === "fg:t:account_migrate") {
|
|
1061
|
+
const fromState = await this.statedb.account.get(tx.tx.from, { traceMigration: false });
|
|
1062
|
+
if (fromState) tx.receipts = getTxReceipts(tx, {
|
|
1063
|
+
config: this.config,
|
|
1064
|
+
fromState
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
if (typeUrl === "fg:t:declare") tx.receipts = getTxReceipts(tx, {
|
|
1068
|
+
config: this.config,
|
|
1069
|
+
time: tx.time
|
|
1070
|
+
});
|
|
1071
|
+
tx.receipts = mergeTxReceipts(tx.receipts);
|
|
1072
|
+
tx.tokenSymbols = await this.getTxTokenSymbols(tx);
|
|
1073
|
+
this.fixReceiptTokens(tx);
|
|
1074
|
+
this.fixTokenSymbols(tx);
|
|
1075
|
+
return attachPaidTxGas(tx);
|
|
1076
|
+
}
|
|
1077
|
+
async _getAllResults(dataKey, fn, limit) {
|
|
1078
|
+
const pageSize = 100;
|
|
1079
|
+
const { paging, [dataKey]: firstPage } = await fn({ size: pageSize });
|
|
1080
|
+
if (limit && paging.total > limit) throw new CustomError("EXCEED_LIMIT", `Total exceeds limit ${limit}`);
|
|
1081
|
+
if (paging.total <= pageSize) return firstPage;
|
|
1082
|
+
const totalPage = Math.ceil(paging.total / pageSize) - 1;
|
|
1083
|
+
const cursors = new Array(totalPage).fill(true).map((_, i) => (i + 1) * pageSize);
|
|
1084
|
+
const step = Number(process.env.RESOLVER_BATCH_CONCURRENCY) || 3;
|
|
1085
|
+
let results = firstPage;
|
|
1086
|
+
for (let i = 0; i < cursors.length; i += step) {
|
|
1087
|
+
const batchResults = await Promise.all(cursors.slice(i, i + step).map(async (cursor) => {
|
|
1088
|
+
const { [dataKey]: list } = await fn({
|
|
1089
|
+
size: pageSize,
|
|
1090
|
+
cursor
|
|
1091
|
+
});
|
|
1092
|
+
return list;
|
|
1093
|
+
}));
|
|
1094
|
+
results = results.concat(batchResults.flat());
|
|
1095
|
+
}
|
|
1096
|
+
return results;
|
|
1097
|
+
}
|
|
1098
|
+
async formatTokenArray(tokens) {
|
|
1099
|
+
const uniqTokens = uniqBy(tokens, "address");
|
|
1100
|
+
const tokenStates = await Promise.all(uniqTokens.map((token) => this.tokenCache.get(token.address)));
|
|
1101
|
+
return uniqTokens.map((token) => ({
|
|
1102
|
+
...token,
|
|
1103
|
+
...extractTokenMeta(token.address, tokenStates)
|
|
1104
|
+
}));
|
|
1105
|
+
}
|
|
1106
|
+
async formatTokenMap(tokens) {
|
|
1107
|
+
if (isEmpty(tokens)) return [];
|
|
1108
|
+
const tasks = Object.keys(tokens).map((address) => this.tokenCache.get(address));
|
|
1109
|
+
const tokenStates = await Promise.all(tasks);
|
|
1110
|
+
return Object.keys(tokens).map((address) => ({
|
|
1111
|
+
value: tokens[address],
|
|
1112
|
+
...extractTokenMeta(address, tokenStates)
|
|
1113
|
+
}));
|
|
1114
|
+
}
|
|
1115
|
+
async getTxTokenSymbols(tx) {
|
|
1116
|
+
if (!tx) return [];
|
|
1117
|
+
const rollupTxs = [
|
|
1118
|
+
"DepositTokenV2Tx",
|
|
1119
|
+
"WithdrawTokenV2Tx",
|
|
1120
|
+
"JoinRollupTx",
|
|
1121
|
+
"LeaveRollupTx",
|
|
1122
|
+
"CreateRollupBlockTx",
|
|
1123
|
+
"ClaimBlockRewardTx"
|
|
1124
|
+
];
|
|
1125
|
+
const typeUrl = formatTxType(tx.tx.itxJson._type || "");
|
|
1126
|
+
const tokens = [];
|
|
1127
|
+
if (isDefaultTokenChanged(tx, this.config.token)) tokens.push(this.tokenItx);
|
|
1128
|
+
if (typeUrl === "AcquireAssetV2Tx") {
|
|
1129
|
+
const factoryInput = (await this.indexdb.factory.get(tx.tx.itxJson.factory))?.input;
|
|
1130
|
+
if (factoryInput?.tokens) tokens.push(...factoryInput.tokens);
|
|
1131
|
+
} else if (typeUrl === "ExchangeV2Tx") {
|
|
1132
|
+
tokens.push(...tx.tx.itxJson.sender?.tokens || []);
|
|
1133
|
+
tokens.push(...tx.tx.itxJson.receiver?.tokens || []);
|
|
1134
|
+
} else if (typeUrl === "TransferV2Tx") tokens.push(...tx.tx.itxJson.tokens || []);
|
|
1135
|
+
else if ([
|
|
1136
|
+
"TransferV3Tx",
|
|
1137
|
+
"AcquireAssetV3Tx",
|
|
1138
|
+
"StakeTx"
|
|
1139
|
+
].includes(typeUrl)) tokens.push(...uniqBy((tx.tx.itxJson.inputs || []).reduce((acc, x) => acc.concat(x.tokens || []), []), "address").filter((x) => x.address));
|
|
1140
|
+
else if ([
|
|
1141
|
+
"RevokeStakeTx",
|
|
1142
|
+
"SlashStakeTx",
|
|
1143
|
+
"ReturnStakeTx"
|
|
1144
|
+
].includes(typeUrl)) tokens.push(...uniqBy((tx.tx.itxJson.outputs || []).reduce((acc, x) => acc.concat(x.tokens || []), []), "address").filter((x) => x.address));
|
|
1145
|
+
else if (typeUrl === "ClaimStakeTx") {
|
|
1146
|
+
const revokeTx = await this.indexdb.tx.get(tx.tx.itxJson.evidence.hash);
|
|
1147
|
+
if (revokeTx) {
|
|
1148
|
+
const revokeTxAny = revokeTx;
|
|
1149
|
+
tokens.push(...uniqBy((revokeTxAny.tx?.itxJson?.outputs || []).reduce((acc, x) => acc.concat(x.tokens || []), []), "address").filter((x) => x.address));
|
|
1150
|
+
} else this.logger.warn("Revoke stake tx not found", { tx });
|
|
1151
|
+
} else if (rollupTxs.includes(typeUrl)) {
|
|
1152
|
+
const rollup = await this.indexdb.rollup.get(tx.tx.itxJson.rollup);
|
|
1153
|
+
if (rollup) tokens.push({
|
|
1154
|
+
address: rollup.tokenAddress,
|
|
1155
|
+
value: "0"
|
|
1156
|
+
});
|
|
1157
|
+
} else if (typeUrl === "CreateTokenTx") return [extractTokenMeta(tx.tx.itxJson.address, [tx.tx.itxJson])];
|
|
1158
|
+
else if (typeUrl === "MintTokenTx" || typeUrl === "BurnTokenTx") {
|
|
1159
|
+
const token = await this.indexdb.tokenFactory.get(tx.tx.itxJson.tokenFactory);
|
|
1160
|
+
if (token) tokens.push({ address: token.tokenAddress });
|
|
1161
|
+
} else if (typeUrl === "CreateTokenFactoryTx") tokens.push({ address: tx.tx.itxJson.token.address });
|
|
1162
|
+
if (tokens.length > 0) return this.formatTokenArray(tokens);
|
|
1163
|
+
return [];
|
|
1164
|
+
}
|
|
1165
|
+
fixReceiptTokens(tx) {
|
|
1166
|
+
tx.receipts.forEach((receipt) => receipt.changes.forEach((x) => {
|
|
1167
|
+
if (x.target === "") x.target = this.tokenItx.address;
|
|
1168
|
+
}));
|
|
1169
|
+
return tx;
|
|
1170
|
+
}
|
|
1171
|
+
fixTokenSymbols(tx) {
|
|
1172
|
+
if (tx.tokenSymbols.findIndex((x) => x.address === this.tokenItx.address) === -1) tx.tokenSymbols.push(extractTokenMeta(this.tokenItx.address, [this.tokenItx]));
|
|
1173
|
+
return tx;
|
|
1174
|
+
}
|
|
1175
|
+
enrichIndexContext(ctx) {
|
|
1176
|
+
if (Array.isArray(ctx.tokenStates)) return {
|
|
1177
|
+
...ctx,
|
|
1178
|
+
tokenStates: [...ctx.tokenStates, this.tokenItx]
|
|
1179
|
+
};
|
|
1180
|
+
if (ctx.tokenState) return {
|
|
1181
|
+
...ctx,
|
|
1182
|
+
tokenStates: [ctx.tokenState]
|
|
1183
|
+
};
|
|
1184
|
+
return ctx;
|
|
1185
|
+
}
|
|
1186
|
+
async formatDelegateState(state) {
|
|
1187
|
+
if (Array.isArray(state.ops) && state.ops.length > 0) await Promise.all(state.ops.map(async (op) => {
|
|
1188
|
+
if (op.value.limit && Array.isArray(op.value.limit.tokens)) op.value.limit.tokens = await this.formatTokenArray(op.value.limit.tokens || []);
|
|
1189
|
+
}));
|
|
1190
|
+
return state;
|
|
1191
|
+
}
|
|
1192
|
+
async buildMigrationChain() {
|
|
1193
|
+
const migrationTxs = await this._getAllResults("transactions", (paging) => this.listTransactions({
|
|
1194
|
+
paging,
|
|
1195
|
+
typeFilter: { types: ["account_migrate"] },
|
|
1196
|
+
validityFilter: { validity: "VALID" }
|
|
1197
|
+
}));
|
|
1198
|
+
const migrationChain = new MigrationChainManager();
|
|
1199
|
+
migrationChain.buildChains(migrationTxs);
|
|
1200
|
+
this.migrationChain = migrationChain;
|
|
1201
|
+
}
|
|
1202
|
+
async getMigrationChain() {
|
|
1203
|
+
if (!this.migrationChain) await this.buildMigrationChain();
|
|
1204
|
+
return this.migrationChain;
|
|
1205
|
+
}
|
|
1206
|
+
/** fix accounts field of legacy approve_withdraw transactions in indexdb */
|
|
1207
|
+
async fixApproveWithdrawAccounts() {
|
|
1208
|
+
const approveWithdrawTxs = await this._getAllResults("transactions", (paging) => this.listTransactions({
|
|
1209
|
+
paging,
|
|
1210
|
+
typeFilter: { types: ["approve_withdraw"] },
|
|
1211
|
+
validityFilter: { validity: ["VALID"] }
|
|
1212
|
+
}));
|
|
1213
|
+
return (await Promise.all(approveWithdrawTxs.map(async (tx) => {
|
|
1214
|
+
const withdrawHash = tx.tx?.itxJson?.withdraw_tx_hash;
|
|
1215
|
+
if (!withdrawHash) return;
|
|
1216
|
+
const indexdbTx = await this.indexdb.tx.get(tx.hash);
|
|
1217
|
+
const from = (await this.getTx({ hash: withdrawHash }))?.tx?.from;
|
|
1218
|
+
if (!from || !indexdbTx) return;
|
|
1219
|
+
const accounts = uniq((indexdbTx.accounts || []).concat([from]).filter(Boolean));
|
|
1220
|
+
debug("fixApproveWithdrawAccounts", {
|
|
1221
|
+
tx,
|
|
1222
|
+
from,
|
|
1223
|
+
accounts
|
|
1224
|
+
});
|
|
1225
|
+
return await this.indexdb.tx.update(tx.hash, { accounts });
|
|
1226
|
+
}))).filter(Boolean);
|
|
1227
|
+
}
|
|
1228
|
+
async updateTokenDistribution({ tokenAddress = this.config.token.address, force = false } = {}) {
|
|
1229
|
+
const validation = Joi.object({
|
|
1230
|
+
tokenAddress: Joi.DID().prefix().role("ROLE_TOKEN").required(),
|
|
1231
|
+
force: Joi.boolean().required()
|
|
1232
|
+
}).validate({
|
|
1233
|
+
tokenAddress,
|
|
1234
|
+
force
|
|
1235
|
+
});
|
|
1236
|
+
if (validation.error) throw new CustomError("INVALID_PARAMS", validation.error.message);
|
|
1237
|
+
return await this.tokenDistribution.updateByToken(tokenAddress, force);
|
|
1238
|
+
}
|
|
1239
|
+
async getTokenDistribution(args = {}) {
|
|
1240
|
+
const { tokenAddress = this.config.token.address } = args;
|
|
1241
|
+
return await this.tokenDistribution.getDistribution(tokenAddress);
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* Fetch data in chunks to handle large datasets
|
|
1245
|
+
* @param fn Function to fetch data
|
|
1246
|
+
* @param params Chunk parameters
|
|
1247
|
+
* @returns Iterator with next function
|
|
1248
|
+
*/
|
|
1249
|
+
listChunks(fn, { total, concurrency = 3, chunkSize = 2100, pageSize = 100, timeKey, dataKey }) {
|
|
1250
|
+
let done = false;
|
|
1251
|
+
let time;
|
|
1252
|
+
const totalPage = Math.ceil(total / pageSize);
|
|
1253
|
+
let curPage = 0;
|
|
1254
|
+
const next = async () => {
|
|
1255
|
+
if (done) return [];
|
|
1256
|
+
let results = [];
|
|
1257
|
+
for (; curPage < totalPage; curPage += concurrency) {
|
|
1258
|
+
const flatResults = (await Promise.all(new Array(Math.min(concurrency, totalPage - curPage)).fill(true).map(async (_, i) => {
|
|
1259
|
+
const { [dataKey]: list } = await fn({
|
|
1260
|
+
size: pageSize,
|
|
1261
|
+
cursor: i * pageSize,
|
|
1262
|
+
time
|
|
1263
|
+
});
|
|
1264
|
+
return list;
|
|
1265
|
+
}))).flat();
|
|
1266
|
+
if (!flatResults.length) {
|
|
1267
|
+
done = true;
|
|
1268
|
+
return results;
|
|
1269
|
+
}
|
|
1270
|
+
results = results.concat(flatResults);
|
|
1271
|
+
time = results.length ? results[results.length - 1][timeKey] : void 0;
|
|
1272
|
+
if (results.length >= chunkSize) return results;
|
|
1273
|
+
}
|
|
1274
|
+
done = true;
|
|
1275
|
+
return results;
|
|
1276
|
+
};
|
|
1277
|
+
return {
|
|
1278
|
+
next,
|
|
1279
|
+
done
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
async listTransactionsChunks(args = {}, { chunkSize = 2100, pageSize = 100 } = {}) {
|
|
1283
|
+
return this.listChunks(({ size, time, cursor }) => this.listTransactions({
|
|
1284
|
+
...args,
|
|
1285
|
+
paging: {
|
|
1286
|
+
order: {
|
|
1287
|
+
field: "time",
|
|
1288
|
+
type: "asc"
|
|
1289
|
+
},
|
|
1290
|
+
...args.paging || {},
|
|
1291
|
+
size,
|
|
1292
|
+
cursor
|
|
1293
|
+
},
|
|
1294
|
+
timeFilter: Object.assign(args.timeFilter || {}, time ? { startDateTime: time } : {})
|
|
1295
|
+
}), {
|
|
1296
|
+
dataKey: "transactions",
|
|
1297
|
+
timeKey: "time",
|
|
1298
|
+
total: await this.indexdb.tx.count(),
|
|
1299
|
+
chunkSize,
|
|
1300
|
+
pageSize
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
async listStakeChunks(args = {}, { chunkSize = 2100, pageSize = 100 } = {}) {
|
|
1304
|
+
return this.listChunks(({ size, time, cursor }) => this.listStakes({
|
|
1305
|
+
...args,
|
|
1306
|
+
paging: {
|
|
1307
|
+
order: {
|
|
1308
|
+
field: "time",
|
|
1309
|
+
type: "asc"
|
|
1310
|
+
},
|
|
1311
|
+
...args.paging || {},
|
|
1312
|
+
size,
|
|
1313
|
+
cursor
|
|
1314
|
+
},
|
|
1315
|
+
timeFilter: Object.assign(args.timeFilter || {}, time ? { startDateTime: time } : {})
|
|
1316
|
+
}), {
|
|
1317
|
+
dataKey: "stakes",
|
|
1318
|
+
timeKey: "renaissanceTime",
|
|
1319
|
+
total: await this.indexdb.stake.count(),
|
|
1320
|
+
chunkSize,
|
|
1321
|
+
pageSize
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
//#endregion
|
|
1327
|
+
export { OCAPResolver as default, formatData, formatDelegationOps };
|