Ejemplo Simple de Keyless Federado
El Ejemplo de Keyless Federado muestra cómo configurar una cuenta Keyless Federada usando Auth0 como proveedor IAM.
Explora el código en el repositorio aptos-keyless-example.
Implementación Paso a Paso
Sección titulada «Implementación Paso a Paso»1. Configuración del Proyecto
Sección titulada «1. Configuración del Proyecto»# Clonar el repositorio de ejemplogit clone https://github.com/aptos-labs/aptos-keyless-example.gitcd aptos-keyless-example/examples/federated-keyless-example
# Instalar dependenciasnpm install
# Configurar variables de entornocp .env.example .env.local
2. Configurar Auth0
Sección titulada «2. Configurar Auth0»export const auth0Config = { domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN!, clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID!, redirectUri: typeof window !== 'undefined' ? window.location.origin : '', audience: process.env.NEXT_PUBLIC_AUTH0_AUDIENCE, scope: 'openid profile email'};
// Configurar Auth0Providerimport { Auth0Provider } from '@auth0/auth0-react';
export function Auth0Wrapper({ children }: { children: React.ReactNode }) { return ( <Auth0Provider domain={auth0Config.domain} clientId={auth0Config.clientId} authorizationParams={{ redirect_uri: auth0Config.redirectUri, audience: auth0Config.audience, scope: auth0Config.scope }} > {children} </Auth0Provider> );}
3. Registrar JWKS en Blockchain
Sección titulada «3. Registrar JWKS en Blockchain»import { Aptos, AptosConfig, Network, Account, Ed25519PrivateKey } from '@aptos-labs/ts-sdk';
async function setupJWKS() { // Configurar Aptos para testnet const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
// Crear o cargar cuenta para JWKS const jwkOwner = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(process.env.JWK_OWNER_PRIVATE_KEY!) });
// URL del issuer de Auth0 const issuer = `https://${process.env.NEXT_PUBLIC_AUTH0_DOMAIN}`;
try { console.log('Registrando JWKS para:', issuer);
// Crear transacción para actualizar JWKS const transaction = await aptos.updateFederatedKeylessJwkSetTransaction({ sender: jwkOwner.accountAddress, iss: issuer });
// Firmar y enviar transacción const response = await aptos.signAndSubmitTransaction({ signer: jwkOwner, transaction });
// Esperar confirmación await aptos.waitForTransaction({ transactionHash: response.hash });
console.log('✅ JWKS registrado exitosamente'); console.log('🔑 Dirección del propietario JWKS:', jwkOwner.accountAddress.toString()); console.log('📝 Hash de transacción:', response.hash);
return jwkOwner.accountAddress.toString();
} catch (error) { console.error('❌ Error registrando JWKS:', error); throw error; }}
// Ejecutar configuraciónsetupJWKS().catch(console.error);
4. Componente de Autenticación Principal
Sección titulada «4. Componente de Autenticación Principal»import React, { useState, useEffect } from 'react';import { useAuth0 } from '@auth0/auth0-react';import { Aptos, AptosConfig, Network, KeylessAccount, EphemeralKeyPair} from '@aptos-labs/ts-sdk';
export function KeylessAuth() { const { loginWithRedirect, logout, isAuthenticated, getIdTokenClaims, user, isLoading } = useAuth0();
const [keylessAccount, setKeylessAccount] = useState<KeylessAccount | null>(null); const [accountLoading, setAccountLoading] = useState(false); const [balance, setBalance] = useState<number>(0);
const aptos = new Aptos(new AptosConfig({ network: Network.TESTNET }));
// Dirección del propietario JWKS (obtenida del paso anterior) const JWK_ADDRESS = process.env.NEXT_PUBLIC_JWK_ADDRESS!;
useEffect(() => { if (isAuthenticated && !keylessAccount && !accountLoading) { createKeylessAccount(); } }, [isAuthenticated, keylessAccount, accountLoading]);
const createKeylessAccount = async () => { try { setAccountLoading(true);
// Obtener token JWT de Auth0 const tokenClaims = await getIdTokenClaims(); if (!tokenClaims?.__raw) { throw new Error('No se pudo obtener token JWT'); }
console.log('🔐 Creando cuenta Keyless...');
// Generar par de claves efímeras const ephemeralKeyPair = EphemeralKeyPair.generate();
// Crear cuenta Keyless const account = await KeylessAccount.create({ jwt: tokenClaims.__raw, ephemeralKeyPair, jwkAddress: JWK_ADDRESS, pepper: await getPepper(tokenClaims.__raw) });
console.log('✅ Cuenta Keyless creada:', account.accountAddress.toString());
// Financiar cuenta en testnet await fundAccount(account);
setKeylessAccount(account); await updateBalance(account);
} catch (error) { console.error('❌ Error creando cuenta Keyless:', error); alert('Error creando cuenta Keyless: ' + error.message); } finally { setAccountLoading(false); } };
const getPepper = async (jwt: string): Promise<Uint8Array> => { // Para testnet, usar el servicio de pepper de Aptos const response = await fetch('https://api.testnet.aptoslabs.com/v1/keyless/pepper', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jwt_b64: btoa(jwt) }) });
if (!response.ok) { throw new Error('Error obteniendo pepper del servicio'); }
const data = await response.json(); return new Uint8Array(Buffer.from(data.pepper, 'hex')); };
const fundAccount = async (account: KeylessAccount) => { try { console.log('💰 Financiando cuenta...');
await aptos.fundAccount({ accountAddress: account.accountAddress, amount: 100000000 // 1 APT });
console.log('✅ Cuenta financiada con 1 APT'); } catch (error) { console.error('❌ Error financiando cuenta:', error); } };
const updateBalance = async (account: KeylessAccount) => { try { const resources = await aptos.getAccountResources({ accountAddress: account.accountAddress });
const coinResource = resources.find( r => r.type === '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>' );
if (coinResource) { const balance = (coinResource.data as any).coin.value; setBalance(parseInt(balance) / 100000000); // Convertir a APT } } catch (error) { console.error('Error obteniendo balance:', error); } };
const sendTransaction = async () => { if (!keylessAccount) return;
try { console.log('📤 Enviando transacción de prueba...');
// Crear transacción simple de transferencia const transaction = await aptos.transaction.build.simple({ sender: keylessAccount.accountAddress, data: { function: '0x1::aptos_account::transfer', functionArguments: [ '0x1', // Dirección de destino (puede ser cualquiera para prueba) 1000000 // 0.01 APT ] } });
// Firmar y enviar transacción const response = await aptos.signAndSubmitTransaction({ signer: keylessAccount, transaction });
console.log('✅ Transacción enviada:', response.hash);
// Esperar confirmación await aptos.waitForTransaction({ transactionHash: response.hash });
console.log('✅ Transacción confirmada'); await updateBalance(keylessAccount);
} catch (error) { console.error('❌ Error enviando transacción:', error); alert('Error enviando transacción: ' + error.message); } };
const handleLogout = () => { setKeylessAccount(null); setBalance(0); logout({ logoutParams: { returnTo: window.location.origin } }); };
if (isLoading || accountLoading) { return ( <div className="flex items-center justify-center min-h-screen"> <div className="text-center"> <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-500"></div> <p className="mt-4 text-lg"> {accountLoading ? 'Configurando tu cuenta blockchain...' : 'Cargando...'} </p> </div> </div> ); }
if (!isAuthenticated) { return ( <div className="flex items-center justify-center min-h-screen"> <div className="max-w-md w-full bg-white rounded-lg shadow-md p-6"> <h1 className="text-2xl font-bold text-center mb-6"> Keyless Federado Demo </h1> <p className="text-gray-600 text-center mb-6"> Conecta con tu cuenta Auth0 para crear una cuenta blockchain sin claves privadas. </p> <button onClick={() => loginWithRedirect()} className="w-full bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" > Iniciar Sesión con Auth0 </button> </div> </div> ); }
return ( <div className="min-h-screen bg-gray-100 py-8"> <div className="max-w-4xl mx-auto px-4"> <div className="bg-white rounded-lg shadow-md p-6"> <div className="flex justify-between items-center mb-6"> <h1 className="text-2xl font-bold">Panel de Cuenta Keyless</h1> <button onClick={handleLogout} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" > Cerrar Sesión </button> </div>
{/* Información del Usuario */} <div className="grid md:grid-cols-2 gap-6 mb-6"> <div className="bg-gray-50 p-4 rounded-lg"> <h3 className="text-lg font-semibold mb-2">Información de Usuario</h3> <p><strong>Email:</strong> {user?.email}</p> <p><strong>Nombre:</strong> {user?.name}</p> <p><strong>Proveedor:</strong> Auth0</p> </div>
<div className="bg-gray-50 p-4 rounded-lg"> <h3 className="text-lg font-semibold mb-2">Cuenta Blockchain</h3> {keylessAccount ? ( <> <p><strong>Dirección:</strong></p> <p className="text-sm font-mono break-all"> {keylessAccount.accountAddress.toString()} </p> <p className="mt-2"><strong>Balance:</strong> {balance.toFixed(4)} APT</p> </> ) : ( <p>Configurando cuenta...</p> )} </div> </div>
{/* Acciones */} {keylessAccount && ( <div className="border-t pt-6"> <h3 className="text-lg font-semibold mb-4">Acciones de Blockchain</h3> <div className="flex gap-4"> <button onClick={() => updateBalance(keylessAccount)} className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded" > Actualizar Balance </button> <button onClick={sendTransaction} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" > Enviar Transacción de Prueba </button> </div> </div> )}
{/* Información Técnica */} <div className="border-t pt-6 mt-6"> <h3 className="text-lg font-semibold mb-4">Información Técnica</h3> <div className="bg-gray-50 p-4 rounded-lg"> <p><strong>Red:</strong> Testnet</p> <p><strong>Dirección JWKS:</strong> <code className="text-sm">{JWK_ADDRESS}</code></p> <p><strong>Estado:</strong> <span className="text-green-600">✅ Conectado</span></p> </div> </div> </div> </div> </div> );}
5. Configuración de Variables de Entorno
Sección titulada «5. Configuración de Variables de Entorno»NEXT_PUBLIC_AUTH0_DOMAIN=your-auth0-domain.auth0.comNEXT_PUBLIC_AUTH0_CLIENT_ID=your-auth0-client-idNEXT_PUBLIC_AUTH0_AUDIENCE=https://your-auth0-domain.auth0.com/api/v2/NEXT_PUBLIC_JWK_ADDRESS=0x123...abcJWK_OWNER_PRIVATE_KEY=0x123...def
6. Aplicación Principal
Sección titulada «6. Aplicación Principal»// pages/_app.tsx (Next.js) o App.tsx (React)import { Auth0Wrapper } from '../components/Auth0Wrapper';import { KeylessAuth } from '../components/KeylessAuth';
export default function App() { return ( <Auth0Wrapper> <KeylessAuth /> </Auth0Wrapper> );}
Funcionalidades del Ejemplo
Sección titulada «Funcionalidades del Ejemplo»✅ Características Implementadas
Sección titulada «✅ Características Implementadas»- Autenticación Auth0: Login social completo
- Creación Automática de Cuenta: Cuenta blockchain sin claves privadas
- Financiamiento Automático: Para testnet/devnet
- Interface Intuitiva: Dashboard limpio y fácil de usar
- Transacciones de Prueba: Demostración de funcionalidad blockchain
- Manejo de Errores: Feedback claro para usuarios
🔧 Personalización
Sección titulada «🔧 Personalización»// Personalizar configuración de cuentaconst customAccountConfig = { network: Network.MAINNET, // Cambiar a mainnet pepperService: 'https://api.mainnet.aptoslabs.com/v1/keyless/pepper', jwkAddress: 'your-mainnet-jwk-address', autoFunding: false // Deshabilitar auto-funding en mainnet};
// Personalizar métodos de autenticación Auth0const auth0CustomConfig = { // Habilitar autenticación por SMS connection: 'sms',
// Configurar autenticación empresarial connection: 'google-oauth2',
// Usar autenticación personalizada connection: 'Username-Password-Authentication'};
🚀 Ejecutar el Ejemplo
Sección titulada «🚀 Ejecutar el Ejemplo»# Desarrollonpm run dev
# Producciónnpm run buildnpm start
# Lintingnpm run lint
# Testingnpm run test
📱 Casos de Uso Demostrados
Sección titulada «📱 Casos de Uso Demostrados»- Onboarding Sin Fricción: Usuario se registra con Auth0 y automáticamente obtiene cuenta blockchain
- Transacciones Simplificadas: Envío de transacciones sin gestionar claves privadas
- Experiencia Web2: Interfaz familiar para usuarios no-crypto
- Seguridad Mantenida: Seguridad blockchain completa sin complejidad de usuario
Este ejemplo demuestra cómo Keyless Federado puede revolucionar la experiencia de usuario en aplicaciones Web3, eliminando barreras técnicas mientras mantiene la seguridad y funcionalidad completa de blockchain.