import { QuarrySDK } from "@quarryprotocol/quarry-sdk"; import type { Network } from "@saberhq/solana-contrib"; import type { TokenInfo } from "@saberhq/token-utils"; import * as fs from "fs/promises"; import { groupBy, keyBy, mapValues } from "lodash"; import { fetchAllTokens } from "../helpers/tokenList"; import { makeProvider, stringify } from "../utils"; export const fetchAllRewarders = async (network: Network): Promise<void> => { const provider = makeProvider(network); const quarry = QuarrySDK.load({ provider }); const allRewarders = await quarry.programs.Mine.account.rewarder.all(); const allQuarries = await quarry.programs.Mine.account.quarry.all(); const { tokens, tokenLists } = await fetchAllTokens(network); const dir = `${__dirname}/../../data/${network}/`; await fs.mkdir(dir, { recursive: true }); // addresses of each quarry const allQuarriesJSON = allQuarries.map((q) => { const stakedTokenInfo = tokens[q.account.tokenMintKey.toString()]; return { rewarder: q.account.rewarder.toString(), quarry: q.publicKey.toString(), stakedToken: { mint: q.account.tokenMintKey.toString(), decimals: q.account.tokenMintDecimals, }, index: q.account.index, slug: stakedTokenInfo?.symbol.toLowerCase() ?? q.account.index.toString(), cached: { index: q.account.index, famineTs: q.account.famineTs.toString(), lastUpdateTs: q.account.lastUpdateTs.toString(), rewardsPerTokenStored: q.account.rewardsPerTokenStored.toString(), rewardsShare: q.account.rewardsShare.toString(), numMiners: q.account.numMiners.toString(), totalTokensDeposited: q.account.totalTokensDeposited.toString(), }, }; }); const allRewarderQuarries = mapValues( groupBy(allQuarriesJSON, (q) => q.rewarder), (v) => { return v .map(({ rewarder: _rewarder, ...rest }) => rest) .sort((a, b) => (a.cached.index < b.cached.index ? -1 : 1)); } ); const allRewardersList = allRewarders.map((rewarder) => { const quarries = allRewarderQuarries[rewarder.publicKey.toString()] ?? []; if (rewarder.account.numQuarries !== quarries.length) { console.warn( `Expected ${ rewarder.account.numQuarries } quarries on rewarder ${rewarder.publicKey.toString()}; got ${ quarries.length }` ); } const rewardsTokenMint = rewarder.account.rewardsTokenMint.toString(); const rewardsTokenInfo: TokenInfo | null = tokens[rewardsTokenMint] ?? null; if (!rewardsTokenInfo) { console.warn( `rewards token ${rewardsTokenMint} not found in any of the token lists` ); } return { rewarder: rewarder.publicKey.toString(), authority: rewarder.account.authority.toString(), rewardsToken: { mint: rewardsTokenMint, decimals: rewardsTokenInfo?.decimals ?? -1, }, mintWrapper: rewarder.account.mintWrapper.toString(), quarries, }; }); const allRewardersJSON = mapValues( keyBy(allRewardersList, (r) => r.rewarder), ({ rewarder: _rewarder, quarries, ...info }) => ({ ...info, quarries: quarries.map( ({ cached: _cached, ...quarryInfo }) => quarryInfo ), }) ); const allRewardersJSONWithCache = mapValues( keyBy(allRewardersList, (r) => r.rewarder), ({ rewarder: _rewarder, quarries, ...info }) => ({ ...info, quarries, }) ); // tmp-token-list await fs.writeFile(`.tmp.token-list.json`, stringify(tokenLists)); // rewarders without the cached values await fs.writeFile(`${dir}/all-rewarders.json`, stringify(allRewardersJSON)); // quarries with cached values -- go in their own files await fs.mkdir(`${dir}/rewarders`, { recursive: true }); for (const [rewarderKey, rewarderInfo] of Object.entries( allRewardersJSONWithCache )) { const rewardsToken = tokens[rewarderInfo.rewardsToken.mint]; await fs.mkdir(`${dir}/rewarders/${rewarderKey}`, { recursive: true }); await fs.writeFile( `${dir}/rewarders/${rewarderKey}/meta.json`, stringify({ ...rewarderInfo, rewardsTokenInfo: rewardsToken }) ); } console.log( `Fetched ${allQuarriesJSON.length} quarries across ${ Object.keys(allRewarders).length } rewarders on ${network}.` ); }; Promise.all([ fetchAllRewarders("mainnet-beta"), fetchAllRewarders("devnet"), ]).catch((err) => { console.error(err); process.exit(1); });