Fase 4: Solana no custodial con PDAs de Anchor
20 de abril de 2026: la Fase 4 lleva el modelo no custodial a Solana. Dos programas Anchor sustituyen el flujo de HD wallet + barrido por enlace: payment_link para pagos únicos y subscription para el cobro automático por delegación SPL. No hay ningún mnemónico en nuestros servidores para los nuevos enlaces de Solana.
En resumen
- El programa
payment_linkes propietario de una PDA por enlace de pago, derivada deseeds = [b"pl", link_id]. Sin keypair, sin semilla, sin barrido. - El programa
subscriptionusa la autoridad delegada de SPL, la misma primitiva en la que ya nos apoyamos en Cobro automático, como un allowance explícito concedido por el pagador. - Los repartos se hacen por CPI a la misma lógica
fee_splitterque construimos en EVM, adaptada a las cuentas de Solana. - El servicio de barrido api/src/withdrawals/services/solana-withdrawal-transfer.service.ts se omite para los enlaces con el flag y se elimina en la Fase 7.
Qué cambia en esta fase
El lado del «después» sustituye la semilla HD de api/src/payments/wallet.service.ts (la rama m/44'/501'/i'/0') por un cálculo de PDA puramente determinista.
Programas introducidos
Nuevo paquete contracts/solana/ (Anchor).
Programa payment_link
Cuentas por enlace:
#[account]
pub struct PaymentLink {
pub link_id: [u8; 32],
pub expected_mint: Pubkey, // e.g. USDC on SOL
pub expected_amount: u64,
pub split: Split,
pub created_at: i64,
pub status: PaymentStatus,
}
Instrucciones:
initialize(link_id, expected_mint, expected_amount, split): diferida; la llama en el primer depósito el envoltorio de tipo factoría.pay(link_id, amount): valida mint + importe, transfiere por CPI a la ATA de la PDA y luego reparte por CPI de forma atómica. EmitePaymentReceived { link_id, payer, amount }.refund(link_id): requiere la multisig y replica el reembolso de EVM.
Semillas de la PDA: [b"pl", link_id]. La PDA no tiene clave privada: firma mediante invoke_signed.
Programa subscription
Aprovecha Approve / Revoke de SPL para delegar la autoridad. El pagador firma spl_token::approve designando la PDA del programa como delegada por hasta cap tokens. Después:
pub fn charge(ctx: Context<Charge>, id: [u8; 32]) -> Result<()>
Cualquiera puede llamarla; comprueba:
clock.unix_timestamp >= next_charge_atclock.unix_timestamp > last_charged_at// monotonicamount_charged + period_amount <= cap- que la autoridad delegada siga presente en la ATA del pagador
Si pasa, spl_token::transfer_checked por CPI cobra de la ATA del pagador (como delegada) y reparte por CPI.
Eventos: SubscriptionCreated, Charged, ChargeSkipped, Canceled. Tienen la misma forma que produce el indexador de EVM, así que api/src/webhooks es agnóstico.
Cambios en la API
- La rama de Solana de
WalletService.generateAddressForNetworkdeja de derivar keypairs y devuelve en su lugarfindProgramAddressSync(["pl", linkId], programId). No se escribe ninguna filaencryptedSeedpara los nuevos enlaces de Solana. - Un nuevo servicio indexador observa los logs del programa (webhooks de Helius o streams de Triton) y emite los eventos de webhook existentes.
- api/src/scheduler/processors/subscription-auto-charge.processor.ts añade una rama para SOL: construye una transacción
chargey la firma con el keeper (camino sin gas mediante una transacción patrocinada, parecido al paymaster de la Fase 2). withdrawals/services/solana-withdrawal-transfer.service.tsno se llama para los enlaces con el flag.
Gas en Solana
Las comisiones de Solana son pequeñas (normalmente 5000 lamports por firma, 0,000005 SOL). El keeper usa un saldo de SOL en caliente con el mismo patrón de alertas que en la Fase 3. Como Solana no tiene un paymaster al estilo de Alchemy, las transacciones patrocinadas usan un patrón simple de cofirmante: el keeper firma como pagador de comisiones y el pagador no necesita SOL en absoluto.
Obsolescencias
- Derivación HD de SOL (
m/44'/501'/i'/0') para enlaces nuevos: no se llama cuando el flag está activado. solana-withdrawal-transfer.service.ts: se omite para los enlaces con el flag.SolanaRelayBridgeExecutorServiceen api/src/withdrawals/services/bridge/solana-relay-bridge-executor.service.ts se mantiene: el puente de SOL a EVM cuando el comercio está en EVM sigue usando Relay, pero el disparador pasa al programa.
Plan de migración
El mismo modelo voluntario que en la Fase 2:
- Enlaces de pago nuevos: direccionados por PDA.
- Suscripciones nuevas: basadas en delegación SPL.
- Suscripciones activas existentes: banner + email en la siguiente renovación para volver a delegar al nuevo programa.
- Los depósitos en curso en direcciones heredadas siguen liquidándose por el camino heredado.
Riesgos y mitigaciones
- La delegación SPL se puede sobrescribir. Mitigación:
chargevuelve a comprobardelegated_amountantes de transferir; si la comprobación falla emiteChargeSkipped(DelegateRevoked)y sigue la misma plantilla de email que el caso de allowance revocado en EVM. - Autoridad de actualización de Anchor. Mitigación: una multisig Squads 3 de 5 es propietaria de la autoridad de actualización, con timelock. Coincide con la postura de la Safe en EVM de la Fase 0.
- SOL envuelto frente a SOL nativo. Mitigación:
payment_linkadmite ambos, con unexpected_mintexplícito por enlace para evitar ambigüedades. - Picos de comisiones de prioridad. Mitigación: cálculo dinámico de la comisión de prioridad en el keeper; reintento de respaldo con mayor prioridad tras un slot.
- Auditoría. Mitigación: auditoría de OtterSec o Neodyme de ambos programas antes del despliegue en mainnet.
Lista de estado
- Workspace de Anchor
contracts/solana/creado - Programa
payment_linkcon todas las instrucciones + tests - Programa
subscriptioncon todas las instrucciones + tests - Indexador con webhooks de Helius publicado
- Rama de SOL del keeper en
subscription-auto-charge.processor.ts - Informe de auditoría publicado
- Despliegue en mainnet desde la multisig Squads
- Grupo canario de comercios
Qué sigue
Bitcoin. La cadena más difícil para la no custodia, y en la que tenemos que ser honestos sobre los límites: Fase 5.
Documentación de referencia: Estado de la Fase 4 · Referencia de contratos · Arquitectura
