UNPKG

@ar.io/sdk

Version:

[![codecov](https://codecov.io/gh/ar-io/ar-io-sdk/graph/badge.svg?token=7dXKcT7dJy)](https://codecov.io/gh/ar-io/ar-io-sdk)

272 lines (271 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.epochDistributionNoticeGqlQueryFallback = exports.epochDistributionNoticeGqlQuery = exports.getEpochDataFromGqlFallback = exports.getEpochDataFromGqlWithCUFallback = exports.getEpochDataFromGql = exports.paginationParamsToTags = exports.pruneTags = exports.validateArweaveId = void 0; exports.isBlockHeight = isBlockHeight; exports.sortAndPaginateEpochDataIntoEligibleDistributions = sortAndPaginateEpochDataIntoEligibleDistributions; exports.removeEligibleRewardsFromEpochData = removeEligibleRewardsFromEpochData; const constants_js_1 = require("../constants.js"); const io_js_1 = require("../types/io.js"); const ao_js_1 = require("./ao.js"); const validateArweaveId = (id) => { return constants_js_1.ARWEAVE_TX_REGEX.test(id); }; exports.validateArweaveId = validateArweaveId; function isBlockHeight(height) { return height !== undefined && !isNaN(parseInt(height.toString())); } /** * Prune tags that are undefined or empty. * @param tags - The tags to prune. * @returns The pruned tags. */ const pruneTags = (tags) => { return tags.filter((tag) => tag.value !== undefined && tag.value !== ''); }; exports.pruneTags = pruneTags; const paginationParamsToTags = (params) => { const tags = [ { name: 'Cursor', value: params?.cursor?.toString() }, { name: 'Limit', value: params?.limit?.toString() }, { name: 'Sort-By', value: params?.sortBy?.toString() }, { name: 'Sort-Order', value: params?.sortOrder?.toString() }, ]; return (0, exports.pruneTags)(tags); }; exports.paginationParamsToTags = paginationParamsToTags; /** * Get the epoch with distribution data for the current epoch * @param arweave - The Arweave instance * @returns The epoch with distribution data */ const getEpochDataFromGql = async ({ arweave, epochIndex, processId = constants_js_1.ARIO_MAINNET_PROCESS_ID, retries = 3, gqlUrl = 'https://arweave-search.goldsky.com/graphql', }) => { // fetch from gql const query = (0, exports.epochDistributionNoticeGqlQuery)({ epochIndex, processId }); // add three retries with exponential backoff for (let i = 0; i < retries; i++) { try { const response = (await fetch(gqlUrl, { method: 'POST', body: query, headers: { 'Content-Type': 'application/json', }, }).then((res) => res.json())); // parse the nodes to get the id if (response?.data?.transactions?.edges?.length === 0) { return undefined; } const id = response.data.transactions.edges[0].node.id; // fetch the transaction from arweave const transaction = await arweave.api.get(id); // assert it is the correct type return (0, ao_js_1.parseAoEpochData)(transaction.data); } catch (error) { if (i === retries - 1) throw error; // Re-throw on final attempt // exponential backoff await new Promise((resolve) => setTimeout(resolve, Math.pow(2, i) * 1000)); } } return undefined; }; exports.getEpochDataFromGql = getEpochDataFromGql; const getEpochDataFromGqlWithCUFallback = async ({ arweave, ao, epochIndex, processId = constants_js_1.ARIO_MAINNET_PROCESS_ID, }) => { const gqlResult = await (0, exports.getEpochDataFromGql)({ arweave, epochIndex, processId, }); if (gqlResult) { return gqlResult; } const gqlFallbackResult = await (0, exports.getEpochDataFromGqlFallback)({ ao, epochIndex, processId, }); if (gqlFallbackResult) { return gqlFallbackResult; } return undefined; }; exports.getEpochDataFromGqlWithCUFallback = getEpochDataFromGqlWithCUFallback; const getEpochDataFromGqlFallback = async ({ ao, epochIndex, processId = constants_js_1.ARIO_MAINNET_PROCESS_ID, gqlUrl = 'https://arweave-search.goldsky.com/graphql', }) => { const query = (0, exports.epochDistributionNoticeGqlQueryFallback)({ epochIndex, processId, }); const response = await fetch(gqlUrl, { method: 'POST', body: query, headers: { 'Content-Type': 'application/json', }, }); const responseJson = (await response.json()); if (responseJson.data.transactions.edges.length === 0) { return undefined; } for (const edge of responseJson.data.transactions.edges) { const id = edge.node.id; const messageResult = await ao .result({ message: id, process: processId, }) .catch(() => undefined); if (!messageResult) { continue; } for (const message of messageResult?.Messages ?? []) { const data = JSON.parse(message.Data); const tags = message.Tags; // check if the message results include epoch-distribution-notice for the requested epoch index if (tags.some((tag) => tag.name === 'Action' && tag.value === 'Epoch-Distribution-Notice') && tags.some((tag) => tag.name === 'Epoch-Index' && tag.value === epochIndex.toString())) { return (0, ao_js_1.parseAoEpochData)(data); } } } return undefined; }; exports.getEpochDataFromGqlFallback = getEpochDataFromGqlFallback; /** * Get the epoch with distribution data for the current epoch * @param arweave - The Arweave instance * @param epochIndex - The index of the epoch * @param processId - The process ID (optional, defaults to ARIO_MAINNET_PROCESS_ID) * @returns string - The stringified GQL query */ const epochDistributionNoticeGqlQuery = ({ epochIndex, processId = constants_js_1.ARIO_MAINNET_PROCESS_ID, authorities = ['fcoN_xJeisVsPXA-trzVAuIiqO3ydLQxM-L4XbrQKzY'], }) => { // write the query const gqlQuery = JSON.stringify({ query: ` query { transactions( tags: [ { name: "From-Process", values: ["${processId}"] } { name: "Action", values: ["Epoch-Distribution-Notice"] } { name: "Epoch-Index", values: ["${epochIndex}"] } { name: "Data-Protocol", values: ["ao"] } ], owners: [${authorities.map((a) => `"${a}"`).join(',')}], first: 1, sort: HEIGHT_DESC ) { edges { node { id } } } } `, }); return gqlQuery; }; exports.epochDistributionNoticeGqlQuery = epochDistributionNoticeGqlQuery; // fallback query if the distribution notice does not get cranked const epochDistributionNoticeGqlQueryFallback = ({ processId = constants_js_1.ARIO_MAINNET_PROCESS_ID, owners = ['OAb-n-ZugyN598kZNpfOy0ACelGVmwCQ0kYbgNGDUK8'], // ar.io team wallet ticks once a day }) => { return JSON.stringify({ query: ` query { transactions( tags: [ { name: "Action", values: ["Tick"] } ], first: 100, owners: [${owners.map((a) => `"${a}"`).join(',')}], recipients: ["${processId}"], sort: HEIGHT_DESC ) { edges { node { id } } } } `, }); }; exports.epochDistributionNoticeGqlQueryFallback = epochDistributionNoticeGqlQueryFallback; function sortAndPaginateEpochDataIntoEligibleDistributions(epochData, params) { const rewards = []; const sortBy = params?.sortBy ?? 'eligibleReward'; const sortOrder = params?.sortOrder ?? 'desc'; const limit = params?.limit ?? 100; if (!(0, io_js_1.isDistributedEpoch)(epochData)) { return { hasMore: false, items: [], totalItems: 0, limit, sortOrder, sortBy, }; } const eligibleDistributions = epochData?.distributions.rewards.eligible; for (const [gatewayAddress, reward] of Object.entries(eligibleDistributions)) { rewards.push({ type: 'operatorReward', recipient: gatewayAddress, eligibleReward: reward.operatorReward, cursorId: gatewayAddress + '_' + gatewayAddress, gatewayAddress, }); for (const [delegateAddress, delegateRewardQty] of Object.entries(reward.delegateRewards)) { rewards.push({ type: 'delegateReward', recipient: delegateAddress, eligibleReward: delegateRewardQty, cursorId: gatewayAddress + '_' + delegateAddress, gatewayAddress, }); } } // sort the rewards by the sortBy rewards.sort((a, b) => { const aSort = a[sortBy]; const bSort = b[sortBy]; if (aSort === bSort || aSort === undefined || bSort === undefined) { return 0; } if (sortOrder === 'asc') { return aSort > bSort ? 1 : -1; } return aSort < bSort ? 1 : -1; }); // paginate the rewards const start = params?.cursor !== undefined ? rewards.findIndex((r) => r.cursorId === params.cursor) + 1 : 0; const end = limit ? start + limit : rewards.length; return { hasMore: end < rewards.length, items: rewards.slice(start, end), totalItems: rewards.length, limit, sortOrder, nextCursor: rewards[end]?.cursorId, sortBy, }; } function removeEligibleRewardsFromEpochData(epochData) { if (!(0, io_js_1.isDistributedEpoch)(epochData)) { return epochData; } return { ...epochData, distributions: { ...epochData.distributions, rewards: { ...epochData.distributions.rewards, // @ts-expect-error -- remove eligible rewards eligible: undefined, }, }, }; }