From 72157dec65ebaa3dd8766b310c24dfedafd84574 Mon Sep 17 00:00:00 2001 From: esser Date: Fri, 12 Jun 2026 13:09:42 +0800 Subject: [PATCH] feat: add merkle api key --- .github/workflows/release.yml | 1 + README.md | 4 ++ src/core/config.ts | 1 + src/core/merkle/merklAPI.ts | 102 ++++++++++++++++++++++++++++------ 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 72b5c81..ee1dc92 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,3 +45,4 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ vars.SENTRY_ORG }} SENTRY_PROJECT: ${{ vars.SENTRY_PROJECT }} + MERKL_API_KEY: ${{ secrets.MERKL_API_KEY_BACK }} diff --git a/README.md b/README.md index e383cd3..ebb637d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ for fetching the apy of different asset on diferrent protocols. For protocols Li Run `yarn start` to launch app. It will save output to `output.json` file by default. See [config](./src/core/config.ts) for env variables. +For Merkl API integration, set `MERKL_API_KEY` environment variable. Anonymous requests are limited to 10 req/sec; a key removes this limit and enables attribution. + +In GitHub Actions releases the secret is expected to be named `MERKL_API_KEY_BACK` and is mapped to `MERKL_API_KEY` for the application. + ## Running as docker ``` diff --git a/src/core/config.ts b/src/core/config.ts index 1dec884..4e336a9 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -2,6 +2,7 @@ export const IS_DEV = process.env.NODE_ENV !== "production"; export const LIDO_AUTH_TOKEN = process.env.LIDO_AUTH_TOKEN; export const RESOLV_AUTH_TOKEN = process.env.RESOLV_AUTH_TOKEN; +export const MERKL_API_KEY = process.env.MERKL_API_KEY; /** * Filename to save output to diff --git a/src/core/merkle/merklAPI.ts b/src/core/merkle/merklAPI.ts index ba4e0e2..6f001de 100644 --- a/src/core/merkle/merklAPI.ts +++ b/src/core/merkle/merklAPI.ts @@ -1,10 +1,20 @@ import type { Address } from "viem"; import { cachedAxios } from "../axios"; +import { MERKL_API_KEY } from "../config"; interface MerkleXYZChain { id: number; name: string; icon: string; + liveCampaigns?: number; + endOfDisputePeriod?: number | string; + lastClaimsOnchainFetchTimestamp?: string; + explorers?: Array<{ + id: string | number; + type: string; + url: string; + chainId: number; + }>; } interface MerkleXYZToken { @@ -15,9 +25,14 @@ interface MerkleXYZToken { decimals: number; icon: string; verified: boolean; + isNative?: boolean; isTest: boolean; + type?: string; price: number | null; symbol: string; + updatedAt?: number; + priceSource?: string; + displaySymbol?: string; } export interface MerklXYZV4Campaign { @@ -25,12 +40,21 @@ export interface MerklXYZV4Campaign { type: string; identifier: Address; name: string; + description?: string; + howToSteps?: Array; status: "LIVE" | "PAST"; - action: "LEND" | "BORROW"; + action: string; tvl: number; apr: number; + maxApr?: number; dailyRewards: number; + maxDailyRewards?: number; tags: Array; + liveCampaigns?: number; + earliestCampaignStart?: string; + latestCampaignStart?: string; + earliestCampaignEnd?: string; + latestCampaignEnd?: string; id: string; tokens: Array; chain: MerkleXYZChain; @@ -38,11 +62,13 @@ export interface MerklXYZV4Campaign { cumulated: number; timestamp: string; breakdowns: Array<{ - id: number; + id?: number; identifier: Address; type: "CAMPAIGN"; value: number; - aprRecordId: string; + aprRecordId?: string; + distributionType?: string; + timestamp?: string; }>; }; tvlRecord: { @@ -58,25 +84,34 @@ export interface MerklXYZV4Campaign { breakdowns: Array<{ token: MerkleXYZToken; amount: string; - id: number; + maxAmount?: string; + id?: string | number; value: number; - campaignId: string; - dailyRewardsRecordid: string; + maxValue?: number; + campaignId?: string; + dailyRewardsRecordid?: string; + dailyRewardsRecordId?: string; + distributionType?: string; + onChainCampaignId?: Address; + timestamp?: string; }>; }; depositUrl: string; explorerAddress: Address; - lastCampaignCreatedAt: string; + lastCampaignCreatedAt: number | string; protocol: { - description: string; - icon: string; id: string; name: string; + description?: string; + icon: string; tags: Array; url: string; + banner?: string | null; + opportunityBannerLight?: string | null; + opportunityBannerDark?: string | null; }; } export type MerkleXYZV4CampaignsResponse = Array; @@ -86,12 +121,17 @@ export interface MerklXYZV4RewardCampaign { computeChainId: number; distributionChainId: number; campaignId: Address; + type?: string; + subType?: number; rewardTokenId: string; amount: string; opportunityId: string; startTimestamp: number; endTimestamp: number; + dailyRewards?: number; + apr?: number; creatorAddress: Address; + isPrivate?: boolean; params: { url: string; duration: number; @@ -103,28 +143,45 @@ export interface MerklXYZV4RewardCampaign { symbolTargetToken: string; decimalsRewardToken: number; decimalsTargetToken: number; + hooks?: Array; + forwardingList?: Array; + forwardingEnabled?: boolean; + computeScoreParameters?: { + computeMethod: string; + }; + distributionMethodParameters?: { + distributionMethod: string; + distributionSettings: Record; + }; }; chain: MerkleXYZChain; rewardToken: MerkleXYZToken; distributionChain: MerkleXYZChain; + distributionType?: string; campaignStatus: { campaignId: string; computedUntil: string; processingStarted: string; - status: "SUCCESS"; + status: "SUCCESS" | string; error: string; details: string; + preComputeStatus?: string; + delay?: number; + preComputeProcessingStarted?: number | string; + createdAt?: number; }; - createdAt: string; + createdAt: string | number; creator: { address: Address; - creatorId: null; + creatorId: string | null; tags: Array; + googleSub?: string | null; + email?: string | null; + tier?: number; + maxNegativeBalance?: number; + missingCreatorAlertDismissedAt?: string | null; }; - distributionType: string; - subType: number; - type: string; } export type MerkleXYZV4RewardCampaignResponse = Array; @@ -140,17 +197,26 @@ export class MerkleXYZApi { static getGearboxCampaignsUrl = () => (domain = MerkleXYZApi.defaultDomain) => - `${domain}/v4/opportunities?name=gearbox`; + `${domain}/v4/opportunities?mainProtocolId=gearbox`; static getGearboxRewardCampaignUrl = (campaignId: Address) => (domain: string) => `${domain}/v4/campaigns?campaignId=${campaignId}`; + private static get headers(): Record | undefined { + return MERKL_API_KEY ? { "X-API-Key": MERKL_API_KEY } : undefined; + } + static fetchWithFallback = async (getUrl: (domain: string) => string) => { + const headers = MerkleXYZApi.headers; try { - return await cachedAxios.get(getUrl(MerkleXYZApi.defaultDomain)); + return await cachedAxios.get(getUrl(MerkleXYZApi.defaultDomain), { + headers, + }); } catch { - return await cachedAxios.get(getUrl(MerkleXYZApi.angleDomain)); + return await cachedAxios.get(getUrl(MerkleXYZApi.angleDomain), { + headers, + }); } }; }