Skip to main content

What is an Adapter?

An adapter is a standardized module that fetches, transforms, and exports points data from a specific protocol or platform. Each adapter implements a consistent interface that allows the SDK to query points balances across multiple protocols uniformly. Adapters abstract away the complexity of different API formats, authentication methods, and data structures, providing a clean interface for retrieving user points data.

The AdapterExport Interface

Every adapter must implement the AdapterExport interface defined in utils/adapter.ts:12-20:
type AdapterExport<T = object> = {
  fetch: (address: string) => Promise<T>;
  data: (data: T) => DetailedData | LabelledDetailedData;
  total: (data: T) => number | LabelledPoints;
  claimable?: (data: T) => boolean;
  rank?: (data: T) => number;
  deprecated?: (data: T) => DeprecatedLabels;
  supportedAddressTypes: AddressType[];
};

Required Properties

fetch

The core data retrieval function that fetches raw points data from the protocol’s API.
fetch: (address: string) => Promise<T>
  • Input: User’s blockchain address (EVM or SVM format)
  • Output: Promise resolving to raw API response data
  • Responsibility: Handle API calls, authentication, error handling
Example from Sonic adapter (adapters/sonic.ts:21-29):
fetch: async (address: string) => {
  return await (
    await fetch(API_URL.replace("{address}", address), {
      headers: {
        "User-Agent": "Checkpoint API (https://checkpoint.exchange)",
      },
    })
  ).json();
}

data

Transforms raw API response into a structured, human-readable format.
data: (data: T) => DetailedData | LabelledDetailedData
  • Input: Raw data from fetch
  • Output: Key-value pairs with display-friendly labels and values
  • Responsibility: Extract relevant fields, format dates, organize nested data
Example from Ether.fi adapter (adapters/etherfi.ts:17-61):
data: ({ TotalPointsSummary }) => {
  const groups: Record<string, Record<string, number>> = {
    "All Time Points": {},
    "Current Points": {},
    "Last Month Points": {},
    "Latest 3 Months Points": {},
  };

  if (TotalPointsSummary && typeof TotalPointsSummary === "object") {
    for (const category in TotalPointsSummary) {
      const points = TotalPointsSummary[category];
      for (const type in points) {
        // Parse and organize into groups
        // ...
      }
    }
  }

  return groups;
}

total

Calculates the total points balance for the address.
total: (data: T) => number | LabelledPoints
  • Input: Raw data from fetch
  • Output: Single number or labeled point categories
  • Responsibility: Sum up all relevant point values
Simple example from Sonic (adapters/sonic.ts:41):
total: (data: Record<string, number>) => data.sonic_points
Labelled example from Dolomite (adapters/dolomite.ts:37-39):
total: ({ airdrop }) => ({
  Minerals: airdrop ? parseFloat(airdrop.amount) ?? 0 : 0,
})

supportedAddressTypes

Specifies which blockchain address formats this adapter supports.
supportedAddressTypes: AddressType[]  // ["evm"] or ["svm"] or ["evm", "svm"]
  • Values: Array containing "evm" and/or "svm"
  • Responsibility: Declare address type compatibility
  • Validation: The SDK validates addresses before calling fetch
All example adapters support EVM addresses:
supportedAddressTypes: ["evm"]

Optional Properties

claimable

Indicates whether the points are currently claimable/withdrawable.
claimable?: (data: T) => boolean
Example from Dolomite (adapters/dolomite.ts:41):
claimable: ({ airdrop }) => Boolean(airdrop)

rank

Extracts the user’s leaderboard ranking if available.
rank?: (data: T) => number
Example from Sonic (adapters/sonic.ts:42):
rank: (data: { rank: number }) => data.rank

deprecated

Marks certain point categories as deprecated with a Unix timestamp.
deprecated?: (data: T) => DeprecatedLabels
Example from Dolomite (adapters/dolomite.ts:42-44):
deprecated: () => ({
  Minerals: 1736467200, // Jan 10th 00:00 UTC
})

Data Flow Through an Adapter

When you query an adapter, the data flows through these stages:
1

Address Validation

The SDK validates the address format and checks if the adapter supports that address type (utils/adapter.ts:32-47).
2

Fetch Raw Data

The adapter’s fetch function retrieves raw data from the protocol’s API.
3

Transform Data

The data and total functions process the raw response into standardized formats.
4

Enhance with Metadata

Optional functions (claimable, rank, deprecated) add additional context.
5

Normalize Output

The SDK normalizes all values to ensure consistent types and formats (utils/adapter.ts:62-69).

Complete Adapter Example

Here’s the full Dolomite adapter showing all concepts together:
import type { AdapterExport } from "../utils/adapter.ts";
import { checksumAddress } from "viem";
import { maybeWrapCORSProxy } from "../utils/cors.ts";

const AIRDROP_URL = await maybeWrapCORSProxy(
  "https://api.dolomite.io/airdrop/regular/{address}"
);

export default {
  fetch: async (address: string) => {
    address = checksumAddress(address as `0x${string}`);
    
    const milestones = await (
      await fetch(AIRDROP_URL.replace("{address}", address), {
        headers: {
          "User-Agent": "Checkpoint API (https://checkpoint.exchange)",
        },
      })
    ).json();
    
    return milestones;
  },
  
  data: ({ airdrop }) => {
    return {
      Minerals: {
        "Airdrop Amount": airdrop ? parseFloat(airdrop.amount) ?? 0 : 0,
        "Level Snapshot": airdrop?.level_snapshot ?? 0,
      },
    };
  },
  
  total: ({ airdrop }) => ({
    Minerals: airdrop ? parseFloat(airdrop.amount) ?? 0 : 0,
  }),
  
  claimable: ({ airdrop }) => Boolean(airdrop),
  
  deprecated: () => ({
    Minerals: 1736467200, // Jan 10th 00:00 UTC
  }),
  
  supportedAddressTypes: ["evm"],
} as AdapterExport;

Type Definitions

Key types used in adapters (utils/adapter.ts:6-9):
type LabelledPoints = { [label: string]: number };
type DetailedData = { [key: string]: string | number };
type LabelledDetailedData = { [label: string]: DetailedData };
type DeprecatedLabels = { [label: string]: number };

Best Practices

Use CORS Proxying

Always wrap API URLs with maybeWrapCORSProxy for browser compatibility. See CORS Handling for details.

Validate and Normalize Addresses

Use checksumAddress or getAddress from viem for EVM addresses to ensure proper formatting.

Handle Missing Data Gracefully

Always provide fallback values when API responses might be incomplete or null.

Include User-Agent Headers

Add identifying headers to API requests for better tracking and debugging.

Next Steps

Address Types

Learn about EVM and SVM address validation

CORS Handling

Understand browser compatibility and CORS proxying

Build docs developers (and LLMs) love