@talismn/balances 3.1.0 → 3.1.2
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/dist/index.d.mts +20 -20
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +17 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +85 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +86 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1694,18 +1694,31 @@ const fetchRpcQueryPack = async (connector, networkId, queries, at) => {
|
|
|
1694
1694
|
return decodeRpcQueryPackChunked(queries, new Map(result.changes), { label: `rpcQueryPack decode ${networkId}` });
|
|
1695
1695
|
};
|
|
1696
1696
|
/**
|
|
1697
|
+
* The connector reports a chain that has been unreachable for a while with a StaleRpcError,
|
|
1698
|
+
* but keeps the subscription registered and re-issues it when a socket reconnects (the
|
|
1699
|
+
* first notification after that is a full snapshot again). Matched by its `type` rather
|
|
1700
|
+
* than `instanceof`, so a second copy of @talismn/chain-connectors still matches.
|
|
1701
|
+
*/
|
|
1702
|
+
const isStaleRpcError = (error) => typeof error === "object" && error !== null && error.type === "STALE_RPC_ERROR";
|
|
1703
|
+
/**
|
|
1697
1704
|
* Wraps the raw state_subscribeStorage subscription and emits, for every storage change
|
|
1698
1705
|
* callback, a SNAPSHOT of the accumulated changes keyed by state key.
|
|
1699
1706
|
*
|
|
1700
1707
|
* A snapshot (shallow copy of string refs, cheap) is required because decoding is chunked
|
|
1701
1708
|
* and asynchronous: a later websocket callback must not mutate the map that an in-flight
|
|
1702
1709
|
* decode is reading.
|
|
1710
|
+
*
|
|
1711
|
+
* A StaleRpcError does not end the stream. Erroring would unsubscribe, and with no
|
|
1712
|
+
* subscription holding the chain's socket, every later request (eg the dtao poll) would
|
|
1713
|
+
* open and close a socket of its own, starting from the network's first RPC each time.
|
|
1703
1714
|
*/
|
|
1704
1715
|
const getRawStorageUpdates$ = (connector, networkId, allStateKeys, timeout) => new rxjs.Observable((subscriber) => {
|
|
1705
1716
|
const changesCache = /* @__PURE__ */ new Map();
|
|
1706
1717
|
const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
|
|
1707
|
-
if (error)
|
|
1708
|
-
|
|
1718
|
+
if (error) {
|
|
1719
|
+
if (isStaleRpcError(error)) log_default.warn(`Storage subscription stale on ${networkId}`);
|
|
1720
|
+
else subscriber.error(error);
|
|
1721
|
+
} else if (result) {
|
|
1709
1722
|
for (const [stateKey, encodedResult] of result.changes) changesCache.set(stateKey, encodedResult);
|
|
1710
1723
|
subscriber.next(new Map(changesCache));
|
|
1711
1724
|
}
|
|
@@ -2510,6 +2523,12 @@ const createBalanceStabilizer = (isEffectivelyEqual, options) => {
|
|
|
2510
2523
|
};
|
|
2511
2524
|
};
|
|
2512
2525
|
/**
|
|
2526
|
+
* Cross-module drift tolerance (see classifySmallAmountDrift): amounts moving by no more
|
|
2527
|
+
* than this classify as drift and re-emit at most once per stabilizer refresh interval.
|
|
2528
|
+
* Kept tight — a real incoming transfer above 0.01% of the position emits immediately.
|
|
2529
|
+
*/
|
|
2530
|
+
const GENERIC_DRIFT_TOLERANCE_BPS = 1n;
|
|
2531
|
+
/**
|
|
2513
2532
|
* Default cross-module drift classifier: balances whose SHAPE is identical (same
|
|
2514
2533
|
* address/token/status, same value set with same types/labels/metas) but whose amounts
|
|
2515
2534
|
* moved by no more than `toleranceBps` classify as "drift" — continuously-moving
|
|
@@ -2734,7 +2753,7 @@ const fetchOnChainTokenData$1 = async (connector, tokenId) => {
|
|
|
2734
2753
|
//#region src/modules/sol-spl/fetchBalances.ts
|
|
2735
2754
|
const SPL_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
2736
2755
|
const dynamicTokenMetadataCache$1 = new _talismn_util.LruMap(1024);
|
|
2737
|
-
const associatedTokenAccountCache = new _talismn_util.LruMap(1024);
|
|
2756
|
+
const associatedTokenAccountCache$1 = new _talismn_util.LruMap(1024);
|
|
2738
2757
|
const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) => {
|
|
2739
2758
|
if (!tokensWithAddresses.length) return {
|
|
2740
2759
|
success: [],
|
|
@@ -2754,7 +2773,7 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
2754
2773
|
return {
|
|
2755
2774
|
mintAddress,
|
|
2756
2775
|
value,
|
|
2757
|
-
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address, mintAddress)
|
|
2776
|
+
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount$1(address, mintAddress)
|
|
2758
2777
|
};
|
|
2759
2778
|
} catch {
|
|
2760
2779
|
log_default.warn("Failed to parse token amount", {
|
|
@@ -2847,16 +2866,16 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
2847
2866
|
};
|
|
2848
2867
|
};
|
|
2849
2868
|
const getBalanceKey$1 = (tokenId, address) => `${tokenId}:${address}`;
|
|
2850
|
-
const getAssociatedTokenAccount = async (owner, mint) => {
|
|
2869
|
+
const getAssociatedTokenAccount$1 = async (owner, mint) => {
|
|
2851
2870
|
const key = `${owner}:${mint}`;
|
|
2852
|
-
const cached = associatedTokenAccountCache.get(key);
|
|
2871
|
+
const cached = associatedTokenAccountCache$1.get(key);
|
|
2853
2872
|
if (cached) return cached;
|
|
2854
2873
|
const [associatedTokenAccount] = await (0, _solana_program_token.findAssociatedTokenPda)({
|
|
2855
2874
|
mint: (0, _solana_kit.address)(mint),
|
|
2856
2875
|
owner: (0, _solana_kit.address)(owner),
|
|
2857
2876
|
tokenProgram: _solana_program_token.TOKEN_PROGRAM_ADDRESS
|
|
2858
2877
|
});
|
|
2859
|
-
associatedTokenAccountCache.set(key, associatedTokenAccount);
|
|
2878
|
+
associatedTokenAccountCache$1.set(key, associatedTokenAccount);
|
|
2860
2879
|
return associatedTokenAccount;
|
|
2861
2880
|
};
|
|
2862
2881
|
//#endregion
|
|
@@ -3306,6 +3325,7 @@ const fetchOnChainTokenData = async (connector, tokenId) => {
|
|
|
3306
3325
|
//#region src/modules/sol-token2022/fetchBalances.ts
|
|
3307
3326
|
const TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
3308
3327
|
const dynamicTokenMetadataCache = /* @__PURE__ */ new Map();
|
|
3328
|
+
const associatedTokenAccountCache = new _talismn_util.LruMap(1024);
|
|
3309
3329
|
const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) => {
|
|
3310
3330
|
if (!tokensWithAddresses.length) return {
|
|
3311
3331
|
success: [],
|
|
@@ -3317,19 +3337,15 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3317
3337
|
const knownTokenIds = new Set(tokensWithAddresses.map(([token]) => token.id));
|
|
3318
3338
|
const unknownMints = /* @__PURE__ */ new Set();
|
|
3319
3339
|
const balancesPerAddress = await Promise.all(accountAddresses.map(async (address) => {
|
|
3320
|
-
const
|
|
3340
|
+
const tokenAccounts = await rpc.getTokenAccountsByOwner((0, _solana_kit.address)(address), { programId: (0, _solana_kit.address)(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send();
|
|
3341
|
+
const tokenAccountHoldings = await Promise.all(tokenAccounts.value.map(async (d) => {
|
|
3321
3342
|
try {
|
|
3322
|
-
const mintAddress = d.account.data.parsed.info
|
|
3323
|
-
const value =
|
|
3324
|
-
const tokenId = (0, _talismn_chaindata_provider.solToken2022TokenId)(networkId, mintAddress);
|
|
3325
|
-
if (!knownTokenIds.has(tokenId) && value !== "0") unknownMints.add(mintAddress);
|
|
3343
|
+
const { mint: mintAddress, state, tokenAmount } = d.account.data.parsed.info;
|
|
3344
|
+
const value = BigInt(tokenAmount.amount ?? "0");
|
|
3326
3345
|
return {
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
address,
|
|
3330
|
-
source: MODULE_TYPE$7,
|
|
3331
|
-
status: "live",
|
|
3332
|
-
value
|
|
3346
|
+
mintAddress,
|
|
3347
|
+
value,
|
|
3348
|
+
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address, mintAddress)
|
|
3333
3349
|
};
|
|
3334
3350
|
} catch {
|
|
3335
3351
|
log_default.warn("Failed to parse token amount", {
|
|
@@ -3338,7 +3354,36 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3338
3354
|
});
|
|
3339
3355
|
return null;
|
|
3340
3356
|
}
|
|
3341
|
-
})
|
|
3357
|
+
}));
|
|
3358
|
+
const holdingsPerTokenId = /* @__PURE__ */ new Map();
|
|
3359
|
+
for (const { mintAddress, value, isSpendable } of tokenAccountHoldings.filter(_talismn_util.isNotNil)) {
|
|
3360
|
+
const tokenId = (0, _talismn_chaindata_provider.solToken2022TokenId)(networkId, mintAddress);
|
|
3361
|
+
if (!knownTokenIds.has(tokenId) && value !== 0n) unknownMints.add(mintAddress);
|
|
3362
|
+
const { total, spendable } = holdingsPerTokenId.get(tokenId) ?? {
|
|
3363
|
+
total: 0n,
|
|
3364
|
+
spendable: 0n
|
|
3365
|
+
};
|
|
3366
|
+
holdingsPerTokenId.set(tokenId, {
|
|
3367
|
+
total: total + value,
|
|
3368
|
+
spendable: spendable + (isSpendable ? value : 0n)
|
|
3369
|
+
});
|
|
3370
|
+
}
|
|
3371
|
+
const balances = [...holdingsPerTokenId].map(([tokenId, { total, spendable }]) => ({
|
|
3372
|
+
tokenId,
|
|
3373
|
+
networkId,
|
|
3374
|
+
address,
|
|
3375
|
+
source: MODULE_TYPE$7,
|
|
3376
|
+
status: "live",
|
|
3377
|
+
...spendable === total ? { value: total.toString() } : { values: [{
|
|
3378
|
+
type: "free",
|
|
3379
|
+
label: "free",
|
|
3380
|
+
amount: total.toString()
|
|
3381
|
+
}, {
|
|
3382
|
+
type: "locked",
|
|
3383
|
+
label: "locked",
|
|
3384
|
+
amount: (total - spendable).toString()
|
|
3385
|
+
}] }
|
|
3386
|
+
}));
|
|
3342
3387
|
setDetectedTokenIds(address, MODULE_TYPE$7, balances.map((b) => b.tokenId));
|
|
3343
3388
|
return [address, balances];
|
|
3344
3389
|
}));
|
|
@@ -3393,6 +3438,18 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3393
3438
|
};
|
|
3394
3439
|
};
|
|
3395
3440
|
const getBalanceKey = (tokenId, address) => `${tokenId}:${address}`;
|
|
3441
|
+
const getAssociatedTokenAccount = async (owner, mint) => {
|
|
3442
|
+
const key = `${owner}:${mint}`;
|
|
3443
|
+
const cached = associatedTokenAccountCache.get(key);
|
|
3444
|
+
if (cached) return cached;
|
|
3445
|
+
const [associatedTokenAccount] = await (0, _solana_program_token.findAssociatedTokenPda)({
|
|
3446
|
+
mint: (0, _solana_kit.address)(mint),
|
|
3447
|
+
owner: (0, _solana_kit.address)(owner),
|
|
3448
|
+
tokenProgram: _solana_program_token_2022.TOKEN_2022_PROGRAM_ADDRESS
|
|
3449
|
+
});
|
|
3450
|
+
associatedTokenAccountCache.set(key, associatedTokenAccount);
|
|
3451
|
+
return associatedTokenAccount;
|
|
3452
|
+
};
|
|
3396
3453
|
//#endregion
|
|
3397
3454
|
//#region src/modules/sol-token2022/fetchTokens.ts
|
|
3398
3455
|
const fetchTokens$7 = async ({ networkId, tokens, connector, cache }) => {
|
|
@@ -4458,16 +4515,10 @@ const getTransferCallData$5 = ({ from, to, value, token, metadataRpc }) => {
|
|
|
4458
4515
|
* dividend accrual on (nearly) every block; the display is informational */
|
|
4459
4516
|
const CLAIM_DRIFT_TOLERANCE_BPS = 100n;
|
|
4460
4517
|
/**
|
|
4461
|
-
* subnet staking positions auto-compound: dividend injections land once per subnet tempo
|
|
4462
|
-
* (staggered across subnets, so with many positions SOME position bumps on nearly every
|
|
4463
|
-
* poll). Moves up to this size classify as drift (surfaced within the refresh interval);
|
|
4464
|
-
* anything larger (a real stake/unstake) emits immediately.
|
|
4465
|
-
*/
|
|
4466
|
-
const STAKE_DRIFT_TOLERANCE_BPS = 100n;
|
|
4467
|
-
/**
|
|
4468
4518
|
* dtao balances embed values that drift on (nearly) every block even when the user's
|
|
4469
|
-
* position is untouched:
|
|
4470
|
-
*
|
|
4519
|
+
* position is untouched: basket claimable quotes move and conviction locks decay.
|
|
4520
|
+
* Stake itself only moves on dividend injections, once per subnet tempo, so every stake
|
|
4521
|
+
* move emits immediately.
|
|
4471
4522
|
*
|
|
4472
4523
|
* Without special handling, every 6s poll re-emits the full result set, which defeats
|
|
4473
4524
|
* every distinctUntilChanged stage downstream and forces the whole pipeline
|
|
@@ -4518,7 +4569,6 @@ const classifyValue = (previous, next) => {
|
|
|
4518
4569
|
if (previous.label === "Claimable rewards") {
|
|
4519
4570
|
if (!isWithinDriftTolerance(previous.amount, next.amount, CLAIM_DRIFT_TOLERANCE_BPS)) drift = true;
|
|
4520
4571
|
} else if (previousLock) drift = true;
|
|
4521
|
-
else if (isWithinDriftTolerance(previous.amount, next.amount, STAKE_DRIFT_TOLERANCE_BPS)) drift = true;
|
|
4522
4572
|
else return changed(`amount ${previous.amount}→${next.amount}`);
|
|
4523
4573
|
}
|
|
4524
4574
|
return { equivalence: drift ? "drift" : "equal" };
|
|
@@ -6801,12 +6851,8 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
|
|
|
6801
6851
|
};
|
|
6802
6852
|
//#endregion
|
|
6803
6853
|
//#region src/BalancesProvider.ts
|
|
6804
|
-
/**
|
|
6805
|
-
|
|
6806
|
-
* than this classify as drift and re-emit at most once per stabilizer refresh interval.
|
|
6807
|
-
* Kept tight — a real incoming transfer above 0.1% of the position emits immediately.
|
|
6808
|
-
*/
|
|
6809
|
-
const GENERIC_DRIFT_TOLERANCE_BPS = 10n;
|
|
6854
|
+
/** Delay before resubscribing a substrate module stream that errored: 2s, doubling, capped at 1min */
|
|
6855
|
+
const substrateModuleRetryDelay = (retryCount) => (0, rxjs.timer)(Math.min(6e4, 2e3 * 2 ** (retryCount - 1)));
|
|
6810
6856
|
const DEFAULT_STORAGE = {
|
|
6811
6857
|
balances: [],
|
|
6812
6858
|
miniMetadatas: []
|
|
@@ -6944,7 +6990,10 @@ var BalancesProvider = class {
|
|
|
6944
6990
|
connector: this.#chainConnectors.substrate,
|
|
6945
6991
|
miniMetadata,
|
|
6946
6992
|
hasFrontierMirrorWithdraw
|
|
6947
|
-
})), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), (0, rxjs.
|
|
6993
|
+
})), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), (0, rxjs.retry)({
|
|
6994
|
+
delay: (_error, retryCount) => substrateModuleRetryDelay(retryCount),
|
|
6995
|
+
resetOnSuccess: true
|
|
6996
|
+
}), (0, rxjs.tap)((results) => {
|
|
6948
6997
|
(0, _talismn_util.reportJsActivity)(`module ${mod.type} ${networkId} (${results.success.length})`);
|
|
6949
6998
|
if (results.dynamicTokens?.length) this.#chaindataProvider.registerDynamicTokens(results.dynamicTokens);
|
|
6950
6999
|
}), (0, rxjs.map)((results) => ({
|