Prueba de concepto de onboarding y transferencia de USDC sin que el usuario final necesite XLM, utilizando arquitectura hexagonal, @stellar/stellar-sdk y @sozu/faucet. El mecanismo de gas sponsorship se implementa con TransactionBuilder.buildFeeBumpTransaction, donde un tercero (sponsor) paga la comisión de red.
Se demostró que una wallet A con 0 XLM (fondada solo con USDC vía Sozu Faucet o Sponsored Reserves) puede enviar USDC a una wallet B, y la comisión de red es pagada por una cuenta sponsor mediante fee-bump, no por el usuario final.
Este es el punto de diseño más importante de la PoC. Documentamos aquí la decisión para que quede constancia en el repositorio y pueda guiar la evolución hacia Mainnet.
En el producto que se está construyendo (cobro en moneda local, conversión automática a USDC en un futuro on/off ramp), el usuario es el dueño de sus fondos. Por eso:
| Criterio | Decisión | Razón |
|---|---|---|
| ¿Quién guarda el secret del usuario? | El usuario, en su propia wallet | Evita que la aplicación sea objetivo de ataques para robar fondos de terceros |
| ¿Quién firma el pago USDC de A → B? | El usuario, con su clave privada | La aplicación nunca ve ni toca el secreto del usuario |
| ¿Quién paga la comisión de red? | El sponsor (fee-bump) | El usuario no necesita XLM para operar |
| ¿Quién crea la wallet y la trustline? | El sponsor (vía Sponsored Reserves) | El usuario nace con cuenta y trustline, pero sin XLM gastable |
| Responsabilidad legal | Baja | La aplicación no es custodio de fondos ajenos |
El modelo custodial (la aplicación guarda el secret del usuario y firma por él) fue descartado para el producto por las siguientes razones:
| Riesgo del modelo custodial | Impacto en este producto |
|---|---|
| Robo masivo | Si el backend es comprometido, todos los secretos de los usuarios están expuestos y sus fondos pueden ser robados |
| Responsabilidad legal | Ser custodio de fondos ajenos implica regulaciones financieras más estrictas (KYC/AML, licencias de transmisor de dinero) |
| Pérdida de confianza | Los usuarios de Web3 esperan ser dueños de sus claves; una app custodial rompe ese principio |
| Punto único de fallo | Un solo servidor guarda todas las claves; si cae o se filtra, todo el sistema se compromete |
El fee-bump es la pieza técnica clave que hace posible que el usuario no necesite XLM para operar, sin que la aplicación tenga que ser custodia de sus fondos:
- El usuario genera su propia wallet y guarda su secret (no-custodial).
- El usuario firma su propia transacción de pago con su clave.
- El sponsor (la aplicación) envuelve ese XDR firmado en una fee-bump transaction y paga la comisión de red.
El usuario nunca necesita XLM para pagar comisiones, pero tampoco necesita confiar su clave privada a la aplicación.
src/
├── domain/ # Entidades de negocio (sin dependencias externas)
│ ├── Wallet.ts # address, secret (en memoria)
│ └── Payment.ts # from, to, amount, asset
├── application/ # Casos de uso y puertos
│ ├── ports/
│ │ ├── WalletProvider.ts # Interfaz: createWallet(): Promise<Wallet>
│ │ ├── PaymentSubmitter.ts # Interfaz: submitWithSponsor(txXdr): Promise<string>
│ │ └── SecretProvider.ts # Interfaz: getSponsorSecret(): Promise<string>
│ └── use-cases/
│ ├── CreateFundedWallet.ts
│ └── SendSponsoredPayment.ts
├── infrastructure/ # Adaptadores (infraestructura externa)
│ ├── sozu/
│ │ └── SozuWalletProvider.ts # Ejecuta `npx @sozu/faucet claim` y parsea JSON
│ ├── stellar/
│ │ ├── StellarPaymentSubmitter.ts # buildFeeBumpTransaction + submit a Horizon
│ │ └── StellarSponsoredWalletProvider.ts # Sponsored Reserves (CAP-33/CAP-31)
│ └── secret/
│ ├── EnvSecretProvider.ts # Lee secreto de variable de entorno (desarrollo)
│ └── EncryptedFileSecretProvider.ts # Lee secreto de archivo cifrado (producción)
└── labstellar/ # Puntos de entrada E2E (scripts de validación en Testnet)
├── e2e-usdc-sozu-fee-bump.ts # Pago USDC con Sozu Faucet + fee-bump
├── e2e-xlm-sponsored-reserves-fee-bump.ts # Pago XLM con Sponsored Reserves + fee-bump
└── e2e-usdc-sponsored-reserves-fee-bump.ts # Pago USDC con Sponsored Reserves + fee-bump
Regla de oro respetada: El dominio y los casos de uso no importan nada de @stellar/stellar-sdk ni de child_process. Solo dependen de las interfaces (puertos).
Se ha integrado Pollar Server API v3 para permitir la conversión entre moneda fiat y cripto (USDC) en Stellar Testnet. La arquitectura sigue siendo NO-CUSTODIAL: el usuario usa su propia wallet Stellar y firma las transacciones con su clave privada.
1. Vincular wallet externa a Pollar (PUT /v3/ramps/customers/wallets/STELLAR)
2. Consultar rutas disponibles (GET /v3/ramps/routes?direction=ONRAMP)
3. Agregar trustlines de los activos de las rutas a la wallet del usuario
4. Cotizar on-ramp (POST /v3/ramps/quotes)
5. Crear orden de rampa (POST /v3/ramps/orders)
6. Firmar pierna de rampa (POST /v3/ramps/orders/{id}/legs/{index}/signature)
7. Poll del estado hasta resolución (GET /v3/ramps/orders/{id})
El flujo off-ramp es el espejo del on-ramp en dirección opuesta. Requiere que la wallet del usuario tenga saldo de USDC antes de ejecutar.
| Script | Descripción |
|---|---|
npm run e2e:usdc-onramp-pollar |
On-ramp fiat → USDC vía Pollar (COP/ARS/PEN → USDC en Stellar Testnet) |
npm run e2e:usdc-offramp-pollar |
Off-ramp USDC → fiat vía Pollar (USDC en Stellar → COP/ARS/PEN) |
- Una wallet Stellar existente con su
STELLAR_SECRET_KEY - La wallet debe tener XLM para comisiones (fondar con Friendbot:
https://friendbot.stellar.org?addr=DIRECCION) - Para off-ramp, la wallet debe tener saldo de USDC
export SPONSOR_SECRET_KEY="SA_..."
export POLAR_SECRET_KEY="sec_testnet_..."
export POLAR_EXTERNAL_USER_ID="mi-usuario-123"
export STELLAR_SECRET_KEY="SA_..."
# On-ramp: 5000 COP → ~4550 USDC (comisión 450 COP)
npm run e2e:usdc-onramp-pollar
# Off-ramp: USDC → fiat (requiere USDC en la wallet)
npm run e2e:usdc-offramp-pollar| Estado | Descripción |
|---|---|
created |
Orden creada, pendiente de acción |
awaiting_payment |
Esperando pago fiat del usuario |
processing |
En proceso de liquidación |
settling_onchain |
Liquidando en cadena Stellar |
completed |
Completado exitosamente |
failed |
Falló |
refunded |
Reembolsado |
| Tipo | Qué hacer |
|---|---|
SIGNATURE |
Firmar transacción Stellar con la clave del usuario |
FIAT_PAYMENT |
Usuario debe realizar el pago fiat (pago local) |
KYC |
Usuario debe completar verificación de identidad |
COSIGN_APPROVAL |
Aprobación de co-firma (wallets co-custody) |
NONE |
Sin acción pendiente |
- Las rutas disponibles en Pollar testnet son: COP/COP (Colombia), ARS/ARS (Argentina), PEN/PEN (Perú) vía HOSTED
- Monto mínimo para on-ramp COP: 4000 COP, máximo: 40000 COP
- El KYC es requerido por Pollar para completar la orden; debe hacerse manualmente por el usuario
- El SDK
@pollar/corerequiere entorno de navegador (no funciona en Node.js directamente) - KYC con Didit: Para activar el KYC, debes configurar tu cuenta de Didit en el dashboard de Pollar (Developers > Identity checks > Verification steps). Pollar requiere que la URL de devolución esté registrada/permitida antes de crear la opción de Didit. Si recibes error "No se pudo crear su opción de Didit", verifica que la URL de devolución esté en la lista de URLs permitidas de tu aplicación en Pollar.
El flujo on-ramp/off-ramp está completamente implementado y listo para ejecutar, pero está bloqueado a la espera de que el soporte de Pollar apruebe la URL de devolución para el webhook de Didit.
Estado:
- ✅ Código implementado y compilando (
tsc --noEmitlimpio) - ✅
PollarRampProviderconstartKyc,getKycStatus,signRampLeg,quoteRamp,createRampOrder,getOrderStatus,linkWallet,getRampRoutes - ✅ Scripts E2E listos (
e2e-usdc-onramp-pollar,e2e-usdc-offramp-pollar) - ✅ Servidor webhook local corriendo en
http://0.0.0.0:3000 - ✅ Tunnel de Cloudflare activo:
https://charger-polar-atom-tourist.trycloudflare.com - ⏳ Pendiente: Soporte de Pollar apruebe la URL
https://charger-polar-atom-tourist.trycloudflare.comcomo URL de devolución permitida - ⏳ Pendiente: Crear opción de Didit en Pollar Dashboard una vez aprobada la URL
Una vez aprobada la URL, ejecutar:
export SPONSOR_SECRET_KEY="SA_TU_SECRET_KEY_DEL_SPONSOR"
export POLAR_SECRET_KEY="sec_testnet_TU_CLAVE_DE_POLLAR"
export POLAR_EXTERNAL_USER_ID="mi-usuario-prueba"
export STELLAR_SECRET_KEY="SA_TU_SECRET_KEY_DE_USUARIO"
npm run e2e:usdc-onramp-pollar| Contexto | Opción recomendada | Razón |
|---|---|---|
| Lab/pruebas (scripts E2E) | redirect |
Simple: el usuario visita una URL en su navegador, completa el KYC y Didit redirige de vuelta a tu webhook |
| Billetera/app de producción | iframe |
El KYC se muestra embebido dentro de tu aplicación; el usuario nunca sale de tu app |
Nota: Para este laboratorio se usa redirect. Si vas a integrar Pollar en una billetera de producción, cambia a iframe para mantener la experiencia de usuario dentro de tu aplicación.
El webhook de Didit es opcional. Tu script E2E ya hace polling cada 5 segundos consultando GET /v3/ramps/kyc/status?corridorId=..., así que funciona con o sin webhook.
Si quieres usar webhook para notificación en tiempo real:
- Configura un servidor que reciba POST de Didit (tu servidor local en
http://0.0.0.0:3000expuesto vía localtunnel o ngrok) - En Didit Dashboard, configura el webhook con:
- URL de endpoint:
https://tu-url-publica.com/webhook/didit - Medio ambiente:
Sandbox - Eventos:
Verificaciones → status.updated,Verificaciones → data.updated
- URL de endpoint:
- En Pollar Dashboard, agrega el mismo webhook secret en la configuración de Didit
| Capa | Tecnología | Versión |
|---|---|---|
| Lenguaje | TypeScript (Node.js) | — |
| SDK Stellar | @stellar/stellar-sdk |
^14.6.1 |
| Fondeo de wallets | @sozu/faucet (CLI) |
^0.2.0 |
| On/Off-Ramp | Pollar Server API v3 | https://server.api.pollar.xyz/v3 |
| SDK Pollar (cliente/navegador) | @pollar/core |
^0.12.0 |
| Red | Stellar Testnet | Horizon: https://horizon-testnet.stellar.org |
| Pruebas | Vitest | ^1.6.0 |
| Ejecución TS | ts-node | ^10.9.2 |
| Compilador | TypeScript | ^5.5.0 |
npm installCrear un archivo .env o exportar en la terminal antes de ejecutar:
export SPONSOR_SECRET_KEY="SA_TU_SECRET_KEY_DEL_SPONSOR"
export SOZU_FAUCET_URL="https://faucet.sozu.capital" # opcional
export POLAR_SECRET_KEY="sec_testnet_TU_CLAVE_DE_POLLAR"
export POLAR_PUBLIC_KEY="pub_testnet_TU_CLAVE_PUBLICA_DE_POLLAR"
export POLAR_EXTERNAL_USER_ID="tu-usuario-unico" # opcional, por defecto 'rewallet-lab-stellar'
export STELLAR_SECRET_KEY="SA_TU_SECRET_KEY_DE_USUARIO" # requerido para on-ramp/off-rampSPONSOR_SECRET_KEY: Clave secreta de una cuenta Stellar Testnet con XLM. Esta cuenta paga las comisiones de red (fee-bump). Nunca hardcodear en el repositorio.SOZU_FAUCET_URL: Origen del faucet Sozu (por defectohttps://faucet.sozu.capital).POLAR_SECRET_KEY: Clave secreta de Pollar Server API (sec_testnet_* / sec_mainnet_*). Se usa para autenticar llamadas a la API de Pollar con el headerx-pollar-api-key.POLAR_PUBLIC_KEY: Clave pública de Pollar (pub_testnet_*). Se usa con el SDK@pollar/coreen entornos de navegador.POLAR_EXTERNAL_USER_ID: ID de usuario externo para Pollar (cualquier string único, 1-128 caracteres). Se usa para asociar órdenes de rampa a un usuario específico.STELLAR_SECRET_KEY: Clave secreta de la wallet Stellar del usuario. Requerido para on-ramp/off-ramp con Pollar. La wallet debe existir en Horizon y tener los trustlines necesarios.
⚠️ Los secretos solo se manejan en memoria. Nunca se escriben en disco ni se versionan.
El puerto SecretProvider permite obtener el secreto del sponsor sin que el dominio ni los casos de uso conozcan el mecanismo de almacenamiento. Esto permite cambiar el proveedor de secretos sin modificar la lógica de negocio.
| Adaptador | Archivo | Cuándo usar |
|---|---|---|
EnvSecretProvider |
src/infrastructure/secret/EnvSecretProvider.ts |
Desarrollo rápido. Lee de process.env. |
EncryptedFileSecretProvider |
src/infrastructure/secret/EncryptedFileSecretProvider.ts |
Producción ligera. Lee de archivo cifrado AES-256-GCM. |
Cada script de src/labstellar/ instancia el proveedor que necesite:
// Desarrollo (variable de entorno)
const sponsorSecretProvider = new EnvSecretProvider('SPONSOR_SECRET_KEY');
const submitter = new StellarPaymentSubmitter(sponsorSecretProvider, HORIZON_URL);
// Producción (archivo cifrado)
const sponsorSecretProvider = new EncryptedFileSecretProvider('.sponsor-secret.enc', 'MASTER_SECRET_KEY');
const submitter = new StellarPaymentSubmitter(sponsorSecretProvider, HORIZON_URL);# 1. Generar una clave maestra de 64 caracteres hexadecimales (256 bits)
MASTER_SECRET_KEY=$(openssl rand -hex 32)
# 2. Crear el archivo cifrado con el secreto del sponsor
MASTER_SECRET_KEY=$MASTER_SECRET_KEY SPONSOR_SECRET_KEY=SA_TU_CLAVE_SPONSOR \
npx ts-node src/infrastructure/secret/encrypt-secret.ts
# 3. Ejecutar usando la clave maestra
export MASTER_SECRET_KEY=$MASTER_SECRET_KEY
npm run e2e:usdc-fee-bump-sozuEl archivo .sponsor-secret.enc generado NO debe commitearse al repositorio.
npm run e2e:usdc-fee-bump-sozuFlujo:
- Wallet A se crea vía Sozu Faucet (
npx @sozu/faucet claim). Nace con USDC y 0 XLM para el usuario. - Wallet B se genera aleatoriamente, se fonda con Friendbot y se le añade la trustline de USDC.
- Se construye y firma una transacción de pago USDC de A → B con la secret de A.
- El XDR se envuelve en una fee-bump transaction con
buildFeeBumpTransaction, se firma con el sponsor y se envía a Horizon. - Se imprime el hash de la transacción en consola.
npm run e2e:xlm-fee-bump-sponsored-reservesFlujo:
- Wallet A se crea vía Sponsored Reserves (CAP-33/CAP-31):
beginSponsoringFutureReserves+createAccount(sponsor paga 1 XLM de reserve).changeTrust(USDC)+endSponsoringFutureReserves(sponsor paga 0.5 XLM de reserve). Todo envuelto en fee-bump pagada por el sponsor.
- Sponsor envía 2 XLM a A para comisiones disponibles (A nace con 0 XLM gastables).
- Wallet B se genera aleatoriamente, se fonda con Friendbot y se le añade la trustline de USDC.
- Se construye y firma una transacción de pago XLM nativo de A → B con la secret de A.
- El XDR se envuelve en una fee-bump transaction y se envía a Horizon.
- Se imprime el hash de la transacción en consola.
npm run e2e:usdc-fee-bump-sponsored-reserves
⚠️ Requisito previo: Wallet A debe tener saldo de USDC antes de ejecutar el pago. Usar Sozu Faucet para fundearla:npx @sozu/faucet claim <DIRECCION_DE_A>
Flujo:
- Wallet A se crea vía Sponsored Reserves (CAP-33/CAP-31):
beginSponsoringFutureReserves+createAccount(sponsor paga 1 XLM de reserve).changeTrust(USDC)+endSponsoringFutureReserves(sponsor paga 0.5 XLM de reserve). Todo envuelto en fee-bump pagada por el sponsor.- A nace con 0 XLM gastables y 0 USDC balance.
- Sponsor envía 2 XLM a A para comisiones disponibles.
- Fondear A con USDC — Paso manual requerido: usar Sozu Faucet (
npx @sozu/faucet claim <DIRECCION_DE_A>) o recibir USDC de otra wallet. - Wallet B se genera aleatoriamente, se fonda con Friendbot y se le añade la trustline de USDC.
- Se construye y firma una transacción de pago USDC de A → B con la secret de A.
- El XDR se envuelve en una fee-bump transaction y se envía a Horizon.
- Se imprime el hash de la transacción en consola.
Nota importante sobre USDC con Sponsored Reserves: Wallet A nace con la trustline USDC activa pero sin balance de USDC. Para hacer un pago en USDC de A → B, primero debe fundearse A con USDC (vía faucet o pago directo), ya que el patrón de sponsorship solo cubre el reserve de la cuenta y el reserve de la trustline, no el saldo del activo.
| Campo | Valor |
|---|---|
| Hash | 8028aabbd79ad15d003b3555a26534019a36772d7bc7d21aa74f194680d8aa7b |
| Estado | ✅ successful: true |
| Fee pagado por (fee_account) | GAWMZWOPC3ESC6YRRGYPWHPKMKRHIFRXBI27NQS6YKTF2JKZHRGUJXJI (sponsor) |
| Wallet emisora (source_account) | GAOYQYFSQDDVWAXZXD4UH7AVQ6G2T2CYHEJCOAI5KSZQU36IWLQL7NWF (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5620394 |
| Fecha | 2026-11-08T08:13:32Z |
| Campo | Valor |
|---|---|
| Hash | 48d42eecfc2797ecf209ae2ebe1300bb2233eb917436b4df1509aed0f4bb8055 |
| Estado | ✅ successful: true |
| Fee pagada por (fee_account) | GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI (sponsor) |
| Wallet emisora (source_account) | GBLHMN6HUKTLL72BY7BHL4UYIE2UBV5O3MBWGHFGYD2ICSAAU4ONPFSZ (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5621655 |
| Fecha | 2026-11-08T09:58:37Z |
| Campo | Valor |
|---|---|
| Hash | db0ae64a00f266c433102c3d0d9d5d20874a0a5d15b59d45119d9c36417e2561 |
| Estado | ✅ successful: true |
| Fee pagada por (fee_account) | GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI (sponsor) |
| Wallet emisora (source_account) | GA4QPRBYM2PQVECGLUXBGFQUZBQNHIJA4K4OIVKBXVVIETDQFQNDLYEI (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5621811 |
| Fecha | 2026-11-08T10:11:37Z |
| Campo | Valor |
|---|---|
| Hash | 7c91e5cc544d36faeb34a13196b60eb6775575ca07ca89f204cac06fa38f53c0 |
| Estado | ✅ successful: true |
| Fee pagada por (fee_account) | GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI (sponsor) |
| Wallet emisora (source_account) | GA3E3OYH3VSU2DABKHCXA6R3275BESVC7AR3W7DX4XAC7MGFWWPFRXCH (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5622025 |
| Fecha | 2026-11-08T10:29:27Z |
| Campo | Valor |
|---|---|
| Hash | 1352a2f114f268ef1ceaa78e8c43b7b149121b67135fb7c38153b8f0a2092dc9 |
| Estado | ✅ successful: true |
| Fee pagada por (fee_account) | GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI (sponsor) |
| Wallet emisora (source_account) | GBSTNFJZ2C2ADVUMHAKFKPV5ICQN2SY4M4WE6ZNR4LQDBGIY6QDF6CYM (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5622561 |
| Fecha | 2026-11-08T11:14:07Z |
Transacción 6: Fee-Bump con Sozu Faucet (pago USDC) — Persistencia de secretos vía archivo cifrado verificada
| Campo | Valor |
|---|---|
| Hash | eae908dd8615c5a1ebe531a02da54d82a07711756247e91b943f568495773515 |
| Estado | ✅ successful: true |
| Fee pagada por (fee_account) | GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI (sponsor) |
| Wallet emisora (source_account) | GDVDKBQCHW273IDPEJGJSLASH47YHHLABDG6Y6PL2IQNPVXFWR2UYIML (wallet A) |
| Comisión cobrada | 200 stroops |
| Ledger | 5622657 |
| Fecha | 2026-11-08T11:22:07Z |
- Transacción 1 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/8028aabbd79ad15d003b3555a26534019a36772d7bc7d21aa74f194680d8aa7b
- Transacción 2 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/48d42eecfc2797ecf209ae2ebe1300bb2233eb917436b4df1509aed0f4bb8055
- Transacción 3 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/db0ae64a00f266c433102c3d0d9d5d20874a0a5d15b59d45119d9c36417e2561
- Transacción 4 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/7c91e5cc544d36faeb34a13196b60eb6775575ca07ca89f204cac06fa38f53c0
- Transacción 5 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/1352a2f114f268ef1ceaa78e8c43b7b149121b67135fb7c38153b8f0a2092dc9
- Transacción 6 en Stellar Expert: https://stellar.expert/explorer/testnet/tx/eae908dd8615c5a1ebe531a02da54d82a07711756247e91b943f568495773515
Transacción 6 (Fee-Bump + Sozu Faucet, con persistencia de secretos vía archivo cifrado AES-256-GCM):
{
"successful": true,
"hash": "eae908dd8615c5a1ebe531a02da54d82a07711756247e91b943f568495773515",
"source_account": "GDVDKBQCHW273IDPEJGJSLASH47YHHLABDG6Y6PL2IQNPVXFWR2UYIML",
"fee_account": "GBK6IQBLLQ6T7ZMHNVTZUVQ6ETISGVZUVWWAP5SJPPBUD66PM4RZFIRI",
"fee_charged": "200",
"max_fee": "200",
"fee_bump_transaction": true,
"inner_transaction": {
"hash": "c459f7f43cc0b06c63c27c001fd48287e0778f64be04bc8bae7daef61178eb68",
"max_fee": "100"
}
}El adaptador SozuWalletProvider implementa reintentos con backoff exponencial para errores recuperables del faucet Sozu:
| Origen | Errores | Comportamiento |
|---|---|---|
| Red (Node.js) | ECONNREFUSED, ECONNRESET, ENETUNREACH, ECONNABORTED, ETIMEDOUT, EAI_AGAIN |
Reintenta con backoff exponencial |
| HTTP | 429 Too Many Requests |
Reintenta con backoff exponencial |
| JSON del faucet | user_cooldown, global_cooldown, rate_limited |
Reintenta esperando nextAvailableAt o delay por defecto |
| Opción | Por defecto | Descripción |
|---|---|---|
maxRetries |
3 |
Número máximo de reintentos |
baseDelayMs |
5000 |
Retraso base en ms para backoff exponencial |
- Intento 1: inmediatamente
- Intento 2: 5 segundos
- Intento 3: 10 segundos
- Intento 4: 20 segundos
- Tope máximo: 60 segundos
Si después de agotar los reintentos el error persiste, se lanza el último error capturado.
npm testRequiere E2E=true y SPONSOR_SECRET_KEY:
E2E=true npm run test:integrationEstos tests se saltan automáticamente si no se definen las variables de entorno.
| Script | Descripción |
|---|---|
npm run e2e:usdc-fee-bump-sozu |
Ejecuta el flujo E2E con Sozu Faucet + fee-bump (pago USDC) contra Testnet y muestra el hash |
npm run e2e:xlm-fee-bump-sponsored-reserves |
Ejecuta el flujo Sponsored Reserves + fee-bump (pago XLM nativo) contra Testnet y muestra el hash |
npm run e2e:usdc-fee-bump-sponsored-reserves |
Ejecuta el flujo Sponsored Reserves + fee-bump (pago USDC) contra Testnet (requiere fondear A con USDC primero) |
npm run e2e:usdc-onramp-pollar |
Ejecuta el flujo on-ramp fiat → USDC vía Pollar Server API v3 (firma + polling) |
npm run e2e:usdc-offramp-pollar |
Ejecuta el flujo off-ramp USDC → fiat vía Pollar Server API v3 (firma + polling) |
npm run e2e:usdc-onramp-pollar-sdk |
Script de prueba con SDK @pollar/core (requiere navegador, no funciona en Node.js) |
npm test |
Ejecuta tests unitarios (sin red) |
npm run test:integration |
Ejecuta tests de integración contra Testnet (requiere E2E=true) |
npm run lint |
Verifica tipos con tsc --noEmit |
npm run build |
Compila TypeScript a dist/ |
-
tsc --noEmitcompila sin errores -
npm testpasa sin errores (tests unitarios, integración skippeada sin red) -
npm run e2e:usdc-fee-bump-sozufinaliza con hash de transacción exitoso (verificado en Testnet) -
npm run e2e:xlm-fee-bump-sponsored-reservesfinaliza con hash de transacción exitoso (verificado en Testnet) -
npm run e2e:usdc-fee-bump-sponsored-reservescompila y está listo para ejecutar (requiere fondear A con USDC) -
npm run e2e:usdc-onramp-pollarcompila y ejecuta flujo on-ramp completo (vinculación, trustlines, quote, orden, firma Stellar) -
npm run e2e:usdc-offramp-pollarcompila y ejecuta flujo off-ramp completo (requiere USDC en wallet) - Las transacciones fee-bump aparecen como exitosas en Stellar Expert Testnet
- La comisión fue pagada por la cuenta del sponsor, no por la wallet A (fee-bump)
- La wallet A tuvo 0 XLM para el usuario final (fondada solo con USDC o Sponsored Reserves)
- Arquitectura hexagonal: dominio y casos de uso sin dependencias de SDK ni child_process
- Secretos solo en memoria, ninguno hardcodeado
- Manejo de errores de red con reintentos y backoff exponencial en
SozuWalletProvider - Persistencia de secretos mediante puerto
SecretProvider(variable de entorno y archivo cifrado) - Integración con Pollar Server API v3: autenticación
x-pollar-api-key,linkWallet,getRampRoutes,quoteRamp,createRampOrder,signRampLeg,getOrderStatus - Arquitectura NO-CUSTODIAL respetada: el usuario firma con su propia clave Stellar
Esta PoC valida el mecanismo principal de fee-bump, sponsored reserves y on/off-ramp con Pollar. Para llevar a producción o extender:
- Persistencia de secretos del sponsor: Ya implementada vía
SecretProvider(variable de entorno y archivo cifrado). Para producción, usar AWS Secrets Manager, HashiCorp Vault, o similar. - Completar KYC en Pollar: El on-ramp se queda en estado
KYC(verificación de identidad). El usuario debe completar el KYC manualmente vía la URL que Pollar proporciona. Para producción, integrar este flujo en el frontend. - SDK @pollar/core en navegador: Para producción frontend, usar el SDK de Pollar con
login(),setUpRampWallet(),signRampLeg()ypollRampOrder()en un entorno de navegador (no funciona en Node.js debido aORIGIN_NOT_ALLOWED). - Off-ramp con USDC real: Una vez completado el on-ramp y recibido USDC en la wallet del usuario, ejecutar el off-ramp para convertir USDC → fiat.
MIT