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.
| Cadena | chainId | PaymentLinkFactory | SubscriptionHub | FeeSplitter |
|---|---|---|---|---|
| Base | 8453 | se publica al desplegar | se publica al desplegar | se publica al desplegar |
| Ethereum | 1 | igual | igual | igual |
| Arbitrum One | 42161 | igual | igual | igual |
| Optimism | 10 | igual | igual | igual |
| Polygon PoS | 137 | igual | igual | igual |
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 desdePaymentLinkReceiverySubscriptionHub. 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.nextChargeAtblock.timestamp > subscription.lastChargedAt(monótono)subscription.amountCharged + subscription.amount <= subscription.captoken.allowance(subscription.payer, address(this)) >= subscription.amounttoken.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)
Solana: programa payment_link
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 hacetransfer_checkedpor 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
Bitcoin: multisig P2WSH por enlace
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.)