Overview
Security is paramount when building AI-powered blockchain applications. This guide covers wallet management, security best practices, and how to keep your Radix Agent Kit applications secure.
🔐 Wallet Types
Radix Agent Kit supports multiple wallet implementations for different security needs:
MnemonicWallet (Default)
Best for: Development, testing, and automated applications
import { RadixMnemonicWallet, RadixNetwork } from "radix-agent-kit";
// Generate a new wallet
const wallet = RadixMnemonicWallet.generateRandom({
networkId: RadixNetwork.Stokenet,
});
// Import existing wallet
const wallet = RadixMnemonicWallet.fromMnemonic("your 24 word phrase", {
networkId: RadixNetwork.Stokenet,
});
Features:
- 24-word BIP-39 mnemonic phrases
- Compatible with Radix Wallet app
- Deterministic key derivation
- Multiple account support
Security Considerations:
- Store mnemonic securely (environment variables, vaults)
- Never log or expose mnemonic phrases
- Use testnet for development
HardwareWallet (Future)
Best for: High-value applications and enhanced security
// Future implementation
import { HardwareWallet } from "radix-agent-kit";
const wallet = new HardwareWallet({
deviceType: "ledger",
networkId: RadixNetwork.Mainnet,
});
Features (Planned):
- Ledger hardware wallet support
- Secure key storage on device
- Transaction signing on hardware
- Air-gapped security
VaultWallet
Best for: Enterprise applications and secure key management
import { VaultWallet } from "radix-agent-kit";
const wallet = new VaultWallet({
vaultUrl: "https://vault.company.com",
keyPath: "secret/radix/agent-key",
networkId: RadixNetwork.Mainnet,
});
Features:
- HashiCorp Vault integration
- Centralized key management
- Audit logging
- Role-based access control
🛡️ Security Best Practices
Environment Variables
Never hardcode sensitive information:
// ❌ BAD: Hardcoded secrets
const agent = new RadixAgent({
mnemonic: "abandon abandon abandon...", // Never do this!
openaiApiKey: "sk-1234567890abcdef",
});
// ✅ GOOD: Use environment variables
import "dotenv/config"; // Required for Node.js environments
import { RadixAgent } from "radix-agent-kit";
const agent = new RadixAgent({
mnemonic: process.env.RADIX_MNEMONIC,
openaiApiKey: process.env.OPENAI_API_KEY,
});
Secure .env File
Create a .env
file with proper permissions:
# Set restrictive permissions
chmod 600 .env
# .env file content
RADIX_MNEMONIC=your_24_word_mnemonic_phrase_here
OPENAI_API_KEY=your_openai_api_key_here
RADIX_NETWORK=stokenet
Network Separation
Always use testnet for development:
import "dotenv/config"; // Required for Node.js environments
import { RadixAgent, RadixNetwork } from "radix-agent-kit";
// Development environment
const agent = new RadixAgent({
networkId: RadixNetwork.Stokenet, // Testnet
mnemonic: process.env.DEV_MNEMONIC,
openaiApiKey: process.env.OPENAI_API_KEY,
});
// Production environment (separate keys!)
const agent = new RadixAgent({
networkId: RadixNetwork.Mainnet,
mnemonic: process.env.PROD_MNEMONIC, // Different mnemonic!
openaiApiKey: process.env.OPENAI_API_KEY,
});
🔒 Key Management
Mnemonic Generation
Generate secure mnemonics:
import { RadixMnemonicWallet } from "radix-agent-kit";
// Generate cryptographically secure mnemonic
const wallet = RadixMnemonicWallet.generateRandom({
networkId: RadixNetwork.Stokenet,
});
console.log("Mnemonic:", wallet.getMnemonic());
console.log("Address:", wallet.getAddress());
// ⚠️ IMPORTANT: New wallets start with zero balance!
// You must fund them manually before use:
// 1. Copy the address above
// 2. Get testnet XRD from Radix Dashboard or Discord faucets
// 3. For mainnet, transfer from existing funded wallet
// Validate mnemonic strength
const isValid = RadixMnemonicWallet.validateMnemonic(mnemonic);
const strength = RadixMnemonicWallet.getMnemonicStrength(mnemonic);
console.log(`Mnemonic is valid: ${isValid}, Strength: ${strength} bits`);
Generated wallets are empty! Programmatically created wallets have zero XRD balance. Always fund new wallets before attempting transactions:
Key Rotation
Regularly rotate keys for production applications:
// Key rotation strategy
class SecureAgentManager {
constructor() {
this.currentWallet = null;
this.keyRotationInterval = 30 * 24 * 60 * 60 * 1000; // 30 days
}
async rotateKeys() {
const oldWallet = this.currentWallet;
const newWallet = RadixMnemonicWallet.generateRandom({
networkId: RadixNetwork.Mainnet,
});
// Transfer funds from old to new wallet
if (oldWallet) {
await this.transferAllFunds(oldWallet, newWallet);
}
this.currentWallet = newWallet;
// Securely store new mnemonic
await this.storeSecurely(newWallet.getMnemonic());
}
}
Multi-Account Management
Use different accounts for different purposes:
const wallet = RadixMnemonicWallet.fromMnemonic(mnemonic, {
networkId: RadixNetwork.Mainnet,
});
// Derive multiple accounts from same mnemonic
const tradingAccount = await wallet.deriveAccount(0);
const stakingAccount = await wallet.deriveAccount(1);
const treasuryAccount = await wallet.deriveAccount(2);
console.log("Trading:", tradingAccount.address);
console.log("Staking:", stakingAccount.address);
console.log("Treasury:", treasuryAccount.address);
🚨 Transaction Security
Always validate user inputs:
function validateTransferInput(address, amount) {
// Validate Radix address format
if (!address.startsWith("account_tdx_") && !address.startsWith("rdx1")) {
throw new Error("Invalid Radix address format");
}
// Validate amount
if (isNaN(amount) || amount <= 0) {
throw new Error("Invalid amount");
}
// Check reasonable limits
if (amount > 1000000) {
throw new Error("Amount exceeds safety limit");
}
return true;
}
// Use validation before transactions
async function safeTransfer(agent, address, amount) {
try {
validateTransferInput(address, amount);
return await agent.run(`Send ${amount} XRD to ${address}`);
} catch (error) {
console.error("Transfer validation failed:", error.message);
throw error;
}
}
Rate Limiting
Implement rate limiting for agent operations:
class RateLimitedAgent {
constructor(agent, maxOperationsPerMinute = 10) {
this.agent = agent;
this.operations = [];
this.maxOps = maxOperationsPerMinute;
}
async run(query) {
const now = Date.now();
// Remove operations older than 1 minute
this.operations = this.operations.filter((time) => now - time < 60000);
// Check rate limit
if (this.operations.length >= this.maxOps) {
throw new Error(
"Rate limit exceeded. Please wait before next operation."
);
}
// Record operation
this.operations.push(now);
// Execute with timeout
return Promise.race([
this.agent.run(query),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("Operation timeout")), 30000)
),
]);
}
}
Transaction Confirmation
Implement confirmation for high-value operations:
class ConfirmationAgent {
constructor(agent, confirmationThreshold = 1000) {
this.agent = agent;
this.threshold = confirmationThreshold;
}
async run(query) {
// Check if operation needs confirmation
if (this.needsConfirmation(query)) {
console.log(`⚠️ High-value operation detected: ${query}`);
console.log('Please confirm by typing "CONFIRM" within 30 seconds:');
const confirmed = await this.waitForConfirmation();
if (!confirmed) {
throw new Error("Operation cancelled - no confirmation received");
}
}
return await this.agent.run(query);
}
needsConfirmation(query) {
// Simple pattern matching for high-value operations
const patterns = [
/send\s+(\d+)\s+xrd/i,
/transfer\s+(\d+)/i,
/stake\s+(\d+)/i,
];
for (const pattern of patterns) {
const match = query.match(pattern);
if (match && parseFloat(match[1]) > this.threshold) {
return true;
}
}
return false;
}
}
🔍 Monitoring & Logging
Secure Logging
Log operations without exposing sensitive data:
class SecureLogger {
static logOperation(operation, success, details = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
operation: operation,
success: success,
// Never log private keys, mnemonics, or full addresses
account: details.account ? this.maskAddress(details.account) : null,
amount: details.amount || null,
transactionId: details.txId || null,
};
console.log(JSON.stringify(logEntry));
}
static maskAddress(address) {
if (!address) return null;
return (
address.substring(0, 10) + "..." + address.substring(address.length - 6)
);
}
}
// Usage
SecureLogger.logOperation("transfer", true, {
account: "account_tdx_2_1c8atrq...",
amount: 100,
txId: "txid_abc123...",
});
Error Handling
Handle errors securely without exposing internals:
async function secureAgentOperation(agent, query) {
try {
const result = await agent.run(query);
SecureLogger.logOperation("agent_query", true, {
query: query.substring(0, 50),
});
return { success: true, result };
} catch (error) {
// Log error without exposing sensitive details
SecureLogger.logOperation("agent_query", false, {
error: error.message,
query: query.substring(0, 50),
});
// Return generic error to user
return {
success: false,
error: "Operation failed. Please check your input and try again.",
};
}
}
🏢 Production Deployment
Environment Separation
Use different configurations for different environments:
// config/production.js
export const productionConfig = {
networkId: RadixNetwork.Mainnet,
logLevel: "warn",
rateLimitPerMinute: 5,
confirmationRequired: true,
monitoringEnabled: true,
};
// config/development.js
export const developmentConfig = {
networkId: RadixNetwork.Stokenet,
logLevel: "debug",
rateLimitPerMinute: 100,
confirmationRequired: false,
monitoringEnabled: false,
};
Health Monitoring
Monitor agent health and performance:
class AgentHealthMonitor {
constructor(agent) {
this.agent = agent;
this.metrics = {
totalOperations: 0,
successfulOperations: 0,
failedOperations: 0,
averageResponseTime: 0,
};
}
async monitoredRun(query) {
const startTime = Date.now();
this.metrics.totalOperations++;
try {
const result = await this.agent.run(query);
this.metrics.successfulOperations++;
this.updateResponseTime(Date.now() - startTime);
return result;
} catch (error) {
this.metrics.failedOperations++;
throw error;
}
}
getHealthStatus() {
const successRate =
this.metrics.successfulOperations / this.metrics.totalOperations;
return {
healthy: successRate > 0.95,
successRate: successRate,
averageResponseTime: this.metrics.averageResponseTime,
totalOperations: this.metrics.totalOperations,
};
}
}
🚨 Security Checklist
Development
Production
Code Security
🆘 Incident Response
Compromised Keys
If you suspect your keys are compromised:
- Immediately stop the agent
- Generate new wallet with fresh mnemonic
- Transfer all funds to new wallet
- Revoke old API keys
- Review logs for suspicious activity
- Update all environment configurations
Emergency Procedures
// Emergency wallet drain function
async function emergencyDrain(compromisedWallet, safeWallet) {
try {
const balances = await gateway.getAccountBalances(
compromisedWallet.getAddress()
);
for (const balance of balances) {
if (balance.amount > 0) {
await transferAllFunds(
compromisedWallet,
safeWallet.getAddress(),
balance.resource
);
}
}
console.log("Emergency drain completed");
} catch (error) {
console.error("Emergency drain failed:", error);
}
}
📚 Additional Resources
Remember: Security is an ongoing process, not a one-time setup. Regularly review and update your security practices as your application grows.
Responses are generated using AI and may contain mistakes.