@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.
Files changed (43) hide show
  1. package/esm/api.d.mts +24 -0
  2. package/esm/api.mjs +53 -0
  3. package/esm/hooks.d.mts +153 -0
  4. package/esm/hooks.mjs +267 -0
  5. package/esm/index.d.mts +201 -0
  6. package/esm/index.mjs +1327 -0
  7. package/esm/migration-chain.d.mts +52 -0
  8. package/esm/migration-chain.mjs +97 -0
  9. package/esm/package.mjs +5 -0
  10. package/esm/token-cache.d.mts +20 -0
  11. package/esm/token-cache.mjs +26 -0
  12. package/esm/token-distribution.d.mts +166 -0
  13. package/esm/token-distribution.mjs +241 -0
  14. package/esm/token-flow.d.mts +139 -0
  15. package/esm/token-flow.mjs +330 -0
  16. package/esm/types.d.mts +115 -0
  17. package/esm/types.mjs +1 -0
  18. package/lib/_virtual/rolldown_runtime.cjs +29 -0
  19. package/lib/api.cjs +54 -0
  20. package/lib/api.d.cts +24 -0
  21. package/lib/hooks.cjs +274 -0
  22. package/lib/hooks.d.cts +153 -0
  23. package/lib/index.cjs +1343 -0
  24. package/lib/index.d.cts +201 -0
  25. package/lib/migration-chain.cjs +99 -0
  26. package/lib/migration-chain.d.cts +52 -0
  27. package/lib/package.cjs +11 -0
  28. package/lib/token-cache.cjs +27 -0
  29. package/lib/token-cache.d.cts +20 -0
  30. package/lib/token-distribution.cjs +243 -0
  31. package/lib/token-distribution.d.cts +166 -0
  32. package/lib/token-flow.cjs +336 -0
  33. package/lib/token-flow.d.cts +139 -0
  34. package/lib/types.cjs +0 -0
  35. package/lib/types.d.cts +115 -0
  36. package/package.json +49 -21
  37. package/lib/api.js +0 -71
  38. package/lib/hooks.js +0 -339
  39. package/lib/index.js +0 -1486
  40. package/lib/migration-chain.js +0 -144
  41. package/lib/token-cache.js +0 -40
  42. package/lib/token-distribution.js +0 -358
  43. package/lib/token-flow.js +0 -445
@@ -1,144 +0,0 @@
1
- const { isEthereumDid } = require('@arcblock/did');
2
-
3
- class MigrationNode {
4
- constructor(address, validFrom, validUntil = null, nextAddress = null) {
5
- this.address = address;
6
- this.validFrom = validFrom;
7
- this.validUntil = validUntil;
8
- this.nextAddress = nextAddress;
9
- }
10
- }
11
-
12
- class MigrationChainManager {
13
- constructor() {
14
- // Main storage for migration chains
15
- this.chains = new Map();
16
- // Index for quick root lookup
17
- this.rootAddresses = new Map();
18
- }
19
-
20
- validateMigration(migration) {
21
- if (!migration?.tx?.from || !migration?.tx?.itxJson?.address || !migration?.time) {
22
- throw new Error('Invalid migration format');
23
- }
24
- if (new Date(migration.time).toString() === 'Invalid Date') {
25
- throw new Error('Invalid timestamp');
26
- }
27
- return true;
28
- }
29
-
30
- /**
31
- * Build migration chains from a list of migration transactions
32
- * @param {Array} migrations - List of migration transactions
33
- */
34
- buildChains(migrations) {
35
- if (!Array.isArray(migrations)) {
36
- throw new Error('Migrations must be an array');
37
- }
38
-
39
- migrations.forEach((migration) => this.validateMigration(migration));
40
-
41
- // Sort migrations by timestamp
42
- const sortedMigrations = [...migrations].sort((a, b) => new Date(a.time) - new Date(b.time));
43
-
44
- for (const migration of sortedMigrations) {
45
- const fromAddr = migration.tx.from;
46
- const toAddr = migration.tx.itxJson.address;
47
- const timestamp = new Date(migration.time);
48
-
49
- this._processMigration(fromAddr, toAddr, timestamp);
50
- }
51
- }
52
-
53
- /**
54
- * Process a single migration and update the chains
55
- * @param {string} fromAddr - Source address
56
- * @param {string} toAddr - Destination address
57
- * @param {Date} timestamp - Migration timestamp
58
- * @private
59
- */
60
- _processMigration(fromAddr, toAddr, timestamp) {
61
- // Find or create the root address for this chain
62
- const rootAddr = this.getRootAddress(fromAddr);
63
-
64
- // Get or create the chain
65
- if (!this.chains.has(rootAddr)) {
66
- this.chains.set(rootAddr, []);
67
- }
68
- const chain = this.chains.get(rootAddr);
69
-
70
- if (chain.length === 0) {
71
- // First migration in the chain
72
- chain.push(new MigrationNode(fromAddr, new Date(0), timestamp));
73
- chain.push(new MigrationNode(toAddr, timestamp));
74
- } else {
75
- // Update the previous node's valid_until and next_address
76
- const lastNode = chain[chain.length - 1];
77
- lastNode.validUntil = timestamp;
78
- lastNode.nextAddress = toAddr;
79
- // Add the new node
80
- chain.push(new MigrationNode(toAddr, timestamp));
81
- }
82
-
83
- // Update root address mapping for the new address
84
- this.rootAddresses.set(this.formatAddress(toAddr), rootAddr);
85
- }
86
-
87
- /**
88
- * Find the valid address at a specific timestamp
89
- * @param {string} address - Address to look up
90
- * @param {Date} timestamp - Timestamp to check
91
- * @returns {string} Valid address at the timestamp
92
- */
93
- findAddressAtTime(address, timestamp) {
94
- const chains = this.getMigrationHistory(address);
95
-
96
- if (!chains?.length) {
97
- return address;
98
- }
99
-
100
- // Binary search for the correct address
101
- let left = 0;
102
- let right = chains.length - 1;
103
-
104
- while (left <= right) {
105
- const mid = Math.floor((left + right) / 2);
106
- const node = chains[mid];
107
-
108
- if (node.validFrom <= timestamp && (!node.validUntil || timestamp < node.validUntil)) {
109
- return node.address;
110
- }
111
- if (timestamp < node.validFrom) {
112
- right = mid - 1;
113
- } else {
114
- left = mid + 1;
115
- }
116
- }
117
-
118
- return chains[chains.length - 1].address;
119
- }
120
-
121
- formatAddress(address) {
122
- return isEthereumDid(address) ? address.toLowerCase() : address;
123
- }
124
-
125
- getRootAddress(address) {
126
- const formattedAddress = this.formatAddress(address);
127
- return this.rootAddresses.get(formattedAddress) || formattedAddress;
128
- }
129
-
130
- /**
131
- * Get the complete migration history for an address
132
- * @param {string} address - Address to look up
133
- * @returns {Array} List of migration nodes
134
- */
135
- getMigrationHistory(address) {
136
- const rootAddr = this.getRootAddress(address);
137
- return this.chains.get(rootAddr) || [];
138
- }
139
- }
140
-
141
- module.exports = {
142
- MigrationNode,
143
- MigrationChainManager,
144
- };
@@ -1,40 +0,0 @@
1
- /**
2
- * 缓存 token 到内存
3
- */
4
- class Cache {
5
- constructor(dbAdapter) {
6
- this.dbAdapter = dbAdapter;
7
- this.cache = new Map();
8
- }
9
-
10
- async get(address) {
11
- if (this.cache.has(address)) {
12
- return this.cache.get(address);
13
- }
14
-
15
- const token = await this.dbAdapter.get(address);
16
- if (token) {
17
- this.cache.set(address, token);
18
- }
19
-
20
- return token;
21
- }
22
-
23
- set(address, token) {
24
- this.cache.set(address, token);
25
- return token;
26
- }
27
- }
28
-
29
- let cache = null;
30
-
31
- module.exports = {
32
- getInstance: (dbAdapter) => {
33
- if (cache !== null) {
34
- return cache;
35
- }
36
-
37
- cache = new Cache(dbAdapter);
38
- return cache;
39
- },
40
- };
@@ -1,358 +0,0 @@
1
- const { BN, isSameDid, fromTokenToUnit } = require('@ocap/util');
2
- const { toTypeInfo } = require('@arcblock/did');
3
- const {
4
- types: { RoleType },
5
- } = require('@ocap/mcrypto');
6
- const { createIndexedTokenDistribution } = require('@ocap/indexdb/lib/util');
7
- const { eachReceipts } = require('@ocap/state/lib/states/tx');
8
- const { isGasStakeAddress } = require('@ocap/tx-protocols/lib/util');
9
- const omit = require('lodash/omit');
10
-
11
- const ZERO = new BN(0);
12
-
13
- class TokenDistributionManager {
14
- constructor(resolver) {
15
- this.resolver = resolver;
16
- this.indexdb = resolver.indexdb;
17
- this.isProcessing = false;
18
- }
19
-
20
- formatDistribution(distribution) {
21
- const { tokenAddress, account, gas, fee, slashedVault, stake, revokedStake, gasStake, other, txTime } =
22
- distribution;
23
- return {
24
- tokenAddress,
25
- account: new BN(account || 0),
26
- gas: new BN(gas || 0),
27
- fee: new BN(fee || 0),
28
- slashedVault: new BN(slashedVault || 0),
29
- stake: new BN(stake || 0),
30
- revokedStake: new BN(revokedStake || 0),
31
- gasStake: new BN(gasStake || 0),
32
- other: new BN(other || 0),
33
- txTime: txTime || new Date(0).toISOString(),
34
- };
35
- }
36
-
37
- async getDistribution(tokenAddress) {
38
- const data = await this.indexdb.tokenDistribution.get(tokenAddress);
39
- return data && createIndexedTokenDistribution(data);
40
- }
41
-
42
- async saveDistribution(distribution, isEnsureLatest = true) {
43
- const data = createIndexedTokenDistribution(distribution);
44
- const indexdbDistribution = await this.getDistribution(data.tokenAddress);
45
-
46
- if (!indexdbDistribution) {
47
- await this.indexdb.tokenDistribution.insert(data);
48
- } else {
49
- // ensure txTime is latest
50
- if (isEnsureLatest) {
51
- const latestTime = Math.max(new Date(indexdbDistribution.txTime).getTime(), new Date(data.txTime).getTime());
52
- data.txTime = new Date(latestTime).toISOString();
53
- }
54
- await this.indexdb.tokenDistribution.update(data.tokenAddress, data);
55
- }
56
-
57
- return data;
58
- }
59
-
60
- /**
61
- * Calculate token distribution based on all transaction data.
62
- * This method is usually used to update historical token distribution data
63
- *
64
- * @param {string} tokenAddress Token address
65
- * @param {boolean} force If force is false, only calculate distributions for new transactions after txTime in indexdb. default is false
66
- * @returns {Promise<Object>}
67
- */
68
- async updateByToken(tokenAddress, force) {
69
- const { logger, config } = this.resolver;
70
-
71
- if (this.isProcessing) {
72
- logger?.logger('Token distribution is already in progress', { tokenAddress, force });
73
- return;
74
- }
75
-
76
- const distribution = force
77
- ? this.formatDistribution({ tokenAddress })
78
- : this.formatDistribution((await this.getDistribution(tokenAddress)) || { tokenAddress });
79
- const isDefaultToken = tokenAddress === config.token.address;
80
-
81
- logger?.info(`Update distribution by token (${tokenAddress})`, {
82
- distribution: createIndexedTokenDistribution(distribution),
83
- });
84
-
85
- this.isProcessing = true;
86
-
87
- try {
88
- if (force && isDefaultToken) {
89
- this.handleModerator(distribution);
90
- }
91
-
92
- const { next } = await this.resolver.listTransactionsChunks({
93
- timeFilter: { startDateTime: distribution.txTime },
94
- // Default token is used for gas payment and may appear in any transaction, so we cannot filter by tokenFilter
95
- tokenFilter: isDefaultToken ? {} : { tokenFilter: { tokens: [tokenAddress] } },
96
- });
97
- let nextData = await next();
98
-
99
- // Process transactions in chunks and update indexdb
100
- while (nextData.length) {
101
- logger?.info('Updating token distribution in chunks', {
102
- chunkSize: nextData.length,
103
- startTime: nextData[0].time,
104
- startHash: nextData[0].hash,
105
- endTime: nextData[nextData.length - 1].time,
106
- });
107
-
108
- const handlePromises = nextData.map((tx) => this.handleTx(tx, distribution));
109
- await Promise.all(handlePromises);
110
-
111
- // update indexdb
112
- await this.saveDistribution(distribution, false);
113
- nextData = await next();
114
- }
115
-
116
- // We cannot distinguish between revokedStake and stake from tx receipts here,
117
- // so we need to read all stake transactions and recalculate token distribution based on their revokeTokens and tokens
118
- await this.splitStake(distribution);
119
- await this.saveDistribution(distribution, false);
120
-
121
- const result = createIndexedTokenDistribution(distribution);
122
-
123
- logger.info(`Token distribution update completed (${tokenAddress})`, { distribution: result });
124
- return result;
125
- } catch (e) {
126
- logger?.error('Token distribution update failed', { error: e });
127
- return null;
128
- } finally {
129
- this.isProcessing = false;
130
- }
131
- }
132
-
133
- /**
134
- * Split out revokedStake / gasStake from stake
135
- *
136
- * @param {Object} distribution
137
- * @returns {Promise<Object>}
138
- */
139
- async splitStake(distribution) {
140
- const { logger } = this.resolver;
141
- const { tokenAddress } = distribution;
142
-
143
- const { next } = await this.resolver.listStakeChunks();
144
-
145
- let nextData = await next();
146
-
147
- // Process transactions in chunks and update indexdb
148
- while (nextData.length) {
149
- logger?.info('Updating stake distribution in chunks', {
150
- chunkSize: nextData.length,
151
- startTime: nextData[0].renaissanceTime,
152
- startAddress: nextData[0].address,
153
- endTime: nextData[nextData.length - 1].renaissanceTime,
154
- });
155
-
156
- nextData.forEach((stakeState) => {
157
- const isGasStake = this.isGasStake(stakeState);
158
- const token = stakeState.tokens.find((x) => x.address === tokenAddress);
159
- const revokedToken = stakeState.revokedTokens.find((x) => x.address === tokenAddress);
160
- const revokedBalance = new BN(revokedToken?.balance || 0);
161
- const balance = new BN(token?.balance || 0);
162
-
163
- // stake -->> revokedStake
164
- distribution.revokedStake = distribution.revokedStake.add(revokedBalance);
165
- distribution.stake = distribution.stake.sub(revokedBalance);
166
-
167
- // stake -->> gasStake
168
- if (isGasStake) {
169
- distribution.gasStake = distribution.gasStake.add(balance);
170
- distribution.stake = distribution.stake.sub(balance);
171
- }
172
- });
173
-
174
- // continue
175
- nextData = await next();
176
- }
177
-
178
- return distribution;
179
- }
180
-
181
- /**
182
- * Update token distribution by a single transaction.
183
- * This method is usually used when a tx is completed.
184
- *
185
- * @param {Object} tx The transaction object
186
- * @param {Object} context The transaction context
187
- * @returns {Promise<Object>} The updated token distributions
188
- */
189
- async updateByTx(tx, context) {
190
- const { logger } = this.resolver;
191
- const ctx = omit(context, 'txn');
192
-
193
- if (this.isProcessing) {
194
- logger?.logger('Token distribution is already in progress', { tx });
195
- return;
196
- }
197
-
198
- const formattedTx = await this.resolver.formatTx(tx);
199
- const tokens = formattedTx.tokenSymbols.map(({ address }) => address);
200
-
201
- // Parse all tokens in this transaction
202
- const results = await Promise.all(
203
- tokens.map(async (tokenAddress) => {
204
- const distribution = this.formatDistribution((await this.getDistribution(tokenAddress)) || { tokenAddress });
205
- await this.handleTx(tx, distribution, { context: ctx, isHandleStake: true });
206
- const data = await this.saveDistribution(distribution);
207
- return data;
208
- })
209
- );
210
-
211
- return results;
212
- }
213
-
214
- /**
215
- * Parse token distribution for a single transaction
216
- * @param {Object} tx
217
- * @param {String} distribution
218
- * @param {Object} param
219
- * @param {Object} param.context
220
- * @param {Boolean} param.isHandleStake Whether to handle revoked / gas stake tokens
221
- * @returns {Promise<Object>} token distribution
222
- */
223
- async handleTx(tx, distribution, { context, isHandleStake = false } = {}) {
224
- if (isHandleStake && !context) {
225
- throw new Error('FORBIDDEN', 'context is missing, handle revoke stake is not supported without context');
226
- }
227
-
228
- const type = tx.tx?.itxJson?._type;
229
- const receipts = tx.receipts || [];
230
- const { tokenAddress } = distribution;
231
-
232
- // Iterate through each transaction receipt and distribute tokens based on account type
233
- const handlePromises = eachReceipts(receipts, async (address, change) => {
234
- if (!isSameDid(change.target, tokenAddress)) return;
235
- // In migrate tx, tokens are frozen for the old account and minted for the new account
236
- // It behaves like minting new tokens in receipts
237
- // So we should skip here
238
- if (change.action === 'migrate') return;
239
-
240
- const roleType = toTypeInfo(address).role;
241
- const value = new BN(change.value);
242
-
243
- // Stake
244
- if (roleType === RoleType.ROLE_STAKE) {
245
- if (isHandleStake) {
246
- const stakeState = await this.getStakeState(address, context);
247
- const isGasStake = this.isGasStake(stakeState);
248
- // stake revokedTokens -->> account tokens
249
- if (type === 'ClaimStakeTx') {
250
- distribution.revokedStake = distribution.revokedStake.add(value);
251
- return;
252
- }
253
- // stake tokens + stake revokedTokens -->> account tokens
254
- if (type === 'SlashStakeTx') {
255
- const beforeState = context.stateSnapshot[address];
256
- const afterState = context.stakeState;
257
- if (!beforeState || !afterState) {
258
- throw new Error('INVALID_TOKEN_DISTRIBUTION', 'stake state is missing on slash stake tx');
259
- }
260
- const revokeTokenDiff = new BN(beforeState.revokedTokens[tokenAddress]).sub(
261
- new BN(afterState.revokedTokens[tokenAddress])
262
- );
263
- const tokenDiff = new BN(beforeState.tokens[tokenAddress]).sub(new BN(afterState.tokens[tokenAddress]));
264
-
265
- distribution.revokedStake = distribution.revokedStake.sub(revokeTokenDiff);
266
- if (isGasStake) {
267
- distribution.gasStake = distribution.gasStake.sub(tokenDiff);
268
- } else {
269
- distribution.stake = distribution.stake.sub(tokenDiff);
270
- }
271
- return;
272
- }
273
- // gasStake tokens -->> account tokens
274
- if (isGasStake) {
275
- distribution.gasStake = distribution.gasStake.add(value);
276
- return;
277
- }
278
- }
279
- distribution.stake = distribution.stake.add(value);
280
- }
281
- // Gas
282
- else if (change.action === 'gas' && value.gt(ZERO)) {
283
- distribution.gas = distribution.gas.add(value);
284
- }
285
- // Fee
286
- else if (change.action === 'fee' && value.gt(ZERO)) {
287
- distribution.fee = distribution.fee.add(value);
288
- }
289
- // SlashVault
290
- else if (
291
- type === 'SlashStakeTx' &&
292
- value.gt(ZERO) &&
293
- isSameDid(address, this.resolver.config.vaults.slashedStake)
294
- ) {
295
- distribution.slashedVault = distribution.slashedVault.add(value);
296
- }
297
- // Others: Account
298
- else {
299
- distribution.account = distribution.account.add(value);
300
- }
301
- });
302
-
303
- await Promise.all(handlePromises);
304
-
305
- if (isHandleStake && type === 'RevokeStakeTx') {
306
- // RevokedStake has no receipts
307
- // stake tokens -->> stake revokedTokens
308
- const { address } = tx.tx.itxJson;
309
- const stakeState = await this.getStakeState(address, context);
310
- const isGasStake = this.isGasStake(stakeState);
311
-
312
- tx.tx.itxJson.outputs.forEach((x) => {
313
- x.tokens
314
- .filter((change) => isSameDid(change.address, tokenAddress))
315
- .forEach((change) => {
316
- const value = new BN(change.value);
317
- distribution.revokedStake = distribution.revokedStake.add(value);
318
- if (isGasStake) {
319
- distribution.gasStake = distribution.gasStake.sub(value);
320
- } else {
321
- distribution.stake = distribution.stake.sub(value);
322
- }
323
- });
324
- });
325
- }
326
-
327
- distribution.txTime = tx.time;
328
-
329
- return distribution;
330
- }
331
-
332
- handleModerator(distribution) {
333
- const { config } = this.resolver;
334
-
335
- if (distribution.tokenAddress !== config.token.address) {
336
- return distribution;
337
- }
338
-
339
- const value = config.accounts
340
- .filter((account) => account.balance > 0)
341
- .map((account) => new BN(account.balance))
342
- .reduce((cur, balance) => cur.add(balance), ZERO);
343
-
344
- distribution.account = distribution.account.add(fromTokenToUnit(value.toString()));
345
- return distribution;
346
- }
347
-
348
- async getStakeState(address, ctx) {
349
- const stakeState = await this.resolver.statedb.stake.get(address, ctx);
350
- return stakeState;
351
- }
352
-
353
- isGasStake(stakeState) {
354
- return isGasStakeAddress(stakeState.sender, stakeState.address) && stakeState.message === 'stake-for-gas';
355
- }
356
- }
357
-
358
- module.exports = { TokenDistributionManager };