Saltar al contenido principal

Fase 1: enlaces de pago basados en contratos en EVM

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

20 de abril de 2026: la Fase 1 es el verdadero inicio de ingeniería de la hoja de ruta no custodial. Sustituimos las EOA derivadas de HD por enlace y las smart accounts de Alchemy por un patrón de factoría + receptor como clon mínimo en EVM. Los fondos se reparten de forma atómica en la transacción de pago; sin barrido, sin custodia.

En resumen​

  • PaymentLinkFactory.computeAddress(linkId) devuelve una dirección CREATE2 determinista. Mostramos esa dirección al pagador antes de que se despliegue nada.
  • PaymentLinkReceiver es un clon mínimo EIP-1167. Acepta token nativo o ERC-20, emite Paid y llama a FeeSplitter.split() en la misma transacción.
  • api/src/payments/wallet.service.ts deja de generar HD wallets para los enlaces EVM; en su lugar llama a la vista de la factoría.
  • El camino de barrido (drainSmartAccountWallet, getSmartAccountClientForDepositWallet) se omite para los enlaces creados con el flag useOnChainReceiver.

Qué cambia en esta fase​

En el lado del «después» no hay ninguna caja que controlemos reteniendo los fondos.

Contratos introducidos​

Todos en un nuevo paquete contracts/evm/.

PaymentLinkFactory​

  • computeAddress(bytes32 linkId) view returns (address): dirección CREATE2 determinista derivada de linkId (el UUID actual del enlace de pago, con hash a bytes32).
  • deployAndPay(bytes32 linkId, Split split, address token, uint256 amount): despliegue diferido + pago en una sola transacción. La factoría clona la implementación de PaymentLinkReceiver, la inicializa con el Split y reenvía los fondos.
  • isDeployed(bytes32 linkId) view returns (bool): auxiliar que usan el indexador y la interfaz.
  • Actualizable detrás de un proxy UUPS, propiedad de la multisig de despliegue (Safe 3 de 5), con timelock de 48 horas en upgradeTo.

PaymentLinkReceiver (clon)​

  • Inmutable tras la inicialización. Guarda la struct Split y la dirección de FeeSplitter.
  • pay(address token, uint256 amount): gestiona token nativo y ERC-20 en un único punto de entrada. Usa SafeERC20 para que funcionen tokens no estándar (USDT). Emite Paid(linkId, payer, token, amount).
  • Llama a FeeSplitter.split() de forma atómica. Si el reparto revierte, el pago revierte.
  • refund(bytes32 txId) explícito, invocable solo por la EOA del keeper del comercio o por la multisig de despliegue.

FeeSplitter​

  • split(Split s, address token, uint256 amount): reparte por puntos básicos. El wei restante va al comercio (sin pérdida de polvo).
  • Emite Split(txId, merchant, platform, referral, bridgeFee) para que el indexador mantenga paridad con los payloads de webhook heredados.

Cambios en la API​

El mayor cambio de código está en api/src/payments/wallet.service.ts. Hoy, getOrCreateAddressForPaymentLink deriva desde hd_wallet_seed, calcula la dirección contrafactual de Alchemy y la guarda en wallet_address. Tras la Fase 1, para las cadenas EVM con el feature flag, esa función pasa a ser:

async function getOrCreateAddressForPaymentLink(paymentLinkId, network) {
if (isEvm(network) && featureFlags.useOnChainReceiver(network.chainId)) {
const address = await factory.computeAddress(hashLinkId(paymentLinkId))
return { address, walletAddressId: null, derivationPath: null }
}
// legacy path unchanged
}

Sin derivación HD. Sin material privado en la base de datos para los enlaces nuevos.

Un nuevo servicio, payments/indexer/evm-paid-event.service.ts, se suscribe a los eventos Paid de cada cadena habilitada mediante viem.watchContractEvent. Cuando hay coincidencia:

  1. Obtiene el paymentLinkId del evento (indexado en bytes32).
  2. Inserta o actualiza una fila PaymentTransaction, con la misma forma que hoy.
  3. Emite los mismos webhooks que emite hoy el camino de finalización del barrido (api/src/webhooks/webhooks.service.ts).

El feature flag useOnChainReceiver se define por chain id. Un enlace creado con el flag desactivado se queda para siempre en el camino de barrido heredado; no migramos enlaces en curso.

Obsolescencias​

En esta fase omitimos, pero no eliminamos:

  • SmartAccountWalletService.drainSmartAccountWallet: ya no se llama en las cadenas con el flag.
  • SmartAccountWalletService.getSmartAccountClientForDepositWallet: no hace falta cuando las direcciones vienen de la factoría.
  • Filas de wallet_address: no se escriben en las cadenas EVM con el flag.

La eliminación real ocurre en la Fase 7, cuando todos los enlaces heredados de la cadena estén liquidados.

Plan de migración​

  • Enlaces nuevos en Base: reciben una dirección CREATE2 calculada por la factoría. La interfaz la muestra igual que hoy.
  • Enlaces abiertos existentes en Base: se quedan en el camino de barrido heredado hasta que caduquen o se liquiden. Sin migración forzada.
  • Otras cadenas EVM: en el camino heredado hasta que la Fase 3 active su flag.

Riesgos y mitigaciones​

  • Se muestra una dirección CREATE2 antes de que exista código. Mitigación: documentar que la recuperabilidad depende del desplegador de la factoría. Limitar maxLinkValue en la factoría durante los primeros 30 días.
  • USDT y otros ERC-20 no estándar. Mitigación: SafeERC20 tanto en PaymentLinkReceiver como en FeeSplitter; tests de fuzzing de Foundry contra un fork de mainnet con USDT.
  • Tokens con comisión por transferencia. Mitigación: lista de tokens permitidos; los que no estén en ella vuelven al camino heredado.
  • Retraso del indexador de la cadena. Mitigación: doble suscripción (Alchemy + RPC pública de respaldo); trabajos de conciliación cada hora; inserción/actualización idempotente.
  • Hallazgo de auditoría durante el desarrollo. Mitigación: auditoría como requisito; sin despliegue en mainnet antes del informe con todo corregido.

Lista de estado​

  • Proyecto Foundry creado en contracts/evm/
  • PaymentLinkFactory + PaymentLinkReceiver + FeeSplitter implementados
  • Tests de fuzzing + invariantes para la aritmética del reparto (el comercio siempre recibe ≥ lo esperado menos comisiones)
  • Tests en fork de USDT / ERC-20 no estándar pasando
  • Feature flag useOnChainReceiver conectado en api/src/payments
  • Indexador evm-paid-event.service.ts publicado y emitiendo los webhooks existentes
  • Auditoría iniciada y correcciones integradas
  • Despliegue en Base mainnet desde la multisig
  • Grupo canario de comercios con el flag activado

Qué sigue​

Con los comercios de Base usando receptores en contrato, la Fase 2 hace lo mismo con las suscripciones: Suscripciones por allowance.


Documentación de referencia: Estado de la Fase 1 · Referencia de contratos · Arquitectura