AWS Lambda Functions - Funciones Serverless
Las Lambda Functions son funciones serverless de AWS que ejecutan tareas específicas bajo demanda, sin necesidad de mantener servidores corriendo 24/7.
¿Por qué Lambda?
Problema: Crear wallets y enviar transacciones son operaciones:
- ⏰ Poco frecuentes (no constantemente)
- 🔒 Sensibles (manejan private keys)
- 📊 Variables en carga (a veces 1/min, a veces 100/min)
Solución tradicional (servidor dedicado):
- ❌ Servidor corriendo siempre (costo alto)
- ❌ Necesitas escalar manualmente
- ❌ Más superficie de ataque
Solución con Lambda:
- ✅ Solo pagas cuando se ejecuta
- ✅ AWS escala automáticamente
- ✅ Entorno aislado por ejecución
- ✅ Timeout automático (seguridad)
Arquitectura de Invocación
⚡ Wallet Creation Lambdas
Funciones para crear wallets en diferentes blockchains.
CreateWalletEVM
Blockchains: Ethereum, BSC, Polygon, Avalanche, Arbitrum, Optimism
Por qué una sola Lambda para todas: Todas las redes EVM usan el mismo formato de addresses (derivadas de ECDSA secp256k1).
// Pseudo-código simplificado
export async function handler(event) {
// 1. Generar keypair
const web3 = new Web3();
const account = web3.eth.accounts.create();
// 2. Resultado
return {
address: account.address, // 0xabc...
privateKey: account.privateKey // 0x123...
};
}
Output ejemplo:
{
"address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
"privateKey": "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8b4e9e98d5e8e3e9f"
}
Nota crítica: El privateKey se envía al Wallet Service que inmediatamente lo hashea. Nunca se guarda en texto plano.
CreateWalletBTC
Blockchain: Bitcoin
Peculiaridad: Bitcoin usa addresses diferentes según el tipo:
- P2PKH (Legacy):
1... - P2SH (SegWit wrapped):
3... - Bech32 (Native SegWit):
bc1...
SwapBits usa Bech32 (nativo SegWit) por fees más bajos.
export async function handler(event) {
const bitcoin = require('bitcoinjs-lib');
const ECPair = bitcoin.ECPairFactory(ecc);
// 1. Generar keypair
const keyPair = ECPair.makeRandom();
// 2. Derivar address Bech32
const { address } = bitcoin.payments.p2wpkh({
pubkey: keyPair.publicKey,
network: bitcoin.networks.bitcoin
});
return {
address, // bc1...
privateKey: keyPair.toWIF()
};
}
CreateWalletSOL
Blockchain: Solana
Peculiaridad: Solana usa Ed25519 (no secp256k1 como EVM).
export async function handler(event) {
const { Keypair } = require('@solana/web3.js');
// 1. Generar keypair Ed25519
const keypair = Keypair.generate();
// 2. Derivar address (base58)
return {
address: keypair.publicKey.toBase58(), // Base58, ~44 chars
privateKey: Buffer.from(keypair.secretKey).toString('hex')
};
}
CreateWalletTON
Blockchain: TON (The Open Network)
Peculiaridad: TON usa contratos de wallet (no solo keypairs).
export async function handler(event) {
const { WalletContractV4, KeyPair } = require('ton');
// 1. Generar keypair
const keyPair = await KeyPair.generate();
// 2. Crear contrato de wallet V4
const wallet = WalletContractV4.create({
publicKey: keyPair.publicKey,
workchain: 0
});
return {
address: wallet.address.toString(),
privateKey: keyPair.secretKey.toString('hex')
};
}
CreateWalletTRX
Blockchain: Tron
export async function handler(event) {
const TronWeb = require('tronweb');
const tronWeb = new TronWeb();
// 1. Generar account
const account = await tronWeb.createAccount();
return {
address: account.address.base58, // Txxxx...
privateKey: account.privateKey
};
}
Otras Wallet Lambdas
- CreateWalletXRPL: Ripple (XRP Ledger)
- CreateWalletDOGE: Dogecoin
- CreateWalletLTC: Litecoin
- CreateWalletDOT: Polkadot
- CreateWalletADA: Cardano
Patrón común:
- Generar keypair según algoritmo del blockchain
- Derivar address según formato del blockchain
- Retornar ambos
📤 Transaction Lambdas
Funciones para enviar transacciones desde hot wallets.
SendHotWalletGlobal
Lambda universal que detecta el tipo de blockchain y ejecuta la lógica correspondiente.
Funcionamiento:
export async function handler(event) {
const { from, to, amount, coin, network, privateKey } = event;
// 1. Detectar familia de blockchain
const networkFamily = detectNetworkFamily(network);
// 2. Ejecutar sender correspondiente
switch(networkFamily) {
case 'EVM':
return await sendEVMTransaction({ from, to, amount, coin, network, privateKey });
case 'SOLANA':
return await sendSolanaTransaction({ from, to, amount, privateKey });
case 'TRON':
return await sendTronTransaction({ from, to, amount, privateKey });
case 'BITCOIN':
return await sendBitcoinTransaction({ from, to, amount, privateKey });
default:
throw new Error(`Unsupported network: ${network}`);
}
}
SendEVMTransaction
async function sendEVMTransaction({ from, to, amount, coin, network, privateKey }) {
const web3 = new Web3(getRpcUrl(network));
// 1. Crear cuenta desde private key
const account = web3.eth.accounts.privateKeyToAccount(privateKey);
// 2. Determinar si es nativo o token
if (coin === 'ETH' || coin === 'BNB' || coin === 'MATIC') {
// Transferencia nativa
const tx = {
from: account.address,
to,
value: web3.utils.toWei(amount, 'ether'),
gas: 21000
};
const signed = await account.signTransaction(tx);
const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
return {
txHash: receipt.transactionHash,
status: 'success'
};
} else {
// Transferencia de token ERC-20
const tokenContract = new web3.eth.Contract(ERC20_ABI, getTokenAddress(coin));
const decimals = await tokenContract.methods.decimals().call();
const amountWei = BigInt(amount * (10 ** decimals));
const tx = tokenContract.methods.transfer(to, amountWei);
const gas = await tx.estimateGas({ from: account.address });
const signed = await account.signTransaction({
to: tokenContract.options.address,
data: tx.encodeABI(),
gas
});
const receipt = await web3.eth.sendSignedTransaction(signed.rawTransaction);
return {
txHash: receipt.transactionHash,
status: 'success'
};
}
}
SendSolanaTransaction
async function sendSolanaTransaction({ from, to, amount, privateKey }) {
const { Connection, Keypair, PublicKey, SystemProgram, Transaction, sendAndConfirmTransaction } = require('@solana/web3.js');
// 1. Conectar a Solana
const connection = new Connection(process.env.SOLANA_RPC_URL);
// 2. Restaurar keypair
const secretKey = Uint8Array.from(Buffer.from(privateKey, 'hex'));
const fromKeypair = Keypair.fromSecretKey(secretKey);
// 3. Crear transacción
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: fromKeypair.publicKey,
toPubkey: new PublicKey(to),
lamports: amount * 1e9 // SOL to lamports
})
);
// 4. Enviar y confirmar
const signature = await sendAndConfirmTransaction(
connection,
transaction,
[fromKeypair]
);
return {
txHash: signature,
status: 'success'
};
}
Lambdas Especializadas
SendHotWalletSol: Versión optimizada solo para Solana
SendHotWalletTon: Versión optimizada solo para TON
SendHotWalletPolkadot: Versión optimizada para Polkadot
¿Por qué versiones especializadas si existe Global?
- Optimización: Menos código, menos cold start
- Límites: Algunas chains necesitan lógica compleja
- Testing: Más fácil testear funciones específicas
🔐 EncryptAndInverse
Utilidad de encriptación/desencriptación.
export async function handler(event) {
const { action, data, key } = event;
if (action === 'encrypt') {
const cipher = crypto.createCipher('aes-256-cbc', key);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return { encrypted };
}
if (action === 'decrypt') {
const decipher = crypto.createDecipher('aes-256-cbc', key);
let decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return { decrypted };
}
throw new Error('Invalid action');
}
Uso: Encriptar private keys antes de pasarlas entre servicios.
🔍 GetDiditData
Obtiene datos de verificación de Didit (proveedor KYC).
export async function handler(event) {
const { sessionId } = event;
// Llamar a API de Didit
const response = await fetch(`https://api.didit.me/v1/sessions/${sessionId}`, {
headers: {
'Authorization': `Bearer ${process.env.DIDIT_API_KEY}`
}
});
const data = await response.json();
return {
status: data.status,
userData: data.user,
documents: data.documents
};
}
Configuración y Deployment
Variables de Entorno
Cada Lambda tiene acceso a:
- AWS Secrets Manager (secretos sensibles)
- Environment Variables (configuración)
// Cargar desde Secrets Manager
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
async function getSecret(secretName) {
const client = new SecretsManagerClient({ region: 'us-east-1' });
const response = await client.send(
new GetSecretValueCommand({ SecretId: secretName })
);
return JSON.parse(response.SecretString);
}
// Uso
const config = await getSecret('production/blockchain/rpc-urls');
const ethRpcUrl = config.ETH_RPC_URL;
Timeouts
Cada Lambda tiene timeout configurado:
- Wallet Creation: 10 segundos
- Send Transaction: 30 segundos (blockchain puede tardar)
- Get Data: 15 segundos
¿Por qué timeouts?
- Evitar Lambdas colgadas (costos)
- Seguridad (si algo falla, no se queda ejecutando)
Memoria
Lambdas configuradas con memoria según necesidad:
- Wallet Creation: 128 MB (operación simple)
- Send Transaction: 256 MB (necesita conectar a blockchain)
- Encrypt: 128 MB
Más memoria = Más CPU en Lambda (no solo RAM).
Invocación desde Servicios
Desde Wallet Service
import { LambdaClient, InvokeCommand } from '@aws-sdk/client-lambda';
class WalletService {
private lambda = new LambdaClient({ region: 'us-east-1' });
async createWallet(userId: string, coin: string) {
const lambdaName = this.getLambdaName(coin);
const command = new InvokeCommand({
FunctionName: lambdaName,
Payload: JSON.stringify({ userId })
});
const response = await this.lambda.send(command);
const result = JSON.parse(Buffer.from(response.Payload).toString());
return result;
}
private getLambdaName(coin: string): string {
const evmCoins = ['ETH', 'BNB', 'MATIC', 'AVAX'];
if (evmCoins.includes(coin)) return 'CreateWalletEVM';
if (coin === 'BTC') return 'CreateWalletBTC';
if (coin === 'SOL') return 'CreateWalletSOL';
// etc...
}
}
Cold Start vs Warm Start
Optimizaciones para Cold Start:
- Minimizar dependencias
- Lazy loading de librerías
- Provisioned concurrency (para Lambdas críticas)
Costos
Pricing de Lambda (aproximado):
- $0.20 por 1 millón de requests
- $0.0000166667 por GB-segundo
Ejemplo real:
- Crear 10,000 wallets/mes
- 128 MB memoria, 2 segundos cada una
- Costo: ~$0.40/mes
vs Servidor dedicado: $50-100/mes
💰 Ahorro: 99%
Monitoreo
Cada Lambda genera métricas en CloudWatch:
- Invocations: Número de invocaciones
- Errors: Errores ocurridos
- Duration: Tiempo de ejecución
- Throttles: Veces que se limitó por concurrencia
Logs: Todos los console.log() van a CloudWatch Logs
export async function handler(event) {
console.log('Creating wallet for user:', event.userId);
try {
const wallet = await createWallet();
console.log('Wallet created:', wallet.address);
return wallet;
} catch (error) {
console.error('Error creating wallet:', error);
throw error;
}
}
Seguridad
IAM Roles
Cada Lambda tiene un IAM Role con permisos mínimos:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:production/*"
},
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
VPC
Lambdas críticas corren dentro de VPC (Virtual Private Cloud) para mayor aislamiento.
Resumen
| Lambda | Propósito | Tiempo típico | Memoria |
|---|---|---|---|
| CreateWalletEVM | Crear wallets EVM | ~1s | 128 MB |
| CreateWalletBTC | Crear wallets Bitcoin | ~2s | 128 MB |
| CreateWalletSOL | Crear wallets Solana | ~1s | 128 MB |
| CreateWalletTON | Crear wallets TON | ~2s | 256 MB |
| SendHotWalletGlobal | Enviar transacciones | ~10s | 256 MB |
| EncryptAndInverse | Encriptar/Desencriptar | ~0.5s | 128 MB |
| GetDiditData | Obtener datos KYC | ~2s | 128 MB |
Para Desarrolladores
Las Lambdas son funciones puras: Reciben input, retornan output, no mantienen estado.
Debugging:
- CloudWatch Logs para ver console.log()
- X-Ray para tracing (si está habilitado)
- Local testing con SAM CLI
Agregar nueva Lambda:
- Crear carpeta en
Funciones/ - Escribir
index.mjscon handler - Desplegar con AWS CLI o Console
- Invocar desde servicio correspondiente
Best practices:
- Keep it simple (una función = una responsabilidad)
- Timeout adecuado (ni muy corto ni muy largo)
- Error handling robusto
- Logging abundante