EarnKit SDK Reference
Complete API documentation for the EarnKit TypeScript SDK. This SDK enables developers to add usage-based monetization to AI agents with reliable billing, wallet integration, and flexible fee models.
Installation
npm install earnkit-sdk
# or
yarn add earnkit-sdk
# or
pnpm add earnkit-sdk
Quick Start
This guide provides a practical overview of the most common SDK workflows, based on the implementation in the example agent.
1. Initialization
First, import and instantiate the EarnKit
class. You only need to do this once per agent in your application.
import { EarnKit, EarnKitApiError } from 'earnkit-sdk';
const agent = new EarnKit({
agentId: process.env.NEXT_PUBLIC_AGENT_ID!, // Your Agent ID from the dashboard
baseUrl: 'https://your-deployed-backend.vercel.app', // The URL of your deployed 'web' app, "https://api.earnkit.com" for production
debug: true, // Optional: Enable debug logging
});
2. Core Flow: Charging for an AI Request
This is the most important pattern. The logic is wrapped in a try/catch
block to ensure users are never charged for failed requests.
async function handleAIRequest(userMessage: string, userWalletAddress: string) {
// This variable is crucial for the release logic
let eventId: string | null = null;
try {
// 1. Track the event to place a hold on the user's balance
console.log("Tracking event...");
const trackResponse = await agent.track({
walletAddress: userWalletAddress,
});
eventId = trackResponse.eventId;
// 2. Execute your core AI logic
console.log("Calling AI...");
const aiResponse = await callMyGeminiAPI(userMessage);
// 3. If the AI call succeeds, capture the event to finalize the charge
console.log("Capturing event...");
await agent.capture({ eventId });
// 4. (Optional) Refresh the user's balance in your UI
refreshBalanceInUI(userWalletAddress);
return aiResponse;
} catch (error) {
// 5. If any step fails, release the hold on the funds
if (eventId) {
console.log("Error occurred, releasing funds...");
await agent.release({ eventId });
}
// 6. Handle specific errors from the SDK
if (error instanceof EarnKitApiError) {
// e.g., "Insufficient funds. Please top up your balance."
throw new Error(`API Error: ${error.message}`);
} else {
// Handle other errors (e.g., AI API failure)
throw new Error("An unexpected error occurred.");
}
}
}
3. User Flow: Topping Up a Balance
This flow shows how to use the SDK to fetch top-up options and process a user’s on-chain transaction.
// Assume wallet is your user's connected wallet object (from Privy, RainbowKit, etc.)
async function handleTopUp(wallet: any, userWalletAddress: string) {
try {
// 1. Fetch the pre-configured purchase options from your dashboard
const { options } = await agent.getTopUpDetails();
if (!options || options.length === 0) {
console.log("No top-up options available.");
return;
}
// 2. Assume the user selects the first option in your UI
const selectedOption = options[0];
// 3. Use the user's wallet to send the on-chain transaction
// The to and value come directly from the selected option
const tx = await wallet.sendTransaction({
to: selectedOption.to,
value: BigInt(selectedOption.value), // Use BigInt to avoid precision loss
chainId: 84532, // e.g., Base Sepolia
});
const txReceipt = await tx.wait(); // Wait for the transaction to be mined
// 4. Submit the transaction hash to the backend for monitoring
await agent.submitTopUpTransaction({
txHash: txReceipt.transactionHash,
walletAddress: userWalletAddress,
amountInEth: selectedOption.amountInEth,
creditsToTopUp: selectedOption.creditsToTopUp,
});
// 5. Poll for the balance update to provide a great UX
const initialBalance = await agent.getBalance({ walletAddress: userWalletAddress });
agent.pollForBalanceUpdate({
walletAddress: userWalletAddress,
initialBalance,
onConfirmation: (newBalance) => {
console.log("Top-up successful! New balance:", newBalance);
// Update your UI here
},
onTimeout: () => {
console.log("Confirmation is taking longer than expected. Please check back soon.");
},
});
} catch (error) {
console.error("Top-up failed:", error);
// Handle errors in your UI
}
}
Configuration
EarnKitConfig
Configuration object for initializing the SDK.
interface EarnKitConfig {
agentId: string; // Required: Your agent ID from the dashboard
baseUrl?: string; // Optional: API base URL (default: http://localhost:3000)
debug?: boolean; // Optional: Enable debug logging (default: false)
requestTimeoutMs?: number; // Optional: Request timeout (default: 30000ms)
}
Example:
const earnkit = new EarnKit({
agentId: 'clxxxxx', // CUID from your dashboard
baseUrl: 'https://api.yourapp.com',
debug: process.env.NODE_ENV === 'development',
requestTimeoutMs: 60000 // 60 seconds
});
Core Methods
track()
Initiates a billable event with provisional charging. This method checks for sufficient funds/credits and creates a pending usage event.
track(params: TrackParams): Promise<{ eventId: string }>
Parameters:
interface TrackParams {
walletAddress: string; // Required: User's wallet address (0x...)
idempotencyKey?: string; // Optional: UUID for duplicate prevention
creditsToDeduct?: number; // Optional: Credits to deduct (credit-based models)
}
Returns:
Promise<{ eventId: string }>
- Unique event ID for capture/release
Examples:
Free Tier Model:
// Charges 0.0001 ETH after 50 free prompts
const { eventId } = await earnkit.track({
walletAddress: '0x742d35Cc6435C0532925a3b8b98C31d5b1c9c2c4'
});
Credit-Based Model:
// Deducts specified credits from user balance
const { eventId } = await earnkit.track({
walletAddress: '0x742d35Cc6435C0532925a3b8b98C31d5b1c9c2c4',
creditsToDeduct: 25 // Override default credits per prompt
});
With Idempotency:
import { v4 as uuidv4 } from 'uuid';
const { eventId } = await earnkit.track({
walletAddress: '0x742d35Cc6435C0532925a3b8b98C31d5b1c9c2c4',
idempotencyKey: uuidv4() // Prevents duplicate charges
});
Throws:
EarnKitInputError
- Invalid wallet address or parametersEarnKitApiError
- Insufficient funds/credits (status 402) or API errors
capture()
Finalizes a pending usage event, confirming the charge. Call this after successful AI operation completion.
capture(params: CaptureParams): Promise<{ success: boolean }>
Parameters:
interface CaptureParams {
eventId: string; // Required: Event ID from track() call
}
Example:
const { eventId } = await earnkit.track({ walletAddress });
try {
const aiResponse = await generateAIResponse(prompt);
// Confirm the charge
await earnkit.capture({ eventId });
return aiResponse;
} catch (error) {
await earnkit.release({ eventId });
throw error;
}
Throws:
EarnKitInputError
- Invalid event IDEarnKitApiError
- Event not found or not capturable (status 404)
release()
Releases a pending usage event, refunding any provisional charges. Call this when AI operations fail.
release(params: ReleaseParams): Promise<{ success: boolean }>
Parameters:
interface ReleaseParams {
eventId: string; // Required: Event ID from track() call
}
Example:
const { eventId } = await earnkit.track({ walletAddress });
try {
const result = await riskyAIOperation();
await earnkit.capture({ eventId });
return result;
} catch (error) {
// Refund user on failure
await earnkit.release({ eventId });
console.error('AI operation failed, charges refunded');
throw error;
}
Throws:
EarnKitInputError
- Invalid event IDEarnKitApiError
- Event not found or not releasable
Balance Management
getBalance()
Retrieves the current ETH and credit balances for a user’s wallet.
getBalance(params: { walletAddress: string }): Promise<UserBalance>
Parameters:
interface { walletAddress: string } // User's wallet address
Returns:
interface UserBalance {
eth: string; // ETH balance as string (e.g., "0.005")
credits: string; // Credit balance as string (e.g., "150")
}
Example:
const balance = await earnkit.getBalance({
walletAddress: '0x742d35Cc6435C0532925a3b8b98C31d5b1c9c2c4'
});
console.log(`User has ${balance.eth} ETH and ${balance.credits} credits`);
// Check if user has sufficient credits before operation
const requiredCredits = 10;
if (parseInt(balance.credits) < requiredCredits) {
throw new Error('Insufficient credits. Please top up.');
}
Top-up & Credit Management
getTopUpDetails()
Fetches available top-up options configured for the agent.
getTopUpDetails(): Promise<TopUpDetailsResponse>
Returns:
interface TopUpDetailsResponse {
options: TopUpOption[];
}
interface TopUpOption {
label: string; // Display label (e.g., "100 Credits")
amountInEth: string; // ETH amount (e.g., "0.001")
to: string; // Developer's deposit address
value: string; // Amount in Wei for transactions
creditsToTopUp?: number; // Credits to add (credit-based models)
}
Example:
const { options } = await earnkit.getTopUpDetails();
options.forEach(option => {
console.log(`${option.label}: ${option.amountInEth} ETH`);
// Example output: "100 Credits: 0.001 ETH"
});
// Use with wallet for transaction
const selectedOption = options[0];
const tx = await wallet.sendTransaction({
to: selectedOption.to,
value: BigInt(selectedOption.value) // Use BigInt to avoid precision loss
});
submitTopUpTransaction()
Submits a completed transaction hash for monitoring and credit processing.
submitTopUpTransaction(params: SubmitTopUpParams): Promise<SubmitTopUpResponse>
Parameters:
interface SubmitTopUpParams {
txHash: string; // Transaction hash from blockchain
walletAddress: string; // User's wallet address
amountInEth: string; // ETH amount sent
creditsToTopUp?: number; // Credits to add (credit-based models)
}
Returns:
interface SubmitTopUpResponse {
status: string; // "PENDING_CONFIRMATION"
message: string; // Status message
}
Example:
// After user sends transaction
const txHash = await wallet.sendTransaction({
to: option.to,
value: option.value
});
// Submit for monitoring
const response = await earnkit.submitTopUpTransaction({
txHash: txHash.hash,
walletAddress: userAddress,
amountInEth: option.amountInEth,
creditsToTopUp: option.creditsToTopUp
});
console.log(response.message); // "Top-up transaction is being monitored."
pollForBalanceUpdate()
Utility method to poll for balance changes after submitting a top-up transaction.
pollForBalanceUpdate(params: PollingParams): void
Parameters:
interface PollingParams {
walletAddress: string;
initialBalance: UserBalance;
onConfirmation: (newBalance: UserBalance) => void;
onTimeout?: () => void;
pollInterval?: number; // Default: 10000ms (10 seconds)
maxPolls?: number; // Default: 30 (5 minutes total)
}
Example:
// Get initial balance
const initialBalance = await earnkit.getBalance({ walletAddress });
// Submit top-up transaction
await earnkit.submitTopUpTransaction({
txHash,
walletAddress,
amountInEth: "0.001",
creditsToTopUp: 100
});
// Poll for updates
earnkit.pollForBalanceUpdate({
walletAddress,
initialBalance,
onConfirmation: (newBalance) => {
console.log('Balance updated!', newBalance);
// Update UI with new balance
updateUserInterface(newBalance);
},
onTimeout: () => {
console.log('Transaction confirmation timed out');
// Handle timeout (transaction may still be processing)
},
pollInterval: 5000, // Check every 5 seconds
maxPolls: 60 // Timeout after 5 minutes
});
Error Handling
The SDK provides specific error types for different failure scenarios:
Error Types
import {
EarnKitInitializationError,
EarnKitInputError,
EarnKitApiError
} from 'earnkit-sdk';
EarnKitInitializationError
Thrown during SDK initialization with invalid configuration.
try {
const earnkit = new EarnKit({ agentId: '' }); // Invalid empty agentId
} catch (error) {
if (error instanceof EarnKitInitializationError) {
console.error('SDK initialization failed:', error.message);
}
}
EarnKitInputError
Thrown when method parameters are invalid.
try {
await earnkit.track({ walletAddress: 'invalid-address' });
} catch (error) {
if (error instanceof EarnKitInputError) {
console.error('Invalid input:', error.message);
// Handle validation error
}
}
EarnKitApiError
Thrown when API calls fail. Contains HTTP status and response details.
try {
await earnkit.track({ walletAddress });
} catch (error) {
if (error instanceof EarnKitApiError) {
console.error(`API Error ${error.status}:`, error.message);
switch (error.status) {
case 402:
console.log('Insufficient funds - prompt user to top up');
break;
case 404:
console.log('Agent not found - check configuration');
break;
case 500:
console.log('Server error - retry later');
break;
}
}
}
Comprehensive Error Handling Example
async function processAIRequest(prompt: string, walletAddress: string) {
let eventId: string | null = null;
try {
// Track usage
const result = await earnkit.track({ walletAddress });
eventId = result.eventId;
// Process AI request
const aiResponse = await generateAIResponse(prompt);
// Confirm charge
await earnkit.capture({ eventId });
return aiResponse;
} catch (error) {
// Release pending charge on any failure
if (eventId) {
try {
await earnkit.release({ eventId });
} catch (releaseError) {
console.error('Failed to release charge:', releaseError);
}
}
// Handle specific error types
if (error instanceof EarnKitApiError) {
switch (error.status) {
case 402:
throw new Error('Insufficient funds. Please top up your balance.');
case 404:
throw new Error('Service configuration error. Please contact support.');
default:
throw new Error('Service temporarily unavailable. Please try again.');
}
}
if (error instanceof EarnKitInputError) {
throw new Error('Invalid request. Please check your input.');
}
// Re-throw other errors
throw error;
}
}
Best Practices
1. Always Use Capture/Release Pattern
// ✅ Good: Proper error handling with release
const { eventId } = await earnkit.track({ walletAddress });
try {
const result = await aiOperation();
await earnkit.capture({ eventId });
return result;
} catch (error) {
await earnkit.release({ eventId }); // Always release on failure
throw error;
}
// ❌ Bad: Missing release on failure
const { eventId } = await earnkit.track({ walletAddress });
const result = await aiOperation(); // If this fails, user is charged but gets no result
await earnkit.capture({ eventId });
2. Use Idempotency for Critical Operations
import { v4 as uuidv4 } from 'uuid';
// Generate idempotency key per unique operation
const operationId = uuidv4();
const { eventId } = await earnkit.track({
walletAddress,
idempotencyKey: operationId // Prevents duplicate charges on retry
});
3. Check Balances Before Expensive Operations
async function expensiveAIOperation(walletAddress: string) {
// Check balance first for better UX
const balance = await earnkit.getBalance({ walletAddress });
if (parseInt(balance.credits) < 50) {
throw new Error('Insufficient credits for this operation');
}
// Proceed with tracking
const { eventId } = await earnkit.track({
walletAddress,
creditsToDeduct: 50
});
// ... rest of operation
}
4. Implement Proper Logging
const earnkit = new EarnKit({
agentId: process.env.EARNKIT_AGENT_ID!,
debug: process.env.NODE_ENV === 'development'
});
// The SDK will log detailed information when debug: true
5. Handle Network Errors Gracefully
try {
await earnkit.track({ walletAddress });
} catch (error) {
if (error instanceof EarnKitApiError && error.status === 0) {
// Network error - the SDK has built-in retry logic
console.log('Network error occurred, SDK will retry automatically');
}
}
Fee Model Configuration
The SDK works with two fee model types configured in your dashboard:
Free Tier Model
interface FreeTierConfig {
threshold: number; // Free prompts per wallet
rate: number; // ETH charged per prompt after threshold
}
Credit-Based Model
interface CreditBasedConfig {
creditsPerPrompt: number;
topUpOptions: {
creditAmount: number;
pricePerCredit: number;
}[];
}
Rate Limiting & Retry Logic
The SDK includes built-in retry logic for reliability:
- Automatic Retries: Up to 2 retries (1 initial + 2 retries) for server errors (5xx) and timeouts
- Exponential Backoff: 1s, 2s, 4s delays between retries
- Request Timeout: 30 seconds default (configurable)
- Error Classification: Only retries transient errors (server errors and timeouts)
// SDK automatically retries transient failures
const earnkit = new EarnKit({
agentId: 'your-agent-id',
requestTimeoutMs: 60000 // Increase timeout for slow networks
});
TypeScript Support
The SDK is fully typed with TypeScript. Import types as needed:
import {
// Core Class
EarnKit,
// Configuration & Data Types
EarnKitConfig,
UserBalance,
TopUpOption,
FreeTierConfig,
CreditBasedConfig,
// Custom Errors
EarnKitApiError,
EarnKitInputError,
EarnKitInitializationError
} from 'earnkit-sdk';
// All methods are properly typed
const balance: UserBalance = await earnkit.getBalance({ walletAddress });
const config: EarnKitConfig = { agentId: 'test' };
For more examples and implementation details, see the main documentation or visit the developer dashboard to configure your agents.