Wallet Management
The SDK provides a wallet resource for managing multi-chain wallets. It supports two modes:
| Mode | Keys Stored | Signing | Best For |
|---|---|---|---|
local (self-custody) | Client-side (~/.loomlay/wallet.json) | Client | Maximum security, self-custody |
custodial | Server-side (encrypted in PostgreSQL) | Server | Simple integration |
Wallet Modes
Self-Custody Mode (Recommended)
In self-custody mode, private keys are generated and encrypted locally on your device. They are never sent to any server. The API only receives public addresses for balance lookups.
const wallet = new OpenClawWallet({
apiKey: 'agent_xxx',
walletMode: 'local',
getPassphrase: async () => process.env.LOOMLAY_WALLET_PASSPHRASE!,
});Custodial Mode
In custodial mode, the API manages keys server-side. This is simpler but requires trusting the server with your keys.
const wallet = new OpenClawWallet({
apiKey: 'agent_xxx',
// walletMode defaults to 'custodial' in SDK
});The OpenClaw Plugin defaults to self-custody (walletMode: 'local'). The SDK defaults to 'custodial' for backwards compatibility.
Create a Wallet
// Keys generated and encrypted locally
const created = await wallet.wallet.create();
console.log('Seed phrase:', created.seedPhrase); // Save securely!
console.log('Solana:', created.wallet.solanaAddress);
console.log('EVM:', created.wallet.evmAddress);
// Public addresses auto-registered with APIThe seed phrase is the only way to recover your wallet. Store it securely and never share it.
Security Best Practices:
- Never log or print seed phrases in production code
- Store seed phrases in encrypted secret managers (e.g., AWS Secrets Manager, HashiCorp Vault)
- Never commit seed phrases to version control
- Use environment variables only for development; use proper secrets management in production
Supported Chains
The SDK derives addresses for multiple chains from a single BIP39 seed phrase:
| Chain | Derivation Path | Curve | Address Format |
|---|---|---|---|
| Solana | m/44'/501'/0'/0' | ed25519 | Base58 |
| Ethereum | m/44'/60'/0'/0/0 | secp256k1 | 0x-prefixed hex |
| Base | Same as Ethereum | secp256k1 | 0x-prefixed hex |
| Arbitrum | Same as Ethereum | secp256k1 | 0x-prefixed hex |
All EVM chains share the same address derived from the secp256k1 curve. Solana uses a separate ed25519-based address.
Get Wallet Info
const info = await wallet.wallet.get();
console.log('Addresses:', {
solana: info.solanaAddress,
evm: info.evmAddress,
});
// Solana balances
console.log('SOL:', info.balances.solana.native);
for (const token of info.balances.solana.tokens) {
console.log(`${token.symbol}: ${token.balance}`);
}
// EVM balances (aggregated across chains)
console.log('ETH:', info.balances.ethereum.native);Balance Caching: Balances are fetched in real-time from chain RPC nodes and portfolio APIs. For high-frequency applications, consider caching balances locally and refreshing periodically (e.g., every 30 seconds) to reduce API calls.
Import an Existing Wallet
Import an existing seed phrase into a local self-custody wallet (self-custody mode only):
const imported = await wallet.wallet.importSeed(
'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
'my-secure-passphrase'
);
console.log('Imported:', imported.wallet.solanaAddress);
// Addresses auto-registered with APIRegister External Addresses
Register externally-managed wallet addresses with the API for balance lookups:
await wallet.wallet.register({
solanaAddress: 'your-solana-address',
evmAddress: '0xyour-evm-address', // Optional
});Export Private Keys
Only export keys when absolutely necessary. Exposing private keys risks losing all funds.
// Decrypts locally using passphrase — no API call needed
const keys = await wallet.wallet.exportKeys('any-seed-phrase');
console.log('Solana private key:', keys.solana.privateKey);
console.log('EVM private key:', keys.evm.privateKey);Portfolio View
Get a comprehensive view of holdings across all chains:
const portfolio = await wallet.portfolio.get();
for (const holding of portfolio.holdings) {
console.log(`${holding.symbol} on ${holding.chain}`);
console.log(` Balance: ${holding.balance}`);
console.log(` Value: $${holding.valueUsd}`);
}
console.log('Total portfolio value:', portfolio.totalValueUsd);Transaction History
const history = await wallet.portfolio.history({
limit: 20,
chain: 'solana', // Optional filter
});
for (const tx of history.transactions) {
console.log(`${tx.type}: ${tx.amount} ${tx.token}`);
console.log(` Hash: ${tx.txHash}`);
console.log(` Time: ${tx.timestamp}`);
}Type Definitions
interface WalletCreateResult {
seedPhrase: string;
solanaAddress: string;
evmAddress: string;
}
interface WalletInfo {
solanaAddress: string;
evmAddress: string;
balances: {
solana: ChainBalance;
ethereum: ChainBalance;
// ... other chains
};
}
interface ChainBalance {
native: string;
tokens: TokenBalance[];
}
interface TokenBalance {
address: string;
symbol: string;
name: string;
balance: string;
decimals: number;
}