@moonwell-fi/moonwell-sdk
Version:
TypeScript Interface for Moonwell
114 lines • 5.15 kB
JavaScript
import { MOONWELL_FETCH_JSON_HEADERS } from "../../common/fetch-headers.js";
const MOONWELL_MERKL_CREATOR = "0x8b621804a7637b781e2BbD58e256a591F2dF7d51";
const CAMPAIGN_IDS_CACHE_TTL_MS = 4 * 60 * 60 * 1000; // 4 hours
const MAX_BREAKDOWN_PAGES = 10;
let campaignIdsCache = null;
/** Resets the in-memory campaign IDs cache. Intended for use in tests only. */
export function resetMerklCampaignIdsCache() {
campaignIdsCache = null;
}
/**
* Fetches all Moonwell Merkl campaign IDs from the API.
* Results are cached in memory for 4 hours since campaigns only change monthly.
*/
export async function getMerklCampaignIds() {
const now = Date.now();
if (campaignIdsCache !== null &&
now - campaignIdsCache.fetchedAt < CAMPAIGN_IDS_CACHE_TTL_MS) {
return campaignIdsCache.ids;
}
try {
const response = await fetch(`https://api.merkl.xyz/v4/campaigns?creatorAddress=${MOONWELL_MERKL_CREATOR}&excludeSubCampaigns=true&items=100`, { headers: MOONWELL_FETCH_JSON_HEADERS });
if (!response.ok) {
console.warn(`Merkl API request failed: ${response.status} ${response.statusText}`);
return campaignIdsCache?.ids ?? [];
}
const data = (await response.json());
const ids = data.map((c) => c.campaignId);
campaignIdsCache = { ids, fetchedAt: now };
return ids;
}
catch (error) {
console.error("Error in getMerklCampaignIds:", error);
return campaignIdsCache?.ids ?? [];
}
}
export async function getMerklRewardsData(campaignId, chainId, account) {
try {
const page0Response = await fetch(`https://api.merkl.xyz/v4/users/${account}/rewards?chainId=${chainId}&test=false&breakdownPage=0&reloadChainId=${chainId}`, {
headers: MOONWELL_FETCH_JSON_HEADERS,
});
if (!page0Response.ok) {
console.warn(`Merkl API request failed: ${page0Response.status} ${page0Response.statusText}`);
return [];
}
const data = (await page0Response.json());
if (data.length === 0) {
return [];
}
// Paginate through remaining breakdown pages to collect all breakdowns
for (let page = 1; page < MAX_BREAKDOWN_PAGES; page++) {
const pageResponse = await fetch(`https://api.merkl.xyz/v4/users/${account}/rewards?chainId=${chainId}&test=false&breakdownPage=${page}&reloadChainId=${chainId}`, {
headers: MOONWELL_FETCH_JSON_HEADERS,
});
if (!pageResponse.ok) {
break;
}
const pageData = (await pageResponse.json());
const allBreakdownsEmpty = pageData.every((reward) => reward.rewards.every((r) => r.breakdowns.length === 0));
if (allBreakdownsEmpty) {
break;
}
// Merge breakdowns from this page into the page-0 data
for (const pageReward of pageData) {
const baseReward = data.find((d) => d.chain.id === pageReward.chain.id);
if (!baseReward)
continue;
for (const pageRewardEntry of pageReward.rewards) {
const baseRewardEntry = baseReward.rewards.find((r) => r.token.address.toLowerCase() ===
pageRewardEntry.token.address.toLowerCase());
if (baseRewardEntry) {
baseRewardEntry.breakdowns.push(...pageRewardEntry.breakdowns);
}
}
}
}
return data
.filter((reward) => reward.rewards.some((r) => r.breakdowns.some((b) => campaignId.includes(b.campaignId))))
.flatMap((reward) => reward.rewards
.filter((r) => r.breakdowns.some((b) => campaignId.includes(b.campaignId)))
.flatMap((r) => r.breakdowns
.filter((b) => campaignId.includes(b.campaignId))
.map((b) => ({
chain: reward.chain.id,
token: r.token,
amount: b.amount,
claimed: b.claimed,
pending: b.pending,
}))));
}
catch (error) {
console.error("Error in getMerklRewardsData:", error);
return [];
}
}
export async function getMerklStakingApr(contractAddress) {
const now = Math.floor(Date.now() / 1000);
try {
const response = await fetch(`https://api.merkl.xyz/v4/campaigns?creatorAddress=${MOONWELL_MERKL_CREATOR}&excludeSubCampaigns=true&items=100`, { headers: MOONWELL_FETCH_JSON_HEADERS });
if (!response.ok) {
console.warn(`Merkl API request failed: ${response.status} ${response.statusText}`);
return 0;
}
const data = (await response.json());
return data
.filter((c) => c.startTimestamp <= now && c.endTimestamp >= now)
.filter((c) => c.params.targetToken.toLowerCase() === contractAddress.toLowerCase())
.reduce((acc, c) => acc + Number(c.apr), 0);
}
catch (error) {
console.error("Error in getMerklStakingApr:", error);
return 0;
}
}
//# sourceMappingURL=common.js.map