Files
alsk1992andClaude Opus 4.5 68ba772363 Add EVM wallet suite, Virtuals/Opinion feeds, VPS hardening skill
EVM Wallet:
- Wallet generation with mnemonic and keystore encryption
- Multi-chain balances (7 chains: ETH, Base, Polygon, Arbitrum, BSC, OP, AVAX)
- Odos swap aggregator integration
- Token transfers (ETH and ERC20)
- Generic contract read/write calls
- CLI commands: /wallet, /swap, /send, /chains

Feeds:
- Virtuals Protocol feed (Base chain AI agents)
- Opinion.trade feed (BNB Chain prediction market)

Skills:
- /harden skill for VPS security auditing
- 10-point security checklist
- Automated safe fixes

Documentation:
- docs/EVM_WALLET.md
- docs/VIRTUALS.md
- docs/OPINION.md
- docs/VPS_SECURITY.md

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 12:01:49 +00:00

8.6 KiB

EVM Wallet Integration

Self-custody wallet management and multi-chain trading.

Overview

The EVM module provides complete wallet and trading capabilities:

┌─────────────────────────────────────────────────────────────┐
│                     EVM Wallet Suite                        │
├─────────────────┬─────────────────┬─────────────────────────┤
│   Wallet        │    Trading      │      Multi-Chain        │
├─────────────────┼─────────────────┼─────────────────────────┤
│ • Generation    │ • Odos swaps    │ • 7 chains supported    │
│ • Mnemonic      │ • Token sends   │ • Balance checking      │
│ • Keystore      │ • ETH transfers │ • Common tokens         │
│ • Encryption    │ • Contract calls│ • Gas estimation        │
└─────────────────┴─────────────────┴─────────────────────────┘

Quick Start

1. Generate Wallet

# Via CLI
/wallet create

# Or programmatically
import { generateWallet } from './evm/wallet';
const wallet = generateWallet();
console.log(wallet.address, wallet.privateKey);

2. Configure

# Set wallet key for trading
export EVM_PRIVATE_KEY="0x..."

# Optional: Custom RPC endpoints
export ETH_RPC_URL="https://your-eth-rpc.com"
export BASE_RPC_URL="https://your-base-rpc.com"

3. Check Balances

/wallet balance 0x1234...

4. Swap & Send

/swap base ETH USDC 0.1      # Swap via Odos
/send base 0x123... 0.1      # Send ETH

Commands

Wallet Management

Command Description
/wallet create [name] Generate new wallet
/wallet list List saved wallets
/wallet balance <address> Check balances across all chains
/chains List supported chains

Trading

Command Description
/swap <chain> <from> <to> <amount> Get swap quote via Odos
/send <chain> <to> <amount> [token] Send ETH or tokens

Supported Chains

Chain ID Native Explorer
Ethereum 1 ETH etherscan.io
Base 8453 ETH basescan.org
Polygon 137 MATIC polygonscan.com
Arbitrum 42161 ETH arbiscan.io
BNB Chain 56 BNB bscscan.com
Optimism 10 ETH optimistic.etherscan.io
Avalanche 43114 AVAX snowtrace.io

Programmatic Usage

Wallet Generation

import {
  generateWallet,
  walletFromMnemonic,
  walletFromPrivateKey,
  encryptKeystore,
  decryptKeystore,
  saveWallet,
  loadWallet,
  listWallets,
} from './evm/wallet';

// Generate random wallet
const wallet = generateWallet();
// { address, privateKey, mnemonic, publicKey }

// From mnemonic (with derivation index)
const wallet2 = walletFromMnemonic('word word word...', 0);

// From private key
const info = walletFromPrivateKey('0x...');

// Encrypt and save
const path = saveWallet(wallet.privateKey, 'mypassword', 'main-wallet');

// Load later
const loaded = loadWallet('main-wallet', 'mypassword');

// List saved
const wallets = listWallets();
// [{ name: 'main-wallet', address: '0x...' }]

Multi-Chain Balances

import {
  getMultiChainBalances,
  getNativeBalance,
  getTokenBalance,
  getChainBalances,
  CHAINS,
  CHAIN_TOKENS,
} from './evm/multichain';

// All chains at once
const balances = await getMultiChainBalances('0x...');
for (const chain of balances.balances) {
  console.log(chain.chainName, chain.native.balance, chain.native.symbol);
  for (const token of chain.tokens) {
    console.log('  ', token.symbol, token.balance);
  }
}

// Single chain
const ethBal = await getNativeBalance('ethereum', '0x...');

// Specific token
const usdc = await getTokenBalance(
  'base',
  '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
  '0x...'
);

// Common tokens are pre-configured
console.log(CHAIN_TOKENS.base.USDC); // USDC address on Base

Odos Swaps

import {
  getOdosQuote,
  executeOdosSwap,
  swapNativeToToken,
  swapTokenToNative,
  swapTokens,
  getSupportedChains,
} from './evm/odos';

// Get quote
const quote = await getOdosQuote({
  chain: 'base',
  inputToken: 'ETH',  // or address
  outputToken: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // USDC
  amount: '0.1',
  slippageBps: 50,  // 0.5%
});
console.log('Output:', quote.outputAmount);
console.log('Price impact:', quote.priceImpact);
console.log('Route:', quote.route);

// Execute swap
const result = await executeOdosSwap({
  chain: 'base',
  inputToken: 'ETH',
  outputToken: '0x833589...',
  amount: '0.1',
  privateKey: process.env.EVM_PRIVATE_KEY!,
  slippageBps: 50,
});

if (result.success) {
  console.log('TX:', result.txHash);
  console.log('Received:', result.outputAmount);
}

// Convenience functions
await swapNativeToToken('polygon', '0x2791...', '100', privateKey);
await swapTokenToNative('arbitrum', '0xaf88...', '1000', privateKey);
await swapTokens('base', '0x...', '0x...', '50', privateKey);

Token Transfers

import {
  sendNative,
  sendToken,
  sendNativeBatch,
  sendTokenBatch,
  estimateNativeTransferGas,
  estimateTokenTransferGas,
} from './evm/transfers';

// Send ETH
const result = await sendNative({
  chain: 'base',
  to: '0x...',
  amount: '0.1',
  privateKey: '0x...',
});

// Send ERC20
const tokenResult = await sendToken({
  chain: 'polygon',
  to: '0x...',
  amount: '100',
  privateKey: '0x...',
  tokenAddress: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', // USDC
});

// Batch transfers
const recipients = [
  { to: '0x111...', amount: '0.01' },
  { to: '0x222...', amount: '0.02' },
];
const results = await sendNativeBatch('ethereum', recipients, privateKey);

// Gas estimation
const gas = await estimateNativeTransferGas('ethereum', '0x...', '0.1');
console.log('Estimated cost:', gas.estimatedCost, 'ETH');

Contract Calls

import {
  callContract,
  writeContract,
  getEventLogs,
  isContract,
  getContractInfo,
  COMMON_ABIS,
  encodeFunctionData,
  decodeFunctionData,
} from './evm/contracts';

// Read-only call
const result = await callContract({
  chain: 'base',
  contractAddress: '0x...',
  abi: COMMON_ABIS.erc20,
  method: 'balanceOf',
  args: ['0x...'],
});
console.log('Balance:', result.result);

// Write call
const writeResult = await writeContract({
  chain: 'polygon',
  contractAddress: '0x...',
  abi: COMMON_ABIS.erc20,
  method: 'approve',
  args: ['0xspender', '1000000000000'],
  privateKey: '0x...',
});
console.log('TX:', writeResult.txHash);

// Query events
const logs = await getEventLogs({
  chain: 'ethereum',
  contractAddress: '0x...',
  abi: COMMON_ABIS.erc20,
  eventName: 'Transfer',
  fromBlock: 19000000,
  toBlock: 'latest',
});

// Check if contract
const isCtx = await isContract('base', '0x...');

// Use pre-built ABIs
// COMMON_ABIS.erc20, erc721, erc1155, multicall, uniswapV2Pair, uniswapV2Router

Keystore Encryption

Wallets are encrypted using:

  • KDF: scrypt (N=262144, r=8, p=1)
  • Cipher: AES-256-CTR
  • MAC: Keccak256

Storage location: ~/.clodds/wallets/

// Encrypt
const keystore = encryptKeystore(privateKey, 'mypassword');

// Decrypt
const privateKey = decryptKeystore(keystore, 'mypassword');

Configuration

Environment Variables

Variable Description
EVM_PRIVATE_KEY Wallet private key for trading
ETH_RPC_URL Ethereum RPC (default: llamarpc)
BASE_RPC_URL Base RPC (default: mainnet.base.org)
POLYGON_RPC_URL Polygon RPC
ARBITRUM_RPC_URL Arbitrum RPC
BSC_RPC_URL BNB Chain RPC
OPTIMISM_RPC_URL Optimism RPC
AVALANCHE_RPC_URL Avalanche RPC

Config File

In ~/.clodds/clodds.json:

{
  "evm": {
    "defaultChain": "base",
    "slippageBps": 50
  }
}

Security Notes

  1. Self-Custody: Private keys never leave your machine
  2. Encrypted Storage: Keystores use scrypt + AES-256
  3. No Cloud: Nothing sent to external services (except RPC calls)
  4. Validate First: Always check quotes before executing swaps

Resources