@ocap/resolver 1.6.3 → 1.6.10
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/lib/hooks.js +308 -0
- package/lib/index.js +719 -177
- package/lib/token-cache.js +38 -0
- package/package.json +15 -10
package/lib/index.js
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
/* eslint-disable no-await-in-loop */
|
|
2
2
|
/* eslint-disable newline-per-chained-call */
|
|
3
3
|
const get = require('lodash/get');
|
|
4
|
+
const set = require('lodash/set');
|
|
5
|
+
const uniq = require('lodash/uniq');
|
|
6
|
+
const pick = require('lodash/pick');
|
|
7
|
+
const isEmpty = require('lodash/isEmpty');
|
|
8
|
+
const isEqual = require('lodash/isEqual');
|
|
9
|
+
const uniqBy = require('lodash/unionBy');
|
|
4
10
|
const Config = require('@ocap/config');
|
|
5
11
|
const states = require('@ocap/state');
|
|
12
|
+
const Joi = require('@arcblock/validator');
|
|
6
13
|
const md5 = require('@ocap/util/lib/md5');
|
|
7
|
-
const
|
|
14
|
+
const CustomError = require('@ocap/util/lib/error');
|
|
15
|
+
const { types } = require('@ocap/mcrypto');
|
|
16
|
+
const { fromTypeUrl } = require('@ocap/message');
|
|
17
|
+
const { toStakeAddress } = require('@arcblock/did-util');
|
|
18
|
+
const { fromPublicKey, toTypeInfo, isValid: isValidDid } = require('@arcblock/did');
|
|
8
19
|
const { toBN, fromTokenToUnit } = require('@ocap/util');
|
|
20
|
+
const { DEFAULT_TOKEN_DECIMAL } = require('@ocap/util/lib/constant');
|
|
9
21
|
const { createExecutor } = require('@ocap/tx-protocols');
|
|
10
|
-
const {
|
|
22
|
+
const { decodeAnySafe } = require('@ocap/tx-protocols/lib/util');
|
|
23
|
+
const {
|
|
24
|
+
createIndexedAccount,
|
|
25
|
+
createIndexedAsset,
|
|
26
|
+
createIndexedFactory,
|
|
27
|
+
createIndexedDelegation,
|
|
28
|
+
createIndexedToken,
|
|
29
|
+
createIndexedTransaction,
|
|
30
|
+
createIndexedStake,
|
|
31
|
+
createIndexedRollup,
|
|
32
|
+
createIndexedRollupBlock,
|
|
33
|
+
isDefaultTokenChanged,
|
|
34
|
+
} = require('@ocap/indexdb/lib/util');
|
|
35
|
+
const { formatTxType } = require('@ocap/util');
|
|
36
|
+
|
|
37
|
+
const { getTxReceipts, mergeTxReceipts, getTxSender, getTxReceiver } = require('@ocap/state/lib/states/tx');
|
|
11
38
|
|
|
12
39
|
const debug = require('debug')(require('../package.json').name);
|
|
40
|
+
const hooks = require('./hooks');
|
|
41
|
+
const { getInstance: getTokenCacheInstance } = require('./token-cache');
|
|
42
|
+
|
|
43
|
+
const noop = (x) => x;
|
|
44
|
+
const CHAIN_ADDR = md5('OCAP_CHAIN_ADDR');
|
|
13
45
|
|
|
14
46
|
const formatData = (data) => {
|
|
15
47
|
if (!data) {
|
|
@@ -27,18 +59,48 @@ const formatData = (data) => {
|
|
|
27
59
|
data.type_url = data.typeUrl;
|
|
28
60
|
return data;
|
|
29
61
|
} catch (err) {
|
|
30
|
-
//
|
|
62
|
+
// Some legacy data contains escaped back slashes
|
|
63
|
+
try {
|
|
64
|
+
JSON.parse(data.value.replace(/\\"/g, '"'));
|
|
65
|
+
data.type_url = data.typeUrl;
|
|
66
|
+
data.value = data.value.replace(/\\"/g, '"');
|
|
67
|
+
return data;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// do nothing
|
|
70
|
+
}
|
|
31
71
|
}
|
|
32
72
|
}
|
|
33
73
|
|
|
34
|
-
const decoded = data.typeUrl ?
|
|
35
|
-
if (['json', 'vc'].includes(decoded.type)) {
|
|
74
|
+
const decoded = data.typeUrl ? decodeAnySafe(data) : data;
|
|
75
|
+
if (['json', 'vc', 'AssetFactory'].includes(decoded.type)) {
|
|
36
76
|
return { typeUrl: decoded.type, type_url: decoded.type, value: JSON.stringify(decoded.value) };
|
|
37
77
|
}
|
|
38
78
|
|
|
39
79
|
return { typeUrl: decoded.type, type_url: decoded.type, value: decoded.value };
|
|
40
80
|
};
|
|
41
81
|
|
|
82
|
+
const formatDelegationOps = (state) => {
|
|
83
|
+
if (state && state.ops && typeof state.ops === 'object') {
|
|
84
|
+
state.ops = Object.keys(state.ops).map((x) => ({ key: x, value: state.ops[x] }));
|
|
85
|
+
}
|
|
86
|
+
return state;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const extractTokenMeta = (address, tokenStates) => {
|
|
90
|
+
const tokenState = tokenStates.filter(Boolean).find((t) => t.address === address);
|
|
91
|
+
if (!tokenState) {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
address,
|
|
97
|
+
decimal: typeof tokenState.decimal === 'undefined' ? DEFAULT_TOKEN_DECIMAL : tokenState.decimal,
|
|
98
|
+
unit: tokenState.unit,
|
|
99
|
+
symbol: tokenState.symbol,
|
|
100
|
+
foreignToken: tokenState.foreignToken,
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
42
104
|
module.exports = class OCAPResolver {
|
|
43
105
|
/**
|
|
44
106
|
* Creates an instance of OCAPResolver.
|
|
@@ -59,41 +121,71 @@ module.exports = class OCAPResolver {
|
|
|
59
121
|
|
|
60
122
|
this.statedb = statedb;
|
|
61
123
|
this.indexdb = indexdb;
|
|
124
|
+
this.filter = filter;
|
|
125
|
+
|
|
62
126
|
this.config = Object.freeze(Config.validate(config));
|
|
127
|
+
this.chainAddr = md5(config.chainId);
|
|
128
|
+
this.tokenItx = Config.genTokenItx(this.config);
|
|
129
|
+
this.consensus = `${statedb.name} v${statedb.version}`;
|
|
63
130
|
|
|
64
|
-
if (
|
|
65
|
-
this.
|
|
131
|
+
if (indexdb) {
|
|
132
|
+
this.tokenCache = getTokenCacheInstance(indexdb.token);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (this.tokenItx) {
|
|
136
|
+
this.config.token.address = this.tokenItx.address;
|
|
137
|
+
if (this.tokenCache) {
|
|
138
|
+
this.tokenCache.set(this.tokenItx.address, this.tokenItx);
|
|
139
|
+
}
|
|
66
140
|
}
|
|
67
141
|
|
|
68
|
-
this.filter = filter;
|
|
69
142
|
this.executor = createExecutor({
|
|
70
143
|
filter,
|
|
71
144
|
runAsLambda: typeof statedb.runAsLambda === 'function' ? statedb.runAsLambda.bind(statedb) : null,
|
|
72
145
|
});
|
|
146
|
+
this.formatTx = this._formatTx.bind(this);
|
|
147
|
+
|
|
148
|
+
if (validateTokenConfig) {
|
|
149
|
+
this.validateTokenConfig();
|
|
150
|
+
}
|
|
73
151
|
|
|
74
152
|
this.connectIndexDB();
|
|
75
|
-
this.runAsLambda((txn) => this.initializeStateDB(txn))
|
|
153
|
+
this.runAsLambda((txn) => this.initializeStateDB(txn)).catch((err) => {
|
|
154
|
+
console.error('Failed to initialize statedb:', err.message);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
});
|
|
76
157
|
}
|
|
77
158
|
|
|
78
159
|
async sendTx({ tx: txBase64 }) {
|
|
79
160
|
debug('sendTx', txBase64);
|
|
80
161
|
|
|
162
|
+
if (process.env.CHAIN_MODE === 'readonly') {
|
|
163
|
+
throw new CustomError('FORBIDDEN', 'This chain node is running in readonly mode');
|
|
164
|
+
}
|
|
165
|
+
|
|
81
166
|
// 0. create new context
|
|
82
167
|
const context = { txBase64, statedb: this.statedb, config: this.config };
|
|
83
168
|
|
|
84
|
-
// 1. execute the transaction
|
|
169
|
+
// 1. execute and persist the transaction
|
|
85
170
|
const result = await this.executor.execute(context);
|
|
86
171
|
|
|
87
172
|
// 2. Return the hash
|
|
88
173
|
return result.txHash;
|
|
89
174
|
}
|
|
90
175
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
176
|
+
getTx({ hash }, ctx) {
|
|
177
|
+
if (Joi.patterns.txHash.test(hash)) {
|
|
178
|
+
return this._getState({
|
|
179
|
+
table: 'tx',
|
|
180
|
+
id: hash.toUpperCase(),
|
|
181
|
+
dataKey: 'tx.itxJson.data',
|
|
182
|
+
onRead: (tx) => this.formatTx(tx, ctx),
|
|
183
|
+
expandContext: false,
|
|
184
|
+
ctx,
|
|
185
|
+
});
|
|
95
186
|
}
|
|
96
|
-
|
|
187
|
+
|
|
188
|
+
return null;
|
|
97
189
|
}
|
|
98
190
|
|
|
99
191
|
getBlock() {
|
|
@@ -110,15 +202,15 @@ module.exports = class OCAPResolver {
|
|
|
110
202
|
|
|
111
203
|
async getChainInfo() {
|
|
112
204
|
return Promise.resolve({
|
|
113
|
-
id:
|
|
205
|
+
id: this.chainAddr,
|
|
114
206
|
totalTxs: await this.indexdb.tx.count(),
|
|
115
207
|
blockHash: md5(''),
|
|
116
208
|
blockHeight: 0,
|
|
117
209
|
blockTime: '',
|
|
118
210
|
version: this.config.version,
|
|
119
|
-
address:
|
|
211
|
+
address: this.chainAddr,
|
|
120
212
|
appHash: md5(new Date().toISOString()),
|
|
121
|
-
consensusVersion: this.
|
|
213
|
+
consensusVersion: this.consensus,
|
|
122
214
|
forgeAppsVersion: [],
|
|
123
215
|
moniker: this.config.chainId,
|
|
124
216
|
network: this.config.chainId,
|
|
@@ -130,9 +222,9 @@ module.exports = class OCAPResolver {
|
|
|
130
222
|
|
|
131
223
|
getNodeInfo() {
|
|
132
224
|
return Promise.resolve({
|
|
133
|
-
address:
|
|
225
|
+
address: this.chainAddr,
|
|
134
226
|
appHash: md5(new Date().toISOString()),
|
|
135
|
-
consensusVersion:
|
|
227
|
+
consensusVersion: this.consensus,
|
|
136
228
|
forgeAppsVersion: [],
|
|
137
229
|
geoInfo: {
|
|
138
230
|
city: 'Unknown',
|
|
@@ -140,7 +232,7 @@ module.exports = class OCAPResolver {
|
|
|
140
232
|
latitude: 0,
|
|
141
233
|
longitude: 0,
|
|
142
234
|
},
|
|
143
|
-
id:
|
|
235
|
+
id: this.chainAddr,
|
|
144
236
|
ip: '',
|
|
145
237
|
moniker: this.config.chainId,
|
|
146
238
|
network: this.config.chainId,
|
|
@@ -165,7 +257,7 @@ module.exports = class OCAPResolver {
|
|
|
165
257
|
blockHeight: 0,
|
|
166
258
|
validators: [
|
|
167
259
|
{
|
|
168
|
-
address: fromPublicKey(
|
|
260
|
+
address: fromPublicKey(this.chainAddr),
|
|
169
261
|
name: '',
|
|
170
262
|
proposerPriority: '0',
|
|
171
263
|
votingPower: '10',
|
|
@@ -178,25 +270,196 @@ module.exports = class OCAPResolver {
|
|
|
178
270
|
return JSON.stringify(this.config);
|
|
179
271
|
}
|
|
180
272
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
273
|
+
getAccountState({ address }, ctx) {
|
|
274
|
+
return this._getState({
|
|
275
|
+
table: 'account',
|
|
276
|
+
id: address,
|
|
277
|
+
dataKey: 'data',
|
|
278
|
+
onRead: async (state) => {
|
|
279
|
+
if (state) {
|
|
280
|
+
if (state.tokens) {
|
|
281
|
+
state.tokens = await this.formatTokenMap(state.tokens);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return state;
|
|
286
|
+
},
|
|
287
|
+
ctx,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
getStakeState({ address }, ctx) {
|
|
292
|
+
return this._getState({
|
|
293
|
+
table: 'stake',
|
|
294
|
+
id: address,
|
|
295
|
+
dataKey: 'data',
|
|
296
|
+
onRead: async (state) => {
|
|
297
|
+
if (state) {
|
|
298
|
+
if (state.tokens) {
|
|
299
|
+
state.tokens = await this.formatTokenMap(state.tokens);
|
|
300
|
+
}
|
|
301
|
+
if (state.revokedTokens) {
|
|
302
|
+
state.revokedTokens = await this.formatTokenMap(state.revokedTokens);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return state;
|
|
307
|
+
},
|
|
308
|
+
ctx,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async getRollupState({ address }, ctx) {
|
|
313
|
+
const rollup = await this._getState({
|
|
314
|
+
table: 'rollup',
|
|
315
|
+
id: address,
|
|
316
|
+
dataKey: 'data',
|
|
317
|
+
onRead: async (state) => {
|
|
318
|
+
if (state) {
|
|
319
|
+
const tokens = await this.formatTokenMap({ [state.tokenAddress]: '0' });
|
|
320
|
+
state.tokenInfo = tokens.find((x) => x.address === state.tokenAddress);
|
|
321
|
+
state.foreignToken = state.tokenInfo.foreignToken;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return state;
|
|
325
|
+
},
|
|
326
|
+
ctx,
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (!rollup) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const indexed = await this.indexdb.rollup.get(address);
|
|
334
|
+
if (indexed) {
|
|
335
|
+
rollup.totalDepositAmount = indexed.totalDepositAmount || '0';
|
|
336
|
+
rollup.totalWithdrawAmount = indexed.totalWithdrawAmount || '0';
|
|
185
337
|
}
|
|
186
|
-
|
|
338
|
+
|
|
339
|
+
return rollup;
|
|
187
340
|
}
|
|
188
341
|
|
|
189
|
-
async
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
342
|
+
async getRollupBlock({ hash, height, rollupAddress }, ctx) {
|
|
343
|
+
if (hash) {
|
|
344
|
+
return this._getState({ table: 'rollupBlock', id: hash, dataKey: 'data', ctx });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const blockHeight = Number(height || 0);
|
|
348
|
+
if (blockHeight > 0 && rollupAddress) {
|
|
349
|
+
const { blocks } = await this._doPaginatedSearch(
|
|
350
|
+
'listRollupBlocks',
|
|
351
|
+
{ height: blockHeight, rollupAddress },
|
|
352
|
+
'blocks',
|
|
353
|
+
'data',
|
|
354
|
+
ctx
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
if (blocks.length) {
|
|
358
|
+
return this._getState({ table: 'rollupBlock', id: blocks[0].hash, dataKey: 'data', ctx });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return null;
|
|
193
362
|
}
|
|
194
|
-
|
|
363
|
+
|
|
364
|
+
throw new Error('Can not get rollup block without hash or height + rollup');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
getAssetState({ address }, ctx) {
|
|
368
|
+
return this._getState({
|
|
369
|
+
table: 'asset',
|
|
370
|
+
id: address,
|
|
371
|
+
dataKey: 'data',
|
|
372
|
+
onRead: (state) => {
|
|
373
|
+
if (state && Array.isArray(state.tagsList)) {
|
|
374
|
+
state.tags = state.tagsList;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return state;
|
|
378
|
+
},
|
|
379
|
+
ctx,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
getEvidenceState({ hash }, ctx) {
|
|
384
|
+
return this._getState({ table: 'evidence', id: hash, dataKey: null, ctx });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
getFactoryState({ address }, ctx) {
|
|
388
|
+
return this._getState({
|
|
389
|
+
table: 'factory',
|
|
390
|
+
id: address,
|
|
391
|
+
dataKey: 'data',
|
|
392
|
+
onRead: async (state) => {
|
|
393
|
+
if (state) {
|
|
394
|
+
state.tokens = await this.formatTokenMap(state.tokens);
|
|
395
|
+
if (Array.isArray(state.input.tokens) && state.input.tokens.length > 0) {
|
|
396
|
+
state.input.tokens = await this.formatTokenArray(state.input.tokens || []);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
state.output.data = formatData(state.output.data);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return state;
|
|
403
|
+
},
|
|
404
|
+
ctx,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async getTokenState({ address }, ctx) {
|
|
409
|
+
const state = await this._getState({ table: 'token', id: address, dataKey: 'data', ctx });
|
|
410
|
+
if (state) {
|
|
411
|
+
if (typeof state.decimal === 'undefined') {
|
|
412
|
+
state.decimal = DEFAULT_TOKEN_DECIMAL;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return state;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
getDelegateState({ address }, ctx) {
|
|
420
|
+
return this._getState({ table: 'delegation', id: address, dataKey: 'data', onRead: formatDelegationOps, ctx });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async getAccountTokens({ address, token }, ctx) {
|
|
424
|
+
if (!address) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
let state = null;
|
|
429
|
+
|
|
430
|
+
const type = toTypeInfo(address);
|
|
431
|
+
if (type.role === types.RoleType.ROLE_ASSET) {
|
|
432
|
+
state = await this._getState({ table: 'factory', id: address, dataKey: 'data', expandContext: false, ctx });
|
|
433
|
+
} else {
|
|
434
|
+
state = await this._getState({ table: 'account', id: address, dataKey: 'data', expandContext: false, ctx });
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (!state || !state.tokens) {
|
|
438
|
+
return [];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const allTokens = Object.keys(state.tokens);
|
|
442
|
+
const wantedTokens = token ? allTokens.filter((x) => x === token) : allTokens;
|
|
443
|
+
|
|
444
|
+
const tokens = await Promise.all(
|
|
445
|
+
wantedTokens.map((x) => this._getState({ table: 'token', id: x, dataKey: 'data', expandContext: false, ctx }))
|
|
446
|
+
);
|
|
447
|
+
const tokensInfo = tokens.reduce((acc, x) => {
|
|
448
|
+
acc[x.address] = { symbol: x.symbol, decimal: x.decimal || DEFAULT_TOKEN_DECIMAL, unit: x.util }; // 兼容没有 decimal 的 token
|
|
449
|
+
return acc;
|
|
450
|
+
}, {});
|
|
451
|
+
|
|
452
|
+
return wantedTokens.map((x) => ({
|
|
453
|
+
address: x,
|
|
454
|
+
...tokensInfo[x],
|
|
455
|
+
balance: state.tokens[x],
|
|
456
|
+
}));
|
|
195
457
|
}
|
|
196
458
|
|
|
197
459
|
getForgeState() {
|
|
460
|
+
const { accounts, token, transaction } = this.config;
|
|
198
461
|
return Promise.resolve({
|
|
199
|
-
accountConfig:
|
|
462
|
+
accountConfig: accounts,
|
|
200
463
|
address: 'forge_state',
|
|
201
464
|
consensus: {
|
|
202
465
|
maxBytes: '150000',
|
|
@@ -207,52 +470,18 @@ module.exports = class OCAPResolver {
|
|
|
207
470
|
validatorChanged: false,
|
|
208
471
|
},
|
|
209
472
|
data: null,
|
|
210
|
-
gas: [],
|
|
211
|
-
protocols: [],
|
|
212
|
-
stakeSummary: [],
|
|
213
473
|
tasks: [],
|
|
214
|
-
token
|
|
215
|
-
tokenSwapConfig: {
|
|
216
|
-
commissionHolderAddress: '',
|
|
217
|
-
commissionRate: 0,
|
|
218
|
-
maxCommission: null,
|
|
219
|
-
minCommission: null,
|
|
220
|
-
revokeCommissionRate: 0,
|
|
221
|
-
},
|
|
474
|
+
token,
|
|
222
475
|
txConfig: {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
restricted: false,
|
|
228
|
-
},
|
|
229
|
-
stake: {
|
|
230
|
-
timeoutGeneral: 86400,
|
|
231
|
-
timeoutStakeForNode: 604800,
|
|
232
|
-
},
|
|
233
|
-
...this.config.transaction,
|
|
476
|
+
...transaction,
|
|
477
|
+
txFee: Object.keys(transaction.txFee)
|
|
478
|
+
.filter((x) => x !== 'default')
|
|
479
|
+
.map((x) => ({ typeUrl: x, fee: fromTokenToUnit(transaction.txFee[x], token.decimal).toString(10) })),
|
|
234
480
|
},
|
|
235
481
|
upgradeInfo: null,
|
|
236
482
|
});
|
|
237
483
|
}
|
|
238
484
|
|
|
239
|
-
getSwapState() {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
async getDelegateState({ address }) {
|
|
244
|
-
const state = await this.runAsLambda((txn) => this.statedb.delegation.get(address, { txn }));
|
|
245
|
-
if (state && state.ops) {
|
|
246
|
-
state.data = formatData(state.data);
|
|
247
|
-
state.ops = Object.keys(state.ops).reduce((acc, key) => {
|
|
248
|
-
acc.push({ key, value: state.ops[key] });
|
|
249
|
-
return acc;
|
|
250
|
-
}, []);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return state;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
485
|
async getForgeStats() {
|
|
257
486
|
const [accountCount, assetCount, txCount] = await Promise.all([
|
|
258
487
|
this.indexdb.account.count(),
|
|
@@ -284,63 +513,119 @@ module.exports = class OCAPResolver {
|
|
|
284
513
|
};
|
|
285
514
|
}
|
|
286
515
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
txs.forEach((x) => {
|
|
290
|
-
x.tx.itxJson.data = formatData(x.tx.itxJson.data);
|
|
291
|
-
});
|
|
292
|
-
return txs;
|
|
516
|
+
listTransactions(args, ctx) {
|
|
517
|
+
return this._doPaginatedSearch('listTransactions', args, 'transactions', 'tx.itxJson.data', ctx);
|
|
293
518
|
}
|
|
294
519
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const { assets = [], ...rest } = res;
|
|
298
|
-
assets.forEach((x) => {
|
|
299
|
-
x.data = formatData(x.data);
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
return { assets, ...rest };
|
|
520
|
+
listAssets(args, ctx) {
|
|
521
|
+
return this._doPaginatedSearch('listAssets', args, 'assets', 'data', ctx);
|
|
303
522
|
}
|
|
304
523
|
|
|
305
|
-
|
|
306
|
-
|
|
524
|
+
listAssetTransactions(args = {}, ctx) {
|
|
525
|
+
if (!args.address) {
|
|
526
|
+
return { transactions: [] };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return this._doPaginatedSearch(
|
|
530
|
+
'listTransactions',
|
|
531
|
+
{ ...args, assetFilter: { assets: [args.address] } },
|
|
532
|
+
'transactions',
|
|
533
|
+
'tx.itxJson.data',
|
|
534
|
+
ctx
|
|
535
|
+
);
|
|
307
536
|
}
|
|
308
537
|
|
|
309
|
-
async
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
x.
|
|
538
|
+
async listFactories(args, ctx) {
|
|
539
|
+
const result = await this._doPaginatedSearch('listFactories', args, 'factories', 'data', ctx);
|
|
540
|
+
result.factories = result.factories.map((x) => {
|
|
541
|
+
x.output.data = formatData(x.output.data);
|
|
542
|
+
return x;
|
|
313
543
|
});
|
|
314
|
-
return txs;
|
|
315
|
-
}
|
|
316
544
|
|
|
317
|
-
|
|
318
|
-
return [];
|
|
545
|
+
return result;
|
|
319
546
|
}
|
|
320
547
|
|
|
321
548
|
listTopAccounts(args) {
|
|
322
|
-
|
|
549
|
+
if (!args.tokenAddress) {
|
|
550
|
+
args.tokenAddress = this.tokenItx.address;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return this._doPaginatedSearch('listTopAccounts', args, 'accounts');
|
|
323
554
|
}
|
|
324
555
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
return null;
|
|
556
|
+
listTokens(args, ctx) {
|
|
557
|
+
return this._doPaginatedSearch('listTokens', args, 'tokens', 'data', ctx);
|
|
328
558
|
}
|
|
329
559
|
|
|
330
|
-
|
|
331
|
-
return
|
|
560
|
+
listStakes(args, ctx) {
|
|
561
|
+
return this._doPaginatedSearch('listStakes', args, 'stakes', 'data', ctx);
|
|
332
562
|
}
|
|
333
563
|
|
|
334
|
-
|
|
335
|
-
return
|
|
564
|
+
listRollups(args, ctx) {
|
|
565
|
+
return this._doPaginatedSearch('listRollups', args, 'rollups', 'data', ctx);
|
|
336
566
|
}
|
|
337
567
|
|
|
338
|
-
|
|
339
|
-
|
|
568
|
+
listRollupBlocks(args, ctx) {
|
|
569
|
+
return this._doPaginatedSearch('listRollupBlocks', args, 'blocks', 'data', ctx);
|
|
340
570
|
}
|
|
341
571
|
|
|
342
|
-
|
|
343
|
-
|
|
572
|
+
async listRollupValidators(args, ctx) {
|
|
573
|
+
if (!args.rollupAddress) {
|
|
574
|
+
return { validators: [] };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const rollup = await this.runAsLambda((txn) => this.statedb.rollup.get(args.rollupAddress, { txn }));
|
|
578
|
+
if (!rollup) {
|
|
579
|
+
return { validators: [] };
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const result = await this._doPaginatedSearch('listRollupValidators', args, 'validators', null, ctx);
|
|
583
|
+
const stakes = (
|
|
584
|
+
await Promise.all(
|
|
585
|
+
result.validators.map((x) => {
|
|
586
|
+
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
587
|
+
return this.runAsLambda((txn) => this.statedb.stake.get(stakeAddress, { txn }));
|
|
588
|
+
})
|
|
589
|
+
)
|
|
590
|
+
).filter(Boolean);
|
|
591
|
+
result.validators.forEach((x) => {
|
|
592
|
+
const stakeAddress = toStakeAddress(x.address, args.rollupAddress);
|
|
593
|
+
const stake = stakes.find((s) => s.address === stakeAddress);
|
|
594
|
+
x.availableStake = stake ? stake.tokens[rollup.tokenAddress] : '0';
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async search(args) {
|
|
601
|
+
if (!args.keyword) {
|
|
602
|
+
return { results: [] };
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const doSearch = async (type, keyword) => {
|
|
606
|
+
const result = await this.indexdb[type].get(keyword);
|
|
607
|
+
return result ? { type, id: keyword } : null;
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
if (Joi.patterns.txHash.test(args.keyword)) {
|
|
611
|
+
const results = await Promise.all([
|
|
612
|
+
doSearch('tx', args.keyword.toUpperCase()),
|
|
613
|
+
doSearch('rollupBlock', args.keyword),
|
|
614
|
+
]);
|
|
615
|
+
return { results: results.filter(Boolean) };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const entitiesByDid = ['account', 'asset', 'delegation', 'factory', 'token', 'stake', 'rollup'];
|
|
619
|
+
if (isValidDid(args.keyword)) {
|
|
620
|
+
const results = await Promise.all(entitiesByDid.map((type) => doSearch(type, args.keyword)));
|
|
621
|
+
return { results: results.filter(Boolean) };
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return { results: [] };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
listBlocks() {
|
|
628
|
+
return { blocks: [] };
|
|
344
629
|
}
|
|
345
630
|
|
|
346
631
|
validateTokenConfig() {
|
|
@@ -352,96 +637,353 @@ module.exports = class OCAPResolver {
|
|
|
352
637
|
}
|
|
353
638
|
}
|
|
354
639
|
|
|
640
|
+
runAsLambda(fn) {
|
|
641
|
+
if (typeof this.statedb.runAsLambda === 'function') {
|
|
642
|
+
return this.statedb.runAsLambda((txn) => fn(txn));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return fn();
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
getChain() {
|
|
649
|
+
return this.runAsLambda((txn) => this.statedb.chain.get(CHAIN_ADDR, { txn }));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
getToken(id) {
|
|
653
|
+
return this.runAsLambda((txn) => this.statedb.token.get(id, { txn }));
|
|
654
|
+
}
|
|
655
|
+
|
|
355
656
|
async initializeStateDB(txn) {
|
|
356
657
|
const { accounts, token } = this.config;
|
|
357
|
-
const { account:
|
|
358
|
-
const { account: accountState } = states;
|
|
658
|
+
const { account: accountDB, chain: chainDB, token: tokenDB } = this.statedb;
|
|
659
|
+
const { account: accountState, chain: chainState, token: tokenState } = states;
|
|
660
|
+
|
|
661
|
+
const ctx = { txn };
|
|
662
|
+
const context = { txTime: new Date().toISOString(), txHash: '' };
|
|
663
|
+
|
|
664
|
+
// Auto persist config to chain state
|
|
665
|
+
// Will throw error if immutable chain config are updated
|
|
666
|
+
const info = await this.getChain();
|
|
667
|
+
if (!info) {
|
|
668
|
+
const state = chainState.create({ ...this.config, address: CHAIN_ADDR }, context);
|
|
669
|
+
const result = await chainDB.create(CHAIN_ADDR, state, ctx);
|
|
670
|
+
debug('create chain state', result);
|
|
671
|
+
} else if (isEqual(pick(info, Object.keys(this.config)), this.config) === false) {
|
|
672
|
+
const state = chainState.update(info, this.config, context);
|
|
673
|
+
const result = await chainDB.update(CHAIN_ADDR, state, ctx);
|
|
674
|
+
debug('update chain state', result);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// Auto persist token state, just once
|
|
678
|
+
// Since the token info should not be changed after restart
|
|
679
|
+
if (this.tokenItx) {
|
|
680
|
+
const existToken = await this.getToken(this.tokenItx.address);
|
|
681
|
+
if (!existToken) {
|
|
682
|
+
const state = tokenState.create(this.tokenItx, context);
|
|
683
|
+
const result = await tokenDB.create(this.tokenItx.address, state, ctx);
|
|
684
|
+
tokenDB.emit('create', result, ctx);
|
|
685
|
+
debug('create token state', result);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Auto populate token holder accounts if not exist
|
|
689
|
+
for (let i = 0; i < accounts.length; i++) {
|
|
690
|
+
const { address, balance } = accounts[i];
|
|
691
|
+
try {
|
|
692
|
+
const existAccount = await accountDB.get(address, ctx);
|
|
693
|
+
if (!existAccount) {
|
|
694
|
+
const balanceStr = fromTokenToUnit(balance, token.decimal).toString(10);
|
|
695
|
+
const state = accountState.create(
|
|
696
|
+
{ address, tokens: { [this.tokenItx.address]: balanceStr }, moniker: 'token-holder' },
|
|
697
|
+
context
|
|
698
|
+
);
|
|
699
|
+
const result = await accountDB.create(address, state, ctx);
|
|
700
|
+
accountDB.emit('create', result, ctx);
|
|
701
|
+
}
|
|
702
|
+
} catch (err) {
|
|
703
|
+
console.error('Failed to initialize initial token holders', err);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
359
708
|
|
|
360
|
-
|
|
709
|
+
connectIndexDB() {
|
|
710
|
+
const getToken = this.getToken.bind(this);
|
|
711
|
+
|
|
712
|
+
this.statedb.tx.on('create', async (x, ctx) => {
|
|
713
|
+
try {
|
|
714
|
+
const tx = await createIndexedTransaction(x, ctx);
|
|
715
|
+
await this.indexdb.tx.insert(tx);
|
|
716
|
+
if (typeof hooks.onCreateTx === 'function') {
|
|
717
|
+
await hooks.onCreateTx(tx, ctx, this.indexdb);
|
|
718
|
+
}
|
|
719
|
+
} catch (error) {
|
|
720
|
+
console.error('create tx index failed', { account: x, error });
|
|
721
|
+
}
|
|
722
|
+
});
|
|
361
723
|
|
|
362
|
-
|
|
363
|
-
for (let i = 0; i < accounts.length; i++) {
|
|
364
|
-
const { address, balance } = accounts[i];
|
|
724
|
+
this.statedb.account.on('create', async (x) => {
|
|
365
725
|
try {
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
726
|
+
await this.indexdb.account.insert(await createIndexedAccount(x, getToken));
|
|
727
|
+
} catch (error) {
|
|
728
|
+
console.error('create account index failed', { account: x, error });
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
this.statedb.account.on('update', async (x) => {
|
|
732
|
+
try {
|
|
733
|
+
await this.indexdb.account.update(x.address, await createIndexedAccount(x, getToken));
|
|
734
|
+
} catch (error) {
|
|
735
|
+
console.error('update account index failed', { account: x, error });
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
const mapping = {
|
|
740
|
+
asset: [createIndexedAsset, 'address'],
|
|
741
|
+
delegation: [createIndexedDelegation, 'address'],
|
|
742
|
+
token: [createIndexedToken, 'address'],
|
|
743
|
+
factory: [createIndexedFactory, 'address'],
|
|
744
|
+
stake: [createIndexedStake, 'address'],
|
|
745
|
+
rollup: [createIndexedRollup, 'address', hooks.onCreateRollup],
|
|
746
|
+
rollupBlock: [createIndexedRollupBlock, 'hash', hooks.onCreateRollupBlock],
|
|
747
|
+
};
|
|
748
|
+
Object.keys(mapping).forEach((table) => {
|
|
749
|
+
const [fn, key, onCreate, onUpdate] = mapping[table];
|
|
750
|
+
|
|
751
|
+
this.statedb[table].on('create', async (x, _ctx) => {
|
|
752
|
+
try {
|
|
753
|
+
const ctx = this.enrichIndexContext(_ctx);
|
|
754
|
+
const doc = await fn(x, ctx);
|
|
755
|
+
await this.indexdb[table].insert(doc);
|
|
756
|
+
if (typeof onCreate === 'function') {
|
|
757
|
+
await onCreate(doc, ctx, this.indexdb);
|
|
758
|
+
}
|
|
759
|
+
} catch (error) {
|
|
760
|
+
console.error(`create ${table} index failed`, { [table]: x, error });
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
this.statedb[table].on('update', async (x, _ctx) => {
|
|
765
|
+
try {
|
|
766
|
+
const ctx = this.enrichIndexContext(_ctx);
|
|
767
|
+
const doc = await fn(x, ctx);
|
|
768
|
+
await this.indexdb[table].update(doc[key], doc);
|
|
769
|
+
if (typeof onUpdate === 'function') {
|
|
770
|
+
await onUpdate(doc, ctx, this.indexdb);
|
|
771
|
+
}
|
|
772
|
+
} catch (error) {
|
|
773
|
+
console.error(`update ${table} index failed`, { [table]: x, error });
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async _getState({ table, id, dataKey, onRead = noop, expandContext = true, ctx }) {
|
|
780
|
+
if (!id) {
|
|
781
|
+
throw new CustomError('INVALID_REQUEST', `Missing required parameter to read ${table} state`);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const state = await this.runAsLambda((txn) => this.statedb[table].get(id, { txn }));
|
|
785
|
+
if (state) {
|
|
786
|
+
if (dataKey) {
|
|
787
|
+
set(state, dataKey, formatData(get(state, dataKey)));
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// expand context transactions
|
|
791
|
+
if (expandContext && state.context) {
|
|
792
|
+
const { genesisTx, renaissanceTx } = state.context;
|
|
793
|
+
const txs = (
|
|
794
|
+
await Promise.all(
|
|
795
|
+
uniq([genesisTx, renaissanceTx])
|
|
796
|
+
.filter(Boolean)
|
|
797
|
+
.map((x) => this.getTx({ hash: x }, ctx))
|
|
798
|
+
)
|
|
799
|
+
).filter(Boolean);
|
|
800
|
+
state.context.genesisTx = txs.find((x) => x.hash === genesisTx);
|
|
801
|
+
state.context.renaissanceTx = txs.find((x) => x.hash === renaissanceTx);
|
|
802
|
+
|
|
803
|
+
if (!state.context.genesisTx) {
|
|
804
|
+
state.context.genesisTx = { hash: genesisTx };
|
|
805
|
+
}
|
|
806
|
+
if (!state.context.renaissanceTx) {
|
|
807
|
+
state.context.renaissanceTx = { hash: renaissanceTx };
|
|
372
808
|
}
|
|
373
|
-
} catch (err) {
|
|
374
|
-
// Do nothing
|
|
375
809
|
}
|
|
376
810
|
}
|
|
811
|
+
|
|
812
|
+
return onRead(state);
|
|
377
813
|
}
|
|
378
814
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
815
|
+
async _doPaginatedSearch(fn, args, listKey, dataKey, ctx) {
|
|
816
|
+
const result = await this.indexdb[fn](args);
|
|
817
|
+
const { [listKey]: items = [], ...rest } = result;
|
|
818
|
+
let data = items;
|
|
819
|
+
|
|
820
|
+
if (dataKey) {
|
|
821
|
+
data = await Promise.all(
|
|
822
|
+
items.map(async (x) => {
|
|
823
|
+
let tx = set(x, dataKey, formatData(get(x, dataKey)));
|
|
824
|
+
if (listKey === 'transactions') {
|
|
825
|
+
tx = await this.formatTx(tx, ctx);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return tx;
|
|
829
|
+
})
|
|
830
|
+
);
|
|
382
831
|
}
|
|
383
832
|
|
|
384
|
-
|
|
833
|
+
const final = { [listKey]: data, ...rest };
|
|
834
|
+
if (!final.paging) {
|
|
835
|
+
final.paging = { cursor: '0', next: false, total: 0 };
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return final;
|
|
385
839
|
}
|
|
386
840
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
841
|
+
async _formatTx(tx, ctx) {
|
|
842
|
+
if (!tx) {
|
|
843
|
+
return tx;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if (!tx.sender && !tx.receiver) {
|
|
847
|
+
const typeUrl = fromTypeUrl(tx.tx.itx.typeUrl);
|
|
848
|
+
tx.sender = getTxSender({ tx: tx.tx, itx: tx.tx.itxJson, typeUrl });
|
|
849
|
+
tx.receiver = getTxReceiver({ tx: tx.tx, itx: tx.tx.itxJson, typeUrl });
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
if (!tx.receipts) {
|
|
853
|
+
tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// https://github.com/blocklet/abt-wallet/issues/681
|
|
857
|
+
if (get(tx, 'tx.itxJson.type_url') === 'fg:t:deposit_token') {
|
|
858
|
+
tx.receipts = getTxReceipts(tx, { config: this.config });
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
tx.receipts = mergeTxReceipts(tx.receipts);
|
|
862
|
+
tx.tokenSymbols = await this.getTxTokenSymbols(tx);
|
|
863
|
+
|
|
864
|
+
this.fixReceiptTokens(tx, ctx);
|
|
865
|
+
this.fixTokenSymbols(tx, ctx);
|
|
866
|
+
|
|
867
|
+
return tx;
|
|
396
868
|
}
|
|
397
869
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
migratedFrom: x.migratedFrom[0] || '',
|
|
404
|
-
migratedTo: x.migratedTo[0] || '',
|
|
405
|
-
moniker: x.moniker,
|
|
406
|
-
nonce: x.nonce,
|
|
407
|
-
numAssets: x.numAssets,
|
|
408
|
-
numTxs: x.numTxs,
|
|
409
|
-
recentNumTxs: [],
|
|
410
|
-
renaissanceTime: x.context.renaissanceTime,
|
|
411
|
-
totalReceivedStakes: '0',
|
|
412
|
-
totalStakes: '0',
|
|
413
|
-
totalUnstakes: '0',
|
|
414
|
-
};
|
|
870
|
+
async formatTokenArray(tokens) {
|
|
871
|
+
const uniqTokens = uniqBy(tokens, 'address');
|
|
872
|
+
const tokenStates = await Promise.all(uniqTokens.map((token) => this.tokenCache.get(token.address)));
|
|
873
|
+
|
|
874
|
+
return uniqTokens.map((token) => ({ ...token, ...extractTokenMeta(token.address, tokenStates) }));
|
|
415
875
|
}
|
|
416
876
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
transferrable: x.transferrable,
|
|
430
|
-
ttl: x.ttl,
|
|
431
|
-
};
|
|
877
|
+
async formatTokenMap(tokens) {
|
|
878
|
+
if (isEmpty(tokens)) {
|
|
879
|
+
return [];
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const tasks = Object.keys(tokens).map((address) => this.tokenCache.get(address));
|
|
883
|
+
const tokenStates = await Promise.all(tasks);
|
|
884
|
+
|
|
885
|
+
return Object.keys(tokens).map((address) => ({
|
|
886
|
+
value: tokens[address],
|
|
887
|
+
...extractTokenMeta(address, tokenStates),
|
|
888
|
+
}));
|
|
432
889
|
}
|
|
433
890
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
891
|
+
async getTxTokenSymbols(tx) {
|
|
892
|
+
if (!tx) {
|
|
893
|
+
return [];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const rollupTxs = [
|
|
897
|
+
'DepositTokenV2Tx',
|
|
898
|
+
'WithdrawTokenV2Tx',
|
|
899
|
+
'JoinRollupTx',
|
|
900
|
+
'LeaveRollupTx',
|
|
901
|
+
'CreateRollupBlockTx',
|
|
902
|
+
'ClaimBlockRewardTx',
|
|
903
|
+
];
|
|
904
|
+
const typeUrl = formatTxType(tx.tx.itxJson._type);
|
|
905
|
+
const tokens = [];
|
|
906
|
+
|
|
907
|
+
if (isDefaultTokenChanged(tx, this.config.token)) {
|
|
908
|
+
tokens.push(this.tokenItx);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (typeUrl === 'AcquireAssetV2Tx') {
|
|
912
|
+
const factory = await this.indexdb.factory.get(tx.tx.itxJson.factory);
|
|
913
|
+
tokens.push(...factory.input.tokens);
|
|
914
|
+
} else if (typeUrl === 'ExchangeV2Tx') {
|
|
915
|
+
tokens.push(...tx.tx.itxJson.sender.tokens);
|
|
916
|
+
tokens.push(...tx.tx.itxJson.receiver.tokens);
|
|
917
|
+
} else if (typeUrl === 'TransferV2Tx') {
|
|
918
|
+
tokens.push(...tx.tx.itxJson.tokens);
|
|
919
|
+
} else if (['TransferV3Tx', 'AcquireAssetV3Tx', 'StakeTx'].includes(typeUrl)) {
|
|
920
|
+
tokens.push(
|
|
921
|
+
...uniqBy(
|
|
922
|
+
tx.tx.itxJson.inputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
923
|
+
'address'
|
|
924
|
+
).filter((x) => x.address)
|
|
925
|
+
);
|
|
926
|
+
} else if (typeUrl === 'RevokeStakeTx') {
|
|
927
|
+
tokens.push(
|
|
928
|
+
...uniqBy(
|
|
929
|
+
tx.tx.itxJson.outputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
930
|
+
'address'
|
|
931
|
+
).filter((x) => x.address)
|
|
932
|
+
);
|
|
933
|
+
} else if (typeUrl === 'ClaimStakeTx') {
|
|
934
|
+
const revokeTx = await this.indexdb.tx.get(tx.tx.itxJson.evidence.hash);
|
|
935
|
+
tokens.push(
|
|
936
|
+
...uniqBy(
|
|
937
|
+
revokeTx.tx.itxJson.outputs.reduce((acc, x) => acc.concat(x.tokens), []),
|
|
938
|
+
'address'
|
|
939
|
+
).filter((x) => x.address)
|
|
940
|
+
);
|
|
941
|
+
} else if (rollupTxs.includes(typeUrl)) {
|
|
942
|
+
const rollup = await this.indexdb.rollup.get(tx.tx.itxJson.rollup);
|
|
943
|
+
tokens.push({ address: rollup.tokenAddress, value: '0' });
|
|
944
|
+
} else if (typeUrl === 'CreateTokenTx') {
|
|
945
|
+
return [extractTokenMeta(tx.tx.itxJson.address, [tx.tx.itxJson])];
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (tokens.length > 0) {
|
|
949
|
+
return this.formatTokenArray(tokens);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
fixReceiptTokens(tx) {
|
|
956
|
+
// Auto populate initial token address in tx receipts
|
|
957
|
+
tx.receipts.forEach((receipt) =>
|
|
958
|
+
receipt.changes.forEach((x) => {
|
|
959
|
+
if (x.target === '') {
|
|
960
|
+
x.target = this.tokenItx.address;
|
|
961
|
+
}
|
|
962
|
+
})
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
return tx;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
fixTokenSymbols(tx) {
|
|
969
|
+
const index = tx.tokenSymbols.findIndex((x) => x.address === this.tokenItx.address);
|
|
970
|
+
if (index === -1) {
|
|
971
|
+
tx.tokenSymbols.push(extractTokenMeta(this.tokenItx.address, [this.tokenItx]));
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return tx;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
enrichIndexContext(ctx) {
|
|
978
|
+
if (Array.isArray(ctx.tokenStates)) {
|
|
979
|
+
ctx.tokenStates.push(this.tokenItx);
|
|
980
|
+
} else if (ctx.tokenState) {
|
|
981
|
+
ctx.tokenStates = [ctx.tokenState];
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
return ctx;
|
|
444
985
|
}
|
|
445
986
|
};
|
|
446
987
|
|
|
447
988
|
module.exports.formatData = formatData;
|
|
989
|
+
module.exports.formatDelegationOps = formatDelegationOps;
|