Saltar al contenido principal

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:

  1. Generar keypair según algoritmo del blockchain
  2. Derivar address según formato del blockchain
  3. 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

LambdaPropósitoTiempo típicoMemoria
CreateWalletEVMCrear wallets EVM~1s128 MB
CreateWalletBTCCrear wallets Bitcoin~2s128 MB
CreateWalletSOLCrear wallets Solana~1s128 MB
CreateWalletTONCrear wallets TON~2s256 MB
SendHotWalletGlobalEnviar transacciones~10s256 MB
EncryptAndInverseEncriptar/Desencriptar~0.5s128 MB
GetDiditDataObtener datos KYC~2s128 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:

  1. Crear carpeta en Funciones/
  2. Escribir index.mjs con handler
  3. Desplegar con AWS CLI o Console
  4. 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