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 identity

In 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 change

Phase 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_PASSPHRASE to 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 reports

Seed 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

  1. Always quote first - Never execute without showing expected outcome
  2. Verify addresses - Transfers are irreversible
  3. Check slippage - High price impact indicates a bad trade
  4. 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 TypeRetry?Strategy
RateLimitErrorYesWait for retryAfter seconds, then retry
NetworkErrorYesExponential backoff (1s, 2s, 4s), max 3 attempts
TimeoutErrorMaybeCheck operation status first, retry if not confirmed
ApiError (4xx)NoFix input parameters before retrying
ApiError (5xx)YesRetry with exponential backoff
ValidationErrorNoFix 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:

ReferenceDescription
wallet-operations.mdWallet creation, security, key export
trading-guide.mdSwaps, transfers, bridges with amount formats
market-analysis.mdDEX data, trending tokens, filtering
token-launch.mdTokenize workflow, tiers, fee structure
error-handling.mdError types, recovery patterns, retries
amount-formats.mdFlexible amounts (decimal, USD, %, max)
chain-reference.mdSupported chains and behaviors

Workflow Playbooks

Step-by-step guides for common scenarios:

WorkflowDescription
first-time-setup.mdRegistration, wallet creation, first trade
token-launch-playbook.mdComplete token launch guide

Amount Formats

The SDK supports flexible amount specifications:

FormatExampleUse 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 balance

Best Practices Summary

  1. Verify before executing - Always get quotes and show expected outcomes
  2. Explain in plain language - Users should understand what's happening
  3. Surface relevant data - Provide market context for informed decisions
  4. Handle errors gracefully - Clear recovery paths for all failures
  5. Protect sensitive data - Never expose seed phrases or API keys
  6. Confirm significant actions - Extra verification for large transactions
  7. Document outcomes - Summarize results with transaction details

Related Documentation