@net-protocol/score 0.1.8 → 0.1.10

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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readContract } from 'viem/actions';
2
- import { getPublicClient, getBaseDataSuffix } from '@net-protocol/core';
2
+ import { getPublicClient, getBaseDataSuffix, NetClient } from '@net-protocol/core';
3
3
  import { decodeAbiParameters, keccak256, encodePacked, getAddress, encodeAbiParameters, isAddressEqual } from 'viem';
4
- import { getStorageKeyBytes } from '@net-protocol/storage';
4
+ import { getStorageKeyBytes, STORAGE_CONTRACT } from '@net-protocol/storage';
5
5
 
6
6
  // src/client/ScoreClient.ts
7
7
 
@@ -2482,6 +2482,44 @@ var user_upvote_default = [
2482
2482
  { type: "error", name: "ZeroUpvotes", inputs: [] }
2483
2483
  ];
2484
2484
 
2485
+ // src/abis/erc20-bulk-info-helper.json
2486
+ var erc20_bulk_info_helper_default = [
2487
+ {
2488
+ type: "function",
2489
+ name: "getTokenInfo",
2490
+ inputs: [
2491
+ {
2492
+ name: "tokenAddresses",
2493
+ type: "address[]",
2494
+ internalType: "address[]"
2495
+ }
2496
+ ],
2497
+ outputs: [
2498
+ {
2499
+ name: "tokenInfos",
2500
+ type: "tuple[]",
2501
+ internalType: "struct ERC20BulkInfoHelper.TokenInfo[]",
2502
+ components: [
2503
+ { name: "name", type: "string", internalType: "string" },
2504
+ { name: "symbol", type: "string", internalType: "string" },
2505
+ { name: "decimals", type: "uint8", internalType: "uint8" },
2506
+ {
2507
+ name: "totalSupply",
2508
+ type: "uint256",
2509
+ internalType: "uint256"
2510
+ },
2511
+ {
2512
+ name: "burnedTokens",
2513
+ type: "uint256",
2514
+ internalType: "uint256"
2515
+ }
2516
+ ]
2517
+ }
2518
+ ],
2519
+ stateMutability: "view"
2520
+ }
2521
+ ];
2522
+
2485
2523
  // src/constants.ts
2486
2524
  var SCORE_CONTRACT = {
2487
2525
  address: "0x0000000fa09b022e5616e5a173b4b67fa2fbcf28",
@@ -2526,8 +2564,10 @@ var MULTI_VERSION_UNISWAP_POOL_INFO_RETRIEVER = {
2526
2564
  var WETH_BY_CHAIN = {
2527
2565
  8453: "0x4200000000000000000000000000000000000006",
2528
2566
  // Base (L2 predeploy)
2529
- 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
2567
+ 1: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
2530
2568
  // Ethereum mainnet
2569
+ 4663: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73"
2570
+ // Robinhood Chain (non-standard WETH; not the OP predeploy)
2531
2571
  };
2532
2572
  function getWethAddress(chainId) {
2533
2573
  const addr = WETH_BY_CHAIN[chainId];
@@ -2542,6 +2582,10 @@ var USER_UPVOTE_CONTRACT = {
2542
2582
  address: "0xa4bc2c63dd0157692fd5f409389e5032e37d8895",
2543
2583
  abi: user_upvote_default
2544
2584
  };
2585
+ var ERC20_BULK_INFO_HELPER_CONTRACT = {
2586
+ address: "0x00000051809cbfacdf7d08ada813836822b880a2",
2587
+ abi: erc20_bulk_info_helper_default
2588
+ };
2545
2589
  var encodeUpvoteKey = (tokenAddress) => {
2546
2590
  return `0x${BigInt(`0x${tokenAddress.slice(2)}`).toString(16).padStart(64, "0")}`;
2547
2591
  };
@@ -3207,41 +3251,6 @@ var UserUpvoteClient = class {
3207
3251
  return hash;
3208
3252
  }
3209
3253
  };
3210
- var encodePoolKey = (poolKey) => {
3211
- if (!poolKey || !isValidPoolKey2(poolKey)) {
3212
- return "0x";
3213
- }
3214
- try {
3215
- return encodeAbiParameters(
3216
- [
3217
- {
3218
- type: "tuple",
3219
- components: [
3220
- { name: "currency0", type: "address" },
3221
- { name: "currency1", type: "address" },
3222
- { name: "fee", type: "uint24" },
3223
- { name: "tickSpacing", type: "int24" },
3224
- { name: "hooks", type: "address" }
3225
- ]
3226
- }
3227
- ],
3228
- [
3229
- {
3230
- currency0: poolKey.currency0,
3231
- currency1: poolKey.currency1,
3232
- fee: poolKey.fee,
3233
- tickSpacing: poolKey.tickSpacing,
3234
- hooks: poolKey.hooks
3235
- }
3236
- ]
3237
- );
3238
- } catch {
3239
- return "0x";
3240
- }
3241
- };
3242
- function isValidPoolKey2(poolKey) {
3243
- return poolKey.fee !== void 0 || poolKey.tickSpacing !== void 0 || poolKey.currency0 !== void 0;
3244
- }
3245
3254
  var V3_V4_FEE_TIERS = [500, 3e3, 1e4, 12e3, 8388608];
3246
3255
  var V4_TICK_SPACINGS = [200];
3247
3256
  var V4_HOOKS = [
@@ -3595,6 +3604,433 @@ async function discoverTokenPool({
3595
3604
  return results[0] ?? null;
3596
3605
  }
3597
3606
 
3598
- export { ALL_STRATEGY_ADDRESSES, DYNAMIC_SPLIT_STRATEGY, LEGACY_UPVOTE_V1_ADDRESS, LEGACY_UPVOTE_V2_ADDRESS, MULTI_VERSION_UNISWAP_BULK_POOL_FINDER, MULTI_VERSION_UNISWAP_POOL_INFO_RETRIEVER, NULL_ADDRESS, PURE_ALPHA_STRATEGY, SCORE_CONTRACT, SUPPORTED_SCORE_CHAINS, ScoreClient, UNIV234_POOLS_STRATEGY, UPVOTE_APP, UPVOTE_PRICE_ETH, UPVOTE_STORAGE_APP, USER_UPVOTE_CONTRACT, UserUpvoteClient, buildUserUpvote, buildUserUpvoteReceived, calculatePriceFromSqrtPriceX96, calculatePriceInUsdc, calculateUpvoteCost, calculateUserTokenBalance, decodeStrategyMetadata, decodeUpvoteMessage, decodeUpvoteStorageBlob, discoverPools, discoverTokenPool, encodePoolKey, encodeUpvoteKey, extractStrategyAddress, extractTokenAddressFromScoreKey, extractTokenAddressesFromMessages, getFeedContentKey, getScoreKey, getStorageScoreKey, getStorageUpvoteContext, getTokenScoreKey, getWethAddress, isDynamicSplitStrategy, isPureAlphaStrategy, isStrategyMessage, isTokenScoreKey, isUniv234PoolsStrategy, isUserUpvoteMessage, parseUserUpvoteMessage, selectStrategy, tokenAddressToUpvoteKeyString, validateUpvoteParams, validateUserUpvoteMessage };
3607
+ // src/ranking/getTokenRankings.ts
3608
+ var DEFAULT_MESSAGE_SCAN_WINDOW = 150;
3609
+ var DEFAULT_MAX_TOKENS = 50;
3610
+ var DEFAULT_MIN_UPVOTES = 500;
3611
+ var DEFAULT_MIN_MARKET_CAP = 4e4;
3612
+ var DEFAULT_RECENCY_HOURS = 48;
3613
+ var USDC_ADDRESS_BY_CHAIN = {
3614
+ 8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
3615
+ };
3616
+ var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
3617
+ async function getTokenRankings(options) {
3618
+ const {
3619
+ chainId,
3620
+ sort = "trending",
3621
+ maxTokens = DEFAULT_MAX_TOKENS,
3622
+ messageScanWindow = DEFAULT_MESSAGE_SCAN_WINDOW,
3623
+ rpcUrl
3624
+ } = options;
3625
+ validateChainSupported(chainId);
3626
+ const minUpvotes = options.thresholds?.minUpvotes ?? DEFAULT_MIN_UPVOTES;
3627
+ const minMarketCap = options.thresholds?.minMarketCap ?? DEFAULT_MIN_MARKET_CAP;
3628
+ const recencyHours = options.thresholds?.recencyHours ?? DEFAULT_RECENCY_HOURS;
3629
+ const nowSec = Date.now() / 1e3;
3630
+ const rpcOverride = rpcUrl ? { rpcUrls: Array.isArray(rpcUrl) ? rpcUrl : [rpcUrl] } : void 0;
3631
+ const publicClient = getPublicClient({ chainId, rpcUrl });
3632
+ const netClient = new NetClient({ chainId, overrides: rpcOverride });
3633
+ const messageGroups = await fetchUpvoteMessages({
3634
+ netClient,
3635
+ messageScanWindow
3636
+ });
3637
+ const storageBlobByKey = await fetchStrategyStorage({
3638
+ publicClient,
3639
+ messageGroups
3640
+ });
3641
+ const wethAddress = getWethAddress(chainId);
3642
+ const { tokenAddresses, latestUpvoteTimestamps } = aggregateAndRank({
3643
+ messageGroups,
3644
+ storageBlobByKey,
3645
+ sort,
3646
+ maxTokens,
3647
+ excludeAddresses: [wethAddress.toLowerCase()],
3648
+ nowSec
3649
+ });
3650
+ if (tokenAddresses.length === 0) return [];
3651
+ const usdcAddress = USDC_ADDRESS_BY_CHAIN[chainId];
3652
+ const pairs = [
3653
+ ...tokenAddresses.map((tokenAddress) => ({
3654
+ tokenAddress,
3655
+ baseTokenAddress: wethAddress
3656
+ })),
3657
+ ...usdcAddress ? [{ tokenAddress: wethAddress, baseTokenAddress: usdcAddress }] : []
3658
+ ];
3659
+ const [tokenInfos, pools, upvoteCounts] = await Promise.all([
3660
+ fetchBulkErc20Info({ publicClient, addresses: tokenAddresses }),
3661
+ discoverPools({ publicClient, pairs, chainId }),
3662
+ fetchAggregateUpvotes({ publicClient, tokenAddresses })
3663
+ ]);
3664
+ if (tokenInfos.length !== tokenAddresses.length) {
3665
+ throw new Error(
3666
+ `ERC20 bulk info returned ${tokenInfos.length} entries for ${tokenAddresses.length} addresses`
3667
+ );
3668
+ }
3669
+ if (upvoteCounts.length !== tokenAddresses.length) {
3670
+ throw new Error(
3671
+ `getUpvotesWithLegacy returned ${upvoteCounts.length} entries for ${tokenAddresses.length} addresses`
3672
+ );
3673
+ }
3674
+ return composeAndFilter({
3675
+ tokenAddresses,
3676
+ tokenInfos,
3677
+ pools,
3678
+ upvoteCounts,
3679
+ latestUpvoteTimestamps,
3680
+ wethAddress,
3681
+ usdcAddress,
3682
+ sort,
3683
+ maxTokens,
3684
+ minUpvotes,
3685
+ minMarketCap,
3686
+ recencyHours,
3687
+ nowSec
3688
+ });
3689
+ }
3690
+ function validateChainSupported(chainId) {
3691
+ if (!SUPPORTED_SCORE_CHAINS.includes(chainId)) {
3692
+ throw new Error(
3693
+ `getTokenRankings: chainId ${chainId} is not supported. Supported chains: ${SUPPORTED_SCORE_CHAINS.join(", ")}`
3694
+ );
3695
+ }
3696
+ }
3697
+ async function fetchUpvoteMessages({
3698
+ netClient,
3699
+ messageScanWindow
3700
+ }) {
3701
+ const legacyFilter = {
3702
+ appAddress: LEGACY_UPVOTE_V1_ADDRESS,
3703
+ topic: "t"
3704
+ };
3705
+ const strategyFilters = [
3706
+ UNIV234_POOLS_STRATEGY.address,
3707
+ PURE_ALPHA_STRATEGY.address,
3708
+ DYNAMIC_SPLIT_STRATEGY.address
3709
+ ].map((stratAddr) => ({
3710
+ appAddress: SCORE_CONTRACT.address,
3711
+ topic: `t${stratAddr.toLowerCase()}`
3712
+ }));
3713
+ const [legacyCount, ...strategyCounts] = await Promise.all([
3714
+ netClient.getMessageCount({ filter: legacyFilter }),
3715
+ ...strategyFilters.map((filter) => netClient.getMessageCount({ filter }))
3716
+ ]);
3717
+ const sliceRange = (count) => ({
3718
+ startIndex: Math.max(0, count - messageScanWindow),
3719
+ endIndex: count
3720
+ });
3721
+ const [legacy, strategy1, strategy2, strategy3] = await Promise.all([
3722
+ legacyCount > 0 ? netClient.getMessagesBatch({
3723
+ filter: legacyFilter,
3724
+ ...sliceRange(legacyCount)
3725
+ }) : Promise.resolve([]),
3726
+ ...strategyFilters.map(
3727
+ (filter, i) => strategyCounts[i] > 0 ? netClient.getMessagesBatch({
3728
+ filter,
3729
+ ...sliceRange(strategyCounts[i])
3730
+ }) : Promise.resolve([])
3731
+ )
3732
+ ]);
3733
+ return { legacy, strategy1, strategy2, strategy3 };
3734
+ }
3735
+ async function fetchStrategyStorage({
3736
+ publicClient,
3737
+ messageGroups
3738
+ }) {
3739
+ const seen = /* @__PURE__ */ new Set();
3740
+ const keys = [];
3741
+ for (const msg of [
3742
+ ...messageGroups.strategy1,
3743
+ ...messageGroups.strategy2,
3744
+ ...messageGroups.strategy3
3745
+ ]) {
3746
+ if (!msg.data || !msg.data.startsWith("0x")) continue;
3747
+ const key = msg.data;
3748
+ if (seen.has(key)) continue;
3749
+ seen.add(key);
3750
+ keys.push({ key, operator: SCORE_CONTRACT.address });
3751
+ }
3752
+ if (keys.length === 0) return /* @__PURE__ */ new Map();
3753
+ const results = await readContract(publicClient, {
3754
+ address: STORAGE_CONTRACT.address,
3755
+ abi: STORAGE_CONTRACT.abi,
3756
+ functionName: "bulkGet",
3757
+ args: [keys]
3758
+ });
3759
+ return buildStorageMapFromBulkGetResults(results, keys);
3760
+ }
3761
+ function buildStorageMapFromBulkGetResults(results, keys) {
3762
+ if (results.length !== keys.length) {
3763
+ throw new Error(
3764
+ `bulkGet returned ${results.length} entries for ${keys.length} keys`
3765
+ );
3766
+ }
3767
+ const map = /* @__PURE__ */ new Map();
3768
+ results.forEach((item, idx) => {
3769
+ const value = item?.value;
3770
+ if (value && value !== "0x") {
3771
+ map.set(keys[idx].key, value);
3772
+ }
3773
+ });
3774
+ return map;
3775
+ }
3776
+ function aggregateAndRank({
3777
+ messageGroups,
3778
+ storageBlobByKey,
3779
+ sort,
3780
+ maxTokens,
3781
+ excludeAddresses = [],
3782
+ nowSec
3783
+ }) {
3784
+ const tokenUpvoteEvents = /* @__PURE__ */ new Map();
3785
+ const excludeSet = new Set(excludeAddresses.map((a) => a.toLowerCase()));
3786
+ const addEvent = (tokenAddress, timestamp, count) => {
3787
+ if (!ADDRESS_RE.test(tokenAddress)) return;
3788
+ const lower = tokenAddress.toLowerCase();
3789
+ if (lower === NULL_ADDRESS || excludeSet.has(lower)) return;
3790
+ if (!Number.isFinite(timestamp) || !Number.isFinite(count)) return;
3791
+ const events = tokenUpvoteEvents.get(lower) ?? [];
3792
+ events.push({ timestamp, count });
3793
+ tokenUpvoteEvents.set(lower, events);
3794
+ };
3795
+ for (const msg of messageGroups.legacy) {
3796
+ if (msg.topic !== "t" || !msg.text) continue;
3797
+ let count = 1;
3798
+ if (msg.data && msg.data.startsWith("0x")) {
3799
+ try {
3800
+ const [votes] = decodeAbiParameters(
3801
+ [{ type: "uint256" }],
3802
+ msg.data
3803
+ );
3804
+ count = Number(votes);
3805
+ } catch {
3806
+ count = 1;
3807
+ }
3808
+ }
3809
+ addEvent(msg.text, Number(msg.timestamp), count);
3810
+ }
3811
+ for (const msg of [
3812
+ ...messageGroups.strategy1,
3813
+ ...messageGroups.strategy2,
3814
+ ...messageGroups.strategy3
3815
+ ]) {
3816
+ if (!msg.topic.startsWith("t") || !msg.data || !msg.data.startsWith("0x")) {
3817
+ continue;
3818
+ }
3819
+ const blob = storageBlobByKey.get(msg.data);
3820
+ if (!blob) continue;
3821
+ const decoded = decodeUpvoteStorageBlob(blob);
3822
+ if (!decoded || !decoded.scoreKey) continue;
3823
+ const tokenAddress = extractTokenAddressFromScoreKey(decoded.scoreKey);
3824
+ if (!tokenAddress) continue;
3825
+ addEvent(tokenAddress, Number(msg.timestamp), decoded.scoreDelta);
3826
+ }
3827
+ const timeWeight = (ts) => Math.exp(-0.1 * ((nowSec - ts) / 3600));
3828
+ const latestUpvoteTimestamps = /* @__PURE__ */ new Map();
3829
+ for (const [addr, events] of tokenUpvoteEvents) {
3830
+ let latest = events[0].timestamp;
3831
+ for (const e of events) if (e.timestamp > latest) latest = e.timestamp;
3832
+ latestUpvoteTimestamps.set(addr, latest);
3833
+ }
3834
+ let ordered;
3835
+ if (sort === "top") {
3836
+ ordered = Array.from(tokenUpvoteEvents.keys());
3837
+ } else {
3838
+ const scores = /* @__PURE__ */ new Map();
3839
+ for (const [addr, events] of tokenUpvoteEvents) {
3840
+ if (sort === "recent") {
3841
+ scores.set(addr, latestUpvoteTimestamps.get(addr) ?? 0);
3842
+ } else {
3843
+ const score = events.reduce(
3844
+ (total, e) => total + e.count * timeWeight(e.timestamp),
3845
+ 0
3846
+ );
3847
+ scores.set(addr, score);
3848
+ }
3849
+ }
3850
+ ordered = Array.from(scores.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([address]) => address);
3851
+ }
3852
+ const fetchLimit = Math.max(maxTokens * 3, 30);
3853
+ return {
3854
+ tokenAddresses: ordered.slice(0, fetchLimit),
3855
+ latestUpvoteTimestamps
3856
+ };
3857
+ }
3858
+ async function fetchBulkErc20Info({
3859
+ publicClient,
3860
+ addresses
3861
+ }) {
3862
+ if (addresses.length === 0) return [];
3863
+ try {
3864
+ const data = await readContract(publicClient, {
3865
+ address: ERC20_BULK_INFO_HELPER_CONTRACT.address,
3866
+ abi: ERC20_BULK_INFO_HELPER_CONTRACT.abi,
3867
+ functionName: "getTokenInfo",
3868
+ args: [addresses]
3869
+ });
3870
+ if (Array.isArray(data) && data.length === addresses.length) return data;
3871
+ } catch {
3872
+ }
3873
+ const results = await Promise.all(
3874
+ addresses.map(async (addr) => {
3875
+ try {
3876
+ const data = await readContract(publicClient, {
3877
+ address: ERC20_BULK_INFO_HELPER_CONTRACT.address,
3878
+ abi: ERC20_BULK_INFO_HELPER_CONTRACT.abi,
3879
+ functionName: "getTokenInfo",
3880
+ args: [[addr]]
3881
+ });
3882
+ if (Array.isArray(data) && data[0]) return data[0];
3883
+ } catch {
3884
+ }
3885
+ return {
3886
+ name: "",
3887
+ symbol: "",
3888
+ decimals: 0,
3889
+ totalSupply: 0n,
3890
+ burnedTokens: 0n
3891
+ };
3892
+ })
3893
+ );
3894
+ return results;
3895
+ }
3896
+ async function fetchAggregateUpvotes({
3897
+ publicClient,
3898
+ tokenAddresses
3899
+ }) {
3900
+ if (tokenAddresses.length === 0) return [];
3901
+ const data = await readContract(publicClient, {
3902
+ address: UPVOTE_APP.address,
3903
+ abi: UPVOTE_APP.abi,
3904
+ functionName: "getUpvotesWithLegacy",
3905
+ args: [
3906
+ tokenAddresses.map((addr) => encodeUpvoteKey(addr)),
3907
+ ALL_STRATEGY_ADDRESSES
3908
+ ]
3909
+ });
3910
+ if (!Array.isArray(data)) {
3911
+ throw new Error(
3912
+ "getUpvotesWithLegacy returned non-array; contract or RPC misbehaving"
3913
+ );
3914
+ }
3915
+ return data.map(Number);
3916
+ }
3917
+ function composeAndFilter({
3918
+ tokenAddresses,
3919
+ tokenInfos,
3920
+ pools,
3921
+ upvoteCounts,
3922
+ latestUpvoteTimestamps,
3923
+ wethAddress,
3924
+ usdcAddress,
3925
+ sort,
3926
+ maxTokens,
3927
+ minUpvotes,
3928
+ minMarketCap,
3929
+ recencyHours,
3930
+ nowSec
3931
+ }) {
3932
+ const wethLower = wethAddress.toLowerCase();
3933
+ const usdcLower = usdcAddress?.toLowerCase();
3934
+ let ethPriceInUsdc;
3935
+ if (usdcLower) {
3936
+ const wethUsdcPool = pools.find(
3937
+ (p) => p.tokenAddress.toLowerCase() === wethLower && p.baseTokenAddress.toLowerCase() === usdcLower
3938
+ );
3939
+ const usdcWethPool = pools.find(
3940
+ (p) => p.tokenAddress.toLowerCase() === usdcLower && p.baseTokenAddress.toLowerCase() === wethLower
3941
+ );
3942
+ if (wethUsdcPool?.price) {
3943
+ ethPriceInUsdc = wethUsdcPool.price;
3944
+ } else if (usdcWethPool?.price) {
3945
+ ethPriceInUsdc = 1 / usdcWethPool.price;
3946
+ }
3947
+ }
3948
+ const tokenAddressToPool = /* @__PURE__ */ new Map();
3949
+ for (const pool of pools) {
3950
+ if (pool.baseTokenAddress.toLowerCase() === wethLower) {
3951
+ tokenAddressToPool.set(pool.tokenAddress.toLowerCase(), pool);
3952
+ }
3953
+ }
3954
+ const composed = tokenAddresses.map((address, i) => {
3955
+ const info = tokenInfos[i];
3956
+ const pool = tokenAddressToPool.get(address.toLowerCase());
3957
+ const priceInUsdc = pool?.price && ethPriceInUsdc ? pool.price * ethPriceInUsdc : void 0;
3958
+ let circulating;
3959
+ if (info?.totalSupply) {
3960
+ const burned = info.burnedTokens ?? 0n;
3961
+ const c = info.totalSupply - burned;
3962
+ circulating = c > 0n ? c : 0n;
3963
+ }
3964
+ const decimals = info?.decimals || void 0;
3965
+ const fdv = circulating != null && decimals != null && priceInUsdc != null ? Number(circulating) / 10 ** decimals * priceInUsdc : void 0;
3966
+ return {
3967
+ address,
3968
+ name: info?.name || void 0,
3969
+ symbol: info?.symbol || void 0,
3970
+ decimals,
3971
+ fdv,
3972
+ priceInUsdc,
3973
+ upvotes: upvoteCounts[i] ?? 0,
3974
+ latestUpvoteTimestamp: latestUpvoteTimestamps.get(address.toLowerCase()) ?? 0
3975
+ };
3976
+ });
3977
+ const valid = composed.filter((t) => {
3978
+ if (!t.name?.trim() || !t.symbol?.trim()) return false;
3979
+ const belowFloor = t.fdv == null || t.fdv < minMarketCap;
3980
+ if (belowFloor) {
3981
+ const hoursSince = (nowSec - t.latestUpvoteTimestamp) / 3600;
3982
+ if (hoursSince > recencyHours) return false;
3983
+ }
3984
+ return true;
3985
+ });
3986
+ let sorted;
3987
+ if (sort === "top") {
3988
+ sorted = [...valid].sort((a, b) => b.upvotes - a.upvotes);
3989
+ } else {
3990
+ const byAddr = new Map(valid.map((t) => [t.address.toLowerCase(), t]));
3991
+ sorted = tokenAddresses.map((addr) => byAddr.get(addr.toLowerCase())).filter((t) => t != null);
3992
+ }
3993
+ const qualifies = (t) => t.upvotes >= minUpvotes || t.fdv != null && t.fdv >= minMarketCap;
3994
+ const top = sorted.filter(qualifies);
3995
+ const rest = sorted.filter((t) => !qualifies(t));
3996
+ return [...top, ...rest].slice(0, maxTokens);
3997
+ }
3998
+ var encodePoolKey = (poolKey) => {
3999
+ if (!poolKey || !isValidPoolKey2(poolKey)) {
4000
+ return "0x";
4001
+ }
4002
+ try {
4003
+ return encodeAbiParameters(
4004
+ [
4005
+ {
4006
+ type: "tuple",
4007
+ components: [
4008
+ { name: "currency0", type: "address" },
4009
+ { name: "currency1", type: "address" },
4010
+ { name: "fee", type: "uint24" },
4011
+ { name: "tickSpacing", type: "int24" },
4012
+ { name: "hooks", type: "address" }
4013
+ ]
4014
+ }
4015
+ ],
4016
+ [
4017
+ {
4018
+ currency0: poolKey.currency0,
4019
+ currency1: poolKey.currency1,
4020
+ fee: poolKey.fee,
4021
+ tickSpacing: poolKey.tickSpacing,
4022
+ hooks: poolKey.hooks
4023
+ }
4024
+ ]
4025
+ );
4026
+ } catch {
4027
+ return "0x";
4028
+ }
4029
+ };
4030
+ function isValidPoolKey2(poolKey) {
4031
+ return poolKey.fee !== void 0 || poolKey.tickSpacing !== void 0 || poolKey.currency0 !== void 0;
4032
+ }
4033
+
4034
+ export { ALL_STRATEGY_ADDRESSES, DYNAMIC_SPLIT_STRATEGY, ERC20_BULK_INFO_HELPER_CONTRACT, LEGACY_UPVOTE_V1_ADDRESS, LEGACY_UPVOTE_V2_ADDRESS, MULTI_VERSION_UNISWAP_BULK_POOL_FINDER, MULTI_VERSION_UNISWAP_POOL_INFO_RETRIEVER, NULL_ADDRESS, PURE_ALPHA_STRATEGY, SCORE_CONTRACT, SUPPORTED_SCORE_CHAINS, ScoreClient, UNIV234_POOLS_STRATEGY, UPVOTE_APP, UPVOTE_PRICE_ETH, UPVOTE_STORAGE_APP, USER_UPVOTE_CONTRACT, UserUpvoteClient, buildUserUpvote, buildUserUpvoteReceived, calculatePriceFromSqrtPriceX96, calculatePriceInUsdc, calculateUpvoteCost, calculateUserTokenBalance, decodeStrategyMetadata, decodeUpvoteMessage, decodeUpvoteStorageBlob, discoverPools, discoverTokenPool, encodePoolKey, encodeUpvoteKey, extractStrategyAddress, extractTokenAddressFromScoreKey, extractTokenAddressesFromMessages, getFeedContentKey, getScoreKey, getStorageScoreKey, getStorageUpvoteContext, getTokenRankings, getTokenScoreKey, getWethAddress, isDynamicSplitStrategy, isPureAlphaStrategy, isStrategyMessage, isTokenScoreKey, isUniv234PoolsStrategy, isUserUpvoteMessage, parseUserUpvoteMessage, selectStrategy, tokenAddressToUpvoteKeyString, validateUpvoteParams, validateUserUpvoteMessage };
3599
4035
  //# sourceMappingURL=index.mjs.map
3600
4036
  //# sourceMappingURL=index.mjs.map