Saltar al contenido principal

Referencia de contratos

Esta página es la referencia de ingeniería de las piezas on-chain de la pila no custodial. Las direcciones y los informes de auditoría se publican abajo por cadena a medida que se lanzan las fases.

Convenciones de direcciones (EVM)​

Los cuatro contratos EVM comparten el mismo desplegador (un desplegador CREATE2 determinista, por ejemplo 0x4e59b44847b379578588920cA78FbF26c0B4956C) y el mismo hash del init code por versión, así que las direcciones de la factory y del hub son las mismas en todas las cadenas EVM. Eso simplifica el código cliente y la documentación.

CadenachainIdPaymentLinkFactorySubscriptionHubFeeSplitter
Base8453se publica al desplegarse publica al desplegarse publica al desplegar
Ethereum1igualigualigual
Arbitrum One42161igualigualigual
Optimism10igualigualigual
Polygon PoS137igualigualigual

Las direcciones de PaymentLinkReceiver son por enlace: factory.computeAddress(keccak256(linkId)).

EVM: PaymentLinkFactory​

Funciones​

  • computeAddress(bytes32 linkId) external view returns (address): dirección CREATE2 determinista. Se puede llamar antes de cualquier despliegue.
  • deployAndPay(bytes32 linkId, Split calldata split, address token, uint256 amount) external payable: despliega el clon en el primer pago y reenvía los fondos.
  • isDeployed(bytes32 linkId) external view returns (bool).
  • setSplit(bytes32 linkId, Split calldata split) external: solo se puede llamar antes del primer pago; fija el reparto inmutable con el que se inicializará el clon.
  • pause() / unpause(): solo el multisig.

Eventos​

  • ReceiverDeployed(bytes32 indexed linkId, address receiver)
  • SplitRegistered(bytes32 indexed linkId, Split split)

Estructura Split​

struct Split {
address merchant;
address platform;
address referral;
address bridgeFee;
uint16 platformBps;
uint16 referralBps;
uint16 bridgeFeeBps;
}

La suma de los campos en bps debe ser < 10000. El resto va al comercio.

EVM: PaymentLinkReceiver (clon mínimo)​

Funciones​

  • pay(address token, uint256 amount) external payable: punto de entrada principal. token == address(0) significa moneda nativa.
  • refund(bytes32 txId, address to, uint256 amount) external: restringida a la EOA keeper o al multisig de despliegue.
  • split() external view returns (Split memory).

Eventos​

  • Paid(bytes32 indexed linkId, address indexed payer, address indexed token, uint256 amount, bytes32 txId)
  • Refunded(bytes32 indexed linkId, bytes32 indexed txId, address to, uint256 amount)

Reverts​

  • InvalidToken, InvalidAmount, SplitFailed, AlreadyRefunded.

EVM: FeeSplitter​

Funciones​

  • split(Split calldata s, address token, uint256 amount) external payable: llamada interna desde PaymentLinkReceiver y SubscriptionHub. Normalmente no se llama directamente.

Eventos​

  • SplitExecuted(bytes32 indexed txId, address merchant, uint256 merchantAmount, address platform, uint256 platformAmount, address referral, uint256 referralAmount, address bridgeFee, uint256 bridgeFeeAmount)

EVM: SubscriptionHub​

Funciones​

  • createSubscription(bytes32 id, address payer, IERC20 token, uint256 amount, uint64 interval, uint256 cap, Split calldata split) external: registra la suscripción on-chain.
  • charge(bytes32 id) external: pública y sin permisos; aplica plazos, tope, allowance y saldo.
  • charge(bytes32[] ids) external: versión por lotes; cada id se valida de forma independiente.
  • cancel(bytes32 id) external: la puede llamar el pagador.
  • subscription(bytes32 id) external view returns (Subscription memory).
  • isDue(bytes32 id) external view returns (bool): auxiliar para los keepers.

Eventos​

  • SubscriptionCreated(bytes32 indexed id, address indexed payer, IERC20 token, uint256 amount, uint64 interval, uint256 cap)
  • Charged(bytes32 indexed id, uint256 amount, uint64 nextChargeAt)
  • ChargeSkipped(bytes32 indexed id, bytes32 reason): motivos: NotDue, InsufficientAllowance, InsufficientBalance, AlreadyChargedThisPeriod, CapExceeded, Paused.
  • Canceled(bytes32 indexed id)

Invariantes que aplica charge​

  • block.timestamp >= subscription.nextChargeAt
  • block.timestamp > subscription.lastChargedAt (monótono)
  • subscription.amountCharged + subscription.amount <= subscription.cap
  • token.allowance(subscription.payer, address(this)) >= subscription.amount
  • token.balanceOf(subscription.payer) >= subscription.amount

Flujo de ejemplo del pagador​

// 1. Payer signs an allowance to the hub
await usdc.approve(subscriptionHub.address, cap)

// 2. Merchant (via OrcaRail) creates the subscription on-chain
await subscriptionHub.createSubscription(
id,
payer,
usdc,
amount,
interval,
cap,
split
)

// 3. Anyone calls charge when due
await subscriptionHub.charge(id)

Cuentas​

#[account]
pub struct PaymentLink {
pub link_id: [u8; 32],
pub expected_mint: Pubkey,
pub expected_amount: u64,
pub split: Split,
pub created_at: i64,
pub status: PaymentStatus,
}

Semillas de la PDA: [b"pl", link_id].

Instrucciones​

  • initialize(link_id, expected_mint, expected_amount, split): inicialización diferida en el primer uso.
  • pay(link_id, amount): transfiere por CPI desde la ATA del pagador a la ATA de la PDA y después reparte por CPI.
  • refund(link_id): restringida al multisig.

Eventos​

  • PaymentReceived { link_id, payer, amount }
  • PaymentRefunded { link_id, to, amount }

Solana: programa subscription​

Cuentas​

#[account]
pub struct Subscription {
pub id: [u8; 32],
pub payer: Pubkey,
pub mint: Pubkey,
pub amount: u64,
pub interval: i64,
pub cap: u64,
pub amount_charged: u64,
pub next_charge_at: i64,
pub last_charged_at: i64,
pub split: Split,
}

Instrucciones​

  • create(id, payer, mint, amount, interval, cap, split): registra la suscripción.
  • charge(id): pública; valida el reloj, el tope y la autoridad de delegado, y después hace transfer_checked por CPI como delegado.
  • cancel(id): solo el pagador.

Eventos (mediante logs del programa)​

  • SubscriptionCreated, Charged { amount, next_charge_at }, ChargeSkipped { reason }, Canceled.

Flujo de ejemplo del pagador​

// 1. Payer approves program PDA as delegate on their USDC ATA
await token.approve(payerUsdcAta, programPda, capAmount)

// 2. create() registers the subscription PDA
// 3. charge() can be called by anyone once due

Plantilla de descriptor​

wsh(multi(2, <merchant_pubkey>, <platform_pubkey>))

Equivalente en taproot (v2, cuando madure el tooling de MuSig2):

tr(<NUMS>, multi_a(2, <merchant_pubkey>, <platform_pubkey>))

Derivación​

  • Clave del comercio: derivada de la xpub registrada por el comercio en el alta, un índice por enlace de pago.
  • Clave de la plataforma: derivada de la xpub del HSM de la plataforma, un índice por enlace de pago.

PSBT de reembolso​

En el momento del depósito se monta una PSBT sin firmar que devuelve al pagador el depósito menos la comisión. Ambos firmantes la firman de inmediato; el resultado se guarda cifrado y se puede difundir en cualquier momento sin más firmas.

Informes de auditoría​

Se publican aquí a medida que se completan. Cada entrada incluye: proveedor, alcance, commit, fecha, enlace al informe y commit de corrección.

(Los informes llegan a medida que se lanzan las fases.)

Consulta también​