A comprehensive SDK for interacting with The Graph Token API built on Scaffold-ETH 2, featuring advanced data fetching hooks, reusable UI components, and complete TypeScript support.
- Overview
- Installation
- API Configuration
- Core Architecture
- Hooks Library
- UI Components
- Configuration System
- Utility Functions
- Type System
- Example Usage
- API Endpoint References
- Troubleshooting
- Contributing
This SDK provides a complete toolkit for interacting with The Graph Token API, enabling developers to easily fetch and display both token and NFT data across multiple EVM networks. It's built as part of a Scaffold-ETH 2 application but the hooks and components can be used in any React project.
Key features:
- React Hooks Library: Specialized hooks for token and NFT API endpoints
- UI Component Collection: Ready-to-use components for displaying token and NFT data
- API Proxy Integration: Secure API communication with authentication handling
- Multi-Network Support: Works with Ethereum, Arbitrum, Base, BSC, Optimism, and Polygon
- TypeScript Support: Full type safety with comprehensive interfaces
- NFT Support: Complete NFT data fetching including collections, items, ownerships, activities, and sales
- Advanced Filtering: Time-based filters, contract filters, and pagination support
- Authentication Error Handling: Clear guidance for API setup and troubleshooting
For a detailed guide on how the components and hooks were built, see the Components Tutorial. To build the test page step-by-step yourself, follow the Test Page Workshop.
# Clone the repository
git clone https://github.com/graphprotocol/examples.git
cd examples/token-api/token-api-scaffold-eth
# Install dependencies
yarn install
# Create an .env.local file with your Graph API token
echo "NEXT_PUBLIC_GRAPH_TOKEN=your_graph_api_token_here" > .env.local
# Start the development server
yarn startVisit http://localhost:3000/token-api to see all components in action.
The SDK requires a Graph API token for authentication. You can obtain one from The Graph Market.
Create a .env.local file in the root directory:
# Required: Graph API Token for authentication
NEXT_PUBLIC_GRAPH_TOKEN=your_graph_api_token_here
# Optional: API URL (defaults to the stage URL if not provided)
NEXT_PUBLIC_GRAPH_API_URL=https://token-api.thegraph.comThe SDK uses Next.js App Router API routes to create a secure proxy for token API requests. This keeps API keys secure and handles authentication properly.
The proxy route is implemented in packages/nextjs/app/api/token-proxy/route.ts:
// token-proxy/route.ts
export async function GET(request: NextRequest) {
try {
// Get query parameters from the request
const searchParams = request.nextUrl.searchParams;
// Get the path to the API endpoint (required)
const path = searchParams.get("path");
if (!path) {
return NextResponse.json(
{ error: "Missing 'path' parameter" },
{ status: 400 }
);
}
// Build the complete URL with the API base URL
const url = new URL(path, API_URL);
// Forward query parameters
searchParams.forEach((value, key) => {
if (key !== "path") {
url.searchParams.append(key, value);
}
});
// Set up authentication headers
const headers: HeadersInit = {
Accept: "application/json",
"Content-Type": "application/json",
};
// Use API key if available, otherwise use JWT token
if (process.env.NEXT_PUBLIC_GRAPH_API_KEY) {
headers["X-Api-Key"] = process.env.NEXT_PUBLIC_GRAPH_API_KEY;
} else if (process.env.NEXT_PUBLIC_GRAPH_TOKEN) {
headers["Authorization"] =
`Bearer ${process.env.NEXT_PUBLIC_GRAPH_TOKEN}`;
}
// Make the API request
const response = await fetch(url.toString(), {
method: "GET",
headers,
cache: "no-store", // Disable caching
});
// Parse and return the response
const data = await response.json();
return NextResponse.json(data, { status: response.status });
} catch (error) {
return NextResponse.json(
{
error:
error instanceof Error
? error.message
: "Unknown error occurred",
},
{ status: 500 }
);
}
}The authentication flow works as follows:
- Client hooks call the local API route (
/api/token-proxy) with the endpoint path and parameters - The API route adds authentication headers (API key or JWT token)
- The API route forwards the request to the Graph Token API
- The API route returns the response to the client
This approach keeps API keys secure (they never leave the server) and prevents CORS issues.
The SDK follows a structured organization:
app/
├── token-api/ # Main directory for the token API
│ ├── _components/ # UI components
│ ├── _config/ # Configuration files
│ ├── _hooks/ # Data fetching hooks
│ ├── _types/ # TypeScript type definitions
│ ├── _utils/ # Utility functions
│ └── page.tsx # Main page showcasing all components
└── api/
└── token-proxy/ # API proxy route
└── route.ts # API handler implementation
The data flow follows this pattern:
- Component Initialization: UI component initializes with state for inputs
- Hook Setup: Component calls a specialized hook with parameters
- API Request: Hook calls the API proxy route with endpoint and parameters
- Authentication: API proxy adds authentication headers
- Data Fetching: API proxy fetches data from the Graph Token API
- Response Handling: Hook processes the response and returns it to the component
- Rendering: Component renders the data with appropriate loading/error states
useTokenApi is the foundation for all specialized hooks. It provides generic API interaction with error handling, loading states, and data formatting.
Location: packages/nextjs/app/token-api/_hooks/useTokenApi.ts
export const useTokenApi = <DataType, ParamsType = Record<string, any>>(
endpoint: string,
params?: ParamsType,
options: TokenApiOptions = {}
) => {
const { skip = false, refetchInterval } = options;
const [data, setData] = useState<DataType | undefined>(undefined);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
const [lastUpdated, setLastUpdated] = useState<number | undefined>(
undefined
);
// Fetch implementation, error handling, and refetch logic...
return {
data,
isLoading,
error,
refetch: fetchData,
lastUpdated,
};
};Key Features:
- Generic Type Parameters: Allows for typed responses with
DataTypeandParamsType - Conditional Fetching: Supports skipping fetches with the
skipoption - Auto-Refetching: Supports auto-refreshing with
refetchInterval - Error Handling: Comprehensive error handling and state management
- Manual Refetch: Provides a function to manually trigger refetches
- Authentication Error Detection: Clear guidance for API setup and troubleshooting
Fetches detailed information about a token contract.
Location: packages/nextjs/app/token-api/_hooks/useTokenMetadata.ts
export const useTokenMetadata = (
contract: string | undefined,
params?: TokenMetadataParams,
options = { skip: contract ? false : true }
) => {
// Format endpoint
const normalizedContract = cleanContractAddress(contract);
return useTokenApi<TokenMetadataResponse>(
normalizedContract ? `tokens/evm/${normalizedContract}` : "",
{ ...params },
options
);
};Parameters:
contract: Token contract addressparams: Optional parametersnetwork_id: Network identifierinclude_market_data: Whether to include price data (default: true)
options: Hook options (skip, refetchInterval)
Response Type:
interface TokenMetadata {
contract_address: string;
name: string;
symbol: string;
decimals: number;
total_supply: string;
logo_url?: string;
market_data?: {
price_usd: number;
price_change_percentage_24h: number;
market_cap: number;
total_volume_24h: number;
};
}Fetches token balances for a wallet address.
Location: packages/nextjs/app/token-api/_hooks/useTokenBalances.ts
export const useTokenBalances = (
address: string | undefined,
params?: TokenBalancesParams,
options = { skip: address ? false : true }
) => {
// Normalize the address
const normalizedAddress =
address && !address.startsWith("0x") ? `0x${address}` : address;
// Call the base hook with the appropriate endpoint
const result = useTokenApi<TokenBalancesResponse>(
normalizedAddress ? `balances/evm/${normalizedAddress}` : "",
{ ...params },
options
);
// Format the result for easier consumption
let formattedData: TokenBalance[] = [];
if (result.data) {
if (Array.isArray(result.data)) {
formattedData = result.data;
} else if ("data" in result.data && Array.isArray(result.data.data)) {
formattedData = result.data.data;
}
}
return {
...result,
data: formattedData,
};
};Parameters:
address: Wallet addressparams: Optional parametersnetwork_id: Network identifierpage: Page numberpage_size: Results per pagemin_amount: Minimum token amountcontract_address: Filter for specific token
options: Hook options
Response Type:
interface TokenBalance {
contract_address: string;
amount: string;
name?: string;
symbol?: string;
decimals?: number;
amount_usd?: number;
}Fetches holder information for a token contract.
Location: packages/nextjs/app/token-api/_hooks/useTokenHolders.ts
export const useTokenHolders = (
contract: string | undefined,
params?: TokenHoldersParams,
options = { skip: contract ? false : true }
) => {
const normalizedContract = cleanContractAddress(contract);
return useTokenApi<TokenHoldersResponse>(
normalizedContract ? `holders/evm/${normalizedContract}` : "",
{ ...params },
options
);
};Parameters:
contract: Token contract addressparams: Optional parametersnetwork_id: Network identifierpage: Page numberpage_size: Results per pageorder_by: Sort order ("asc" or "desc")
options: Hook options
Response Type:
interface TokenHolder {
address: string;
balance: string;
last_updated_block: number;
balance_usd?: number;
token_share?: number;
}
interface TokenHoldersResponse {
holders: TokenHolder[];
pagination: {
page: number;
page_size: number;
total_pages: number;
};
total: number;
}Fetches token transfer events.
Location: packages/nextjs/app/token-api/_hooks/useTokenTransfers.ts
export const useTokenTransfers = (
address: string | undefined, // Address used as the 'to' parameter by default
params?: TokenTransfersParams,
options = { skip: address ? false : true }
) => {
// Endpoint for the base hook
const endpoint = "transfers/evm";
// Prepare query parameters, adding 'address' as 'to' param
const queryParams: Record<string, any> = {
...params, // Spread other parameters like network_id, contract, limit, from
to: address,
network_id: params?.network_id, // Ensure network_id is passed
};
// Clean up undefined params
Object.keys(queryParams).forEach((key) => {
if (queryParams[key] === undefined) {
delete queryParams[key];
}
});
// Call the base API hook
return useTokenApi<TokenTransfersResponse>(endpoint, queryParams, options);
};Parameters:
address: Wallet address (used as thetoparameter)params: Optional parametersnetwork_id: Network identifier (required by API)from: Filter by sender addressto: Filter by recipient address (overridden by the mainaddressargument)contract: Filter by token contractstartTime,endTime: Filter by timestamp (Unix seconds)orderBy,orderDirection: Sorting optionslimit: Results per pagepage: Page number
options: Hook options (skip, refetchInterval)
Response Type:
interface TokenTransferItem {
block_num: number;
datetime?: string;
timestamp?: number;
date?: string;
contract: string;
from: string;
to: string;
amount: string;
transaction_id: string;
decimals: number;
symbol: string;
network_id: string;
price_usd?: number;
value_usd?: number;
}
interface TokenTransfersResponse {
data: TokenTransferItem[];
pagination?: {
page: number;
page_size: number;
total_pages: number;
};
total_results?: number;
}Fetches price history for a liquidity pool.
Location: packages/nextjs/app/token-api/_hooks/useTokenOHLCByPool.ts
export function useTokenOHLCByPool(
pool: string | undefined,
params?: PoolOHLCParams,
options = { skip: false }
) {
// Normalize and clean the pool address
const normalizedPool = pool ? cleanContractAddress(pool) : undefined;
// Default skip to true if no pool address is provided
const skip = options.skip || !normalizedPool;
// Create the endpoint path
const endpoint = normalizedPool ? `ohlc/pools/evm/${normalizedPool}` : "";
// Call the base API hook with the proper configuration
return useTokenApi<PoolOHLCResponse>(
endpoint,
{ ...params },
{ ...options, skip }
);
}Parameters:
pool: Pool contract addressparams: Optional parametersnetwork_id: Network identifierfrom_timestamp: Start timestampto_timestamp: End timestampresolution: Data resolution ("5m", "15m", "30m", "1h", "2h", "4h", "1d", "1w")page: Page numberpage_size: Results per page
options: Hook options
Response Type:
interface OHLCDataPoint {
timestamp: number;
open: number;
high: number;
low: number;
close: number;
volume_token0: number;
volume_token1: number;
volume_usd?: number;
}
interface PoolOHLCResponse {
ohlc?: OHLCDataPoint[];
pool_address?: string;
token0_address?: string;
token0_symbol?: string;
token1_address?: string;
token1_symbol?: string;
protocol?: string;
network_id?: string;
resolution?: string;
pagination?: {
page: number;
page_size: number;
total_pages: number;
};
}Fetches price history for a token contract.
Location: packages/nextjs/app/token-api/_hooks/useTokenOHLCByContract.ts
export function useTokenOHLCByContract(
options: UseTokenOHLCByContractOptions = {}
) {
const {
contract,
network,
timeframe = 86400,
limit = 100,
enabled = true,
startTime,
endTime,
} = options;
const normalizedContract = contract?.toLowerCase();
const endpoint = normalizedContract
? `ohlc/prices/evm/${normalizedContract}`
: "";
return useTokenApi<ContractOHLCResponse>(
endpoint,
{
network_id: network,
interval: timeframe === 86400 ? "1d" : "1h",
limit,
startTime,
endTime,
},
{
skip: !normalizedContract || !enabled,
}
);
}Parameters:
options: Configuration optionscontract: Token contract addressnetwork: Network identifiertimeframe: Time interval in seconds (default: 86400)startTime: Start timestamp (Unix seconds)endTime: End timestamp (Unix seconds)limit: Number of results (default: 100)enabled: Whether to enable the query (default: true)
Response Type:
interface ContractOHLCResponse {
contract_address?: string;
token_name?: string;
token_symbol?: string;
token_decimals?: number;
network_id?: NetworkId;
resolution?: string;
ohlc?: OHLCDataPoint[];
data?: Array<{
datetime: string;
ticker: string;
open: number;
high: number;
low: number;
close: number;
volume: number;
}>;
}Fetches information about liquidity pools.
Location: packages/nextjs/app/token-api/_hooks/useTokenPools.ts
export const useTokenPools = (params?: PoolsParams, options = {}) => {
return useTokenApi<PoolsResponse>("pools/evm", { ...params }, options);
};Parameters:
params: Optional parametersnetwork_id: Network identifiertoken: Filter by token addresspool: Filter by pool addresssymbol: Filter by token symbolfactory: Filter by factory addressprotocol: Filter by protocolpage: Page numberpage_size: Results per pagesort_by: Sort field ("tvl" or "creation_date")sort_direction: Sort direction ("asc" or "desc")include_reserves: Include reserve data
options: Hook options
Response Type:
interface Pool {
block_num: number;
datetime: string;
transaction_id: string;
factory: string;
pool: string;
token0: TokenInfo;
token1: TokenInfo;
fee: number;
protocol: string;
network_id: string;
}
interface PoolsResponse {
data: Pool[];
pagination: {
previous_page: number;
current_page: number;
next_page: number;
total_pages: number;
};
total_results: number;
}Fetches DEX swap events.
Location: packages/nextjs/app/token-api/_hooks/useTokenSwaps.ts
export const useTokenSwaps = (
params: SwapsParams,
options: { skip?: boolean } = {}
) => {
return useTokenApi<Swap[]>("swaps/evm", params, options);
};Parameters:
params: Query parametersnetwork_id: Network identifier (required)pool: Filter by pool addresscaller: Filter by caller addresssender: Filter by sender addressrecipient: Filter by recipient addresstx_hash: Filter by transaction hashprotocol: Filter by protocolpage: Page numberpage_size: Results per page
options: Hook options
Response Type:
interface Swap {
block_num: number;
datetime: string;
transaction_id: string;
caller: string;
pool: string;
factory?: string;
sender: string;
recipient: string;
network_id: string;
amount0: string;
amount1: string;
token0?: { address: string; symbol: string; decimals: number } | string;
token1?: { address: string; symbol: string; decimals: number } | string;
amount0_usd?: number;
amount1_usd?: number;
protocol?: string;
}Fetches NFT collection data for a specific contract address.
Location: packages/nextjs/app/token-api/_hooks/useNFTCollections.ts
export function useNFTCollections(options: UseNFTCollectionsOptions) {
const { contractAddress, network = "mainnet", enabled = true } = options;
const normalizedContractAddress = normalizeContractAddress(contractAddress);
const endpoint = `nft/collections/evm/${normalizedContractAddress}`;
return useTokenApi<NFTCollection[]>(
endpoint,
{ network_id: network },
{ skip: !normalizedContractAddress || !enabled }
);
}Parameters:
contractAddress: NFT contract address (required)network: Network identifier (default: "mainnet")enabled: Whether to enable the query (default: true)
Response Type:
interface NFTCollection {
token_standard: string;
contract: string;
contract_creation: string;
contract_creator: string;
symbol: string;
name: string;
base_uri?: string;
total_supply: number;
total_unique_supply?: number;
owners: number;
total_transfers: number;
network_id: NetworkId;
}Fetches individual NFT items from a collection.
Location: packages/nextjs/app/token-api/_hooks/useNFTItems.ts
export function useNFTItems(options: UseNFTItemsOptions) {
const {
contractAddress,
network = "mainnet",
enabled = true,
...params
} = options;
const normalizedContractAddress = normalizeContractAddress(contractAddress);
const endpoint = `nft/items/evm/${normalizedContractAddress}`;
return useTokenApi<NFTItem[]>(
endpoint,
{ network_id: network, ...params },
{ skip: !normalizedContractAddress || !enabled }
);
}Parameters:
contractAddress: NFT contract address (required)network: Network identifiertoken_id: Specific token ID to fetchlimit: Number of results per pagepage: Page numberenabled: Whether to enable the query
Response Type:
interface NFTItem {
contract: string;
token_id: string;
owner: string;
token_uri?: string;
metadata?: {
name?: string;
description?: string;
image?: string;
attributes?: Array<{
trait_type: string;
value: any;
}>;
};
network_id: NetworkId;
}Fetches NFT ownership data for a wallet address.
Location: packages/nextjs/app/token-api/_hooks/useNFTOwnerships.ts
export function useNFTOwnerships(options: UseNFTOwnershipsOptions) {
const {
walletAddress,
network = "mainnet",
enabled = true,
...params
} = options;
const normalizedWalletAddress = normalizeContractAddress(walletAddress);
const endpoint = `nft/ownerships/evm/${normalizedWalletAddress}`;
return useTokenApi<NFTOwnership[]>(
endpoint,
{ network_id: network, ...params },
{ skip: !normalizedWalletAddress || !enabled }
);
}Parameters:
walletAddress: Wallet address to check ownership for (required)network: Network identifiercontract_address: Filter by specific NFT contractlimit: Number of results per pagepage: Page numberenabled: Whether to enable the query
Response Type:
interface NFTOwnership {
contract: string;
token_id: string;
owner: string;
balance: string;
token_uri?: string;
metadata?: {
name?: string;
description?: string;
image?: string;
};
network_id: NetworkId;
}Fetches NFT activity/transaction history with advanced filtering.
Location: packages/nextjs/app/token-api/_hooks/useNFTActivities.ts
export function useNFTActivities(options: UseNFTActivitiesOptions | null) {
if (!options?.contract_address) {
// Contract address is required for NFT Activities API
return {
data: undefined,
isLoading: false,
error: "Contract address is required",
};
}
const endpoint = "nft/activities/evm";
return useTokenApi<NFTActivity[]>(endpoint, options, {
skip: !options.contract_address,
});
}Parameters:
contract_address: NFT contract address (required)network_id: Network identifierany: Filter by any address (from/to/contract)from_address: Filter by sender addressto_address: Filter by recipient addressstartTime: Start timestamp (Unix seconds)endTime: End timestamp (Unix seconds)orderBy: Sort field ("timestamp")orderDirection: Sort direction ("asc" | "desc")limit: Number of results per pagepage: Page number
Response Type:
interface NFTActivity {
"@type": string;
timestamp: string;
block_num: number;
tx_hash: string;
contract: string;
token_id: string;
from: string;
to: string;
amount: string;
network_id: NetworkId;
}Fetches NFT holder information for a specific contract.
Location: packages/nextjs/app/token-api/_hooks/useNFTHolders.ts
export function useNFTHolders(options: UseNFTHoldersOptions) {
const { contractAddress, network = "mainnet", enabled = true } = options;
const normalizedContractAddress = normalizeContractAddress(contractAddress);
const endpoint = `nft/holders/evm/${normalizedContractAddress}`;
return useTokenApi<NFTHolder[]>(
endpoint,
{ network_id: network },
{ skip: !normalizedContractAddress || !enabled }
);
}Parameters:
contractAddress: NFT contract address (required)network: Network identifier (default: "mainnet")enabled: Whether to enable the query (default: true)
Response Type:
interface NFTHolder {
token_standard: string; // ERC721, ERC1155, etc.
address: string; // Holder's wallet address
quantity: number; // Number of tokens held
unique_tokens: number; // Number of unique tokens held
percentage: number; // Percentage of total supply held
network_id: NetworkId;
}Fetches NFT sales/marketplace data.
Location: packages/nextjs/app/token-api/_hooks/useNFTSales.ts
export function useNFTSales(options: UseNFTSalesOptions = {}) {
const { network, enabled = true, token, ...otherParams } = options;
// Map 'token' parameter to 'contract' for the API
const queryParams: any = {
network_id: network,
...otherParams,
};
if (token) {
queryParams.contract = token; // API expects 'contract' not 'token'
}
return useTokenApi<NFTSale[]>("nft/sales/evm", queryParams, {
skip: !enabled,
});
}Parameters:
network: Network identifierany: Filter by any address involvedofferer: Seller addressrecipient: Buyer addresstoken: NFT contract address (mapped to 'contract' for API)startTime: Start timestamp (Unix seconds)endTime: End timestamp (Unix seconds)orderBy: Sort field ("timestamp")orderDirection: Sort direction ("asc" | "desc")limit: Number of results per pagepage: Page numberenabled: Whether to enable the query
Response Type:
interface NFTSale {
timestamp: string;
block_num: number;
tx_hash: string;
token: string;
token_id: number;
symbol: string;
name: string;
offerer: string;
recipient: string;
sale_amount: number;
sale_currency: string;
}All components follow these common patterns:
- Collapsible UI: Uses
<details>and<summary>for better space management - Form Inputs: Uses Scaffold-ETH components for address inputs
- Loading States: Clear loading indicators during API requests
- Error Handling: User-friendly error messages
- Network Selection: Dropdown for network selection
- Pagination: For navigating large result sets
- Responsive Design: Works on all screen sizes
The SDK includes UI components for each data type:
Token Components:
- GetMetadata: Displays token metadata information
- GetBalances: Shows token balances for an address
- GetHolders: Lists token holders with pagination
- GetTransfers: Displays token transfer history
- GetOHLCByContract: Shows price charts for tokens
- GetOHLCByPool: Shows price charts for liquidity pools
- GetSwaps: Displays DEX swap events
- GetPools: Lists liquidity pools
NFT Components:
- GetNFTCollections: Displays NFT collection metadata and statistics
- GetNFTItems: Shows individual NFTs from a collection with metadata
- GetNFTOwnerships: Lists NFTs owned by a wallet address
- GetNFTActivities: Displays NFT transaction history with advanced filtering
- GetNFTHolders: Shows NFT holder information and distribution statistics
- GetNFTSales: Shows NFT marketplace sales data
Example usage:
import { GetMetadata } from "~~/app/token-api/_components/GetMetadata";
import { GetBalances } from "~~/app/token-api/_components/GetBalances";
import { GetNFTCollections } from "~~/app/token-api/_components/GetNFTCollections";
import { GetNFTOwnerships } from "~~/app/token-api/_components/GetNFTOwnerships";
import { GetNFTHolders } from "~~/app/token-api/_components/GetNFTHolders";
export default function YourPage() {
return (
<div>
{/* Token Components */}
<GetMetadata
initialContractAddress="0xc944E90C64B2c07662A292be6244BDf05Cda44a7"
initialNetwork="mainnet"
isOpen={true}
/>
<GetBalances
initialAddress="0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
initialNetwork="mainnet"
isOpen={true}
/>
{/* NFT Components */}
<GetNFTCollections isOpen={true} />
<GetNFTOwnerships isOpen={true} />
<GetNFTHolders isOpen={true} />
</div>
);
}Location: packages/nextjs/app/token-api/_config/networks.ts
Defines supported networks and provides helper functions:
export const EVM_NETWORKS: EVMNetwork[] = [
{ id: "mainnet", name: "Ethereum", blockExplorer: "https://etherscan.io" },
{ id: "base", name: "Base", blockExplorer: "https://basescan.org" },
{
id: "arbitrum-one",
name: "Arbitrum",
blockExplorer: "https://arbiscan.io",
},
{ id: "bsc", name: "BSC", blockExplorer: "https://bscscan.com" },
{
id: "optimism",
name: "Optimism",
blockExplorer: "https://optimistic.etherscan.io",
},
{ id: "matic", name: "Polygon", blockExplorer: "https://polygonscan.com" },
];
// Helper functions
export const getNetworkById = (id: NetworkId): EVMNetwork | undefined => {
/*...*/
};
export const getNetworkName = (id: NetworkId): string => {
/*...*/
};
export const getBlockExplorerTokenUrl = (
networkId: NetworkId,
tokenAddress: string
): string => {
/*...*/
};
export const getBlockExplorerAddressUrl = (
networkId: NetworkId,
address: string
): string => {
/*...*/
};
export const getBlockExplorerTxUrl = (
networkId: NetworkId,
txHash: string
): string => {
/*...*/
};Location: packages/nextjs/app/token-api/_config/protocols.ts
Defines supported protocols and provides helper functions:
export const PROTOCOLS: Protocol[] = [
{ id: "uniswap_v2", name: "Uniswap V2" },
{ id: "uniswap_v3", name: "Uniswap V3" },
];
// Helper functions
export const getProtocolById = (id: ProtocolId): Protocol | undefined => {
/*...*/
};
export const getProtocolName = (id: ProtocolId): string => {
/*...*/
};
export const formatProtocolDisplay = (protocolId: ProtocolId): string => {
/*...*/
};Location: packages/nextjs/app/token-api/_config/exampleTokens.ts
Provides example tokens for each network for testing purposes:
export const EXAMPLE_TOKENS: Record<NetworkId, TokenExample[]> = {
mainnet: [
{
address: "0xc944E90C64B2c07662A292be6244BDf05Cda44a7",
name: "The Graph",
symbol: "GRT",
decimals: 18,
description: "Indexing protocol for querying networks",
},
// More tokens...
],
// More networks...
};
// Helper functions
export const getExampleTokensForNetwork = (
networkId: NetworkId
): TokenExample[] => {
/*...*/
};
export const getFirstExampleTokenForNetwork = (
networkId: NetworkId
): TokenExample | undefined => {
/*...*/
};
export const getExampleTokenAddress = (networkId: NetworkId): string => {
/*...*/
};Location: packages/nextjs/app/token-api/_config/timeConfig.ts
Defines time intervals and spans for data querying:
export const TIME_INTERVALS: TimeInterval[] = [
{ id: "1h", name: "1 Hour" },
{ id: "4h", name: "4 Hours" },
{ id: "1d", name: "1 Day" },
{ id: "1w", name: "1 Week" },
];
export const TIME_SPANS: TimeSpan[] = [
{ id: "1d", name: "Last 24 Hours", seconds: 86400 },
{ id: "7d", name: "Last 7 Days", seconds: 604800 },
{ id: "30d", name: "Last 30 Days", seconds: 2592000 },
// More time spans...
];
// Helper functions
export const getTimeSpanById = (id: string): TimeSpan | undefined => {
/*...*/
};
export const getTimeIntervalById = (id: string): TimeInterval | undefined => {
/*...*/
};
export const getTimeRange = (timeSpanId: string) => {
/*...*/
};Location: packages/nextjs/app/token-api/_config/blockTimeUtils.ts
Provides utilities for converting between block numbers and timestamps:
// Current block numbers (estimated for May 2024)
export const CURRENT_BLOCK_NUMBERS: Record<NetworkId, number> = {
mainnet: 19200000, // Ethereum mainnet
"arbitrum-one": 175000000, // Arbitrum
// More networks...
};
// Average block time in seconds for different networks
export const BLOCK_TIMES: Record<NetworkId, number> = {
mainnet: 12, // Ethereum mainnet
"arbitrum-one": 0.25, // Arbitrum
// More networks...
};
// Helper function to estimate date from block number and network
export const estimateDateFromBlock = (
blockNum: number | undefined,
networkId: NetworkId
): Date => {
/*...*/
};Location: packages/nextjs/app/token-api/_utils/utils.ts
Provides utilities for working with addresses:
/**
* Cleans a contract address by removing spaces, converting to lowercase, and ensuring 0x prefix
* @param address The contract address to clean
* @returns The cleaned address
*/
export function cleanContractAddress(address?: string): string {
if (!address) return "";
// Remove spaces, convert to lowercase
let cleaned = address.trim().toLowerCase();
// Ensure it has the 0x prefix
if (!cleaned.startsWith("0x")) {
cleaned = "0x" + cleaned;
}
return cleaned;
}Location: packages/nextjs/app/token-api/_types/index.ts
Defines common types used throughout the SDK:
export type NetworkId =
| "mainnet"
| "base"
| "arbitrum-one"
| "bsc"
| "optimism"
| "matic";
export interface TokenApiOptions {
skip?: boolean;
refetchInterval?: number;
}
export interface PaginationInfo {
page: number;
page_size: number;
total_pages: number;
}Visit http://localhost:3000/token-api to see a comprehensive example of all components in action.
The SDK covers the following Token API endpoints:
Token Endpoints:
/tokens/evm/{contract}- Token metadata/balances/evm/{address}- Token balances/holders/evm/{contract}- Token holders/transfers/evm- Token transfers/ohlc/pools/evm/{pool}- Pool OHLC data/ohlc/prices/evm/{contract}- Token OHLC data/pools/evm- Liquidity pools/swaps/evm- DEX swaps
NFT Endpoints:
/nft/collections/evm/{contract}- NFT collection data/nft/items/evm/{contract}- NFT items/nft/ownerships/evm/{address}- NFT ownerships/nft/activities/evm- NFT activities/nft/holders/evm/{contract}- NFT holders/nft/sales/evm- NFT sales
Major Improvements in Latest Version:
- Authentication Fix: All APIs now properly handle authentication with comprehensive error messages
- Data Structure Normalization: Hooks return arrays directly (
NFTCollection[]) instead of nested response objects - NFT Activities Requirements: Contract address is now a required parameter with proper validation
- Time Filtering: Automatic 30-day time ranges prevent database timeouts on popular contracts
- Interface Completeness: Added missing fields like
token_standardandtotal_unique_supplyto NFT interfaces - Error Handling: Enhanced error detection for authentication, validation, and timeout issues
Breaking Changes:
- Hook return types changed from
{data: T[]}toT[]directly - NFT Activities API requires
contract_addressparameter (no longer optional) - Time filtering now uses default ranges to prevent timeouts
Problem: Getting 401 unauthorized errors Solution:
- Ensure you have a valid Graph API token from The Graph Market
- Add the token to your
.env.localfile:NEXT_PUBLIC_GRAPH_TOKEN=your_token_here
- Restart your development server after adding the token
- Check the browser console for authentication error messages
Problem: Components showing "No valid data found" despite successful API responses Solution:
- This was resolved in recent updates where hooks now return data arrays directly
- Ensure you're using the latest version of the hooks
- Check that components use
Array.isArray(data)instead ofdata?.data - All hooks now return proper array types (e.g.,
NFTCollection[],NFTItem[]) instead of wrapper objects
Problem: Getting 400 errors with "contract field validation" messages Solution:
- The NFT Activities API requires a contract address parameter
- Ensure you provide a valid NFT contract address (not just a wallet address)
- Use time filters to prevent database timeouts on popular contracts
- Contract address is now a required parameter with proper validation
Problem: Getting 500 errors with "Query took too long" messages Solution:
- Add time filters (startTime and endTime) to your queries
- Use the provided time range buttons (Last 24h, Last 7 days, etc.)
- Popular NFT contracts like BAYC require time filtering to avoid timeouts
- Default 30-day time ranges are now automatically applied to prevent timeouts
Problem: Fetch errors or network timeouts Solution:
- Check your internet connection
- Verify the API URL is correct in your
.env.local - Check if The Graph Token API service is operational
- Try with a different network (mainnet, base, etc.)
Problem: TypeScript errors about missing properties Solution:
- Ensure you're using the correct interfaces from
_typesdirectory - Check that hook return types match what your component expects
- Some API responses may have optional fields - use optional chaining (
?.)
Problem: Components appear but don't fetch data Solution:
- Check that the
enabledorskipparameters are set correctly - Verify that required parameters (like contract addresses) are provided
- Check the browser console for error messages
- Ensure the component's
shouldFetchstate is properly managed
Contributions are welcome! Please read the contributing guidelines and submit pull requests for any improvements.