AI Agent Best Practices
This guide covers best practices for developers building AI agents that interact with wallets, execute trades, and manage digital assets using the OpenClaw Wallet SDK.
Introduction
AI agents operating with real financial assets require careful design. Unlike traditional software, agents make autonomous decisions that can result in irreversible transactions. This guide establishes patterns for building agents that are:
- Safe - Verify before executing, handle errors gracefully
- Transparent - Explain actions in plain language
- Deliberate - Require confirmation for significant operations
- Secure - Protect credentials and never expose sensitive data
Core Workflow
Every agent operation should follow this six-phase workflow:
1. AUTHENTICATE → 2. UNDERSTAND → 3. VALIDATE → 4. EXECUTE → 5. VERIFY → 6. DOCUMENT
│ │ │ │ │ │
│ │ │ │ │ └─ Summarize result
│ │ │ │ └─ Confirm success
│ │ │ └─ Execute with user approval
│ │ └─ Get quotes, check balances
│ └─ Clarify what user wants
└─ Ensure API key is configured
Phase 1: Authenticate
Before any operation, verify you have a valid API key and wallet access:
import { OpenClawWallet } from '@loomlay/openclaw-wallet-sdk';
// Self-custody mode (recommended) — keys stay on your device
const client = new OpenClawWallet({
apiKey: process.env.LOOMLAY_API_KEY,
walletMode: 'local',
getPassphrase: async () => process.env.LOOMLAY_WALLET_PASSPHRASE!,
});If no API key exists, register for one (rate limited to 1 per IP):
const { apiKey } = await OpenClawWallet.register();
// Store this securely - it's your identityIn self-custody mode, the agent needs a passphrase to sign transactions. Set LOOMLAY_WALLET_PASSPHRASE as an environment variable so it doesn't appear in conversation logs.
Phase 2: Understand
Clarify user intent before taking action:
For trading requests:
- Which tokens? (resolve names to addresses if needed)
- How much? (decimal, USD, percentage, or max?)
- Which chain? (Solana by default, or EVM chains)
For research requests:
- What metric matters? (volume, price change, liquidity)
- What timeframe? (5m, 1h, 6h, 24h)
- What filters? (minimum liquidity, safety score)
Phase 3: Validate
Always validate before executing:
// Get a quote first
const quote = await client.trading.getSwapQuote({
inputToken: 'SOL',
outputToken: 'USDC',
amount: '$100'
});
// Show the user what they'll receive
// "You'll swap ~1.2 SOL for ~$99.50 USDC (0.5% price impact)"Phase 4: Execute
Only execute after user confirmation:
const result = await client.trading.swap({
inputToken: 'SOL',
outputToken: 'USDC',
amount: '$100'
});Phase 5: Verify
Confirm the result:
const wallet = await client.wallet.get();
// Check new balance reflects the expected changePhase 6: Document
Summarize what happened:
- What was executed
- Transaction hash (if applicable)
- New balances
- Any follow-up actions needed
Security Guidelines
Self-Custody Security
In self-custody mode, the agent handles real cryptographic operations locally:
Critical Security Rules:
- Seed phrase shown once -
wallet.create()returns the seed phrase exactly once - Never log secrets - Do not include seed phrases, passphrases, or private keys in output
- Passphrase via env var - Use
LOOMLAY_WALLET_PASSPHRASEto avoid conversation logs - Keys never transmitted - Only public addresses and signed transactions leave the device
// Self-custody wallet creation
const { seedPhrase, wallet } = await client.wallet.create();
// Tell user: "Your seed phrase is shown once. Store it securely offline."
// Tell user: "Your keys are encrypted on this device at ~/.loomlay/wallet.json"
// NEVER do this:
// - console.log(seedPhrase)
// - store in database
// - send to analytics
// - include in error reportsSeed Phrase Handling
The seed phrase is the master key to all funds. Handle with extreme care:
When users create a wallet, guide them:
Your seed phrase (SAVE THIS NOW):
word1 word2 word3 word4 word5 word6
word7 word8 word9 word10 word11 word12
This phrase is your master key to all funds.
- Write it down on paper
- Store in a secure location
- Never share with anyone
- If lost, funds are unrecoverable
Transaction Safety
- Always quote first - Never execute without showing expected outcome
- Verify addresses - Transfers are irreversible
- Check slippage - High price impact indicates a bad trade
- Confirm large amounts - Extra verification for significant value
// Get quote with price impact
const quote = await client.trading.getSwapQuote({
inputToken: 'SOL',
outputToken: 'USDC',
amount: '100',
});
// Warn on high price impact
if (quote.priceImpact > 2) {
// "Warning: 3.5% price impact. Consider a smaller trade."
}
// Only execute after user confirms
const result = await client.trading.swap({...});API Key Security
API Key Best Practices:
- Store in environment variables, never hardcode
- One key per agent/application
- Revoke immediately if compromised
// Good: Load from environment
const client = new OpenClawWallet({
apiKey: process.env.LOOMLAY_API_KEY!,
});
// Bad: Hardcoded key
const client = new OpenClawWallet({
apiKey: 'agent_abc123...', // Never do this!
});Quality Gate Checklist
Before executing any transaction, verify these conditions:
Safety Checks
- User explicitly requested this action
- Quote shown and confirmed for trades
- Address validated for transfers
- Sufficient balance confirmed
- Slippage/price impact acceptable
Information Provided
- Expected outcome explained
- Fees/costs disclosed
- Risks mentioned if applicable
- Transaction hash provided after execution
Never Do
- Execute without user confirmation
- Log or display seed phrases
- Assume token addresses (search first)
- Ignore error responses
- Skip quote step for trades
Standardized Response Format
All SDK operations return a consistent response format for predictable error handling:
interface ToolResponse<T = unknown> {
success: boolean;
data?: T;
error?: {
message: string;
code?: string;
retryAfter?: number;
};
}Success Response
{
success: true,
data: {
txHash: "abc123...",
outputAmount: "99.50",
// ... operation-specific data
}
}Error Response
{
success: false,
error: {
message: "Rate limited. Retry after 30 seconds.",
code: "RATE_LIMITED",
retryAfter: 30
}
}Using the Response Format
import { executeTool } from '@loomlay/openclaw-wallet-sdk';
const response = await executeTool(() => client.trading.swap(params));
if (response.success) {
// Handle success
console.log('Swap completed:', response.data);
} else {
// Handle error
console.log('Swap failed:', response.error?.message);
}Error Handling Patterns
Error Types
import {
RateLimitError,
ApiError,
NetworkError,
TimeoutError,
ValidationError,
} from '@loomlay/openclaw-wallet-sdk';Recovery Strategies
| Error Type | Retry? | Strategy |
|---|---|---|
RateLimitError | Yes | Wait for retryAfter seconds, then retry |
NetworkError | Yes | Exponential backoff (1s, 2s, 4s), max 3 attempts |
TimeoutError | Maybe | Check operation status first, retry if not confirmed |
ApiError (4xx) | No | Fix input parameters before retrying |
ApiError (5xx) | Yes | Retry with exponential backoff |
ValidationError | No | Fix validation issues before retrying |
Rate Limit Handling
async function withRateLimitRetry<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
if (error instanceof RateLimitError && error.retryAfter) {
console.log(`Rate limited. Waiting ${error.retryAfter}s...`);
await sleep(error.retryAfter * 1000);
return await fn();
}
throw error;
}
}Exponential Backoff
async function withExponentialBackoff<T>(
fn: () => Promise<T>,
maxAttempts = 3,
baseDelayMs = 1000
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (error instanceof ApiError && error.status < 500) {
throw error; // Don't retry client errors
}
if (attempt < maxAttempts - 1) {
const delay = baseDelayMs * Math.pow(2, attempt);
await sleep(delay);
}
}
}
throw lastError!;
}Transaction Safety Pattern
For operations that might be partially complete:
async function safeSwap(params: SwapParams) {
// 1. Get quote first
const quote = await client.trading.getSwapQuote(params);
// 2. Confirm with user
if (!userConfirms(quote)) return null;
try {
// 3. Execute
return await client.trading.swap(params);
} catch (error) {
if (error instanceof TimeoutError) {
// Transaction might have succeeded - check balance
const wallet = await client.wallet.get();
return { status: 'unknown', wallet };
}
throw error;
}
}User-Friendly Error Messages
Transform technical errors into understandable messages:
function getUserMessage(error: unknown): string {
if (error instanceof RateLimitError) {
return `Too many requests. Please wait ${error.retryAfter} seconds.`;
}
if (error instanceof ApiError) {
switch (error.code) {
case 'UNAUTHORIZED':
return 'Authentication failed. Please check your API key.';
case 'INSUFFICIENT_FUNDS':
return 'Not enough balance to complete this transaction.';
case 'SLIPPAGE_EXCEEDED':
return 'Price moved too much. Try again with higher slippage.';
default:
return error.message;
}
}
if (error instanceof NetworkError) {
return 'Network connection failed. Please check your internet.';
}
return 'An unexpected error occurred.';
}Skill Documentation Structure
The SDK includes a skill system for AI agents, organized as follows:
Main Skill File
SKILL.md - The primary instruction file containing:
- Agent philosophy and approach
- Core workflow phases
- Security guidelines
- Quick reference tables
- Quality gate checklist
Reference Guides
Detailed documentation for specific capabilities:
| Reference | Description |
|---|---|
wallet-operations.md | Wallet creation, security, key export |
trading-guide.md | Swaps, transfers, bridges with amount formats |
market-analysis.md | DEX data, trending tokens, filtering |
token-launch.md | Tokenize workflow, tiers, fee structure |
error-handling.md | Error types, recovery patterns, retries |
amount-formats.md | Flexible amounts (decimal, USD, %, max) |
chain-reference.md | Supported chains and behaviors |
Workflow Playbooks
Step-by-step guides for common scenarios:
| Workflow | Description |
|---|---|
first-time-setup.md | Registration, wallet creation, first trade |
token-launch-playbook.md | Complete token launch guide |
Amount Formats
The SDK supports flexible amount specifications:
| Format | Example | Use Case |
|---|---|---|
| Decimal | "1.5" | Exact amount |
| USD | "$100" | Dollar value (auto-converts) |
| Percentage | "50%" | Half of balance |
| Max | "max" | Entire balance |
// All valid:
await client.trading.swap({ amount: '1.5', ... }); // 1.5 tokens
await client.trading.swap({ amount: '$100', ... }); // $100 worth
await client.trading.swap({ amount: '50%', ... }); // Half balance
await client.trading.swap({ amount: 'max', ... }); // Full balanceBest Practices Summary
- Verify before executing - Always get quotes and show expected outcomes
- Explain in plain language - Users should understand what's happening
- Surface relevant data - Provide market context for informed decisions
- Handle errors gracefully - Clear recovery paths for all failures
- Protect sensitive data - Never expose seed phrases or API keys
- Confirm significant actions - Extra verification for large transactions
- Document outcomes - Summarize results with transaction details
Related Documentation
- Getting Started - Set up the SDK
- Security Best Practices - Detailed security guidelines
- Error Handling - Complete error reference
- Trading - Swaps, transfers, and bridges
- Tokenize - Launch your own token