@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
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/migration-chain.d.ts
|
|
2
|
+
interface Migration {
|
|
3
|
+
time: string;
|
|
4
|
+
tx: {
|
|
5
|
+
from: string;
|
|
6
|
+
itxJson: {
|
|
7
|
+
address: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
declare class MigrationNode {
|
|
12
|
+
address: string;
|
|
13
|
+
validFrom: Date;
|
|
14
|
+
validUntil: Date | null;
|
|
15
|
+
nextAddress: string | null;
|
|
16
|
+
constructor(address: string, validFrom: Date, validUntil?: Date | null, nextAddress?: string | null);
|
|
17
|
+
}
|
|
18
|
+
declare class MigrationChainManager {
|
|
19
|
+
chains: Map<string, MigrationNode[]>;
|
|
20
|
+
rootAddresses: Map<string, string>;
|
|
21
|
+
constructor();
|
|
22
|
+
validateMigration(migration: Migration): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Build migration chains from a list of migration transactions
|
|
25
|
+
* @param migrations - List of migration transactions
|
|
26
|
+
*/
|
|
27
|
+
buildChains(migrations: Migration[]): void;
|
|
28
|
+
/**
|
|
29
|
+
* Process a single migration and update the chains
|
|
30
|
+
* @param fromAddr - Source address
|
|
31
|
+
* @param toAddr - Destination address
|
|
32
|
+
* @param timestamp - Migration timestamp
|
|
33
|
+
*/
|
|
34
|
+
private _processMigration;
|
|
35
|
+
/**
|
|
36
|
+
* Find the valid address at a specific timestamp
|
|
37
|
+
* @param address - Address to look up
|
|
38
|
+
* @param timestamp - Timestamp to check
|
|
39
|
+
* @returns Valid address at the timestamp
|
|
40
|
+
*/
|
|
41
|
+
findAddressAtTime(address: string, timestamp: Date): string;
|
|
42
|
+
formatAddress(address: string): string;
|
|
43
|
+
getRootAddress(address: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Get the complete migration history for an address
|
|
46
|
+
* @param address - Address to look up
|
|
47
|
+
* @returns List of migration nodes
|
|
48
|
+
*/
|
|
49
|
+
getMigrationHistory(address: string): MigrationNode[];
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { MigrationChainManager, MigrationNode };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isEthereumDid } from "@arcblock/did";
|
|
2
|
+
|
|
3
|
+
//#region src/migration-chain.ts
|
|
4
|
+
var MigrationNode = class {
|
|
5
|
+
constructor(address, validFrom, validUntil = null, nextAddress = null) {
|
|
6
|
+
this.address = address;
|
|
7
|
+
this.validFrom = validFrom;
|
|
8
|
+
this.validUntil = validUntil;
|
|
9
|
+
this.nextAddress = nextAddress;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var MigrationChainManager = class {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.chains = /* @__PURE__ */ new Map();
|
|
15
|
+
this.rootAddresses = /* @__PURE__ */ new Map();
|
|
16
|
+
}
|
|
17
|
+
validateMigration(migration) {
|
|
18
|
+
if (!migration?.tx?.from || !migration?.tx?.itxJson?.address || !migration?.time) throw new Error("Invalid migration format");
|
|
19
|
+
if (new Date(migration.time).toString() === "Invalid Date") throw new Error("Invalid timestamp");
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Build migration chains from a list of migration transactions
|
|
24
|
+
* @param migrations - List of migration transactions
|
|
25
|
+
*/
|
|
26
|
+
buildChains(migrations) {
|
|
27
|
+
if (!Array.isArray(migrations)) throw new Error("Migrations must be an array");
|
|
28
|
+
migrations.forEach((migration) => this.validateMigration(migration));
|
|
29
|
+
const sortedMigrations = [...migrations].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
30
|
+
for (const migration of sortedMigrations) {
|
|
31
|
+
const fromAddr = migration.tx.from;
|
|
32
|
+
const toAddr = migration.tx.itxJson.address;
|
|
33
|
+
const timestamp = new Date(migration.time);
|
|
34
|
+
this._processMigration(fromAddr, toAddr, timestamp);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Process a single migration and update the chains
|
|
39
|
+
* @param fromAddr - Source address
|
|
40
|
+
* @param toAddr - Destination address
|
|
41
|
+
* @param timestamp - Migration timestamp
|
|
42
|
+
*/
|
|
43
|
+
_processMigration(fromAddr, toAddr, timestamp) {
|
|
44
|
+
const rootAddr = this.getRootAddress(fromAddr);
|
|
45
|
+
if (!this.chains.has(rootAddr)) this.chains.set(rootAddr, []);
|
|
46
|
+
const chain = this.chains.get(rootAddr);
|
|
47
|
+
if (chain.length === 0) {
|
|
48
|
+
chain.push(new MigrationNode(fromAddr, /* @__PURE__ */ new Date(0), timestamp));
|
|
49
|
+
chain.push(new MigrationNode(toAddr, timestamp));
|
|
50
|
+
} else {
|
|
51
|
+
const lastNode = chain[chain.length - 1];
|
|
52
|
+
lastNode.validUntil = timestamp;
|
|
53
|
+
lastNode.nextAddress = toAddr;
|
|
54
|
+
chain.push(new MigrationNode(toAddr, timestamp));
|
|
55
|
+
}
|
|
56
|
+
this.rootAddresses.set(this.formatAddress(toAddr), rootAddr);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find the valid address at a specific timestamp
|
|
60
|
+
* @param address - Address to look up
|
|
61
|
+
* @param timestamp - Timestamp to check
|
|
62
|
+
* @returns Valid address at the timestamp
|
|
63
|
+
*/
|
|
64
|
+
findAddressAtTime(address, timestamp) {
|
|
65
|
+
const chains = this.getMigrationHistory(address);
|
|
66
|
+
if (!chains?.length) return address;
|
|
67
|
+
let left = 0;
|
|
68
|
+
let right = chains.length - 1;
|
|
69
|
+
while (left <= right) {
|
|
70
|
+
const mid = Math.floor((left + right) / 2);
|
|
71
|
+
const node = chains[mid];
|
|
72
|
+
if (node.validFrom <= timestamp && (!node.validUntil || timestamp < node.validUntil)) return node.address;
|
|
73
|
+
if (timestamp < node.validFrom) right = mid - 1;
|
|
74
|
+
else left = mid + 1;
|
|
75
|
+
}
|
|
76
|
+
return chains[chains.length - 1].address;
|
|
77
|
+
}
|
|
78
|
+
formatAddress(address) {
|
|
79
|
+
return isEthereumDid(address) ? address.toLowerCase() : address;
|
|
80
|
+
}
|
|
81
|
+
getRootAddress(address) {
|
|
82
|
+
const formattedAddress = this.formatAddress(address);
|
|
83
|
+
return this.rootAddresses.get(formattedAddress) || formattedAddress;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get the complete migration history for an address
|
|
87
|
+
* @param address - Address to look up
|
|
88
|
+
* @returns List of migration nodes
|
|
89
|
+
*/
|
|
90
|
+
getMigrationHistory(address) {
|
|
91
|
+
const rootAddr = this.getRootAddress(address);
|
|
92
|
+
return this.chains.get(rootAddr) || [];
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { MigrationChainManager, MigrationNode };
|
package/esm/package.mjs
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TIndexedTokenState } from "./types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/token-cache.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Token DB adapter interface for cache
|
|
7
|
+
*/
|
|
8
|
+
interface ITokenDBAdapter {
|
|
9
|
+
get(address: string): Promise<TIndexedTokenState | null>;
|
|
10
|
+
}
|
|
11
|
+
declare class Cache {
|
|
12
|
+
private dbAdapter;
|
|
13
|
+
private cache;
|
|
14
|
+
constructor(dbAdapter: ITokenDBAdapter);
|
|
15
|
+
get(address: string): Promise<TIndexedTokenState | null | undefined>;
|
|
16
|
+
set(address: string, token: TIndexedTokenState): TIndexedTokenState;
|
|
17
|
+
}
|
|
18
|
+
declare function getInstance(dbAdapter: ITokenDBAdapter): Cache;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { getInstance };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region src/token-cache.ts
|
|
2
|
+
var Cache = class {
|
|
3
|
+
constructor(dbAdapter) {
|
|
4
|
+
this.dbAdapter = dbAdapter;
|
|
5
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
6
|
+
}
|
|
7
|
+
async get(address) {
|
|
8
|
+
if (this.cache.has(address)) return this.cache.get(address);
|
|
9
|
+
const token = await this.dbAdapter.get(address);
|
|
10
|
+
if (token) this.cache.set(address, token);
|
|
11
|
+
return token;
|
|
12
|
+
}
|
|
13
|
+
set(address, token) {
|
|
14
|
+
this.cache.set(address, token);
|
|
15
|
+
return token;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
let cache = null;
|
|
19
|
+
function getInstance(dbAdapter) {
|
|
20
|
+
if (cache !== null) return cache;
|
|
21
|
+
cache = new Cache(dbAdapter);
|
|
22
|
+
return cache;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
//#endregion
|
|
26
|
+
export { getInstance };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { IChainConfig, IChunkIterator, IIndexDB, IIndexTable, IPipelineLogger, IResolverTransaction, IStateDB, IStateTable, ITokenInfo, TTokenDistribution } from "./types.mjs";
|
|
2
|
+
import { BN } from "@ocap/util";
|
|
3
|
+
|
|
4
|
+
//#region src/token-distribution.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Token balance in array format (used in stake state for distribution)
|
|
8
|
+
*/
|
|
9
|
+
interface ITokenBalance {
|
|
10
|
+
address: string;
|
|
11
|
+
balance: string;
|
|
12
|
+
value?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Stake state with array-format tokens (for distribution calculation)
|
|
16
|
+
*/
|
|
17
|
+
interface IDistributionStakeState {
|
|
18
|
+
address: string;
|
|
19
|
+
sender: string;
|
|
20
|
+
message: string;
|
|
21
|
+
tokens: ITokenBalance[];
|
|
22
|
+
revokedTokens: ITokenBalance[];
|
|
23
|
+
renaissanceTime?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* State snapshot for tracking token changes
|
|
27
|
+
*/
|
|
28
|
+
interface IStateSnapshot {
|
|
29
|
+
[address: string]: {
|
|
30
|
+
tokens: Record<string, string>;
|
|
31
|
+
revokedTokens: Record<string, string>;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Transaction context for distribution calculation
|
|
36
|
+
*/
|
|
37
|
+
interface IResolverTransactionContext {
|
|
38
|
+
txn?: unknown;
|
|
39
|
+
stateSnapshot?: IStateSnapshot;
|
|
40
|
+
stakeState?: {
|
|
41
|
+
tokens: Record<string, string>;
|
|
42
|
+
revokedTokens: Record<string, string>;
|
|
43
|
+
};
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Distribution with BN values (for calculation)
|
|
48
|
+
*/
|
|
49
|
+
interface IDistribution {
|
|
50
|
+
tokenAddress: string;
|
|
51
|
+
account: InstanceType<typeof BN>;
|
|
52
|
+
gas: InstanceType<typeof BN>;
|
|
53
|
+
fee: InstanceType<typeof BN>;
|
|
54
|
+
slashedVault: InstanceType<typeof BN>;
|
|
55
|
+
stake: InstanceType<typeof BN>;
|
|
56
|
+
revokedStake: InstanceType<typeof BN>;
|
|
57
|
+
gasStake: InstanceType<typeof BN>;
|
|
58
|
+
other: InstanceType<typeof BN>;
|
|
59
|
+
txTime: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Raw distribution input (string/number values)
|
|
63
|
+
*/
|
|
64
|
+
interface IRawDistribution {
|
|
65
|
+
tokenAddress: string;
|
|
66
|
+
account?: string | number;
|
|
67
|
+
gas?: string | number;
|
|
68
|
+
fee?: string | number;
|
|
69
|
+
slashedVault?: string | number;
|
|
70
|
+
stake?: string | number;
|
|
71
|
+
revokedStake?: string | number;
|
|
72
|
+
gasStake?: string | number;
|
|
73
|
+
other?: string | number;
|
|
74
|
+
txTime?: string;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Options for handleTx method
|
|
78
|
+
*/
|
|
79
|
+
interface IHandleTxOptions {
|
|
80
|
+
context?: IResolverTransactionContext;
|
|
81
|
+
isHandleStake?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolver interface for TokenDistributionManager
|
|
85
|
+
*/
|
|
86
|
+
interface IDistributionResolver {
|
|
87
|
+
indexdb: IIndexDB;
|
|
88
|
+
statedb: IStateDB & {
|
|
89
|
+
stake: IStateTable<IDistributionStakeState>;
|
|
90
|
+
};
|
|
91
|
+
config: IChainConfig;
|
|
92
|
+
logger?: IPipelineLogger;
|
|
93
|
+
formatTx: (tx: IResolverTransaction) => Promise<IResolverTransaction & {
|
|
94
|
+
tokenSymbols: ITokenInfo[];
|
|
95
|
+
}>;
|
|
96
|
+
listTransactionsChunks: (params: {
|
|
97
|
+
timeFilter?: {
|
|
98
|
+
startDateTime: string;
|
|
99
|
+
};
|
|
100
|
+
tokenFilter?: {
|
|
101
|
+
tokenFilter?: {
|
|
102
|
+
tokens: string[];
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
}) => Promise<IChunkIterator<IResolverTransaction>>;
|
|
106
|
+
listStakeChunks: () => Promise<IChunkIterator<IDistributionStakeState>>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* TokenDistribution IndexDB table interface
|
|
110
|
+
*/
|
|
111
|
+
type ITokenDistributionTable = IIndexTable<TTokenDistribution> & {
|
|
112
|
+
insert: (data: TTokenDistribution) => Promise<void>;
|
|
113
|
+
update: (tokenAddress: string, data: TTokenDistribution) => Promise<void>;
|
|
114
|
+
};
|
|
115
|
+
declare class TokenDistributionManager {
|
|
116
|
+
resolver: IDistributionResolver;
|
|
117
|
+
indexdb: IIndexDB & {
|
|
118
|
+
tokenDistribution: ITokenDistributionTable;
|
|
119
|
+
};
|
|
120
|
+
isProcessing: boolean;
|
|
121
|
+
constructor(resolver: IDistributionResolver);
|
|
122
|
+
formatDistribution(distribution: IRawDistribution): IDistribution;
|
|
123
|
+
getDistribution(tokenAddress: string): Promise<TTokenDistribution | null>;
|
|
124
|
+
saveDistribution(distribution: IDistribution, isEnsureLatest?: boolean): Promise<TTokenDistribution>;
|
|
125
|
+
/**
|
|
126
|
+
* Calculate token distribution based on all transaction data.
|
|
127
|
+
* This method is usually used to update historical token distribution data
|
|
128
|
+
*
|
|
129
|
+
* @param tokenAddress Token address
|
|
130
|
+
* @param force If force is false, only calculate distributions for new transactions after txTime in indexdb. default is false
|
|
131
|
+
* @returns The updated token distribution or null if failed
|
|
132
|
+
*/
|
|
133
|
+
updateByToken(tokenAddress: string, force?: boolean): Promise<TTokenDistribution | null | undefined>;
|
|
134
|
+
/**
|
|
135
|
+
* Split out revokedStake / gasStake from stake
|
|
136
|
+
*
|
|
137
|
+
* @param distribution The distribution object to update
|
|
138
|
+
* @returns The updated distribution
|
|
139
|
+
*/
|
|
140
|
+
splitStake(distribution: IDistribution): Promise<IDistribution>;
|
|
141
|
+
/**
|
|
142
|
+
* Update token distribution by a single transaction.
|
|
143
|
+
* This method is usually used when a tx is completed.
|
|
144
|
+
*
|
|
145
|
+
* @param tx The transaction object
|
|
146
|
+
* @param context The transaction context
|
|
147
|
+
* @returns The updated token distributions
|
|
148
|
+
*/
|
|
149
|
+
updateByTx(tx: IResolverTransaction, context: IResolverTransactionContext): Promise<TTokenDistribution[] | undefined>;
|
|
150
|
+
/**
|
|
151
|
+
* Parse token distribution for a single transaction
|
|
152
|
+
* @param tx The transaction
|
|
153
|
+
* @param distribution The distribution to update
|
|
154
|
+
* @param options Handle options
|
|
155
|
+
* @returns The updated token distribution
|
|
156
|
+
*/
|
|
157
|
+
handleTx(tx: IResolverTransaction, distribution: IDistribution, {
|
|
158
|
+
context,
|
|
159
|
+
isHandleStake
|
|
160
|
+
}?: IHandleTxOptions): Promise<IDistribution>;
|
|
161
|
+
handleModerator(distribution: IDistribution): IDistribution;
|
|
162
|
+
getStakeState(address: string, ctx: IResolverTransactionContext): Promise<IDistributionStakeState>;
|
|
163
|
+
isGasStake(stakeState: IDistributionStakeState): boolean;
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { TokenDistributionManager };
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { BN, fromTokenToUnit, isSameDid } from "@ocap/util";
|
|
2
|
+
import { toTypeInfo } from "@arcblock/did";
|
|
3
|
+
import { types } from "@ocap/mcrypto";
|
|
4
|
+
import { isGasStakeAddress } from "@ocap/tx-protocols/lib/util";
|
|
5
|
+
import omit from "lodash/omit.js";
|
|
6
|
+
import { createIndexedTokenDistribution } from "@ocap/indexdb/lib/util";
|
|
7
|
+
import { eachReceipts } from "@ocap/state/lib/states/tx";
|
|
8
|
+
|
|
9
|
+
//#region src/token-distribution.ts
|
|
10
|
+
const { RoleType } = types;
|
|
11
|
+
const ZERO = new BN(0);
|
|
12
|
+
var TokenDistributionManager = class {
|
|
13
|
+
constructor(resolver) {
|
|
14
|
+
this.resolver = resolver;
|
|
15
|
+
this.indexdb = resolver.indexdb;
|
|
16
|
+
this.isProcessing = false;
|
|
17
|
+
}
|
|
18
|
+
formatDistribution(distribution) {
|
|
19
|
+
const { tokenAddress, account, gas, fee, slashedVault, stake, revokedStake, gasStake, other, txTime } = distribution;
|
|
20
|
+
return {
|
|
21
|
+
tokenAddress,
|
|
22
|
+
account: new BN(account || 0),
|
|
23
|
+
gas: new BN(gas || 0),
|
|
24
|
+
fee: new BN(fee || 0),
|
|
25
|
+
slashedVault: new BN(slashedVault || 0),
|
|
26
|
+
stake: new BN(stake || 0),
|
|
27
|
+
revokedStake: new BN(revokedStake || 0),
|
|
28
|
+
gasStake: new BN(gasStake || 0),
|
|
29
|
+
other: new BN(other || 0),
|
|
30
|
+
txTime: txTime || (/* @__PURE__ */ new Date(0)).toISOString()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
async getDistribution(tokenAddress) {
|
|
34
|
+
const data = await this.indexdb.tokenDistribution.get(tokenAddress);
|
|
35
|
+
return data && createIndexedTokenDistribution(data);
|
|
36
|
+
}
|
|
37
|
+
async saveDistribution(distribution, isEnsureLatest = true) {
|
|
38
|
+
const data = createIndexedTokenDistribution(distribution);
|
|
39
|
+
const indexdbDistribution = await this.getDistribution(data.tokenAddress);
|
|
40
|
+
if (!indexdbDistribution) await this.indexdb.tokenDistribution.insert(data);
|
|
41
|
+
else {
|
|
42
|
+
if (isEnsureLatest) {
|
|
43
|
+
const latestTime = Math.max(new Date(indexdbDistribution.txTime).getTime(), new Date(data.txTime).getTime());
|
|
44
|
+
data.txTime = new Date(latestTime).toISOString();
|
|
45
|
+
}
|
|
46
|
+
await this.indexdb.tokenDistribution.update(data.tokenAddress, data);
|
|
47
|
+
}
|
|
48
|
+
return data;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Calculate token distribution based on all transaction data.
|
|
52
|
+
* This method is usually used to update historical token distribution data
|
|
53
|
+
*
|
|
54
|
+
* @param tokenAddress Token address
|
|
55
|
+
* @param force If force is false, only calculate distributions for new transactions after txTime in indexdb. default is false
|
|
56
|
+
* @returns The updated token distribution or null if failed
|
|
57
|
+
*/
|
|
58
|
+
async updateByToken(tokenAddress, force) {
|
|
59
|
+
const { logger, config } = this.resolver;
|
|
60
|
+
if (this.isProcessing) {
|
|
61
|
+
logger?.debug?.("Token distribution is already in progress", {
|
|
62
|
+
tokenAddress,
|
|
63
|
+
force
|
|
64
|
+
});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const distribution = force ? this.formatDistribution({ tokenAddress }) : this.formatDistribution(await this.getDistribution(tokenAddress) || { tokenAddress });
|
|
68
|
+
const isDefaultToken = tokenAddress === config.token.address;
|
|
69
|
+
logger?.info(`Update distribution by token (${tokenAddress})`, { distribution: createIndexedTokenDistribution(distribution) });
|
|
70
|
+
this.isProcessing = true;
|
|
71
|
+
try {
|
|
72
|
+
if (force && isDefaultToken) this.handleModerator(distribution);
|
|
73
|
+
const { next } = await this.resolver.listTransactionsChunks({
|
|
74
|
+
timeFilter: { startDateTime: distribution.txTime },
|
|
75
|
+
tokenFilter: isDefaultToken ? {} : { tokenFilter: { tokens: [tokenAddress] } }
|
|
76
|
+
});
|
|
77
|
+
let nextData = await next();
|
|
78
|
+
while (nextData.length) {
|
|
79
|
+
logger?.info("Updating token distribution in chunks", {
|
|
80
|
+
chunkSize: nextData.length,
|
|
81
|
+
startTime: nextData[0].time,
|
|
82
|
+
startHash: nextData[0].hash,
|
|
83
|
+
endTime: nextData[nextData.length - 1].time
|
|
84
|
+
});
|
|
85
|
+
const handlePromises = nextData.map((tx) => this.handleTx(tx, distribution));
|
|
86
|
+
await Promise.all(handlePromises);
|
|
87
|
+
await this.saveDistribution(distribution, false);
|
|
88
|
+
nextData = await next();
|
|
89
|
+
}
|
|
90
|
+
await this.splitStake(distribution);
|
|
91
|
+
await this.saveDistribution(distribution, false);
|
|
92
|
+
const result = createIndexedTokenDistribution(distribution);
|
|
93
|
+
logger?.info(`Token distribution update completed (${tokenAddress})`, { distribution: result });
|
|
94
|
+
return result;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
logger?.error("Token distribution update failed", { error: e });
|
|
97
|
+
return null;
|
|
98
|
+
} finally {
|
|
99
|
+
this.isProcessing = false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Split out revokedStake / gasStake from stake
|
|
104
|
+
*
|
|
105
|
+
* @param distribution The distribution object to update
|
|
106
|
+
* @returns The updated distribution
|
|
107
|
+
*/
|
|
108
|
+
async splitStake(distribution) {
|
|
109
|
+
const { logger } = this.resolver;
|
|
110
|
+
const { tokenAddress } = distribution;
|
|
111
|
+
const { next } = await this.resolver.listStakeChunks();
|
|
112
|
+
let nextData = await next();
|
|
113
|
+
while (nextData.length) {
|
|
114
|
+
logger?.info("Updating stake distribution in chunks", {
|
|
115
|
+
chunkSize: nextData.length,
|
|
116
|
+
startTime: nextData[0].renaissanceTime,
|
|
117
|
+
startAddress: nextData[0].address,
|
|
118
|
+
endTime: nextData[nextData.length - 1].renaissanceTime
|
|
119
|
+
});
|
|
120
|
+
nextData.forEach((stakeState) => {
|
|
121
|
+
const isGasStake = this.isGasStake(stakeState);
|
|
122
|
+
const token = stakeState.tokens.find((x) => x.address === tokenAddress);
|
|
123
|
+
const revokedBalance = new BN(stakeState.revokedTokens.find((x) => x.address === tokenAddress)?.balance || 0);
|
|
124
|
+
const balance = new BN(token?.balance || 0);
|
|
125
|
+
distribution.revokedStake = distribution.revokedStake.add(revokedBalance);
|
|
126
|
+
distribution.stake = distribution.stake.sub(revokedBalance);
|
|
127
|
+
if (isGasStake) {
|
|
128
|
+
distribution.gasStake = distribution.gasStake.add(balance);
|
|
129
|
+
distribution.stake = distribution.stake.sub(balance);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
nextData = await next();
|
|
133
|
+
}
|
|
134
|
+
return distribution;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Update token distribution by a single transaction.
|
|
138
|
+
* This method is usually used when a tx is completed.
|
|
139
|
+
*
|
|
140
|
+
* @param tx The transaction object
|
|
141
|
+
* @param context The transaction context
|
|
142
|
+
* @returns The updated token distributions
|
|
143
|
+
*/
|
|
144
|
+
async updateByTx(tx, context) {
|
|
145
|
+
const { logger } = this.resolver;
|
|
146
|
+
const ctx = omit(context, "txn");
|
|
147
|
+
if (this.isProcessing) {
|
|
148
|
+
logger?.debug?.("Token distribution is already in progress", { tx });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const tokens = (await this.resolver.formatTx(tx)).tokenSymbols.map(({ address }) => address);
|
|
152
|
+
return await Promise.all(tokens.map(async (tokenAddress) => {
|
|
153
|
+
const distribution = this.formatDistribution(await this.getDistribution(tokenAddress) || { tokenAddress });
|
|
154
|
+
await this.handleTx(tx, distribution, {
|
|
155
|
+
context: ctx,
|
|
156
|
+
isHandleStake: true
|
|
157
|
+
});
|
|
158
|
+
return await this.saveDistribution(distribution);
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Parse token distribution for a single transaction
|
|
163
|
+
* @param tx The transaction
|
|
164
|
+
* @param distribution The distribution to update
|
|
165
|
+
* @param options Handle options
|
|
166
|
+
* @returns The updated token distribution
|
|
167
|
+
*/
|
|
168
|
+
async handleTx(tx, distribution, { context, isHandleStake = false } = {}) {
|
|
169
|
+
if (isHandleStake && !context) throw new Error("context is missing, handle revoke stake is not supported without context");
|
|
170
|
+
const type = tx.tx?.itxJson?._type;
|
|
171
|
+
const receipts = tx.receipts || [];
|
|
172
|
+
const { tokenAddress } = distribution;
|
|
173
|
+
const handlePromises = eachReceipts(receipts, async (address, change) => {
|
|
174
|
+
if (!isSameDid(change.target, tokenAddress)) return;
|
|
175
|
+
if (change.action === "migrate") return;
|
|
176
|
+
const roleType = toTypeInfo(address).role;
|
|
177
|
+
const value = new BN(change.value);
|
|
178
|
+
if (roleType === RoleType.ROLE_STAKE) {
|
|
179
|
+
if (isHandleStake) {
|
|
180
|
+
const stakeState = await this.getStakeState(address, context);
|
|
181
|
+
const isGasStakeResult = this.isGasStake(stakeState);
|
|
182
|
+
if (type === "ClaimStakeTx") {
|
|
183
|
+
distribution.revokedStake = distribution.revokedStake.add(value);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (type === "SlashStakeTx") {
|
|
187
|
+
const beforeState = context.stateSnapshot[address];
|
|
188
|
+
const afterState = context.stakeState;
|
|
189
|
+
if (!beforeState || !afterState) throw new Error("stake state is missing on slash stake tx");
|
|
190
|
+
const revokeTokenDiff = new BN(beforeState.revokedTokens[tokenAddress]).sub(new BN(afterState.revokedTokens[tokenAddress]));
|
|
191
|
+
const tokenDiff = new BN(beforeState.tokens[tokenAddress]).sub(new BN(afterState.tokens[tokenAddress]));
|
|
192
|
+
distribution.revokedStake = distribution.revokedStake.sub(revokeTokenDiff);
|
|
193
|
+
if (isGasStakeResult) distribution.gasStake = distribution.gasStake.sub(tokenDiff);
|
|
194
|
+
else distribution.stake = distribution.stake.sub(tokenDiff);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (isGasStakeResult) {
|
|
198
|
+
distribution.gasStake = distribution.gasStake.add(value);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
distribution.stake = distribution.stake.add(value);
|
|
203
|
+
} else if (change.action === "gas" && value.gt(ZERO)) distribution.gas = distribution.gas.add(value);
|
|
204
|
+
else if (change.action === "fee" && value.gt(ZERO)) distribution.fee = distribution.fee.add(value);
|
|
205
|
+
else if (type === "SlashStakeTx" && value.gt(ZERO) && isSameDid(address, this.resolver.config.vaults.slashedStake ?? "")) distribution.slashedVault = distribution.slashedVault.add(value);
|
|
206
|
+
else distribution.account = distribution.account.add(value);
|
|
207
|
+
});
|
|
208
|
+
await Promise.all(handlePromises);
|
|
209
|
+
if (isHandleStake && type === "RevokeStakeTx") {
|
|
210
|
+
const { address } = tx.tx.itxJson;
|
|
211
|
+
const stakeState = await this.getStakeState(address, context);
|
|
212
|
+
const isGasStakeResult = this.isGasStake(stakeState);
|
|
213
|
+
tx.tx.itxJson.outputs.forEach((x) => {
|
|
214
|
+
x.tokens.filter((change) => isSameDid(change.address, tokenAddress)).forEach((change) => {
|
|
215
|
+
const value = new BN(change.value || change.balance || 0);
|
|
216
|
+
distribution.revokedStake = distribution.revokedStake.add(value);
|
|
217
|
+
if (isGasStakeResult) distribution.gasStake = distribution.gasStake.sub(value);
|
|
218
|
+
else distribution.stake = distribution.stake.sub(value);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
distribution.txTime = tx.time;
|
|
223
|
+
return distribution;
|
|
224
|
+
}
|
|
225
|
+
handleModerator(distribution) {
|
|
226
|
+
const { config } = this.resolver;
|
|
227
|
+
if (distribution.tokenAddress !== config.token.address) return distribution;
|
|
228
|
+
const value = config.accounts.filter((account) => Number(account.balance) > 0).map((account) => new BN(account.balance)).reduce((cur, balance) => cur.add(balance), ZERO);
|
|
229
|
+
distribution.account = distribution.account.add(fromTokenToUnit(value.toString()));
|
|
230
|
+
return distribution;
|
|
231
|
+
}
|
|
232
|
+
async getStakeState(address, ctx) {
|
|
233
|
+
return await this.resolver.statedb.stake.get(address, ctx);
|
|
234
|
+
}
|
|
235
|
+
isGasStake(stakeState) {
|
|
236
|
+
return isGasStakeAddress(stakeState.sender, stakeState.address) && stakeState.message === "stake-for-gas";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
//#endregion
|
|
241
|
+
export { TokenDistributionManager };
|