@ar.io/sdk
Version:
[](https://codecov.io/gh/ar-io/ar-io-sdk)
272 lines (271 loc) • 10.1 kB
JavaScript
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,
},
},
};
}
;