Saltar al contenido principal

Fase 4: Solana no custodial con PDAs de Anchor

· 6 min de lectura
OrcaRail
Crypto payment rails for web2 apps

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_link es propietario de una PDA por enlace de pago, derivada de seeds = [b"pl", link_id]. Sin keypair, sin semilla, sin barrido.
  • El programa subscription usa 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_splitter que 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).

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. Emite PaymentReceived { 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_at
  • clock.unix_timestamp > last_charged_at // monotonic
  • amount_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.generateAddressForNetwork deja de derivar keypairs y devuelve en su lugar findProgramAddressSync(["pl", linkId], programId). No se escribe ninguna fila encryptedSeed para 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 charge y 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.ts no 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.
  • SolanaRelayBridgeExecutorService en 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: charge vuelve a comprobar delegated_amount antes de transferir; si la comprobación falla emite ChargeSkipped(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_link admite ambos, con un expected_mint explí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_link con todas las instrucciones + tests
  • Programa subscription con 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