diff --git a/README.md b/README.md
index d3d19bbb..9a87db19 100644
--- a/README.md
+++ b/README.md
@@ -1,166 +1,1802 @@
-
-
-
+# fetchOnrampOptions
-
+## Overview
-[](https://github.com/base/docs/graphs/contributors)
-[](https://github.com/base/docs/graphs/contributors)
-[](https://github.com/base/docs/stargazers)
-
-[](https://github.com/base/docs/blob/main/LICENSE.md)
+The `fetchOnrampOptions` utility is a powerful function that retrieves jurisdiction-specific onramp data, including supported fiat currencies and available cryptocurrency assets. This utility is essential for building compliant onramp experiences that respect regional regulatory requirements and asset availability.
-
+## Table of Contents
+- [Overview](#overview)
+- [What is fetchOnrampOptions?](#what-is-fetchonrampoptions)
+- [Use Cases](#use-cases)
+- [Prerequisites](#prerequisites)
+- [Basic Usage](#basic-usage)
+- [Parameters](#parameters)
+- [Return Value](#return-value)
+- [Advanced Usage](#advanced-usage)
+- [Best Practices](#best-practices)
+- [Error Handling](#error-handling)
+- [Country-Specific Guidelines](#country-specific-guidelines)
+- [Troubleshooting](#troubleshooting)
+- [Related Resources](#related-resources)
-[](https://base.org)
-[](https://base.mirror.xyz/)
-[](https://docs.base.org/)
-[](https://base.org/discord)
-[](https://twitter.com/Base)
+---
-
+## What is fetchOnrampOptions?
-[](https://github.com/base/docs/pulls)
-[](https://github.com/base/docs/issues)
+`fetchOnrampOptions` queries the Coinbase Onramp API to determine:
-Base Docs are community-managed. We welcome and encourage contributions from everyone to keep these docs accurate, helpful, and up to date.
+1. **Available Fiat Currencies** - Which fiat currencies can be used for purchases (e.g., USD, EUR, GBP)
+2. **Supported Crypto Assets** - Which cryptocurrencies can be purchased in the specified jurisdiction
+3. **Network Availability** - Which blockchain networks are available for each asset
+4. **Regional Restrictions** - Jurisdiction-specific limitations and requirements
-> Note: This repository powers the public Base documentation site. Content lives under `docs/`.
+This information enables you to:
+- Build compliant onramp flows that only show available options
+- Provide accurate asset selection based on user location
+- Handle regional variations in supported assets and currencies
+- Create seamless user experiences with pre-filtered options
-## Local development
+---
-Prerequisite: Node.js v19+.
+## Use Cases
-1. Clone the repository.
-2. Install the Mint CLI to preview documentation changes locally:
+### 1. Dynamic Asset Filtering
+Filter available cryptocurrencies based on user location to ensure compliance:
-```bash
-npm i -g mint
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+async function getAvailableAssets(country: string, subdivision?: string) {
+ const options = await fetchOnrampOptions({
+ country,
+ subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ return options.assets.map(asset => ({
+ id: asset.id,
+ name: asset.name,
+ availableNetworks: asset.networks.length
+ }));
+}
```
-3. Preview locally (run from the `docs/` directory where `docs.json` lives):
+### 2. Currency Selection UI
+Build a currency selector that only shows available options:
-```bash
-cd docs
-mint dev
+```tsx
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+import { useState, useEffect } from 'react';
+
+function CurrencySelector({ country, subdivision }) {
+ const [currencies, setCurrencies] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ async function loadCurrencies() {
+ try {
+ const options = await fetchOnrampOptions({ country, subdivision });
+ setCurrencies(options.fiatCurrencies);
+ } catch (error) {
+ console.error('Failed to load currencies:', error);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ loadCurrencies();
+ }, [country, subdivision]);
+
+ if (loading) return Loading currencies...
;
+
+ return (
+
+ {currencies.map(currency => (
+
+ {currency.symbol} {currency.name}
+
+ ))}
+
+ );
+}
```
-Alternatively, without a global install:
+### 3. Compliance Validation
+Validate that a user's intended purchase is available in their region:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+async function validatePurchase(
+ country: string,
+ subdivision: string | undefined,
+ assetId: string,
+ fiatCurrencyId: string
+): Promise<{ valid: boolean; reason?: string }> {
+ const options = await fetchOnrampOptions({ country, subdivision });
+
+ // Check if fiat currency is supported
+ const isFiatSupported = options.fiatCurrencies.some(
+ currency => currency.id === fiatCurrencyId
+ );
+
+ if (!isFiatSupported) {
+ return {
+ valid: false,
+ reason: `${fiatCurrencyId} is not supported in ${country}`
+ };
+ }
+
+ // Check if asset is available
+ const isAssetAvailable = options.assets.some(
+ asset => asset.id === assetId
+ );
+
+ if (!isAssetAvailable) {
+ return {
+ valid: false,
+ reason: `${assetId} is not available for purchase in ${country}`
+ };
+ }
+
+ return { valid: true };
+}
+```
+
+### 4. Network Selection
+Display available networks for a specific asset:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+async function getAssetNetworks(
+ country: string,
+ assetId: string,
+ subdivision?: string
+) {
+ const options = await fetchOnrampOptions({ country, subdivision });
+
+ const asset = options.assets.find(a => a.id === assetId);
+
+ if (!asset) {
+ throw new Error(`Asset ${assetId} not available in ${country}`);
+ }
+
+ return asset.networks.map(network => ({
+ id: network.id,
+ name: network.name,
+ chainId: network.config.chainId
+ }));
+}
+
+// Usage
+const ethNetworks = await getAssetNetworks('US', 'ETH', 'CA');
+console.log(ethNetworks);
+// [{ id: 'ethereum', name: 'Ethereum', chainId: '0x1' }, ...]
+```
+
+---
+
+## Prerequisites
+### API Key Requirements
+You need a Coinbase Onramp API key to use this utility. There are two ways to provide it:
+
+1. **Via OnchainKitProvider** (React applications):
+ ```tsx
+ import { OnchainKitProvider } from '@coinbase/onchainkit';
+
+ function App() {
+ return (
+
+ {/* Your app */}
+
+ );
+ }
+ ```
+
+2. **Direct Parameter** (non-React or outside provider):
+ ```typescript
+ const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA',
+ apiKey: 'your-api-key'
+ });
+ ```
+
+### Getting an API Key
+1. Visit the [Coinbase Developer Platform](https://portal.cdp.coinbase.com)
+2. Create a project or select an existing one
+3. Navigate to the Onramp section
+4. Generate or copy your API key
+
+### Dependencies
```bash
-npx mint dev
+npm install @coinbase/onchainkit
+# or
+yarn add @coinbase/onchainkit
+# or
+pnpm add @coinbase/onchainkit
+```
+
+---
+
+## Basic Usage
+
+### Simple Example
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+async function checkAvailability() {
+ const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA', // Required for US
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ console.log('Available currencies:', options.fiatCurrencies);
+ console.log('Available assets:', options.assets);
+}
+
+checkAvailability();
+```
+
+### React Component Example
+
+```tsx
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+import { useState, useEffect } from 'react';
+
+function OnrampOptions({ country, subdivision }) {
+ const [options, setOptions] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ async function loadOptions() {
+ try {
+ const data = await fetchOnrampOptions({
+ country,
+ subdivision
+ });
+ setOptions(data);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ loadOptions();
+ }, [country, subdivision]);
+
+ if (loading) return Loading options...
;
+ if (error) return Error: {error}
;
+
+ return (
+
+
Available Currencies
+
+ {options.fiatCurrencies.map(currency => (
+
+ {currency.symbol} {currency.name}
+
+ ))}
+
+
+
Available Assets
+
+ {options.assets.map(asset => (
+
+ {asset.name} ({asset.networks.length} networks)
+
+ ))}
+
+
+ );
+}
+```
+
+### Next.js Server-Side Example
+
+```typescript
+// app/api/onramp-options/route.ts
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+import { NextResponse } from 'next/server';
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url);
+ const country = searchParams.get('country');
+ const subdivision = searchParams.get('subdivision');
+
+ if (!country) {
+ return NextResponse.json(
+ { error: 'Country parameter is required' },
+ { status: 400 }
+ );
+ }
+
+ try {
+ const options = await fetchOnrampOptions({
+ country,
+ subdivision: subdivision || undefined,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ return NextResponse.json(options);
+ } catch (error) {
+ return NextResponse.json(
+ { error: 'Failed to fetch onramp options' },
+ { status: 500 }
+ );
+ }
+}
+```
+
+---
+
+## Parameters
+
+### Complete Parameter Reference
+
+```typescript
+interface FetchOnrampOptionsParams {
+ country: string;
+ subdivision?: string;
+ apiKey?: string;
+}
+```
+
+### `country` (required)
+
+**Type:** `string`
+
+**Description:** ISO 3166-1 alpha-2 two-letter country code representing the user's country of residence.
+
+**Format:** Uppercase two-letter code (e.g., `'US'`, `'GB'`, `'DE'`)
+
+**Examples:**
+```typescript
+// United States
+fetchOnrampOptions({ country: 'US', subdivision: 'NY' });
+
+// United Kingdom
+fetchOnrampOptions({ country: 'GB' });
+
+// Germany
+fetchOnrampOptions({ country: 'DE' });
+
+// Japan
+fetchOnrampOptions({ country: 'JP' });
+
+// Brazil
+fetchOnrampOptions({ country: 'BR' });
+```
+
+**Validation:**
+- Must be a valid ISO 3166-1 alpha-2 code
+- Must be uppercase
+- Cannot be empty or null
+
+**Common Country Codes:**
+| Country | Code | Subdivision Required |
+|---------|------|---------------------|
+| United States | US | Yes |
+| United Kingdom | GB | No |
+| Canada | CA | No |
+| Germany | DE | No |
+| France | FR | No |
+| Japan | JP | No |
+| Australia | AU | No |
+| Brazil | BR | No |
+| India | IN | No |
+| Singapore | SG | No |
+
+### `subdivision` (conditionally required)
+
+**Type:** `string | undefined`
+
+**Description:** ISO 3166-2 two-letter subdivision code representing the user's state or province within their country.
+
+**Required When:**
+- `country = 'US'` - Required for United States residents due to state-specific regulations
+
+**Format:** Uppercase two-letter code (e.g., `'NY'`, `'CA'`, `'TX'`)
+
+**Examples:**
+```typescript
+// US residents - subdivision required
+fetchOnrampOptions({ country: 'US', subdivision: 'CA' }); // California
+fetchOnrampOptions({ country: 'US', subdivision: 'NY' }); // New York
+fetchOnrampOptions({ country: 'US', subdivision: 'TX' }); // Texas
+
+// Non-US residents - subdivision optional
+fetchOnrampOptions({ country: 'GB' }); // UK - no subdivision needed
+fetchOnrampOptions({ country: 'CA' }); // Canada - optional
+```
+
+**US State Codes Reference:**
+| State | Code | State | Code |
+|-------|------|-------|------|
+| Alabama | AL | Montana | MT |
+| Alaska | AK | Nebraska | NE |
+| Arizona | AZ | Nevada | NV |
+| Arkansas | AR | New Hampshire | NH |
+| California | CA | New Jersey | NJ |
+| Colorado | CO | New Mexico | NM |
+| Connecticut | CT | New York | NY |
+| Delaware | DE | North Carolina | NC |
+| Florida | FL | North Dakota | ND |
+| Georgia | GA | Ohio | OH |
+| Hawaii | HI | Oklahoma | OK |
+| Idaho | ID | Oregon | OR |
+| Illinois | IL | Pennsylvania | PA |
+| Indiana | IN | Rhode Island | RI |
+| Iowa | IA | South Carolina | SC |
+| Kansas | KS | South Dakota | SD |
+| Kentucky | KY | Tennessee | TN |
+| Louisiana | LA | Texas | TX |
+| Maine | ME | Utah | UT |
+| Maryland | MD | Vermont | VT |
+| Massachusetts | MA | Virginia | VA |
+| Michigan | MI | Washington | WA |
+| Minnesota | MN | West Virginia | WV |
+| Mississippi | MS | Wisconsin | WI |
+| Missouri | MO | Wyoming | WY |
+
+**Why US Requires Subdivision:**
+Certain US states have specific cryptocurrency regulations and restrictions. For example:
+- New York requires a BitLicense for certain operations
+- Some states have different asset availability
+- Regulatory compliance varies by state
+
+### `apiKey` (conditionally required)
+
+**Type:** `string | undefined`
+
+**Description:** Your Coinbase Onramp API key for authentication.
+
+**Required When:**
+- Using the utility outside of `OnchainKitProvider` context
+- Using in a non-React environment (Node.js, serverless functions)
+- Server-side rendering without provider context
+
+**Optional When:**
+- Used within a React component tree wrapped by `OnchainKitProvider`
+- The provider already has an API key configured
+
+**Examples:**
+```typescript
+// With explicit API key
+const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA',
+ apiKey: 'your-api-key-here'
+});
+
+// With environment variable
+const options = await fetchOnrampOptions({
+ country: 'GB',
+ apiKey: process.env.COINBASE_ONRAMP_API_KEY
+});
+
+// In React with provider (no apiKey needed)
+function MyComponent() {
+ const [options, setOptions] = useState(null);
+
+ useEffect(() => {
+ fetchOnrampOptions({ country: 'US', subdivision: 'CA' })
+ .then(setOptions);
+ }, []);
+
+ // ...
+}
+```
+
+**Security Best Practices:**
+```typescript
+// ✅ Good: Use environment variables
+apiKey: process.env.COINBASE_ONRAMP_API_KEY
+
+// ✅ Good: Server-side only
+// In Next.js API route or server component
+apiKey: process.env.ONRAMP_API_KEY
+
+// ❌ Bad: Never hardcode in client-side code
+apiKey: 'pk_live_123456789' // Don't do this!
+
+// ❌ Bad: Never commit to version control
+apiKey: 'your-actual-key-here' // Don't do this!
+```
+
+---
+
+## Return Value
+
+### Type Definition
+
+```typescript
+interface OnrampOptionsResponseData {
+ fiatCurrencies: FiatCurrency[];
+ assets: Asset[];
+}
+
+interface FiatCurrency {
+ id: string; // Currency code (e.g., 'USD')
+ name: string; // Full name (e.g., 'US Dollar')
+ symbol: string; // Currency symbol (e.g., '$')
+}
+
+interface Asset {
+ id: string; // Asset symbol (e.g., 'ETH')
+ name: string; // Full name (e.g., 'Ethereum')
+ networks: Network[]; // Available networks
+}
+
+interface Network {
+ id: string; // Network identifier (e.g., 'ethereum')
+ name: string; // Display name (e.g., 'Ethereum')
+ config: {
+ chainId: string; // Hex chain ID (e.g., '0x1')
+ };
+}
+```
+
+### Example Response
+
+```typescript
+{
+ fiatCurrencies: [
+ {
+ id: "USD",
+ name: "US Dollar",
+ symbol: "$"
+ },
+ {
+ id: "EUR",
+ name: "Euro",
+ symbol: "€"
+ }
+ ],
+ assets: [
+ {
+ id: "ETH",
+ name: "Ethereum",
+ networks: [
+ {
+ id: "ethereum",
+ name: "Ethereum",
+ config: {
+ chainId: "0x1" // Mainnet
+ }
+ },
+ {
+ id: "base",
+ name: "Base",
+ config: {
+ chainId: "0x2105" // Base mainnet
+ }
+ }
+ ]
+ },
+ {
+ id: "BTC",
+ name: "Bitcoin",
+ networks: [
+ {
+ id: "bitcoin",
+ name: "Bitcoin",
+ config: {
+ chainId: "0x0" // Bitcoin doesn't use EVM chain IDs
+ }
+ }
+ ]
+ },
+ {
+ id: "USDC",
+ name: "USD Coin",
+ networks: [
+ {
+ id: "ethereum",
+ name: "Ethereum",
+ config: {
+ chainId: "0x1"
+ }
+ },
+ {
+ id: "base",
+ name: "Base",
+ config: {
+ chainId: "0x2105"
+ }
+ },
+ {
+ id: "polygon",
+ name: "Polygon",
+ config: {
+ chainId: "0x89"
+ }
+ }
+ ]
+ }
+ ]
+}
+```
+
+### Understanding the Response
+
+#### Fiat Currencies Array
+Each fiat currency object contains:
+- **`id`**: The ISO 4217 currency code used in transactions
+- **`name`**: Human-readable currency name for display
+- **`symbol`**: The currency symbol for formatting amounts
+
+```typescript
+// Using fiat currency data
+options.fiatCurrencies.forEach(currency => {
+ console.log(`${currency.symbol} ${currency.name} (${currency.id})`);
+});
+// Output: $ US Dollar (USD)
+```
+
+#### Assets Array
+Each asset object contains:
+- **`id`**: The asset symbol/ticker (e.g., ETH, BTC, USDC)
+- **`name`**: Full asset name for display
+- **`networks`**: Array of blockchain networks where this asset can be purchased
+
+```typescript
+// Finding a specific asset
+const eth = options.assets.find(asset => asset.id === 'ETH');
+console.log(`${eth.name} available on ${eth.networks.length} networks`);
+```
+
+#### Networks Array
+Each network object contains:
+- **`id`**: Network identifier for API calls
+- **`name`**: Display name for the network
+- **`config.chainId`**: Hexadecimal chain ID for wallet connections
+
+```typescript
+// Converting chain ID to decimal
+const chainIdHex = network.config.chainId;
+const chainIdDecimal = parseInt(chainIdHex, 16);
+
+// Example: '0x1' -> 1 (Ethereum Mainnet)
+// Example: '0x2105' -> 8453 (Base Mainnet)
+```
+
+---
+
+## Advanced Usage
+
+### 1. Caching Strategy
+
+Implement caching to reduce API calls and improve performance:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+interface CacheEntry {
+ data: OnrampOptionsResponseData;
+ timestamp: number;
+}
+
+class OnrampOptionsCache {
+ private cache: Map = new Map();
+ private ttl: number; // Time to live in milliseconds
+
+ constructor(ttlMinutes: number = 60) {
+ this.ttl = ttlMinutes * 60 * 1000;
+ }
+
+ private getCacheKey(country: string, subdivision?: string): string {
+ return `${country}${subdivision ? `-${subdivision}` : ''}`;
+ }
+
+ private isExpired(entry: CacheEntry): boolean {
+ return Date.now() - entry.timestamp > this.ttl;
+ }
+
+ async getOptions(
+ country: string,
+ subdivision?: string
+ ): Promise {
+ const key = this.getCacheKey(country, subdivision);
+ const cached = this.cache.get(key);
+
+ if (cached && !this.isExpired(cached)) {
+ console.log('Cache hit for', key);
+ return cached.data;
+ }
+
+ console.log('Cache miss for', key);
+ const data = await fetchOnrampOptions({
+ country,
+ subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ this.cache.set(key, {
+ data,
+ timestamp: Date.now()
+ });
+
+ return data;
+ }
+
+ clear(): void {
+ this.cache.clear();
+ }
+
+ invalidate(country: string, subdivision?: string): void {
+ const key = this.getCacheKey(country, subdivision);
+ this.cache.delete(key);
+ }
+}
+
+// Usage
+const cache = new OnrampOptionsCache(30); // 30-minute TTL
+
+async function getOptions(country: string, subdivision?: string) {
+ return cache.getOptions(country, subdivision);
+}
+```
+
+### 2. Custom React Hook
+
+Create a reusable hook with built-in caching and error handling:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+import { useState, useEffect, useCallback } from 'react';
+
+interface UseOnrampOptionsResult {
+ options: OnrampOptionsResponseData | null;
+ loading: boolean;
+ error: Error | null;
+ refetch: () => Promise;
+}
+
+function useOnrampOptions(
+ country: string,
+ subdivision?: string
+): UseOnrampOptionsResult {
+ const [options, setOptions] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const fetchOptions = useCallback(async () => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const data = await fetchOnrampOptions({
+ country,
+ subdivision
+ });
+ setOptions(data);
+ } catch (err) {
+ setError(err instanceof Error ? err : new Error('Unknown error'));
+ } finally {
+ setLoading(false);
+ }
+ }, [country, subdivision]);
+
+ useEffect(() => {
+ fetchOptions();
+ }, [fetchOptions]);
+
+ return { options, loading, error, refetch: fetchOptions };
+}
+
+// Usage
+function AssetSelector({ country, subdivision }) {
+ const { options, loading, error, refetch } = useOnrampOptions(
+ country,
+ subdivision
+ );
+
+ if (loading) return Loading assets...
;
+ if (error) {
+ return (
+
+
Error loading options: {error.message}
+
Retry
+
+ );
+ }
+
+ return (
+
+ {options?.assets.map(asset => (
+
+ {asset.name}
+
+ ))}
+
+ );
+}
+```
+
+### 3. Location Detection Integration
+
+Combine with geolocation to automatically detect user location:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+interface LocationData {
+ country: string;
+ subdivision?: string;
+}
+
+async function detectUserLocation(): Promise {
+ // Using a geolocation API service
+ const response = await fetch('https://ipapi.co/json/');
+ const data = await response.json();
+
+ return {
+ country: data.country_code,
+ subdivision: data.region_code || undefined
+ };
+}
+
+async function getOptionsForUser(): Promise {
+ const location = await detectUserLocation();
+
+ console.log(`Detected location: ${location.country}`, location.subdivision);
+
+ return fetchOnrampOptions({
+ country: location.country,
+ subdivision: location.subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+}
+
+// React component example
+function AutoDetectOnramp() {
+ const [location, setLocation] = useState(null);
+ const [options, setOptions] = useState(null);
+
+ useEffect(() => {
+ async function setup() {
+ const userLocation = await detectUserLocation();
+ setLocation(userLocation);
+
+ const onrampOptions = await fetchOnrampOptions({
+ country: userLocation.country,
+ subdivision: userLocation.subdivision
+ });
+ setOptions(onrampOptions);
+ }
+
+ setup();
+ }, []);
+
+ if (!location || !options) {
+ return Detecting your location...
;
+ }
+
+ return (
+
+
Your location: {location.country} {location.subdivision}
+
+
+ );
+}
+```
+
+### 4. Multi-Region Comparison
+
+Compare available options across different regions:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+interface RegionComparison {
+ country: string;
+ subdivision?: string;
+ fiatCurrencies: number;
+ assets: number;
+ uniqueAssets: string[];
+}
+
+async function compareRegions(
+ regions: Array<{ country: string; subdivision?: string }>
+): Promise {
+ const comparisons = await Promise.all(
+ regions.map(async (region) => {
+ const options = await fetchOnrampOptions({
+ ...region,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ return {
+ ...region,
+ fiatCurrencies: options.fiatCurrencies.length,
+ assets: options.assets.length,
+ uniqueAssets: options.assets.map(a => a.id)
+ };
+ })
+ );
+
+ return comparisons;
+}
+
+// Usage
+const comparison = await compareRegions([
+ { country: 'US', subdivision: 'NY' },
+ { country: 'US', subdivision: 'CA' },
+ { country: 'GB' },
+ { country: 'DE' }
+]);
+
+console.table(comparison);
+```
+
+### 5. Asset Availability Checker
+
+Create a utility to check if specific assets are available:
+
+```typescript
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+
+interface AssetAvailability {
+ available: boolean;
+ networks?: Network[];
+ reason?: string;
+}
+
+async function checkAssetAvailability(
+ country: string,
+ assetId: string,
+ subdivision?: string
+): Promise {
+ try {
+ const options = await fetchOnrampOptions({
+ country,
+ subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ const asset = options.assets.find(a => a.id === assetId);
+
+ if (!asset) {
+ return {
+ available: false,
+ reason: `${assetId} is not available in ${country}`
+ };
+ }
+
+ return {
+ available: true,
+ networks: asset.networks
+ };
+ } catch (error) {
+ return {
+ available: false,
+ reason: `Error checking availability: ${error.message}`
+ };
+ }
+}
+
+// Usage
+const ethAvailability = await checkAssetAvailability('US', 'ETH', 'CA');
+
+if (ethAvailability.available) {
+ console.log(`ETH available on networks:`, ethAvailability.networks);
+} else {
+ console.log(ethAvailability.reason);
+}
+```
+
+### 6. Dynamic Form Builder
+
+Build a form that adapts based on available options:
+
+```tsx
+import { fetchOnrampOptions } from '@coinbase/onchainkit/fund';
+import { useState, useEffect } from 'react';
+
+function DynamicOnrampForm({ country, subdivision }) {
+ const [options, setOptions] = useState(null);
+ const [selectedCurrency, setSelectedCurrency] = useState('');
+ const [selectedAsset, setSelectedAsset] = useState('');
+ const [selectedNetwork, setSelectedNetwork] = useState('');
+
+ useEffect(() => {
+ async function loadOptions() {
+ const data = await fetchOnrampOptions({ country, subdivision });
+ setOptions(data);
+
+ // Set defaults
+ if (data.fiatCurrencies.length > 0) {
+ setSelectedCurrency(data.fiatCurrencies[0].id);
+ }
+ if (data.assets.length > 0) {
+ setSelectedAsset(data.assets[0].id);
+ if (data.assets[0].networks.length > 0) {
+ setSelectedNetwork(data.assets[0].networks[0].id);
+ }
+ }
+ }
+
+ loadOptions();
+ }, [country, subdivision]);
+
+ if (!options) return Loading...
;
+
+ const selectedAssetData = options.assets.find(a => a.id === selectedAsset);
+
+ return (
+
+ );
+}
+```
+
+---
+
+## Best Practices
+
+### 1. Performance Optimization
+
+**Cache Aggressively**
+```typescript
+// Cache options for at least 30-60 minutes
+// Options don't change frequently
+const CACHE_TTL = 60 * 60 * 1000; // 1 hour
+```
+
+**Parallel Requests**
+```typescript
+// When you need options for multiple regions
+const [usOptions, ukOptions] = await Promise.all([
+ fetchOnrampOptions({ country: 'US', subdivision: 'CA' }),
+ fetchOnrampOptions({ country: 'GB' })
+]);
+```
+
+**Prefetch on Route Load**
+```typescript
+// In Next.js, prefetch in getServerSideProps or loader
+export async function getServerSideProps() {
+ const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA',
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ return { props: { options } };
+}
+```
+
+### 2. Error Handling
+
+**Graceful Fallbacks**
+```typescript
+async function getOptionsWithFallback(
+ country: string,
+ subdivision?: string
+) {
+ try {
+ return await fetchOnrampOptions({ country, subdivision });
+ } catch (error) {
+ console.error('Failed to fetch options:', error);
+
+ // Return empty options as fallback
+ return {
+ fiatCurrencies: [],
+ assets: []
+ };
+ }
+}
+```
+
+**Retry Logic**
+```typescript
+async function fetchWithRetry(
+ params: FetchOnrampOptionsParams,
+ maxRetries: number = 3
+): Promise {
+ let lastError: Error;
+
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await fetchOnrampOptions(params);
+ } catch (error) {
+ lastError = error as Error;
+
+ if (i < maxRetries - 1) {
+ // Wait before retrying (exponential backoff)
+ await new Promise(resolve =>
+ setTimeout(resolve, Math.pow(2, i) * 1000)
+ );
+ }
+ }
+ }
+
+ throw lastError;
+}
+```
+
+### 3. Type Safety
+
+**Strict Typing**
+```typescript
+import type { OnrampOptionsResponseData } from '@coinbase/onchainkit/fund';
+
+interface ProcessedOptions {
+ currencies: string[];
+ assetsByNetwork: Record;
+}
+
+function processOptions(
+ options: OnrampOptionsResponseData
+): ProcessedOptions {
+ return {
+ currencies: options.fiatCurrencies.map(c => c.id),
+ assetsByNetwork: options.assets.reduce((acc, asset) => {
+ asset.networks.forEach(network => {
+ if (!acc[network.id]) {
+ acc[network.id] = [];
+ }
+ acc[network.id].push(asset.id);
+ });
+ return acc;
+ }, {} as Record)
+ };
+}
+```
+
+**Type Guards**
+```typescript
+function isValidCountryCode(code: string): boolean {
+ return /^[A-Z]{2}$/.test(code);
+}
+
+function isValidSubdivisionCode(code: string): boolean {
+ return /^[A-Z]{2}$/.test(code);
+}
+
+async function safelyFetchOptions(
+ country: string,
+ subdivision?: string
+) {
+ if (!isValidCountryCode(country)) {
+ throw new Error(`Invalid country code: ${country}`);
+ }
+
+ if (subdivision && !isValidSubdivisionCode(subdivision)) {
+ throw new Error(`Invalid subdivision code: ${subdivision}`);
+ }
+
+ return fetchOnrampOptions({ country, subdivision });
+}
+```
+
+### 4. User Experience
+
+**Loading States**
+```tsx
+function LoadingAwareSelector({ country, subdivision }) {
+ const [options, setOptions] = useState(null);
+ const [loading, setLoading] = useState(true);
+
+ useEffect(() => {
+ async function load() {
+ setLoading(true);
+ const data = await fetchOnrampOptions({ country, subdivision });
+ setOptions(data);
+ setLoading(false);
+ }
+ load();
+ }, [country, subdivision]);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return ;
+}
+```
+
+**Progressive Enhancement**
+```tsx
+function ProgressiveOnrampFlow() {
+ const [step, setStep] = useState(1);
+ const [country, setCountry] = useState('');
+ const [options, setOptions] = useState(null);
+
+ // Step 1: Get country
+ if (step === 1) {
+ return (
+ {
+ setCountry(c);
+ setStep(2);
+ }}
+ />
+ );
+ }
+
+ // Step 2: Load options and show asset selector
+ if (step === 2) {
+ return (
+ setStep(3)}
+ />
+ );
+ }
+
+ // Step 3: Complete purchase
+ return ;
+}
+```
+
+### 5. Security
+
+**Server-Side API Keys**
+```typescript
+// ✅ Good: Server-side only
+// pages/api/onramp-options.ts
+export default async function handler(req, res) {
+ const options = await fetchOnrampOptions({
+ country: req.query.country,
+ subdivision: req.query.subdivision,
+ apiKey: process.env.ONRAMP_API_KEY // Server-side only
+ });
+
+ res.json(options);
+}
+
+// ❌ Bad: Client-side API key exposure
+function ClientComponent() {
+ const options = await fetchOnrampOptions({
+ country: 'US',
+ apiKey: 'pk_live_123...' // Never do this!
+ });
+}
+```
+
+**Input Validation**
+```typescript
+import { z } from 'zod';
+
+const LocationSchema = z.object({
+ country: z.string().length(2).regex(/^[A-Z]{2}$/),
+ subdivision: z.string().length(2).regex(/^[A-Z]{2}$/).optional()
+});
+
+async function validatedFetchOptions(params: unknown) {
+ const validated = LocationSchema.parse(params);
+ return fetchOnrampOptions({
+ ...validated,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+}
+```
+
+---
+
+## Error Handling
+
+### Common Errors
+
+#### 1. Missing API Key
+
+**Error:**
+```
+Error: API key is required
```
-### Troubleshooting
+**Solution:**
+```typescript
+// Ensure API key is provided via provider or parameter
+const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA',
+ apiKey: process.env.ONRAMP_API_KEY // Add this
+});
+```
-- Ensure Node.js v19+ is installed and that you run `mint dev` from the directory containing `docs.json` (usually `docs/`).
-- Local preview differs from production: run `mint update` to update the CLI.
+#### 2. Invalid Country Code
-## How to contribute
+**Error:**
+```
+Error: Invalid country code
+```
-1. **Fork and branch**: Fork `base/docs` and create a descriptive branch for your change.
-2. **Edit content in `docs/`**: Follow the structure and style guidelines below. Preview locally with the Mint CLI.
-3. **Open a pull request**: Provide a clear summary and links to related pages. The docs team and community will review.
+**Solution:**
+```typescript
+// Use valid ISO 3166-1 alpha-2 codes
+const options = await fetchOnrampOptions({
+ country: 'US', // ✅ Correct
+ // country: 'USA', // ❌ Wrong - must be 2 letters
+ subdivision: 'CA'
+});
+```
-> Tip: Prefer small, focused PRs. Link related guides and references directly in your content.
+#### 3. Missing Required Subdivision
-## Documentation structure
+**Error:**
+```
+Error: Subdivision is required for US residents
+```
-### Core principle: maintain existing structure
+**Solution:**
+```typescript
+// Always provide subdivision for US
+const options = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA' // Required for US
+});
+```
-> Warning: Do not create new top-level sections. Place all new content within existing folders under `docs/`.
+#### 4. Network/API Errors
-The Base documentation is organized into established sections (for example: `get-started/`, `learn/`, `base-account/`, `base-app/`, `base-chain/`, `cookbook/`, `mini-apps/`, `onchainkit/`). Fit new content into the most relevant existing section.
+**Error:**
+```
+Error: Failed to fetch onramp options
+```
-### Navigation policy
+**Solution:**
+```typescript
+async function robustFetch(country: string, subdivision?: string) {
+ try {
+ return await fetchOnrampOptions({ country, subdivision });
+ } catch (error) {
+ if (error.message.includes('network')) {
+ // Network error - retry
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ return fetchOnrampOptions({ country, subdivision });
+ }
+
+ if (error.message.includes('rate limit')) {
+ // Rate limited - wait longer
+ await new Promise(resolve => setTimeout(resolve, 5000));
+ return fetchOnrampOptions({ country, subdivision });
+ }
+
+ throw error;
+ }
+}
+```
+
+### Comprehensive Error Handler
+
+```typescript
+interface ErrorResult {
+ success: false;
+ error: string;
+ code: string;
+}
+
+interface SuccessResult {
+ success: true;
+ data: OnrampOptionsResponseData;
+}
+
+type FetchResult = SuccessResult | ErrorResult;
-> Note: We generally do not change the global navigation (top-level tabs) or sidebar sections unless there is a clear, broadly beneficial need. Contributions should focus on improving existing pages and adding new pages within current sections.
+async function safeFetchOnrampOptions(
+ country: string,
+ subdivision?: string
+): Promise {
+ try {
+ // Validate inputs
+ if (!country || country.length !== 2) {
+ return {
+ success: false,
+ error: 'Invalid country code. Must be 2-letter ISO code.',
+ code: 'INVALID_COUNTRY'
+ };
+ }
-### Section purpose and placement
+ if (country === 'US' && !subdivision) {
+ return {
+ success: false,
+ error: 'Subdivision required for US residents.',
+ code: 'MISSING_SUBDIVISION'
+ };
+ }
-- **Quickstart**: End-to-end setup to first success. Keep concise and current.
-- **Concepts**: Explanations of components, architecture, and design philosophy.
-- **Guides**: Step-by-step, action-oriented tutorials for specific tasks.
-- **Examples**: Complete, runnable examples demonstrating real-world usage.
-- **Technical Reference**: API/method/component specs with parameters and return types.
-- **Contribute**: Information for contributors and process updates.
+ // Fetch options
+ const data = await fetchOnrampOptions({
+ country,
+ subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
-#### Cookbook scope
+ return {
+ success: true,
+ data
+ };
-- The `cookbook/` section hosts use case-focused guides and patterns, not product-specific documentation.
-- Prefer cross-cutting solutions that illustrate how to build on Base across tools and scenarios.
+ } catch (error) {
+ console.error('fetchOnrampOptions error:', error);
-> Warning: Avoid subsection proliferation:
-> - Put all guides at the same level within the Guides section.
-> - Organize Reference by component/feature, not per use case.
-> - Use cross-links instead of adding new structural layers.
+ if (error.message.includes('API key')) {
+ return {
+ success: false,
+ error: 'API key is missing or invalid.',
+ code: 'INVALID_API_KEY'
+ };
+ }
+
+ if (error.message.includes('rate limit')) {
+ return {
+ success: false,
+ error: 'Too many requests. Please try again later.',
+ code: 'RATE_LIMITED'
+ };
+ }
+
+ return {
+ success: false,
+ error: 'Failed to fetch onramp options. Please try again.',
+ code: 'UNKNOWN_ERROR'
+ };
+ }
+}
+
+// Usage
+const result = await safeFetchOnrampOptions('US', 'CA');
+
+if (result.success) {
+ console.log('Options:', result.data);
+} else {
+ console.error(`Error [${result.code}]:`, result.error);
+}
+```
-## Style and formatting
+---
-### Writing style
+## Country-Specific Guidelines
-1. Be concise and consistent; use active voice and second person.
-2. Focus on the happy path; mention alternatives briefly where relevant.
-3. Use explicit, descriptive headings and filenames.
-4. Maintain consistent terminology; introduce abbreviations on first use.
+### United States
-### AI-friendly content
+**Requirements:**
+- Subdivision (state code) is **mandatory**
+- Different states may have different asset restrictions
-- Use clear, explicit language and link related pages directly.
-- Prefer bulleted lists for options/steps when not sequential.
-- Name and reference libraries and tools explicitly.
-- Use semantic, readable URLs and avoid ambiguous abbreviations.
+**Example:**
+```typescript
+// California
+const caOptions = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'CA'
+});
+
+// New York
+const nyOptions = await fetchOnrampOptions({
+ country: 'US',
+ subdivision: 'NY'
+});
+```
+
+**State Considerations:**
+- New York has BitLicense requirements
+- Some states restrict certain assets
+- Regulatory landscape varies by state
+
+### United Kingdom
+
+**Requirements:**
+- No subdivision required
+- FCA regulated
+
+**Example:**
+```typescript
+const ukOptions = await fetchOnrampOptions({
+ country: 'GB'
+});
+```
+
+### European Union
+
+**Requirements:**
+- Use country-specific codes (DE, FR, IT, etc.)
+- No subdivision typically required
+- MiCA regulation applies
+
+**Examples:**
+```typescript
+// Germany
+const deOptions = await fetchOnrampOptions({ country: 'DE' });
+
+// France
+const frOptions = await fetchOnrampOptions({ country: 'FR' });
+
+// Netherlands
+const nlOptions = await fetchOnrampOptions({ country: 'NL' });
+```
+
+### Asia-Pacific
+
+**Requirements vary by country:**
+
+```typescript
+// Japan
+const jpOptions = await fetchOnrampOptions({ country: 'JP' });
+
+// Singapore
+const sgOptions = await fetchOnrampOptions({ country: 'SG' });
+
+// Australia
+const auOptions = await fetchOnrampOptions({ country: 'AU' });
+
+// South Korea
+const krOptions = await fetchOnrampOptions({ country: 'KR' });
+```
-> Checklist:
-> - Would a Large Language Model understand and follow this content?
-> - Can an engineer copy, paste, and run the examples as-is?
+---
+
+## Troubleshooting
+
+### Issue: Empty Assets Array
+
+**Symptom:** `options.assets` returns an empty array
+
+**Possible Causes:**
+1. The country/region doesn't support crypto purchases
+2. Regulatory restrictions in that jurisdiction
+3. Service temporarily unavailable in that region
+
+**Solution:**
+```typescript
+const options = await fetchOnrampOptions({ country, subdivision });
+
+if (options.assets.length === 0) {
+ console.warn(`No assets available in ${country}`);
+ // Show appropriate message to user
+ return 'Crypto purchases are not available in your region';
+}
+```
+
+### Issue: Unexpected Asset Restrictions
+
+**Symptom:** Expected assets are missing from the response
+
+**Cause:** Regional regulatory restrictions
+
+**Solution:**
+```typescript
+// Check if specific asset is available
+function isAssetAvailable(
+ options: OnrampOptionsResponseData,
+ assetId: string
+): boolean {
+ return options.assets.some(asset => asset.id === assetId);
+}
+
+const hasETH = isAssetAvailable(options, 'ETH');
+if (!hasETH) {
+ console.log('ETH not available in this region');
+}
+```
+
+### Issue: Stale Data
+
+**Symptom:** Options seem outdated
+
+**Cause:** Aggressive caching without invalidation
+
+**Solution:**
+```typescript
+// Implement cache invalidation
+class CacheWithInvalidation {
+ private cache = new Map();
+
+ async getOptions(country: string, subdivision?: string) {
+ const key = `${country}-${subdivision}`;
+ const cached = this.cache.get(key);
+
+ if (cached && Date.now() - cached.timestamp < 3600000) {
+ return cached.data;
+ }
+
+ const data = await fetchOnrampOptions({ country, subdivision });
+ this.cache.set(key, { data, timestamp: Date.now() });
+ return data;
+ }
+
+ invalidateAll() {
+ this.cache.clear();
+ }
+}
+```
+
+### Issue: Slow Response Times
+
+**Symptom:** Function takes several seconds to respond
+
+**Solutions:**
+
+1. **Implement caching:**
+```typescript
+// Cache at application level
+const optionsCache = new Map();
+
+async function getCachedOptions(country: string, subdivision?: string) {
+ const key = `${country}-${subdivision}`;
+
+ if (optionsCache.has(key)) {
+ return optionsCache.get(key);
+ }
+
+ const options = await fetchOnrampOptions({ country, subdivision });
+ optionsCache.set(key, options);
+ return options;
+}
+```
+
+2. **Use server-side caching:**
+```typescript
+// In Next.js with SWR
+import useSWR from 'swr';
+
+function useOnrampOptions(country: string, subdivision?: string) {
+ const { data, error } = useSWR(
+ ['onramp-options', country, subdivision],
+ () => fetchOnrampOptions({ country, subdivision }),
+ {
+ revalidateOnFocus: false,
+ revalidateOnReconnect: false,
+ dedupingInterval: 3600000 // 1 hour
+ }
+ );
+
+ return { options: data, loading: !error && !data, error };
+}
+```
+
+### Debug Helper
+
+```typescript
+async function debugFetchOnrampOptions(
+ country: string,
+ subdivision?: string
+) {
+ console.group('fetchOnrampOptions Debug');
+ console.log('Parameters:', { country, subdivision });
+ console.log('API Key present:', !!process.env.ONRAMP_API_KEY);
+ console.time('Fetch duration');
+
+ try {
+ const options = await fetchOnrampOptions({
+ country,
+ subdivision,
+ apiKey: process.env.ONRAMP_API_KEY
+ });
+
+ console.log('Success!');
+ console.log('Fiat currencies:', options.fiatCurrencies.length);
+ console.log('Assets:', options.assets.length);
+ console.log('Assets list:', options.assets.map(a => a.id));
+ console.timeEnd('Fetch duration');
+
+ return options;
+
+ } catch (error) {
+ console.error('Error:', error.message);
+ console.error('Stack:', error.stack);
+ console.timeEnd('Fetch duration');
+ throw error;
+
+ } finally {
+ console.groupEnd();
+ }
+}
+```
-### Mintlify formatting
+---
-- Start main sections with H2 (`##`) and subsections with H3 (`###`).
-- Use fenced code blocks with language and optional filename.
-- Wrap images in ` ` and include `alt` text.
-- Use callouts for emphasis: ``, ``, ``, ``, ``.
-- For procedures, prefer `` / ``.
-- For alternatives, use `` / ``.
-- For API docs, use ``, ``, and request/response examples.
+## Related Resources
-### Code examples
+### OnchainKit Documentation
+- [OnchainKit Overview](https://onchainkit.xyz)
+- [Fund Components](https://onchainkit.xyz/fund/introduction)
+- [Type Definitions](https://onchainkit.xyz/fund/types)
+- [Getting Started Guide](https://onchainkit.xyz/getting-started)
-- Provide complete, runnable examples with realistic data.
-- Include proper error handling and edge cases.
-- Specify language and filename when helpful.
-- Show expected output or verification steps.
+### API References
+- [`fetchOnrampOptions` API](https://onchainkit.xyz/fund/fetch-onramp-options)
+- [`OnrampOptionsResponseData` Type](https://onchainkit.xyz/fund/types#onrampoptionsresponsedata)
+- [Coinbase Onramp API Documentation](https://docs.cdp.coinbase.com/onramp)
-## Third-party guides policy
+### Related Utilities
+- `FundButton` - Complete onramp UI component
+- `fetchOnrampQuote` - Get pricing for transactions
+- `fetchOnrampTransaction` - Check transaction status
-> Warning: We generally do not accept guides that primarily document a third-party product. Exceptions require a clear Base-focused use case and a tight integration with Base products. Simply deploying on Base or connecting to Base Account/Base App is not sufficient.
+### External Resources
+- [ISO 3166-1 Country Codes](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)
+- [ISO 3166-2 Subdivision Codes](https://en.wikipedia.org/wiki/ISO_3166-2)
+- [Coinbase Developer Platform](https://portal.cdp.coinbase.com)
-If your goal is to increase discoverability of your product, please request inclusion on the Base Ecosystem page instead. See the instructions for [updating the Base Ecosystem page](https://github.com/base/web?tab=readme-ov-file#updating-the-base-ecosystem-page).
+### Community
+- [OnchainKit GitHub](https://github.com/coinbase/onchainkit)
+- [Discord Community](https://discord.gg/coinbase)
+- [Stack Overflow](https://stackoverflow.com/questions/tagged/onchainkit)
-## Review checklist (before submitting a PR)
+---
-- [ ] Fits within existing structure (no new top-level sections)
-- [ ] Minimal, necessary subsections only
-- [ ] Consistent terminology; abbreviations introduced on first use
-- [ ] Code examples are complete, runnable, and validated
-- [ ] Cross-links to related guides/examples/references are included
-- [ ] Uses Mintlify components and heading hierarchy correctly
-- [ ] Accessible images with descriptive `alt` text and frames
-- [ ] AI-friendly: explicit, link-rich, and easy to follow
+## Summary
-## Submission process
+The `fetchOnrampOptions` utility is essential for building compliant, user-friendly onramp experiences. Key takeaways:
-1. Create a PR to `https://github.com/base/docs` with your changes.
-2. Include a clear description of the change and impacted pages.
-3. Request review from the docs team.
-4. Address feedback and iterate.
-5. Once approved, changes will be merged and published.
+✅ **Always provide country code** (ISO 3166-1 alpha-2)
+✅ **Provide subdivision for US users** (state code required)
+✅ **Implement caching** to improve performance
+✅ **Handle errors gracefully** with fallbacks
+✅ **Validate inputs** before making API calls
+✅ **Secure API keys** (use environment variables)
+✅ **Test with multiple regions** to ensure compatibility
-## Publishing changes
+By following this guide, you'll be able to integrate Coinbase Onramp seamlessly into your application while respecting regional regulations and providing excellent user experience.
-The core team will review opened PRs. The SLA is 2 weeks, generally on a first-come, first-served basis outside of urgent changes.
+---
-## Storybook for UI components
+**Last Updated:** January 2026
-See `storybook/README.md` for details on local Storybook and component docs.
+For the latest information and updates, visit the [official OnchainKit documentation](https://onchainkit.xyz).