@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/lib/index.d.cts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { IAddressArgs, IAssetState, IChainConfig, IChunkIterator, IDataObject, IDelegateState, IEvidenceState, IFilter, IGetAccountStateArgs, IGetAccountTokensArgs, IGetStateParams, IIndexDB, IListChunksParams, IPaginatedResult, IPipelineLogger, IResolver, IResolverAccountState, IResolverChainInfo, IResolverContext, IResolverFactoryState, IResolverForgeStats, IResolverListAccountsResult, IResolverListAssetsResult, IResolverListDelegationsResult, IResolverListFactoriesResult, IResolverListRollupBlocksResult, IResolverListRollupValidatorsResult, IResolverListRollupsResult, IResolverListStakesResult, IResolverListTokenFactoriesResult, IResolverListTokensResult, IResolverListTransactionsResult, IResolverNetInfo, IResolverNodeInfo, IResolverPaging, IResolverParams, IResolverRollupState, IResolverStakeState, IResolverTransaction, IResolverValidatorsInfo, IRollupBlock, IStateDB, ITokenCache, ITokenFactoryState, ITokenFlowsResult, ITokenInfo, ITokenState, TIndexedTokenState, TRequestEstimateGas, TRequestGetEvidenceState, TRequestGetRollupBlock, TRequestGetTokenDistribution, TRequestGetTx, TRequestListAssetTransactions, TRequestListAssets, TRequestListDelegations, TRequestListFactories, TRequestListRollupBlocks, TRequestListRollupValidators, TRequestListRollups, TRequestListStakes, TRequestListTokenFactories, TRequestListTokenFlows, TRequestListTokens, TRequestListTopAccounts, TRequestListTransactions, TRequestSearch, TRequestVerifyAccountRisk, TTokenDistribution, TVerifyAccountRiskResult } from "./types.cjs";
|
|
2
|
+
import { MigrationChainManager } from "./migration-chain.cjs";
|
|
3
|
+
import { TokenDistributionManager } from "./token-distribution.cjs";
|
|
4
|
+
import { createExecutor } from "@ocap/tx-protocols";
|
|
5
|
+
import Queue from "queue";
|
|
6
|
+
|
|
7
|
+
//#region src/index.d.ts
|
|
8
|
+
declare const formatData: (data: IDataObject | null | undefined) => IDataObject | null | undefined;
|
|
9
|
+
declare const formatDelegationOps: (state: IDelegateState) => any;
|
|
10
|
+
declare class OCAPResolver implements IResolver {
|
|
11
|
+
statedb: IStateDB;
|
|
12
|
+
indexdb: IIndexDB;
|
|
13
|
+
filter?: IFilter;
|
|
14
|
+
config: IChainConfig;
|
|
15
|
+
chainAddr: string;
|
|
16
|
+
tokenItx: TIndexedTokenState | null;
|
|
17
|
+
consensus: string;
|
|
18
|
+
queue: Queue;
|
|
19
|
+
tokenCache: ITokenCache;
|
|
20
|
+
tokenDistribution: TokenDistributionManager;
|
|
21
|
+
executor: ReturnType<typeof createExecutor>;
|
|
22
|
+
logger: IPipelineLogger;
|
|
23
|
+
migrationChain?: MigrationChainManager;
|
|
24
|
+
formatTx: (tx: IResolverTransaction | null, ctx?: IResolverContext) => Promise<IResolverTransaction | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Creates an instance of OCAPResolver.
|
|
27
|
+
* @param params Parameters to bootstrap the resolver
|
|
28
|
+
* @param params.statedb @ocap/statedb adapter
|
|
29
|
+
* @param params.indexdb @ocap/indexdb adapter
|
|
30
|
+
* @param params.config @ocap/config object
|
|
31
|
+
* @param params.filter bloom filter to do anti-replay check
|
|
32
|
+
* @param params.validateTokenConfig should we validate token supply and token-holder balance, should be disabled when starting an existing chain
|
|
33
|
+
*/
|
|
34
|
+
constructor({
|
|
35
|
+
statedb,
|
|
36
|
+
indexdb,
|
|
37
|
+
config,
|
|
38
|
+
filter,
|
|
39
|
+
validateTokenConfig,
|
|
40
|
+
logger
|
|
41
|
+
}: IResolverParams);
|
|
42
|
+
sendTx({
|
|
43
|
+
tx: txBase64,
|
|
44
|
+
extra
|
|
45
|
+
}: {
|
|
46
|
+
tx: string;
|
|
47
|
+
extra?: Record<string, unknown>;
|
|
48
|
+
}, ctx?: IResolverContext): Promise<string>;
|
|
49
|
+
getTx({
|
|
50
|
+
hash
|
|
51
|
+
}: TRequestGetTx, ctx?: IResolverContext): Promise<IResolverTransaction | null>;
|
|
52
|
+
getBlock(): null;
|
|
53
|
+
getBlocks(): never[];
|
|
54
|
+
getUnconfirmedTxs(): never[];
|
|
55
|
+
getChainInfo(): Promise<IResolverChainInfo>;
|
|
56
|
+
getNodeInfo(): Promise<IResolverNodeInfo>;
|
|
57
|
+
getNetInfo(): Promise<IResolverNetInfo>;
|
|
58
|
+
getValidatorsInfo(): Promise<IResolverValidatorsInfo>;
|
|
59
|
+
getConfig(): string;
|
|
60
|
+
getAccountState({
|
|
61
|
+
address,
|
|
62
|
+
traceMigration,
|
|
63
|
+
expandContext
|
|
64
|
+
}: IGetAccountStateArgs & {
|
|
65
|
+
traceMigration?: boolean;
|
|
66
|
+
}, ctx?: IResolverContext): Promise<IResolverAccountState | null>;
|
|
67
|
+
getStakeState({
|
|
68
|
+
address
|
|
69
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<IResolverStakeState | null>;
|
|
70
|
+
getRollupState({
|
|
71
|
+
address
|
|
72
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<IResolverRollupState | null>;
|
|
73
|
+
getRollupBlock({
|
|
74
|
+
hash,
|
|
75
|
+
height,
|
|
76
|
+
rollupAddress
|
|
77
|
+
}: Partial<TRequestGetRollupBlock>, ctx?: IResolverContext): Promise<IRollupBlock | null>;
|
|
78
|
+
getAssetState({
|
|
79
|
+
address
|
|
80
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<IAssetState | null>;
|
|
81
|
+
getEvidenceState({
|
|
82
|
+
hash
|
|
83
|
+
}: TRequestGetEvidenceState, ctx?: IResolverContext): Promise<IEvidenceState | null>;
|
|
84
|
+
getFactoryState({
|
|
85
|
+
address
|
|
86
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<IResolverFactoryState | null>;
|
|
87
|
+
getTokenState({
|
|
88
|
+
address
|
|
89
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<ITokenState | null>;
|
|
90
|
+
getTokenFactoryState(args: IAddressArgs, ctx?: IResolverContext): Promise<ITokenFactoryState | null>;
|
|
91
|
+
getDelegateState({
|
|
92
|
+
address
|
|
93
|
+
}: IAddressArgs, ctx?: IResolverContext): Promise<IDelegateState | null>;
|
|
94
|
+
getAccountTokens({
|
|
95
|
+
address,
|
|
96
|
+
token
|
|
97
|
+
}: IGetAccountTokensArgs, ctx?: IResolverContext): Promise<ITokenInfo[]>;
|
|
98
|
+
getForgeState(): Promise<Record<string, unknown>>;
|
|
99
|
+
getForgeStats(): Promise<IResolverForgeStats>;
|
|
100
|
+
listTransactions(args?: Partial<TRequestListTransactions>, ctx?: IResolverContext): Promise<IResolverListTransactionsResult>;
|
|
101
|
+
listAssets(args?: Partial<TRequestListAssets>, ctx?: IResolverContext): Promise<IResolverListAssetsResult>;
|
|
102
|
+
listAssetTransactions(args?: Partial<TRequestListAssetTransactions>, ctx?: IResolverContext): Promise<IResolverListTransactionsResult>;
|
|
103
|
+
listFactories(args?: Partial<TRequestListFactories>, ctx?: IResolverContext): Promise<IResolverListFactoriesResult>;
|
|
104
|
+
listTopAccounts(args?: Partial<TRequestListTopAccounts>): Promise<IResolverListAccountsResult>;
|
|
105
|
+
listTokens(args?: Partial<TRequestListTokens>, ctx?: IResolverContext): Promise<IResolverListTokensResult>;
|
|
106
|
+
listTokenFactories(args?: Partial<TRequestListTokenFactories>, ctx?: IResolverContext): Promise<IResolverListTokenFactoriesResult>;
|
|
107
|
+
listStakes(args?: Partial<TRequestListStakes>, ctx?: IResolverContext): Promise<IResolverListStakesResult>;
|
|
108
|
+
listRollups(args?: Partial<TRequestListRollups>, ctx?: IResolverContext): Promise<IResolverListRollupsResult>;
|
|
109
|
+
listRollupBlocks(args?: Partial<TRequestListRollupBlocks>, ctx?: IResolverContext): Promise<IResolverListRollupBlocksResult>;
|
|
110
|
+
listRollupValidators(args?: Partial<TRequestListRollupValidators>, ctx?: IResolverContext): Promise<IResolverListRollupValidatorsResult>;
|
|
111
|
+
listDelegations(args?: Partial<TRequestListDelegations>, ctx?: IResolverContext): Promise<IResolverListDelegationsResult>;
|
|
112
|
+
verifyAccountRisk(args?: Partial<TRequestVerifyAccountRisk>, ctx?: IResolverContext): Promise<TVerifyAccountRiskResult | null> | null;
|
|
113
|
+
listTokenFlows(args?: Partial<TRequestListTokenFlows>, ctx?: IResolverContext): Promise<ITokenFlowsResult> | never[];
|
|
114
|
+
search(args: Partial<TRequestSearch>): Promise<{
|
|
115
|
+
results: Array<{
|
|
116
|
+
type: string;
|
|
117
|
+
id: string;
|
|
118
|
+
}>;
|
|
119
|
+
}>;
|
|
120
|
+
estimateGas(args?: Partial<TRequestEstimateGas>): {
|
|
121
|
+
estimate: {
|
|
122
|
+
max: string;
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
listBlocks(): {
|
|
126
|
+
blocks: never[];
|
|
127
|
+
};
|
|
128
|
+
validateTokenConfig(): void;
|
|
129
|
+
runAsLambda<T>(fn: (txn?: unknown) => T | Promise<T>, args?: Record<string, unknown>): Promise<T>;
|
|
130
|
+
getChain(): Promise<Record<string, unknown> | null>;
|
|
131
|
+
getToken(id: string): Promise<ITokenState | null>;
|
|
132
|
+
initializeStateDB(): Promise<void>;
|
|
133
|
+
connectIndexDB(): void;
|
|
134
|
+
_getState<T = Record<string, unknown>>({
|
|
135
|
+
table,
|
|
136
|
+
id,
|
|
137
|
+
dataKey,
|
|
138
|
+
extraParams,
|
|
139
|
+
onRead,
|
|
140
|
+
expandContext,
|
|
141
|
+
ctx
|
|
142
|
+
}: IGetStateParams): Promise<T | null>;
|
|
143
|
+
_doPaginatedSearch<TArgs extends Record<string, unknown>>(fn: string, args: TArgs, listKey: string, dataKey?: string | string[] | null, ctx?: IResolverContext): Promise<Record<string, unknown>>;
|
|
144
|
+
_formatTx(tx: IResolverTransaction | null, ctx?: IResolverContext): Promise<IResolverTransaction | null>;
|
|
145
|
+
_getAllResults<T>(dataKey: string, fn: (paging: IResolverPaging) => Promise<any>, limit?: number): Promise<T[]>;
|
|
146
|
+
formatTokenArray(tokens: ITokenInfo[]): Promise<ITokenInfo[]>;
|
|
147
|
+
formatTokenMap(tokens: Record<string, string>): Promise<ITokenInfo[]>;
|
|
148
|
+
getTxTokenSymbols(tx: IResolverTransaction): Promise<ITokenInfo[]>;
|
|
149
|
+
fixReceiptTokens(tx: IResolverTransaction): IResolverTransaction;
|
|
150
|
+
fixTokenSymbols(tx: IResolverTransaction): IResolverTransaction;
|
|
151
|
+
enrichIndexContext(ctx: Record<string, unknown>): Record<string, unknown>;
|
|
152
|
+
formatDelegateState(state: IDelegateState): Promise<IDelegateState>;
|
|
153
|
+
buildMigrationChain(): Promise<void>;
|
|
154
|
+
getMigrationChain(): Promise<MigrationChainManager>;
|
|
155
|
+
/** fix accounts field of legacy approve_withdraw transactions in indexdb */
|
|
156
|
+
fixApproveWithdrawAccounts(): Promise<IResolverTransaction[]>;
|
|
157
|
+
updateTokenDistribution({
|
|
158
|
+
tokenAddress,
|
|
159
|
+
force
|
|
160
|
+
}?: {
|
|
161
|
+
tokenAddress?: string | undefined;
|
|
162
|
+
force?: boolean | undefined;
|
|
163
|
+
}): Promise<unknown>;
|
|
164
|
+
getTokenDistribution(args?: Partial<TRequestGetTokenDistribution>): Promise<TTokenDistribution | null>;
|
|
165
|
+
/**
|
|
166
|
+
* Fetch data in chunks to handle large datasets
|
|
167
|
+
* @param fn Function to fetch data
|
|
168
|
+
* @param params Chunk parameters
|
|
169
|
+
* @returns Iterator with next function
|
|
170
|
+
*/
|
|
171
|
+
listChunks<T extends Record<string, unknown>>(fn: (params: {
|
|
172
|
+
size: number;
|
|
173
|
+
cursor: number;
|
|
174
|
+
time?: string;
|
|
175
|
+
}) => Promise<{
|
|
176
|
+
[key: string]: T[];
|
|
177
|
+
}>, {
|
|
178
|
+
total,
|
|
179
|
+
concurrency,
|
|
180
|
+
chunkSize,
|
|
181
|
+
pageSize,
|
|
182
|
+
timeKey,
|
|
183
|
+
dataKey
|
|
184
|
+
}: IListChunksParams): IChunkIterator<T>;
|
|
185
|
+
listTransactionsChunks(args?: Partial<TRequestListTransactions>, {
|
|
186
|
+
chunkSize,
|
|
187
|
+
pageSize
|
|
188
|
+
}?: {
|
|
189
|
+
chunkSize?: number | undefined;
|
|
190
|
+
pageSize?: number | undefined;
|
|
191
|
+
}): Promise<IChunkIterator<IResolverTransaction>>;
|
|
192
|
+
listStakeChunks(args?: Partial<TRequestListStakes>, {
|
|
193
|
+
chunkSize,
|
|
194
|
+
pageSize
|
|
195
|
+
}?: {
|
|
196
|
+
chunkSize?: number | undefined;
|
|
197
|
+
pageSize?: number | undefined;
|
|
198
|
+
}): Promise<IChunkIterator<IResolverStakeState>>;
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
export { type IChainConfig, type IChunkIterator, type IDataObject, type IPaginatedResult, type IResolverPaging, type IResolverParams, type IResolverTransaction, type ITokenInfo, OCAPResolver as default, formatData, formatDelegationOps };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let _arcblock_did = require("@arcblock/did");
|
|
3
|
+
|
|
4
|
+
//#region src/migration-chain.ts
|
|
5
|
+
var MigrationNode = class {
|
|
6
|
+
constructor(address, validFrom, validUntil = null, nextAddress = null) {
|
|
7
|
+
this.address = address;
|
|
8
|
+
this.validFrom = validFrom;
|
|
9
|
+
this.validUntil = validUntil;
|
|
10
|
+
this.nextAddress = nextAddress;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var MigrationChainManager = class {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.chains = /* @__PURE__ */ new Map();
|
|
16
|
+
this.rootAddresses = /* @__PURE__ */ new Map();
|
|
17
|
+
}
|
|
18
|
+
validateMigration(migration) {
|
|
19
|
+
if (!migration?.tx?.from || !migration?.tx?.itxJson?.address || !migration?.time) throw new Error("Invalid migration format");
|
|
20
|
+
if (new Date(migration.time).toString() === "Invalid Date") throw new Error("Invalid timestamp");
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build migration chains from a list of migration transactions
|
|
25
|
+
* @param migrations - List of migration transactions
|
|
26
|
+
*/
|
|
27
|
+
buildChains(migrations) {
|
|
28
|
+
if (!Array.isArray(migrations)) throw new Error("Migrations must be an array");
|
|
29
|
+
migrations.forEach((migration) => this.validateMigration(migration));
|
|
30
|
+
const sortedMigrations = [...migrations].sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime());
|
|
31
|
+
for (const migration of sortedMigrations) {
|
|
32
|
+
const fromAddr = migration.tx.from;
|
|
33
|
+
const toAddr = migration.tx.itxJson.address;
|
|
34
|
+
const timestamp = new Date(migration.time);
|
|
35
|
+
this._processMigration(fromAddr, toAddr, timestamp);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Process a single migration and update the chains
|
|
40
|
+
* @param fromAddr - Source address
|
|
41
|
+
* @param toAddr - Destination address
|
|
42
|
+
* @param timestamp - Migration timestamp
|
|
43
|
+
*/
|
|
44
|
+
_processMigration(fromAddr, toAddr, timestamp) {
|
|
45
|
+
const rootAddr = this.getRootAddress(fromAddr);
|
|
46
|
+
if (!this.chains.has(rootAddr)) this.chains.set(rootAddr, []);
|
|
47
|
+
const chain = this.chains.get(rootAddr);
|
|
48
|
+
if (chain.length === 0) {
|
|
49
|
+
chain.push(new MigrationNode(fromAddr, /* @__PURE__ */ new Date(0), timestamp));
|
|
50
|
+
chain.push(new MigrationNode(toAddr, timestamp));
|
|
51
|
+
} else {
|
|
52
|
+
const lastNode = chain[chain.length - 1];
|
|
53
|
+
lastNode.validUntil = timestamp;
|
|
54
|
+
lastNode.nextAddress = toAddr;
|
|
55
|
+
chain.push(new MigrationNode(toAddr, timestamp));
|
|
56
|
+
}
|
|
57
|
+
this.rootAddresses.set(this.formatAddress(toAddr), rootAddr);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Find the valid address at a specific timestamp
|
|
61
|
+
* @param address - Address to look up
|
|
62
|
+
* @param timestamp - Timestamp to check
|
|
63
|
+
* @returns Valid address at the timestamp
|
|
64
|
+
*/
|
|
65
|
+
findAddressAtTime(address, timestamp) {
|
|
66
|
+
const chains = this.getMigrationHistory(address);
|
|
67
|
+
if (!chains?.length) return address;
|
|
68
|
+
let left = 0;
|
|
69
|
+
let right = chains.length - 1;
|
|
70
|
+
while (left <= right) {
|
|
71
|
+
const mid = Math.floor((left + right) / 2);
|
|
72
|
+
const node = chains[mid];
|
|
73
|
+
if (node.validFrom <= timestamp && (!node.validUntil || timestamp < node.validUntil)) return node.address;
|
|
74
|
+
if (timestamp < node.validFrom) right = mid - 1;
|
|
75
|
+
else left = mid + 1;
|
|
76
|
+
}
|
|
77
|
+
return chains[chains.length - 1].address;
|
|
78
|
+
}
|
|
79
|
+
formatAddress(address) {
|
|
80
|
+
return (0, _arcblock_did.isEthereumDid)(address) ? address.toLowerCase() : address;
|
|
81
|
+
}
|
|
82
|
+
getRootAddress(address) {
|
|
83
|
+
const formattedAddress = this.formatAddress(address);
|
|
84
|
+
return this.rootAddresses.get(formattedAddress) || formattedAddress;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the complete migration history for an address
|
|
88
|
+
* @param address - Address to look up
|
|
89
|
+
* @returns List of migration nodes
|
|
90
|
+
*/
|
|
91
|
+
getMigrationHistory(address) {
|
|
92
|
+
const rootAddr = this.getRootAddress(address);
|
|
93
|
+
return this.chains.get(rootAddr) || [];
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
exports.MigrationChainManager = MigrationChainManager;
|
|
99
|
+
exports.MigrationNode = MigrationNode;
|
|
@@ -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 };
|
package/lib/package.cjs
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
|
|
2
|
+
//#region src/token-cache.ts
|
|
3
|
+
var Cache = class {
|
|
4
|
+
constructor(dbAdapter) {
|
|
5
|
+
this.dbAdapter = dbAdapter;
|
|
6
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
7
|
+
}
|
|
8
|
+
async get(address) {
|
|
9
|
+
if (this.cache.has(address)) return this.cache.get(address);
|
|
10
|
+
const token = await this.dbAdapter.get(address);
|
|
11
|
+
if (token) this.cache.set(address, token);
|
|
12
|
+
return token;
|
|
13
|
+
}
|
|
14
|
+
set(address, token) {
|
|
15
|
+
this.cache.set(address, token);
|
|
16
|
+
return token;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
let cache = null;
|
|
20
|
+
function getInstance(dbAdapter) {
|
|
21
|
+
if (cache !== null) return cache;
|
|
22
|
+
cache = new Cache(dbAdapter);
|
|
23
|
+
return cache;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
exports.getInstance = getInstance;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { TIndexedTokenState } from "./types.cjs";
|
|
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,243 @@
|
|
|
1
|
+
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
|
|
2
|
+
let _ocap_util = require("@ocap/util");
|
|
3
|
+
let _arcblock_did = require("@arcblock/did");
|
|
4
|
+
let _ocap_mcrypto = require("@ocap/mcrypto");
|
|
5
|
+
let _ocap_tx_protocols_lib_util = require("@ocap/tx-protocols/lib/util");
|
|
6
|
+
let lodash_omit = require("lodash/omit");
|
|
7
|
+
lodash_omit = require_rolldown_runtime.__toESM(lodash_omit);
|
|
8
|
+
let _ocap_indexdb_lib_util = require("@ocap/indexdb/lib/util");
|
|
9
|
+
let _ocap_state_lib_states_tx = require("@ocap/state/lib/states/tx");
|
|
10
|
+
|
|
11
|
+
//#region src/token-distribution.ts
|
|
12
|
+
const { RoleType } = _ocap_mcrypto.types;
|
|
13
|
+
const ZERO = new _ocap_util.BN(0);
|
|
14
|
+
var TokenDistributionManager = class {
|
|
15
|
+
constructor(resolver) {
|
|
16
|
+
this.resolver = resolver;
|
|
17
|
+
this.indexdb = resolver.indexdb;
|
|
18
|
+
this.isProcessing = false;
|
|
19
|
+
}
|
|
20
|
+
formatDistribution(distribution) {
|
|
21
|
+
const { tokenAddress, account, gas, fee, slashedVault, stake, revokedStake, gasStake, other, txTime } = distribution;
|
|
22
|
+
return {
|
|
23
|
+
tokenAddress,
|
|
24
|
+
account: new _ocap_util.BN(account || 0),
|
|
25
|
+
gas: new _ocap_util.BN(gas || 0),
|
|
26
|
+
fee: new _ocap_util.BN(fee || 0),
|
|
27
|
+
slashedVault: new _ocap_util.BN(slashedVault || 0),
|
|
28
|
+
stake: new _ocap_util.BN(stake || 0),
|
|
29
|
+
revokedStake: new _ocap_util.BN(revokedStake || 0),
|
|
30
|
+
gasStake: new _ocap_util.BN(gasStake || 0),
|
|
31
|
+
other: new _ocap_util.BN(other || 0),
|
|
32
|
+
txTime: txTime || (/* @__PURE__ */ new Date(0)).toISOString()
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async getDistribution(tokenAddress) {
|
|
36
|
+
const data = await this.indexdb.tokenDistribution.get(tokenAddress);
|
|
37
|
+
return data && (0, _ocap_indexdb_lib_util.createIndexedTokenDistribution)(data);
|
|
38
|
+
}
|
|
39
|
+
async saveDistribution(distribution, isEnsureLatest = true) {
|
|
40
|
+
const data = (0, _ocap_indexdb_lib_util.createIndexedTokenDistribution)(distribution);
|
|
41
|
+
const indexdbDistribution = await this.getDistribution(data.tokenAddress);
|
|
42
|
+
if (!indexdbDistribution) await this.indexdb.tokenDistribution.insert(data);
|
|
43
|
+
else {
|
|
44
|
+
if (isEnsureLatest) {
|
|
45
|
+
const latestTime = Math.max(new Date(indexdbDistribution.txTime).getTime(), new Date(data.txTime).getTime());
|
|
46
|
+
data.txTime = new Date(latestTime).toISOString();
|
|
47
|
+
}
|
|
48
|
+
await this.indexdb.tokenDistribution.update(data.tokenAddress, data);
|
|
49
|
+
}
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Calculate token distribution based on all transaction data.
|
|
54
|
+
* This method is usually used to update historical token distribution data
|
|
55
|
+
*
|
|
56
|
+
* @param tokenAddress Token address
|
|
57
|
+
* @param force If force is false, only calculate distributions for new transactions after txTime in indexdb. default is false
|
|
58
|
+
* @returns The updated token distribution or null if failed
|
|
59
|
+
*/
|
|
60
|
+
async updateByToken(tokenAddress, force) {
|
|
61
|
+
const { logger, config } = this.resolver;
|
|
62
|
+
if (this.isProcessing) {
|
|
63
|
+
logger?.debug?.("Token distribution is already in progress", {
|
|
64
|
+
tokenAddress,
|
|
65
|
+
force
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const distribution = force ? this.formatDistribution({ tokenAddress }) : this.formatDistribution(await this.getDistribution(tokenAddress) || { tokenAddress });
|
|
70
|
+
const isDefaultToken = tokenAddress === config.token.address;
|
|
71
|
+
logger?.info(`Update distribution by token (${tokenAddress})`, { distribution: (0, _ocap_indexdb_lib_util.createIndexedTokenDistribution)(distribution) });
|
|
72
|
+
this.isProcessing = true;
|
|
73
|
+
try {
|
|
74
|
+
if (force && isDefaultToken) this.handleModerator(distribution);
|
|
75
|
+
const { next } = await this.resolver.listTransactionsChunks({
|
|
76
|
+
timeFilter: { startDateTime: distribution.txTime },
|
|
77
|
+
tokenFilter: isDefaultToken ? {} : { tokenFilter: { tokens: [tokenAddress] } }
|
|
78
|
+
});
|
|
79
|
+
let nextData = await next();
|
|
80
|
+
while (nextData.length) {
|
|
81
|
+
logger?.info("Updating token distribution in chunks", {
|
|
82
|
+
chunkSize: nextData.length,
|
|
83
|
+
startTime: nextData[0].time,
|
|
84
|
+
startHash: nextData[0].hash,
|
|
85
|
+
endTime: nextData[nextData.length - 1].time
|
|
86
|
+
});
|
|
87
|
+
const handlePromises = nextData.map((tx) => this.handleTx(tx, distribution));
|
|
88
|
+
await Promise.all(handlePromises);
|
|
89
|
+
await this.saveDistribution(distribution, false);
|
|
90
|
+
nextData = await next();
|
|
91
|
+
}
|
|
92
|
+
await this.splitStake(distribution);
|
|
93
|
+
await this.saveDistribution(distribution, false);
|
|
94
|
+
const result = (0, _ocap_indexdb_lib_util.createIndexedTokenDistribution)(distribution);
|
|
95
|
+
logger?.info(`Token distribution update completed (${tokenAddress})`, { distribution: result });
|
|
96
|
+
return result;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
logger?.error("Token distribution update failed", { error: e });
|
|
99
|
+
return null;
|
|
100
|
+
} finally {
|
|
101
|
+
this.isProcessing = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Split out revokedStake / gasStake from stake
|
|
106
|
+
*
|
|
107
|
+
* @param distribution The distribution object to update
|
|
108
|
+
* @returns The updated distribution
|
|
109
|
+
*/
|
|
110
|
+
async splitStake(distribution) {
|
|
111
|
+
const { logger } = this.resolver;
|
|
112
|
+
const { tokenAddress } = distribution;
|
|
113
|
+
const { next } = await this.resolver.listStakeChunks();
|
|
114
|
+
let nextData = await next();
|
|
115
|
+
while (nextData.length) {
|
|
116
|
+
logger?.info("Updating stake distribution in chunks", {
|
|
117
|
+
chunkSize: nextData.length,
|
|
118
|
+
startTime: nextData[0].renaissanceTime,
|
|
119
|
+
startAddress: nextData[0].address,
|
|
120
|
+
endTime: nextData[nextData.length - 1].renaissanceTime
|
|
121
|
+
});
|
|
122
|
+
nextData.forEach((stakeState) => {
|
|
123
|
+
const isGasStake = this.isGasStake(stakeState);
|
|
124
|
+
const token = stakeState.tokens.find((x) => x.address === tokenAddress);
|
|
125
|
+
const revokedBalance = new _ocap_util.BN(stakeState.revokedTokens.find((x) => x.address === tokenAddress)?.balance || 0);
|
|
126
|
+
const balance = new _ocap_util.BN(token?.balance || 0);
|
|
127
|
+
distribution.revokedStake = distribution.revokedStake.add(revokedBalance);
|
|
128
|
+
distribution.stake = distribution.stake.sub(revokedBalance);
|
|
129
|
+
if (isGasStake) {
|
|
130
|
+
distribution.gasStake = distribution.gasStake.add(balance);
|
|
131
|
+
distribution.stake = distribution.stake.sub(balance);
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
nextData = await next();
|
|
135
|
+
}
|
|
136
|
+
return distribution;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Update token distribution by a single transaction.
|
|
140
|
+
* This method is usually used when a tx is completed.
|
|
141
|
+
*
|
|
142
|
+
* @param tx The transaction object
|
|
143
|
+
* @param context The transaction context
|
|
144
|
+
* @returns The updated token distributions
|
|
145
|
+
*/
|
|
146
|
+
async updateByTx(tx, context) {
|
|
147
|
+
const { logger } = this.resolver;
|
|
148
|
+
const ctx = (0, lodash_omit.default)(context, "txn");
|
|
149
|
+
if (this.isProcessing) {
|
|
150
|
+
logger?.debug?.("Token distribution is already in progress", { tx });
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const tokens = (await this.resolver.formatTx(tx)).tokenSymbols.map(({ address }) => address);
|
|
154
|
+
return await Promise.all(tokens.map(async (tokenAddress) => {
|
|
155
|
+
const distribution = this.formatDistribution(await this.getDistribution(tokenAddress) || { tokenAddress });
|
|
156
|
+
await this.handleTx(tx, distribution, {
|
|
157
|
+
context: ctx,
|
|
158
|
+
isHandleStake: true
|
|
159
|
+
});
|
|
160
|
+
return await this.saveDistribution(distribution);
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Parse token distribution for a single transaction
|
|
165
|
+
* @param tx The transaction
|
|
166
|
+
* @param distribution The distribution to update
|
|
167
|
+
* @param options Handle options
|
|
168
|
+
* @returns The updated token distribution
|
|
169
|
+
*/
|
|
170
|
+
async handleTx(tx, distribution, { context, isHandleStake = false } = {}) {
|
|
171
|
+
if (isHandleStake && !context) throw new Error("context is missing, handle revoke stake is not supported without context");
|
|
172
|
+
const type = tx.tx?.itxJson?._type;
|
|
173
|
+
const receipts = tx.receipts || [];
|
|
174
|
+
const { tokenAddress } = distribution;
|
|
175
|
+
const handlePromises = (0, _ocap_state_lib_states_tx.eachReceipts)(receipts, async (address, change) => {
|
|
176
|
+
if (!(0, _ocap_util.isSameDid)(change.target, tokenAddress)) return;
|
|
177
|
+
if (change.action === "migrate") return;
|
|
178
|
+
const roleType = (0, _arcblock_did.toTypeInfo)(address).role;
|
|
179
|
+
const value = new _ocap_util.BN(change.value);
|
|
180
|
+
if (roleType === RoleType.ROLE_STAKE) {
|
|
181
|
+
if (isHandleStake) {
|
|
182
|
+
const stakeState = await this.getStakeState(address, context);
|
|
183
|
+
const isGasStakeResult = this.isGasStake(stakeState);
|
|
184
|
+
if (type === "ClaimStakeTx") {
|
|
185
|
+
distribution.revokedStake = distribution.revokedStake.add(value);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (type === "SlashStakeTx") {
|
|
189
|
+
const beforeState = context.stateSnapshot[address];
|
|
190
|
+
const afterState = context.stakeState;
|
|
191
|
+
if (!beforeState || !afterState) throw new Error("stake state is missing on slash stake tx");
|
|
192
|
+
const revokeTokenDiff = new _ocap_util.BN(beforeState.revokedTokens[tokenAddress]).sub(new _ocap_util.BN(afterState.revokedTokens[tokenAddress]));
|
|
193
|
+
const tokenDiff = new _ocap_util.BN(beforeState.tokens[tokenAddress]).sub(new _ocap_util.BN(afterState.tokens[tokenAddress]));
|
|
194
|
+
distribution.revokedStake = distribution.revokedStake.sub(revokeTokenDiff);
|
|
195
|
+
if (isGasStakeResult) distribution.gasStake = distribution.gasStake.sub(tokenDiff);
|
|
196
|
+
else distribution.stake = distribution.stake.sub(tokenDiff);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (isGasStakeResult) {
|
|
200
|
+
distribution.gasStake = distribution.gasStake.add(value);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
distribution.stake = distribution.stake.add(value);
|
|
205
|
+
} else if (change.action === "gas" && value.gt(ZERO)) distribution.gas = distribution.gas.add(value);
|
|
206
|
+
else if (change.action === "fee" && value.gt(ZERO)) distribution.fee = distribution.fee.add(value);
|
|
207
|
+
else if (type === "SlashStakeTx" && value.gt(ZERO) && (0, _ocap_util.isSameDid)(address, this.resolver.config.vaults.slashedStake ?? "")) distribution.slashedVault = distribution.slashedVault.add(value);
|
|
208
|
+
else distribution.account = distribution.account.add(value);
|
|
209
|
+
});
|
|
210
|
+
await Promise.all(handlePromises);
|
|
211
|
+
if (isHandleStake && type === "RevokeStakeTx") {
|
|
212
|
+
const { address } = tx.tx.itxJson;
|
|
213
|
+
const stakeState = await this.getStakeState(address, context);
|
|
214
|
+
const isGasStakeResult = this.isGasStake(stakeState);
|
|
215
|
+
tx.tx.itxJson.outputs.forEach((x) => {
|
|
216
|
+
x.tokens.filter((change) => (0, _ocap_util.isSameDid)(change.address, tokenAddress)).forEach((change) => {
|
|
217
|
+
const value = new _ocap_util.BN(change.value || change.balance || 0);
|
|
218
|
+
distribution.revokedStake = distribution.revokedStake.add(value);
|
|
219
|
+
if (isGasStakeResult) distribution.gasStake = distribution.gasStake.sub(value);
|
|
220
|
+
else distribution.stake = distribution.stake.sub(value);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
distribution.txTime = tx.time;
|
|
225
|
+
return distribution;
|
|
226
|
+
}
|
|
227
|
+
handleModerator(distribution) {
|
|
228
|
+
const { config } = this.resolver;
|
|
229
|
+
if (distribution.tokenAddress !== config.token.address) return distribution;
|
|
230
|
+
const value = config.accounts.filter((account) => Number(account.balance) > 0).map((account) => new _ocap_util.BN(account.balance)).reduce((cur, balance) => cur.add(balance), ZERO);
|
|
231
|
+
distribution.account = distribution.account.add((0, _ocap_util.fromTokenToUnit)(value.toString()));
|
|
232
|
+
return distribution;
|
|
233
|
+
}
|
|
234
|
+
async getStakeState(address, ctx) {
|
|
235
|
+
return await this.resolver.statedb.stake.get(address, ctx);
|
|
236
|
+
}
|
|
237
|
+
isGasStake(stakeState) {
|
|
238
|
+
return (0, _ocap_tx_protocols_lib_util.isGasStakeAddress)(stakeState.sender, stakeState.address) && stakeState.message === "stake-for-gas";
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
//#endregion
|
|
243
|
+
exports.TokenDistributionManager = TokenDistributionManager;
|