@talismn/balances 3.1.0 → 3.1.1
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 +48 -48
- package/dist/index.d.mts.map +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +76 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +77 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { EvmErc20TokenSchema, EvmNativeTokenSchema, EvmUniswapV2TokenSchema, MIN
|
|
|
3
3
|
import { frontierH160ToSs58Mirror, frontierSs58ToTruncatedH160, getAccountPlatformFromAddress, isAddressEqual, isEthereumAddress, isOnCurveSolanaAddress, isSolanaAddress, normalizeAddress } from "@talismn/crypto";
|
|
4
4
|
import { BigMath, LruMap, createTimeSlicer, forEachWithYield, getErrorMessage, getSharedObservable, isAbortError, isArrayOf, isBigInt, isErrorOfName, isNotNil, isTruthy, keepAlive, mapWithYield, planckToTokens, reportJsActivity, switchMapChunked, yieldToEventLoop } from "@talismn/util";
|
|
5
5
|
import { assign, fromPairs, isEqual, keyBy, keys, mergeWith, omit, toPairs, uniq, values } from "lodash-es";
|
|
6
|
-
import { BehaviorSubject, EMPTY, Observable, ReplaySubject, auditTime, catchError, combineLatest, defer, distinctUntilChanged, filter, firstValueFrom, from, map, of, shareReplay, startWith, switchMap, tap, timer } from "rxjs";
|
|
6
|
+
import { BehaviorSubject, EMPTY, Observable, ReplaySubject, auditTime, catchError, combineLatest, defer, distinctUntilChanged, filter, firstValueFrom, from, map, of, retry, shareReplay, startWith, switchMap, tap, timer } from "rxjs";
|
|
7
7
|
import { encodeFunctionData, erc20Abi, erc20Abi_bytes32, getContract, hexToString, parseAbi, withRetry } from "viem";
|
|
8
8
|
import { newTokenRates } from "@talismn/token-rates";
|
|
9
9
|
import anylogger from "anylogger";
|
|
@@ -1666,18 +1666,31 @@ const fetchRpcQueryPack = async (connector, networkId, queries, at) => {
|
|
|
1666
1666
|
return decodeRpcQueryPackChunked(queries, new Map(result.changes), { label: `rpcQueryPack decode ${networkId}` });
|
|
1667
1667
|
};
|
|
1668
1668
|
/**
|
|
1669
|
+
* The connector reports a chain that has been unreachable for a while with a StaleRpcError,
|
|
1670
|
+
* but keeps the subscription registered and re-issues it when a socket reconnects (the
|
|
1671
|
+
* first notification after that is a full snapshot again). Matched by its `type` rather
|
|
1672
|
+
* than `instanceof`, so a second copy of @talismn/chain-connectors still matches.
|
|
1673
|
+
*/
|
|
1674
|
+
const isStaleRpcError = (error) => typeof error === "object" && error !== null && error.type === "STALE_RPC_ERROR";
|
|
1675
|
+
/**
|
|
1669
1676
|
* Wraps the raw state_subscribeStorage subscription and emits, for every storage change
|
|
1670
1677
|
* callback, a SNAPSHOT of the accumulated changes keyed by state key.
|
|
1671
1678
|
*
|
|
1672
1679
|
* A snapshot (shallow copy of string refs, cheap) is required because decoding is chunked
|
|
1673
1680
|
* and asynchronous: a later websocket callback must not mutate the map that an in-flight
|
|
1674
1681
|
* decode is reading.
|
|
1682
|
+
*
|
|
1683
|
+
* A StaleRpcError does not end the stream. Erroring would unsubscribe, and with no
|
|
1684
|
+
* subscription holding the chain's socket, every later request (eg the dtao poll) would
|
|
1685
|
+
* open and close a socket of its own, starting from the network's first RPC each time.
|
|
1675
1686
|
*/
|
|
1676
1687
|
const getRawStorageUpdates$ = (connector, networkId, allStateKeys, timeout) => new Observable((subscriber) => {
|
|
1677
1688
|
const changesCache = /* @__PURE__ */ new Map();
|
|
1678
1689
|
const promUnsub = connector.subscribe(networkId, "state_subscribeStorage", "state_storage", [allStateKeys], (error, result) => {
|
|
1679
|
-
if (error)
|
|
1680
|
-
|
|
1690
|
+
if (error) {
|
|
1691
|
+
if (isStaleRpcError(error)) log_default.warn(`Storage subscription stale on ${networkId}`);
|
|
1692
|
+
else subscriber.error(error);
|
|
1693
|
+
} else if (result) {
|
|
1681
1694
|
for (const [stateKey, encodedResult] of result.changes) changesCache.set(stateKey, encodedResult);
|
|
1682
1695
|
subscriber.next(new Map(changesCache));
|
|
1683
1696
|
}
|
|
@@ -2706,7 +2719,7 @@ const fetchOnChainTokenData$1 = async (connector, tokenId) => {
|
|
|
2706
2719
|
//#region src/modules/sol-spl/fetchBalances.ts
|
|
2707
2720
|
const SPL_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
2708
2721
|
const dynamicTokenMetadataCache$1 = new LruMap(1024);
|
|
2709
|
-
const associatedTokenAccountCache = new LruMap(1024);
|
|
2722
|
+
const associatedTokenAccountCache$1 = new LruMap(1024);
|
|
2710
2723
|
const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) => {
|
|
2711
2724
|
if (!tokensWithAddresses.length) return {
|
|
2712
2725
|
success: [],
|
|
@@ -2726,7 +2739,7 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
2726
2739
|
return {
|
|
2727
2740
|
mintAddress,
|
|
2728
2741
|
value,
|
|
2729
|
-
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address$2, mintAddress)
|
|
2742
|
+
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount$1(address$2, mintAddress)
|
|
2730
2743
|
};
|
|
2731
2744
|
} catch {
|
|
2732
2745
|
log_default.warn("Failed to parse token amount", {
|
|
@@ -2819,16 +2832,16 @@ const fetchBalances$8 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
2819
2832
|
};
|
|
2820
2833
|
};
|
|
2821
2834
|
const getBalanceKey$1 = (tokenId, address) => `${tokenId}:${address}`;
|
|
2822
|
-
const getAssociatedTokenAccount = async (owner, mint) => {
|
|
2835
|
+
const getAssociatedTokenAccount$1 = async (owner, mint) => {
|
|
2823
2836
|
const key = `${owner}:${mint}`;
|
|
2824
|
-
const cached = associatedTokenAccountCache.get(key);
|
|
2837
|
+
const cached = associatedTokenAccountCache$1.get(key);
|
|
2825
2838
|
if (cached) return cached;
|
|
2826
2839
|
const [associatedTokenAccount] = await findAssociatedTokenPda({
|
|
2827
2840
|
mint: address(mint),
|
|
2828
2841
|
owner: address(owner),
|
|
2829
2842
|
tokenProgram: TOKEN_PROGRAM_ADDRESS
|
|
2830
2843
|
});
|
|
2831
|
-
associatedTokenAccountCache.set(key, associatedTokenAccount);
|
|
2844
|
+
associatedTokenAccountCache$1.set(key, associatedTokenAccount);
|
|
2832
2845
|
return associatedTokenAccount;
|
|
2833
2846
|
};
|
|
2834
2847
|
//#endregion
|
|
@@ -3278,6 +3291,7 @@ const fetchOnChainTokenData = async (connector, tokenId) => {
|
|
|
3278
3291
|
//#region src/modules/sol-token2022/fetchBalances.ts
|
|
3279
3292
|
const TOKEN_2022_PROGRAM_ID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb";
|
|
3280
3293
|
const dynamicTokenMetadataCache = /* @__PURE__ */ new Map();
|
|
3294
|
+
const associatedTokenAccountCache = new LruMap(1024);
|
|
3281
3295
|
const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) => {
|
|
3282
3296
|
if (!tokensWithAddresses.length) return {
|
|
3283
3297
|
success: [],
|
|
@@ -3289,19 +3303,15 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3289
3303
|
const knownTokenIds = new Set(tokensWithAddresses.map(([token]) => token.id));
|
|
3290
3304
|
const unknownMints = /* @__PURE__ */ new Set();
|
|
3291
3305
|
const balancesPerAddress = await Promise.all(accountAddresses.map(async (address$1) => {
|
|
3292
|
-
const
|
|
3306
|
+
const tokenAccounts = await rpc.getTokenAccountsByOwner(address(address$1), { programId: address(TOKEN_2022_PROGRAM_ID) }, { encoding: "jsonParsed" }).send();
|
|
3307
|
+
const tokenAccountHoldings = await Promise.all(tokenAccounts.value.map(async (d) => {
|
|
3293
3308
|
try {
|
|
3294
|
-
const mintAddress = d.account.data.parsed.info
|
|
3295
|
-
const value =
|
|
3296
|
-
const tokenId = solToken2022TokenId(networkId, mintAddress);
|
|
3297
|
-
if (!knownTokenIds.has(tokenId) && value !== "0") unknownMints.add(mintAddress);
|
|
3309
|
+
const { mint: mintAddress, state, tokenAmount } = d.account.data.parsed.info;
|
|
3310
|
+
const value = BigInt(tokenAmount.amount ?? "0");
|
|
3298
3311
|
return {
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
source: MODULE_TYPE$7,
|
|
3303
|
-
status: "live",
|
|
3304
|
-
value
|
|
3312
|
+
mintAddress,
|
|
3313
|
+
value,
|
|
3314
|
+
isSpendable: value !== 0n && state !== "frozen" && d.pubkey === await getAssociatedTokenAccount(address$1, mintAddress)
|
|
3305
3315
|
};
|
|
3306
3316
|
} catch {
|
|
3307
3317
|
log_default.warn("Failed to parse token amount", {
|
|
@@ -3310,7 +3320,36 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3310
3320
|
});
|
|
3311
3321
|
return null;
|
|
3312
3322
|
}
|
|
3313
|
-
})
|
|
3323
|
+
}));
|
|
3324
|
+
const holdingsPerTokenId = /* @__PURE__ */ new Map();
|
|
3325
|
+
for (const { mintAddress, value, isSpendable } of tokenAccountHoldings.filter(isNotNil)) {
|
|
3326
|
+
const tokenId = solToken2022TokenId(networkId, mintAddress);
|
|
3327
|
+
if (!knownTokenIds.has(tokenId) && value !== 0n) unknownMints.add(mintAddress);
|
|
3328
|
+
const { total, spendable } = holdingsPerTokenId.get(tokenId) ?? {
|
|
3329
|
+
total: 0n,
|
|
3330
|
+
spendable: 0n
|
|
3331
|
+
};
|
|
3332
|
+
holdingsPerTokenId.set(tokenId, {
|
|
3333
|
+
total: total + value,
|
|
3334
|
+
spendable: spendable + (isSpendable ? value : 0n)
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
const balances = [...holdingsPerTokenId].map(([tokenId, { total, spendable }]) => ({
|
|
3338
|
+
tokenId,
|
|
3339
|
+
networkId,
|
|
3340
|
+
address: address$1,
|
|
3341
|
+
source: MODULE_TYPE$7,
|
|
3342
|
+
status: "live",
|
|
3343
|
+
...spendable === total ? { value: total.toString() } : { values: [{
|
|
3344
|
+
type: "free",
|
|
3345
|
+
label: "free",
|
|
3346
|
+
amount: total.toString()
|
|
3347
|
+
}, {
|
|
3348
|
+
type: "locked",
|
|
3349
|
+
label: "locked",
|
|
3350
|
+
amount: (total - spendable).toString()
|
|
3351
|
+
}] }
|
|
3352
|
+
}));
|
|
3314
3353
|
setDetectedTokenIds(address$1, MODULE_TYPE$7, balances.map((b) => b.tokenId));
|
|
3315
3354
|
return [address$1, balances];
|
|
3316
3355
|
}));
|
|
@@ -3365,6 +3404,18 @@ const fetchBalances$7 = async ({ networkId, tokensWithAddresses, connector }) =>
|
|
|
3365
3404
|
};
|
|
3366
3405
|
};
|
|
3367
3406
|
const getBalanceKey = (tokenId, address) => `${tokenId}:${address}`;
|
|
3407
|
+
const getAssociatedTokenAccount = async (owner, mint) => {
|
|
3408
|
+
const key = `${owner}:${mint}`;
|
|
3409
|
+
const cached = associatedTokenAccountCache.get(key);
|
|
3410
|
+
if (cached) return cached;
|
|
3411
|
+
const [associatedTokenAccount] = await findAssociatedTokenPda({
|
|
3412
|
+
mint: address(mint),
|
|
3413
|
+
owner: address(owner),
|
|
3414
|
+
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS
|
|
3415
|
+
});
|
|
3416
|
+
associatedTokenAccountCache.set(key, associatedTokenAccount);
|
|
3417
|
+
return associatedTokenAccount;
|
|
3418
|
+
};
|
|
3368
3419
|
//#endregion
|
|
3369
3420
|
//#region src/modules/sol-token2022/fetchTokens.ts
|
|
3370
3421
|
const fetchTokens$7 = async ({ networkId, tokens, connector, cache }) => {
|
|
@@ -6779,6 +6830,8 @@ const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, sp
|
|
|
6779
6830
|
* Kept tight — a real incoming transfer above 0.1% of the position emits immediately.
|
|
6780
6831
|
*/
|
|
6781
6832
|
const GENERIC_DRIFT_TOLERANCE_BPS = 10n;
|
|
6833
|
+
/** Delay before resubscribing a substrate module stream that errored: 2s, doubling, capped at 1min */
|
|
6834
|
+
const substrateModuleRetryDelay = (retryCount) => timer(Math.min(6e4, 2e3 * 2 ** (retryCount - 1)));
|
|
6782
6835
|
const DEFAULT_STORAGE = {
|
|
6783
6836
|
balances: [],
|
|
6784
6837
|
miniMetadatas: []
|
|
@@ -6916,7 +6969,10 @@ var BalancesProvider = class {
|
|
|
6916
6969
|
connector: this.#chainConnectors.substrate,
|
|
6917
6970
|
miniMetadata,
|
|
6918
6971
|
hasFrontierMirrorWithdraw
|
|
6919
|
-
})), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)),
|
|
6972
|
+
})), stabilizeModuleResults(mod.type === "substrate-dtao" ? void 0 : classifySmallAmountDrift(GENERIC_DRIFT_TOLERANCE_BPS)), retry({
|
|
6973
|
+
delay: (_error, retryCount) => substrateModuleRetryDelay(retryCount),
|
|
6974
|
+
resetOnSuccess: true
|
|
6975
|
+
}), tap((results) => {
|
|
6920
6976
|
reportJsActivity(`module ${mod.type} ${networkId} (${results.success.length})`);
|
|
6921
6977
|
if (results.dynamicTokens?.length) this.#chaindataProvider.registerDynamicTokens(results.dynamicTokens);
|
|
6922
6978
|
}), map((results) => ({
|