boing-sdk 0.3.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/CHANGELOG.md +26 -0
- package/README.md +191 -0
- package/dist/accessList.d.ts +22 -0
- package/dist/accessList.d.ts.map +1 -0
- package/dist/accessList.js +45 -0
- package/dist/bincode.d.ts +92 -0
- package/dist/bincode.d.ts.map +1 -0
- package/dist/bincode.js +154 -0
- package/dist/callAbi.d.ts +119 -0
- package/dist/callAbi.d.ts.map +1 -0
- package/dist/callAbi.js +156 -0
- package/dist/calldata.d.ts +35 -0
- package/dist/calldata.d.ts.map +1 -0
- package/dist/calldata.js +93 -0
- package/dist/canonicalDeployArtifacts.d.ts +154 -0
- package/dist/canonicalDeployArtifacts.d.ts.map +1 -0
- package/dist/canonicalDeployArtifacts.js +271 -0
- package/dist/canonicalTestnet.d.ts +15 -0
- package/dist/canonicalTestnet.d.ts.map +1 -0
- package/dist/canonicalTestnet.js +15 -0
- package/dist/canonicalTestnetDex.d.ts +17 -0
- package/dist/canonicalTestnetDex.d.ts.map +1 -0
- package/dist/canonicalTestnetDex.js +17 -0
- package/dist/chainIds.d.ts +18 -0
- package/dist/chainIds.d.ts.map +1 -0
- package/dist/chainIds.js +56 -0
- package/dist/client.d.ts +223 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +659 -0
- package/dist/connectionMonitor.d.ts +47 -0
- package/dist/connectionMonitor.d.ts.map +1 -0
- package/dist/connectionMonitor.js +93 -0
- package/dist/create2.d.ts +94 -0
- package/dist/create2.d.ts.map +1 -0
- package/dist/create2.js +225 -0
- package/dist/dappDeploy.d.ts +100 -0
- package/dist/dappDeploy.d.ts.map +1 -0
- package/dist/dappDeploy.js +140 -0
- package/dist/dappUiHelpers.d.ts +28 -0
- package/dist/dappUiHelpers.d.ts.map +1 -0
- package/dist/dappUiHelpers.js +69 -0
- package/dist/defaultReferenceFungibleSecuredRuntimeBytecodeHex.d.ts +6 -0
- package/dist/defaultReferenceFungibleSecuredRuntimeBytecodeHex.d.ts.map +1 -0
- package/dist/defaultReferenceFungibleSecuredRuntimeBytecodeHex.js +5 -0
- package/dist/defaultReferenceFungibleSecuredTemplateBytecodeHex.d.ts +6 -0
- package/dist/defaultReferenceFungibleSecuredTemplateBytecodeHex.d.ts.map +1 -0
- package/dist/defaultReferenceFungibleSecuredTemplateBytecodeHex.js +5 -0
- package/dist/defaultReferenceFungibleTemplateBytecodeHex.d.ts +6 -0
- package/dist/defaultReferenceFungibleTemplateBytecodeHex.d.ts.map +1 -0
- package/dist/defaultReferenceFungibleTemplateBytecodeHex.js +5 -0
- package/dist/defaultReferenceNftCollectionTemplateBytecodeHex.d.ts +7 -0
- package/dist/defaultReferenceNftCollectionTemplateBytecodeHex.d.ts.map +1 -0
- package/dist/defaultReferenceNftCollectionTemplateBytecodeHex.js +6 -0
- package/dist/dexIntegration.d.ts +61 -0
- package/dist/dexIntegration.d.ts.map +1 -0
- package/dist/dexIntegration.js +193 -0
- package/dist/erc721Logs.d.ts +21 -0
- package/dist/erc721Logs.d.ts.map +1 -0
- package/dist/erc721Logs.js +69 -0
- package/dist/errors.d.ts +60 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +153 -0
- package/dist/hex.d.ts +27 -0
- package/dist/hex.d.ts.map +1 -0
- package/dist/hex.js +82 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/indexerBatch.d.ts +111 -0
- package/dist/indexerBatch.d.ts.map +1 -0
- package/dist/indexerBatch.js +253 -0
- package/dist/indexerGaps.d.ts +50 -0
- package/dist/indexerGaps.d.ts.map +1 -0
- package/dist/indexerGaps.js +117 -0
- package/dist/indexerSync.d.ts +61 -0
- package/dist/indexerSync.d.ts.map +1 -0
- package/dist/indexerSync.js +100 -0
- package/dist/nativeAmm.d.ts +64 -0
- package/dist/nativeAmm.d.ts.map +1 -0
- package/dist/nativeAmm.js +174 -0
- package/dist/nativeAmmLogs.d.ts +48 -0
- package/dist/nativeAmmLogs.d.ts.map +1 -0
- package/dist/nativeAmmLogs.js +114 -0
- package/dist/nativeAmmLpVault.d.ts +94 -0
- package/dist/nativeAmmLpVault.d.ts.map +1 -0
- package/dist/nativeAmmLpVault.js +205 -0
- package/dist/nativeAmmPool.d.ts +124 -0
- package/dist/nativeAmmPool.d.ts.map +1 -0
- package/dist/nativeAmmPool.js +245 -0
- package/dist/nativeContractSubmit.d.ts +26 -0
- package/dist/nativeContractSubmit.d.ts.map +1 -0
- package/dist/nativeContractSubmit.js +23 -0
- package/dist/nativeDexDirectory.d.ts +83 -0
- package/dist/nativeDexDirectory.d.ts.map +1 -0
- package/dist/nativeDexDirectory.js +147 -0
- package/dist/nativeDexDirectoryApi.d.ts +121 -0
- package/dist/nativeDexDirectoryApi.d.ts.map +1 -0
- package/dist/nativeDexDirectoryApi.js +408 -0
- package/dist/nativeDexFactory.d.ts +25 -0
- package/dist/nativeDexFactory.d.ts.map +1 -0
- package/dist/nativeDexFactory.js +72 -0
- package/dist/nativeDexFactoryLogs.d.ts +19 -0
- package/dist/nativeDexFactoryLogs.d.ts.map +1 -0
- package/dist/nativeDexFactoryLogs.js +61 -0
- package/dist/nativeDexFactoryPool.d.ts +61 -0
- package/dist/nativeDexFactoryPool.d.ts.map +1 -0
- package/dist/nativeDexFactoryPool.js +120 -0
- package/dist/nativeDexIndexerStats.d.ts +96 -0
- package/dist/nativeDexIndexerStats.d.ts.map +1 -0
- package/dist/nativeDexIndexerStats.js +448 -0
- package/dist/nativeDexLedgerRouter.d.ts +67 -0
- package/dist/nativeDexLedgerRouter.d.ts.map +1 -0
- package/dist/nativeDexLedgerRouter.js +108 -0
- package/dist/nativeDexLpPositions.d.ts +39 -0
- package/dist/nativeDexLpPositions.d.ts.map +1 -0
- package/dist/nativeDexLpPositions.js +69 -0
- package/dist/nativeDexNftIndexer.d.ts +26 -0
- package/dist/nativeDexNftIndexer.d.ts.map +1 -0
- package/dist/nativeDexNftIndexer.js +50 -0
- package/dist/nativeDexPoolHistory.d.ts +40 -0
- package/dist/nativeDexPoolHistory.d.ts.map +1 -0
- package/dist/nativeDexPoolHistory.js +110 -0
- package/dist/nativeDexReceiptArchive.d.ts +25 -0
- package/dist/nativeDexReceiptArchive.d.ts.map +1 -0
- package/dist/nativeDexReceiptArchive.js +47 -0
- package/dist/nativeDexRouting.d.ts +160 -0
- package/dist/nativeDexRouting.d.ts.map +1 -0
- package/dist/nativeDexRouting.js +345 -0
- package/dist/nativeDexSeamless.d.ts +86 -0
- package/dist/nativeDexSeamless.d.ts.map +1 -0
- package/dist/nativeDexSeamless.js +131 -0
- package/dist/nativeDexSwap2Router.d.ts +45 -0
- package/dist/nativeDexSwap2Router.d.ts.map +1 -0
- package/dist/nativeDexSwap2Router.js +276 -0
- package/dist/nativeLpShareToken.d.ts +54 -0
- package/dist/nativeLpShareToken.d.ts.map +1 -0
- package/dist/nativeLpShareToken.js +135 -0
- package/dist/nativeTokenSecurity.d.ts +59 -0
- package/dist/nativeTokenSecurity.d.ts.map +1 -0
- package/dist/nativeTokenSecurity.js +59 -0
- package/dist/networkProfile.d.ts +8 -0
- package/dist/networkProfile.d.ts.map +1 -0
- package/dist/networkProfile.js +29 -0
- package/dist/newHeadsWs.d.ts +43 -0
- package/dist/newHeadsWs.d.ts.map +1 -0
- package/dist/newHeadsWs.js +139 -0
- package/dist/preflightGate.d.ts +16 -0
- package/dist/preflightGate.d.ts.map +1 -0
- package/dist/preflightGate.js +29 -0
- package/dist/receiptLogs.d.ts +29 -0
- package/dist/receiptLogs.d.ts.map +1 -0
- package/dist/receiptLogs.js +66 -0
- package/dist/referenceFungibleSecuredDeployBytecode.d.ts +54 -0
- package/dist/referenceFungibleSecuredDeployBytecode.d.ts.map +1 -0
- package/dist/referenceFungibleSecuredDeployBytecode.js +274 -0
- package/dist/referenceNft.d.ts +14 -0
- package/dist/referenceNft.d.ts.map +1 -0
- package/dist/referenceNft.js +34 -0
- package/dist/referenceToken.d.ts +14 -0
- package/dist/referenceToken.d.ts.map +1 -0
- package/dist/referenceToken.js +29 -0
- package/dist/retryAfter.d.ts +6 -0
- package/dist/retryAfter.d.ts.map +1 -0
- package/dist/retryAfter.js +24 -0
- package/dist/rpcCapabilities.d.ts +43 -0
- package/dist/rpcCapabilities.d.ts.map +1 -0
- package/dist/rpcCapabilities.js +159 -0
- package/dist/rpcDoctor.d.ts +27 -0
- package/dist/rpcDoctor.d.ts.map +1 -0
- package/dist/rpcDoctor.js +66 -0
- package/dist/rpcSurfaceUi.d.ts +32 -0
- package/dist/rpcSurfaceUi.d.ts.map +1 -0
- package/dist/rpcSurfaceUi.js +49 -0
- package/dist/submitFlow.d.ts +70 -0
- package/dist/submitFlow.d.ts.map +1 -0
- package/dist/submitFlow.js +121 -0
- package/dist/transactionBuilder.d.ts +55 -0
- package/dist/transactionBuilder.d.ts.map +1 -0
- package/dist/transactionBuilder.js +100 -0
- package/dist/types.d.ts +436 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/walletProvider.d.ts +46 -0
- package/dist/walletProvider.d.ts.map +1 -0
- package/dist/walletProvider.js +126 -0
- package/package.json +44 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pair directory contract — access lists, `contract_call` payloads, storage reads.
|
|
3
|
+
* Matches `boing_execution::native_dex_factory` + `native_dex_factory_count_key`.
|
|
4
|
+
*/
|
|
5
|
+
import { mergeAccessListWithSimulation } from './accessList.js';
|
|
6
|
+
import { decodeBoingStorageWordU128 } from './nativeAmmPool.js';
|
|
7
|
+
import { encodeNativeDexGetPairAtCalldata } from './nativeDexFactory.js';
|
|
8
|
+
import { ensureHex, validateHex32 } from './hex.js';
|
|
9
|
+
import { buildContractCallTransaction, fetchNextNonce, signTransactionInput, } from './transactionBuilder.js';
|
|
10
|
+
const HEX_RE = /^[0-9a-fA-F]+$/;
|
|
11
|
+
function normalizeCalldataHex(calldataHex) {
|
|
12
|
+
const h = ensureHex(calldataHex.trim());
|
|
13
|
+
const raw = h.slice(2);
|
|
14
|
+
if (raw.length % 2 !== 0) {
|
|
15
|
+
throw new Error('calldata must be even-length hex');
|
|
16
|
+
}
|
|
17
|
+
if (!HEX_RE.test(raw)) {
|
|
18
|
+
throw new Error('calldata: invalid hex');
|
|
19
|
+
}
|
|
20
|
+
return `0x${raw.toLowerCase()}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `boing_getContractStorage` key for pair count (`native_dex_factory::native_dex_factory_count_key`).
|
|
24
|
+
* 16 zero bytes, **`BOINGDEX`** (8 bytes), 4 zero bytes, **`0xffffffff`** (big-endian).
|
|
25
|
+
*/
|
|
26
|
+
export const NATIVE_DEX_FACTORY_COUNT_KEY_HEX = `0x${'00'.repeat(16)}424f494e4744455800000000ffffffff`;
|
|
27
|
+
/** `read` / `write`: signer + directory contract (parallel scheduling minimum). */
|
|
28
|
+
export function buildNativeDexFactoryAccessList(senderHex32, factoryHex32) {
|
|
29
|
+
const s = validateHex32(senderHex32).toLowerCase();
|
|
30
|
+
const f = validateHex32(factoryHex32).toLowerCase();
|
|
31
|
+
return { read: [s, f], write: [s, f] };
|
|
32
|
+
}
|
|
33
|
+
export function buildNativeDexFactoryContractCallTx(senderHex32, factoryHex32, calldataHex) {
|
|
34
|
+
return {
|
|
35
|
+
type: 'contract_call',
|
|
36
|
+
contract: validateHex32(factoryHex32).toLowerCase(),
|
|
37
|
+
calldata: normalizeCalldataHex(calldataHex),
|
|
38
|
+
access_list: buildNativeDexFactoryAccessList(senderHex32, factoryHex32),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function mergeNativeDexFactoryAccessListWithSimulation(senderHex32, factoryHex32, sim) {
|
|
42
|
+
const base = buildNativeDexFactoryAccessList(senderHex32, factoryHex32);
|
|
43
|
+
return mergeAccessListWithSimulation(base.read, base.write, sim);
|
|
44
|
+
}
|
|
45
|
+
/** Decode `pairs_count` return data (32-byte word); count is in the low **8** bytes (matches on-chain layout). */
|
|
46
|
+
export function decodeNativeDexFactoryPairsCountReturnData(returnDataHex) {
|
|
47
|
+
const w = decodeBoingStorageWordU128(ensureHex(returnDataHex));
|
|
48
|
+
return w & 0xffffffffffffffffn;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Decode `get_pair_at` return data (**96** bytes = three account words).
|
|
52
|
+
*/
|
|
53
|
+
export function decodeNativeDexFactoryGetPairAtReturnData(returnDataHex) {
|
|
54
|
+
const raw = ensureHex(returnDataHex).slice(2).toLowerCase();
|
|
55
|
+
if (!HEX_RE.test(raw)) {
|
|
56
|
+
throw new Error('get_pair_at return data: invalid hex');
|
|
57
|
+
}
|
|
58
|
+
if (raw.length < 192) {
|
|
59
|
+
throw new Error('get_pair_at return data: expected 96 bytes (192 hex chars)');
|
|
60
|
+
}
|
|
61
|
+
const word = (i) => validateHex32(`0x${raw.slice(i * 64, i * 64 + 64)}`);
|
|
62
|
+
return {
|
|
63
|
+
tokenAHex: word(0),
|
|
64
|
+
tokenBHex: word(1),
|
|
65
|
+
poolHex: word(2),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/** Read registered pair count from contract storage (no simulation). */
|
|
69
|
+
export async function fetchNativeDexFactoryPairsCount(client, factoryHex32) {
|
|
70
|
+
const f = validateHex32(factoryHex32);
|
|
71
|
+
const w = await client.getContractStorage(f, NATIVE_DEX_FACTORY_COUNT_KEY_HEX);
|
|
72
|
+
return decodeNativeDexFactoryPairsCountReturnData(w.value);
|
|
73
|
+
}
|
|
74
|
+
function sameAccountHex(a, b) {
|
|
75
|
+
return validateHex32(a).toLowerCase() === validateHex32(b).toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find a registered pool address for **`(tokenA, tokenB)`** in either order.
|
|
79
|
+
*
|
|
80
|
+
* There is no on-chain reverse map; this helper reads **`pairs_count`** from storage, then simulates
|
|
81
|
+
* **`get_pair_at(i)`** for each index until a matching triplet is found.
|
|
82
|
+
*
|
|
83
|
+
* Requires a signer because **`boing_simulateTransaction`** expects a signed tx envelope (same as submit).
|
|
84
|
+
*/
|
|
85
|
+
export async function findNativeDexFactoryPoolByTokens(client, factoryHex32, tokenAHex32, tokenBHex32, options) {
|
|
86
|
+
const cap = BigInt(options.maxPairsToScan ?? 4096);
|
|
87
|
+
const count = await fetchNativeDexFactoryPairsCount(client, factoryHex32);
|
|
88
|
+
const n = count < cap ? count : cap;
|
|
89
|
+
const factory = validateHex32(factoryHex32).toLowerCase();
|
|
90
|
+
const sender = validateHex32(options.senderHex).toLowerCase();
|
|
91
|
+
const nonce = await fetchNextNonce(client, sender);
|
|
92
|
+
const al = buildNativeDexFactoryAccessList(sender, factory);
|
|
93
|
+
for (let i = 0n; i < n; i++) {
|
|
94
|
+
const calldata = encodeNativeDexGetPairAtCalldata(Number(i));
|
|
95
|
+
const tx = buildContractCallTransaction({
|
|
96
|
+
nonce,
|
|
97
|
+
senderHex: sender,
|
|
98
|
+
contractHex: factory,
|
|
99
|
+
calldata,
|
|
100
|
+
accessList: al,
|
|
101
|
+
});
|
|
102
|
+
const signedHex = await signTransactionInput(tx, options.secretKey32);
|
|
103
|
+
const sim = await client.simulateTransaction(signedHex);
|
|
104
|
+
if (!sim.success || sim.return_data == null || sim.return_data === '0x') {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const row = decodeNativeDexFactoryGetPairAtReturnData(sim.return_data);
|
|
109
|
+
const hitDirect = sameAccountHex(tokenAHex32, row.tokenAHex) && sameAccountHex(tokenBHex32, row.tokenBHex);
|
|
110
|
+
const hitSwap = sameAccountHex(tokenAHex32, row.tokenBHex) && sameAccountHex(tokenBHex32, row.tokenAHex);
|
|
111
|
+
if (hitDirect || hitSwap) {
|
|
112
|
+
return row.poolHex.toLowerCase();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native CP DEX indexer stats (pools, AMM log aggregates, optional reserve history, 24h swap window via block timestamps).
|
|
3
|
+
* Used by boing.finance CLI/Pages and boing.network Cloudflare Workers (KV-backed history).
|
|
4
|
+
*/
|
|
5
|
+
import type { BoingClient } from './client.js';
|
|
6
|
+
import { type NativeDexIntegrationOverrides } from './dexIntegration.js';
|
|
7
|
+
import { type CpPoolVenue } from './nativeDexRouting.js';
|
|
8
|
+
export type NativeDexIndexerHistoryPoint = {
|
|
9
|
+
t: number;
|
|
10
|
+
ra: string;
|
|
11
|
+
rb: string;
|
|
12
|
+
};
|
|
13
|
+
export type NativeDexIndexerPersistedDoc = {
|
|
14
|
+
history: Record<string, NativeDexIndexerHistoryPoint[]>;
|
|
15
|
+
lastHeadHeight?: number;
|
|
16
|
+
savedAt?: number;
|
|
17
|
+
};
|
|
18
|
+
/** Load/save full persisted JSON (same shape as CLI state file). */
|
|
19
|
+
export interface NativeDexIndexerHistoryStore {
|
|
20
|
+
get(): Promise<string | null>;
|
|
21
|
+
put(body: string): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
export type NativeDexIndexerTokenMeta = {
|
|
24
|
+
id: string;
|
|
25
|
+
symbol: string;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
export type NativeDexIndexerStatsOptions = {
|
|
29
|
+
overrides?: NativeDexIntegrationOverrides;
|
|
30
|
+
/** Inclusive factory `register_pair` scan from this block (omit / NaN to skip). */
|
|
31
|
+
registerFromBlock?: number;
|
|
32
|
+
/** Max inclusive block span for `boing_getLogs` per pool (clamped 1..50000). */
|
|
33
|
+
logScanBlocks?: number;
|
|
34
|
+
/** When set, merge reserve samples and return accumulated `history`. */
|
|
35
|
+
historyStore?: NativeDexIndexerHistoryStore | null;
|
|
36
|
+
/** Defaults to `Date.now()`. */
|
|
37
|
+
nowMs?: number;
|
|
38
|
+
/** JSON string: token hex → USD per atomic unit (+ optional default / defaulta / defaultb). */
|
|
39
|
+
tokenUsdJson?: string;
|
|
40
|
+
/** JSON array of `{ id, symbol?, name? }` merged into `tokenDirectory`. */
|
|
41
|
+
tokenDirectoryExtraJson?: string;
|
|
42
|
+
};
|
|
43
|
+
export type NativeDexIndexerPoolRow = {
|
|
44
|
+
poolHex: string;
|
|
45
|
+
tokenAHex: string;
|
|
46
|
+
tokenBHex: string;
|
|
47
|
+
/** From **`boing_listDexPools.createdAtHeight`** when the node supports discovery RPC (merged after stats build). */
|
|
48
|
+
createdAtHeight?: number;
|
|
49
|
+
/** From **`boing_listDexPools`** when merged (same source as **`tokenADecimals`** on RPC pool rows). */
|
|
50
|
+
tokenADecimals?: number;
|
|
51
|
+
/** From **`boing_listDexPools`** when merged. */
|
|
52
|
+
tokenBDecimals?: number;
|
|
53
|
+
/** On-chain reserve A (decimal string); aligns with `boing_listDexPools.reserveA` when present. */
|
|
54
|
+
reserveA?: string;
|
|
55
|
+
/** On-chain reserve B (decimal string); aligns with `boing_listDexPools.reserveB` when present. */
|
|
56
|
+
reserveB?: string;
|
|
57
|
+
/** Swaps in the full `[head - logScanBlocks + 1, head]` window. */
|
|
58
|
+
swapCount: number;
|
|
59
|
+
swapCount24h: number;
|
|
60
|
+
/** Alias for UIs that read `swaps24h`. */
|
|
61
|
+
swaps24h: number;
|
|
62
|
+
/** Sum of `amountIn` for swaps whose block time is within the last 24h (UTC wall vs `nowMs`). */
|
|
63
|
+
volume24hApprox: string;
|
|
64
|
+
/** Sum of `amountIn` for all swaps in the scan window. */
|
|
65
|
+
volumeScanWindowApprox: string;
|
|
66
|
+
tvlApprox: string;
|
|
67
|
+
/** Present when `tokenUsdJson` maps prices for at least one leg. */
|
|
68
|
+
tvlUsdApprox?: string;
|
|
69
|
+
note: string;
|
|
70
|
+
};
|
|
71
|
+
export type NativeDexIndexerStatsPayload = {
|
|
72
|
+
/**
|
|
73
|
+
* HTTP discovery mirror version (`docs/HANDOFF_Boing_Network_Global_Token_Discovery.md` §4).
|
|
74
|
+
* Present on indexer **`/stats`** payloads built by **`buildNativeDexIndexerStatsForClient`**.
|
|
75
|
+
*/
|
|
76
|
+
schemaVersion?: number;
|
|
77
|
+
updatedAt: string;
|
|
78
|
+
note: string;
|
|
79
|
+
headHeight: number | null;
|
|
80
|
+
pools: NativeDexIndexerPoolRow[];
|
|
81
|
+
history: Record<string, NativeDexIndexerHistoryPoint[]>;
|
|
82
|
+
tokenDirectory: NativeDexIndexerTokenMeta[];
|
|
83
|
+
/** Alias of **`tokenDirectory`** for RPC-aligned consumers (`boing_listDexTokens`-style naming). */
|
|
84
|
+
tokens?: NativeDexIndexerTokenMeta[];
|
|
85
|
+
};
|
|
86
|
+
export declare function parseNativeDexIndexerPersistedDoc(raw: string | null | undefined): NativeDexIndexerPersistedDoc;
|
|
87
|
+
export declare function appendVenuesToHistoryDoc(doc: NativeDexIndexerPersistedDoc, venues: readonly CpPoolVenue[], headHeight: number, nowMs: number, maxPerPool?: number): NativeDexIndexerPersistedDoc;
|
|
88
|
+
/**
|
|
89
|
+
* Build DEX override map from a plain env object (Cloudflare `env`, etc.).
|
|
90
|
+
*/
|
|
91
|
+
export declare function buildDexOverridesFromPlainEnv(env: Record<string, string | undefined> | null | undefined): NativeDexIntegrationOverrides;
|
|
92
|
+
/**
|
|
93
|
+
* Core indexer run (RPC via `client`). Does not create the client.
|
|
94
|
+
*/
|
|
95
|
+
export declare function buildNativeDexIndexerStatsForClient(client: BoingClient, opts?: NativeDexIndexerStatsOptions): Promise<NativeDexIndexerStatsPayload>;
|
|
96
|
+
//# sourceMappingURL=nativeDexIndexerStats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nativeDexIndexerStats.d.ts","sourceRoot":"","sources":["../src/nativeDexIndexerStats.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native CP DEX indexer stats (pools, AMM log aggregates, optional reserve history, 24h swap window via block timestamps).
|
|
3
|
+
* Used by boing.finance CLI/Pages and boing.network Cloudflare Workers (KV-backed history).
|
|
4
|
+
*/
|
|
5
|
+
import { fetchNativeDexIntegrationDefaults } from './dexIntegration.js';
|
|
6
|
+
import { isBoingRpcMethodNotFound } from './errors.js';
|
|
7
|
+
import { getLogsChunked, mapWithConcurrencyLimit } from './indexerBatch.js';
|
|
8
|
+
import { fetchNativeDexDirectorySnapshot } from './nativeDexDirectory.js';
|
|
9
|
+
import { filterMapNativeAmmRpcLogs } from './nativeAmmLogs.js';
|
|
10
|
+
import { NATIVE_CONSTANT_PRODUCT_TOKEN_A_KEY_HEX, NATIVE_CONSTANT_PRODUCT_TOKEN_B_KEY_HEX, } from './nativeAmmPool.js';
|
|
11
|
+
import { hydrateCpPoolVenuesFromRpc } from './nativeDexRouting.js';
|
|
12
|
+
import { validateHex32 } from './hex.js';
|
|
13
|
+
const SYNTH_A = '0x0000000000000000000000000000000000000000000000000000000000000e01';
|
|
14
|
+
const SYNTH_B = '0x0000000000000000000000000000000000000000000000000000000000000e02';
|
|
15
|
+
const MAX_HISTORY_PER_POOL = 200;
|
|
16
|
+
const MAX_BLOCK_HEADERS_FOR_24H = 512;
|
|
17
|
+
function storageWordToAccountHex(valueHex) {
|
|
18
|
+
if (!valueHex || typeof valueHex !== 'string')
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const v = validateHex32(valueHex.trim());
|
|
22
|
+
if (/^0x0+$/i.test(v))
|
|
23
|
+
return null;
|
|
24
|
+
return `0x${v.slice(2).toLowerCase()}`;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function fetchNativeCpPoolTokenRow(client, poolHex32) {
|
|
31
|
+
const pool = validateHex32(poolHex32.trim());
|
|
32
|
+
const [wa, wb] = await Promise.all([
|
|
33
|
+
client.getContractStorage(pool, NATIVE_CONSTANT_PRODUCT_TOKEN_A_KEY_HEX),
|
|
34
|
+
client.getContractStorage(pool, NATIVE_CONSTANT_PRODUCT_TOKEN_B_KEY_HEX),
|
|
35
|
+
]);
|
|
36
|
+
let tokenAHex = storageWordToAccountHex(wa.value);
|
|
37
|
+
let tokenBHex = storageWordToAccountHex(wb.value);
|
|
38
|
+
if (!tokenAHex && !tokenBHex) {
|
|
39
|
+
tokenAHex = SYNTH_A;
|
|
40
|
+
tokenBHex = SYNTH_B;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
tokenAHex = tokenAHex || SYNTH_A;
|
|
44
|
+
tokenBHex = tokenBHex || SYNTH_B;
|
|
45
|
+
}
|
|
46
|
+
return { poolHex: pool, tokenAHex, tokenBHex };
|
|
47
|
+
}
|
|
48
|
+
export function parseNativeDexIndexerPersistedDoc(raw) {
|
|
49
|
+
if (!raw)
|
|
50
|
+
return { history: {} };
|
|
51
|
+
try {
|
|
52
|
+
const p = JSON.parse(raw);
|
|
53
|
+
if (p && typeof p === 'object' && p !== null && 'history' in p) {
|
|
54
|
+
const h = p.history;
|
|
55
|
+
if (h && typeof h === 'object') {
|
|
56
|
+
return { history: { ...h } };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
/* ignore */
|
|
62
|
+
}
|
|
63
|
+
return { history: {} };
|
|
64
|
+
}
|
|
65
|
+
export function appendVenuesToHistoryDoc(doc, venues, headHeight, nowMs, maxPerPool = MAX_HISTORY_PER_POOL) {
|
|
66
|
+
const history = { ...doc.history };
|
|
67
|
+
for (const v of venues) {
|
|
68
|
+
const k = v.poolHex.toLowerCase();
|
|
69
|
+
const ra = v.reserveA.toString();
|
|
70
|
+
const rb = v.reserveB.toString();
|
|
71
|
+
const arr = Array.isArray(history[k]) ? [...history[k]] : [];
|
|
72
|
+
const last = arr[arr.length - 1];
|
|
73
|
+
if (!last || last.ra !== ra || last.rb !== rb) {
|
|
74
|
+
arr.push({ t: nowMs, ra, rb });
|
|
75
|
+
}
|
|
76
|
+
history[k] = arr.slice(-maxPerPool);
|
|
77
|
+
}
|
|
78
|
+
return { history, lastHeadHeight: headHeight, savedAt: nowMs };
|
|
79
|
+
}
|
|
80
|
+
function parseUsdMap(json) {
|
|
81
|
+
if (!json?.trim())
|
|
82
|
+
return {};
|
|
83
|
+
try {
|
|
84
|
+
const o = JSON.parse(json);
|
|
85
|
+
if (!o || typeof o !== 'object')
|
|
86
|
+
return {};
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const [key, val] of Object.entries(o)) {
|
|
89
|
+
const k = key.trim().toLowerCase();
|
|
90
|
+
if (k === 'default' || k === 'defaulta' || k === 'defaultb') {
|
|
91
|
+
out[k] = String(val);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!/^0x[0-9a-f]{64}$/i.test(k))
|
|
95
|
+
continue;
|
|
96
|
+
out[`0x${k.slice(2).toLowerCase()}`] = String(val);
|
|
97
|
+
}
|
|
98
|
+
return out;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function estimatePoolTvlUsd(tokenAHex, tokenBHex, reserveA, reserveB, usdMap) {
|
|
105
|
+
const keys = Object.keys(usdMap);
|
|
106
|
+
if (keys.length === 0)
|
|
107
|
+
return null;
|
|
108
|
+
const a = tokenAHex.trim().toLowerCase();
|
|
109
|
+
const b = tokenBHex.trim().toLowerCase();
|
|
110
|
+
const pa = usdMap[a] ?? usdMap.defaulta ?? usdMap.default ?? null;
|
|
111
|
+
const pb = usdMap[b] ?? usdMap.defaultb ?? usdMap.default ?? null;
|
|
112
|
+
if (pa == null && pb == null)
|
|
113
|
+
return null;
|
|
114
|
+
try {
|
|
115
|
+
const ra = Number(reserveA);
|
|
116
|
+
const rb = Number(reserveB);
|
|
117
|
+
const fa = pa != null ? parseFloat(String(pa)) : 0;
|
|
118
|
+
const fb = pb != null ? parseFloat(String(pb)) : 0;
|
|
119
|
+
if (!Number.isFinite(ra) || !Number.isFinite(rb) || !Number.isFinite(fa) || !Number.isFinite(fb))
|
|
120
|
+
return null;
|
|
121
|
+
const partA = pa != null ? ra * fa : 0;
|
|
122
|
+
const partB = pb != null ? rb * fb : 0;
|
|
123
|
+
const tvl = partA + partB;
|
|
124
|
+
return Number.isFinite(tvl) ? tvl : null;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function blockTimestampToMs(ts) {
|
|
131
|
+
if (!Number.isFinite(ts))
|
|
132
|
+
return 0;
|
|
133
|
+
return ts < 1e12 ? ts * 1000 : ts;
|
|
134
|
+
}
|
|
135
|
+
async function fetchBlockTimeMap(client, heights) {
|
|
136
|
+
const uniq = [...new Set(heights.filter((h) => Number.isInteger(h) && h >= 0))].slice(0, MAX_BLOCK_HEADERS_FOR_24H);
|
|
137
|
+
const m = new Map();
|
|
138
|
+
if (uniq.length === 0)
|
|
139
|
+
return m;
|
|
140
|
+
const blocks = await mapWithConcurrencyLimit(uniq, 4, (h) => client.getBlockByHeight(h, false));
|
|
141
|
+
for (let i = 0; i < uniq.length; i++) {
|
|
142
|
+
const h = uniq[i];
|
|
143
|
+
const b = blocks[i];
|
|
144
|
+
const raw = b?.header?.timestamp;
|
|
145
|
+
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
146
|
+
m.set(h, blockTimestampToMs(raw));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return m;
|
|
150
|
+
}
|
|
151
|
+
function tokenDirectoryFromVenues(venues) {
|
|
152
|
+
const by = new Map();
|
|
153
|
+
for (const v of venues) {
|
|
154
|
+
for (const raw of [v.tokenAHex, v.tokenBHex]) {
|
|
155
|
+
const id = validateHex32(raw.trim());
|
|
156
|
+
const low = id.toLowerCase();
|
|
157
|
+
if (by.has(low))
|
|
158
|
+
continue;
|
|
159
|
+
by.set(low, {
|
|
160
|
+
id: low,
|
|
161
|
+
symbol: `${id.slice(0, 8)}…${id.slice(-4)}`,
|
|
162
|
+
name: `Pool token ${id.slice(0, 10)}…`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return [...by.values()].sort((x, y) => x.symbol.localeCompare(y.symbol));
|
|
167
|
+
}
|
|
168
|
+
function mergeTokenDirectoryExtra(base, extraJson) {
|
|
169
|
+
if (!extraJson?.trim())
|
|
170
|
+
return base;
|
|
171
|
+
try {
|
|
172
|
+
const arr = JSON.parse(extraJson);
|
|
173
|
+
if (!Array.isArray(arr))
|
|
174
|
+
return base;
|
|
175
|
+
const by = new Map(base.map((t) => [t.id.toLowerCase(), { ...t }]));
|
|
176
|
+
for (const row of arr) {
|
|
177
|
+
if (!row || typeof row !== 'object')
|
|
178
|
+
continue;
|
|
179
|
+
const idRaw = row.id ?? row.address;
|
|
180
|
+
if (typeof idRaw !== 'string')
|
|
181
|
+
continue;
|
|
182
|
+
let id;
|
|
183
|
+
try {
|
|
184
|
+
id = validateHex32(idRaw.trim());
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const low = id.toLowerCase();
|
|
190
|
+
const symbol = String(row.symbol || base.find((b) => b.id === low)?.symbol || '').slice(0, 16);
|
|
191
|
+
const name = String(row.name || '').slice(0, 80);
|
|
192
|
+
by.set(low, {
|
|
193
|
+
id: low,
|
|
194
|
+
symbol: symbol || `${id.slice(0, 8)}…`,
|
|
195
|
+
name: name || `Token ${id.slice(0, 10)}…`,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return [...by.values()].sort((x, y) => x.symbol.localeCompare(y.symbol));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return base;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Build DEX override map from a plain env object (Cloudflare `env`, etc.).
|
|
206
|
+
*/
|
|
207
|
+
export function buildDexOverridesFromPlainEnv(env) {
|
|
208
|
+
const o = {};
|
|
209
|
+
if (!env || typeof env !== 'object')
|
|
210
|
+
return o;
|
|
211
|
+
const pick = (...keys) => {
|
|
212
|
+
for (const k of keys) {
|
|
213
|
+
const v = env[k];
|
|
214
|
+
if (v != null && String(v).trim())
|
|
215
|
+
return String(v).trim();
|
|
216
|
+
}
|
|
217
|
+
return '';
|
|
218
|
+
};
|
|
219
|
+
const pool = pick('REACT_APP_BOING_NATIVE_AMM_POOL', 'VITE_BOING_NATIVE_AMM_POOL', 'BOING_NATIVE_AMM_POOL');
|
|
220
|
+
const fac = pick('REACT_APP_BOING_NATIVE_VM_DEX_FACTORY', 'VITE_BOING_NATIVE_VM_DEX_FACTORY', 'BOING_NATIVE_VM_DEX_FACTORY', 'BOING_DEX_FACTORY_HEX');
|
|
221
|
+
const hop = pick('REACT_APP_BOING_NATIVE_VM_SWAP_ROUTER', 'VITE_BOING_NATIVE_VM_SWAP_ROUTER', 'BOING_NATIVE_VM_SWAP_ROUTER', 'BOING_NATIVE_DEX_MULTIHOP_SWAP_ROUTER');
|
|
222
|
+
const l2 = pick('REACT_APP_BOING_NATIVE_DEX_LEDGER_ROUTER_V2', 'VITE_BOING_NATIVE_DEX_LEDGER_ROUTER_V2', 'BOING_NATIVE_DEX_LEDGER_ROUTER_V2');
|
|
223
|
+
const l3 = pick('REACT_APP_BOING_NATIVE_DEX_LEDGER_ROUTER_V3', 'VITE_BOING_NATIVE_DEX_LEDGER_ROUTER_V3', 'BOING_NATIVE_DEX_LEDGER_ROUTER_V3');
|
|
224
|
+
const vault = pick('REACT_APP_BOING_NATIVE_AMM_LP_VAULT', 'VITE_BOING_NATIVE_AMM_LP_VAULT', 'BOING_NATIVE_AMM_LP_VAULT');
|
|
225
|
+
const share = pick('REACT_APP_BOING_NATIVE_AMM_LP_SHARE_TOKEN', 'VITE_BOING_NATIVE_AMM_LP_SHARE_TOKEN', 'BOING_NATIVE_AMM_LP_SHARE_TOKEN');
|
|
226
|
+
if (pool)
|
|
227
|
+
o.nativeCpPoolAccountHex = pool;
|
|
228
|
+
if (fac)
|
|
229
|
+
o.nativeDexFactoryAccountHex = fac;
|
|
230
|
+
if (hop)
|
|
231
|
+
o.nativeDexMultihopSwapRouterAccountHex = hop;
|
|
232
|
+
if (l2)
|
|
233
|
+
o.nativeDexLedgerRouterV2AccountHex = l2;
|
|
234
|
+
if (l3)
|
|
235
|
+
o.nativeDexLedgerRouterV3AccountHex = l3;
|
|
236
|
+
if (vault)
|
|
237
|
+
o.nativeAmmLpVaultAccountHex = vault;
|
|
238
|
+
if (share)
|
|
239
|
+
o.nativeLpShareTokenAccountHex = share;
|
|
240
|
+
return o;
|
|
241
|
+
}
|
|
242
|
+
async function mergePoolDiscoveryFromListDexPoolsRpc(client, factoryHex, pools) {
|
|
243
|
+
if (!factoryHex?.trim() || pools.length === 0)
|
|
244
|
+
return;
|
|
245
|
+
let factoryNorm;
|
|
246
|
+
try {
|
|
247
|
+
factoryNorm = validateHex32(factoryHex.trim());
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const map = new Map();
|
|
254
|
+
let cursor = null;
|
|
255
|
+
for (;;) {
|
|
256
|
+
const page = await client.listDexPoolsPage({ factory: factoryNorm, cursor, limit: 500 });
|
|
257
|
+
for (const p of page.pools) {
|
|
258
|
+
const k = p.poolHex.toLowerCase();
|
|
259
|
+
const cur = { ...map.get(k) };
|
|
260
|
+
if (typeof p.createdAtHeight === 'number' && Number.isFinite(p.createdAtHeight)) {
|
|
261
|
+
cur.createdAtHeight = p.createdAtHeight;
|
|
262
|
+
}
|
|
263
|
+
if (typeof p.tokenADecimals === 'number' && Number.isFinite(p.tokenADecimals)) {
|
|
264
|
+
cur.tokenADecimals = p.tokenADecimals;
|
|
265
|
+
}
|
|
266
|
+
if (typeof p.tokenBDecimals === 'number' && Number.isFinite(p.tokenBDecimals)) {
|
|
267
|
+
cur.tokenBDecimals = p.tokenBDecimals;
|
|
268
|
+
}
|
|
269
|
+
map.set(k, cur);
|
|
270
|
+
}
|
|
271
|
+
const next = page.nextCursor;
|
|
272
|
+
if (!next)
|
|
273
|
+
break;
|
|
274
|
+
cursor = next;
|
|
275
|
+
}
|
|
276
|
+
for (const row of pools) {
|
|
277
|
+
const m = map.get(row.poolHex.toLowerCase());
|
|
278
|
+
if (!m)
|
|
279
|
+
continue;
|
|
280
|
+
if (m.createdAtHeight !== undefined)
|
|
281
|
+
row.createdAtHeight = m.createdAtHeight;
|
|
282
|
+
if (m.tokenADecimals !== undefined)
|
|
283
|
+
row.tokenADecimals = m.tokenADecimals;
|
|
284
|
+
if (m.tokenBDecimals !== undefined)
|
|
285
|
+
row.tokenBDecimals = m.tokenBDecimals;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch (e) {
|
|
289
|
+
if (isBoingRpcMethodNotFound(e))
|
|
290
|
+
return;
|
|
291
|
+
throw e;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Core indexer run (RPC via `client`). Does not create the client.
|
|
296
|
+
*/
|
|
297
|
+
export async function buildNativeDexIndexerStatsForClient(client, opts = {}) {
|
|
298
|
+
const logScanBlocks = Math.min(Math.max(1, opts.logScanBlocks ?? 8000), 50000);
|
|
299
|
+
const registerFromBlock = opts.registerFromBlock ?? NaN;
|
|
300
|
+
const nowMs = opts.nowMs ?? Date.now();
|
|
301
|
+
const cutoffMs = nowMs - 86400000;
|
|
302
|
+
const usdMap = parseUsdMap(opts.tokenUsdJson);
|
|
303
|
+
const ov = opts.overrides && Object.keys(opts.overrides).length > 0 ? opts.overrides : undefined;
|
|
304
|
+
const d = await fetchNativeDexIntegrationDefaults(client, ov);
|
|
305
|
+
const poolHex = d.nativeCpPoolAccountHex;
|
|
306
|
+
if (!poolHex) {
|
|
307
|
+
return {
|
|
308
|
+
schemaVersion: 1,
|
|
309
|
+
updatedAt: new Date().toISOString(),
|
|
310
|
+
note: 'No native CP pool in RPC defaults / overrides',
|
|
311
|
+
headHeight: null,
|
|
312
|
+
pools: [],
|
|
313
|
+
history: {},
|
|
314
|
+
tokenDirectory: [],
|
|
315
|
+
tokens: [],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const row = await fetchNativeCpPoolTokenRow(client, poolHex);
|
|
319
|
+
let venues = await hydrateCpPoolVenuesFromRpc(client, [row], { concurrency: 4 });
|
|
320
|
+
let registerMeta = null;
|
|
321
|
+
if (Number.isFinite(registerFromBlock) && registerFromBlock >= 0) {
|
|
322
|
+
try {
|
|
323
|
+
const snap = await fetchNativeDexDirectorySnapshot(client, {
|
|
324
|
+
overrides: ov,
|
|
325
|
+
registerLogs: { fromBlock: registerFromBlock },
|
|
326
|
+
});
|
|
327
|
+
const logs = snap.registerLogs;
|
|
328
|
+
registerMeta = { count: Array.isArray(logs) ? logs.length : 0, fromBlock: registerFromBlock };
|
|
329
|
+
if (logs?.length) {
|
|
330
|
+
const rows = logs.map((l) => ({
|
|
331
|
+
poolHex: l.poolHex,
|
|
332
|
+
tokenAHex: l.tokenAHex,
|
|
333
|
+
tokenBHex: l.tokenBHex,
|
|
334
|
+
}));
|
|
335
|
+
const dirVenues = await hydrateCpPoolVenuesFromRpc(client, rows, { concurrency: 4 });
|
|
336
|
+
const byPool = new Map();
|
|
337
|
+
for (const venue of [...venues, ...dirVenues]) {
|
|
338
|
+
byPool.set(venue.poolHex.toLowerCase(), venue);
|
|
339
|
+
}
|
|
340
|
+
venues = [...byPool.values()];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
registerMeta = { error: e instanceof Error ? e.message : String(e) };
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const lightSnap = await fetchNativeDexDirectorySnapshot(client, { overrides: ov });
|
|
348
|
+
const headHeight = typeof lightSnap.headHeight === 'number' ? lightSnap.headHeight : null;
|
|
349
|
+
let history = {};
|
|
350
|
+
if (opts.historyStore) {
|
|
351
|
+
const raw = await opts.historyStore.get();
|
|
352
|
+
const doc = parseNativeDexIndexerPersistedDoc(raw);
|
|
353
|
+
const next = appendVenuesToHistoryDoc(doc, venues, headHeight ?? 0, nowMs);
|
|
354
|
+
await opts.historyStore.put(JSON.stringify(next));
|
|
355
|
+
history = next.history;
|
|
356
|
+
}
|
|
357
|
+
const pools = [];
|
|
358
|
+
for (const v of venues) {
|
|
359
|
+
const pool = v.poolHex.toLowerCase();
|
|
360
|
+
let swapCount = 0;
|
|
361
|
+
let volumeInSum = 0n;
|
|
362
|
+
let addLiquidityCount = 0;
|
|
363
|
+
let removeLiquidityCount = 0;
|
|
364
|
+
let swapCount24h = 0;
|
|
365
|
+
let volume24h = 0n;
|
|
366
|
+
if (headHeight != null && Number.isFinite(headHeight)) {
|
|
367
|
+
const toB = Math.floor(headHeight);
|
|
368
|
+
const fromB = Math.max(0, toB - logScanBlocks + 1);
|
|
369
|
+
try {
|
|
370
|
+
const logs = await getLogsChunked(client, { fromBlock: fromB, toBlock: toB, address: pool }, { maxConcurrent: 1 });
|
|
371
|
+
const parsed = filterMapNativeAmmRpcLogs(logs);
|
|
372
|
+
const swapHeights = [];
|
|
373
|
+
for (const ev of parsed) {
|
|
374
|
+
if (ev.address && ev.address.toLowerCase() !== pool)
|
|
375
|
+
continue;
|
|
376
|
+
switch (ev.kind) {
|
|
377
|
+
case 'swap':
|
|
378
|
+
swapCount += 1;
|
|
379
|
+
volumeInSum += ev.amountIn;
|
|
380
|
+
swapHeights.push(ev.block_height);
|
|
381
|
+
break;
|
|
382
|
+
case 'addLiquidity':
|
|
383
|
+
addLiquidityCount += 1;
|
|
384
|
+
break;
|
|
385
|
+
case 'removeLiquidity':
|
|
386
|
+
removeLiquidityCount += 1;
|
|
387
|
+
break;
|
|
388
|
+
default:
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const timeMap = await fetchBlockTimeMap(client, swapHeights);
|
|
393
|
+
for (const ev of parsed) {
|
|
394
|
+
if (ev.kind !== 'swap')
|
|
395
|
+
continue;
|
|
396
|
+
if (ev.address && ev.address.toLowerCase() !== pool)
|
|
397
|
+
continue;
|
|
398
|
+
const tms = timeMap.get(ev.block_height);
|
|
399
|
+
if (tms != null && tms >= cutoffMs) {
|
|
400
|
+
swapCount24h += 1;
|
|
401
|
+
volume24h += ev.amountIn;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
/* zeros */
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const tvlUsd = estimatePoolTvlUsd(v.tokenAHex, v.tokenBHex, v.reserveA, v.reserveB, usdMap);
|
|
410
|
+
const rowOut = {
|
|
411
|
+
poolHex: v.poolHex,
|
|
412
|
+
tokenAHex: v.tokenAHex,
|
|
413
|
+
tokenBHex: v.tokenBHex,
|
|
414
|
+
reserveA: v.reserveA.toString(),
|
|
415
|
+
reserveB: v.reserveB.toString(),
|
|
416
|
+
swapCount,
|
|
417
|
+
swapCount24h,
|
|
418
|
+
swaps24h: swapCount24h,
|
|
419
|
+
volume24hApprox: volume24h.toString(),
|
|
420
|
+
volumeScanWindowApprox: volumeInSum.toString(),
|
|
421
|
+
tvlApprox: `reserveA=${v.reserveA.toString()} reserveB=${v.reserveB.toString()}`,
|
|
422
|
+
note: `window ${logScanBlocks} blocks · swapsWindow ${swapCount} · addLiq ${addLiquidityCount} · remLiq ${removeLiquidityCount}`,
|
|
423
|
+
};
|
|
424
|
+
if (tvlUsd != null && Number.isFinite(tvlUsd)) {
|
|
425
|
+
rowOut.tvlUsdApprox = tvlUsd.toFixed(4);
|
|
426
|
+
}
|
|
427
|
+
pools.push(rowOut);
|
|
428
|
+
}
|
|
429
|
+
const tokenDirectory = mergeTokenDirectoryExtra(tokenDirectoryFromVenues(venues), opts.tokenDirectoryExtraJson);
|
|
430
|
+
const noteParts = [
|
|
431
|
+
'boing-sdk nativeDexIndexerStats v2 · volume24h uses block header timestamps (cap unique headers)',
|
|
432
|
+
headHeight != null ? `head ${headHeight}` : 'no head',
|
|
433
|
+
`scanBlocks ${logScanBlocks}`,
|
|
434
|
+
];
|
|
435
|
+
if (registerMeta)
|
|
436
|
+
noteParts.push(`registerLogs ${JSON.stringify(registerMeta)}`);
|
|
437
|
+
await mergePoolDiscoveryFromListDexPoolsRpc(client, d.nativeDexFactoryAccountHex, pools);
|
|
438
|
+
return {
|
|
439
|
+
schemaVersion: 1,
|
|
440
|
+
updatedAt: new Date().toISOString(),
|
|
441
|
+
note: noteParts.join(' · '),
|
|
442
|
+
headHeight,
|
|
443
|
+
pools,
|
|
444
|
+
history,
|
|
445
|
+
tokenDirectory,
|
|
446
|
+
tokens: tokenDirectory,
|
|
447
|
+
};
|
|
448
|
+
}
|