APIEscrow
Escrow for Pay‑Per‑Call (PPC) requests and Subscription purchases. Pulls SYL from consumers, snapshots fee BPS, coordinates with AccessRegistry/APIConsensus, and pays out via pull‑payments (withdraw).
This contract is UUPS‑upgradeable, ownable, and pausable. It uses SafeERC20 and ReentrancyGuard. Interact with the proxy address listed under Architecture → Addresses & ABIs.
Responsibilities
- PPC: lock funds for a single request (
lockForCall), emit events, and later settle (success) or refund (failure). - Subscription: take payment once (
purchaseSubscription), snapshot fee BPS, and instruct AccessRegistry to record the active window. - Maintain withdrawable balances for recipients (Provider, Node Pool, Platform, and any refunds).
- Enforce fee splits using basis points (
providerBps + nodeBps + platformBps = 10_000). - Wire up AccessRegistry, APIConsensus, NodeRegistry/nodePool, and platformTreasury addresses.
Data structures (conceptual)
struct FeeBps {
uint16 providerBps; // must sum to 10_000 with the others
uint16 nodeBps;
uint16 platformBps;
}
struct Lock {
address consumer;
bytes32 apiId;
uint256 price; // SYL amount locked for PPC
uint64 expiresAtMs; // request deadline in ms
bool settled; // idempotency guard
}State (selected)
syl()→ ERC‑20 token (SYL)accessRegistry()/apiConsensus()/nodeRegistry()platformTreasury()→ address receiving platform sharenodePool()→ address receiving node sharedefaultFeeBps → FeeBpsapiFeeOverride[apiId] → FeeBps (optional)locks[requestId] → Lockwithdrawable[address] → uint256
Flow summaries
Pay‑Per‑Call
-
Lock — Consumer calls
lockForCall(apiId, requestHash, expiresAtMs):- Reads price & plan from AccessRegistry; requires plan PayPerCall and active.
- Checks
expiresAtMsis within Registry.maxRequestExpiryMs. - Pulls
priceSYL frommsg.sender→ escrow. - Creates requestId by calling Registry (
createRequestFor), storesLock, and emitsLocked.
-
Finalize — Consensus tallies snapshots and later calls:
settleSuccess(requestId)→ distributespriceaccording to snapshotted BPS.settleFailure(requestId, reason)→ credits full refund toconsumer.
-
Withdraw — Recipients call
withdraw()to pull their balances. EmitsWithdrawn.
Subscription
-
Purchase — Consumer calls
purchaseSubscription(apiId):- Requires plan Subscription, reads
price,duration,callLimit. - Pulls
priceSYL frommsg.sender. - Snapshots BPS and immediately credits Provider/NodePool/Platform balances.
- Instructs Registry to
recordSubscription(consumer, apiId, startTs, endTs, amountPaid). - (Event surface for UI comes primarily from Registry’s
SubscriptionRecorded.)
- Requires plan Subscription, reads
-
Usage — Per‑call does not lock funds; clients may call
AccessRegistry.createRequest(...)for analytics.
Public interface (selected)
User‑facing
function lockForCall(bytes32 apiId, bytes32 requestHash, uint64 expiresAtMs) external returns (bytes32 requestId)function purchaseSubscription(bytes32 apiId) externalfunction withdraw() externalfunction withdrawableOf(address account) external view returns (uint256)
Consensus‑only
function settleSuccess(bytes32 requestId) externalfunction settleFailure(bytes32 requestId, uint8 reason) external
Owner/admin
function setDefaultFeeBps(uint16 providerBps, uint16 nodeBps, uint16 platformBps) externalfunction setApiFeeBps(bytes32 apiId, uint16 providerBps, uint16 nodeBps, uint16 platformBps) externalfunction clearApiFeeBps(bytes32 apiId) externalfunction setPlatformTreasury(address) externalfunction setNodePool(address) externalfunction setAccessRegistry(address) externalfunction setApiConsensus(address) externalfunction setNodeRegistry(address) externalfunction pause()/unpause()and UUPSupgradeTo/upgradeToAndCall(...)
BPS sum check: every setter validates
providerBps + nodeBps + platformBps == 10_000.
Settlement math (PPC)
Let price be the locked amount and bps = {provider, node, platform} snapshotted at lock time.
platformShare = price * platformBps / 10_000
nodeShare = price * nodeBps / 10_000
providerShare = price * providerBps / 10_000On success: credit shares to platformTreasury, nodePool, and the provider payout (derived via AccessRegistry.providerOwner or a per‑API payout field). On failure: credit price back to the consumer.
All transfers are pull‑payments via withdraw(); no external calls during settlement.
Events
Locked(bytes32 indexed requestId, bytes32 indexed apiId, address indexed consumer, uint256 price, uint64 expiresAtMs)Settled(bytes32 indexed requestId, bytes32 indexed apiId, bool success, uint256 providerShare, uint256 nodeShare, uint256 platformShare)Refunded(bytes32 indexed requestId, bytes32 indexed apiId, uint8 reason, uint256 amount)Withdrawn(address indexed account, uint256 amount)- (Admin)
FeeBpsSet(bytes32 indexed apiIdOrZero, uint16 providerBps, uint16 nodeBps, uint16 platformBps) - (Admin)
PlatformTreasurySet(address)/NodePoolSet(address)
For request birth and consensus progress events, see Architecture → Events.
Guards & invariants
- Idempotent:
settleSuccess/settleFailureare guarded bylocks[requestId].settled. - Active only: PPC locks require
AccessRegistry.isApiActive(apiId) == trueand planPayPerCall. - Expiry bound:
expiresAtMsmust be future and withinAccessRegistry.maxRequestExpiryMs. - BPS: provider+node+platform must equal 10,000.
- Safety:
nonReentranton settlement/withdraw; SafeERC20 for SYL moves.
Minimal ABI (client)
These are the methods most UIs need. Full admin surface lives on this page for reference.
[
{"type":"function","stateMutability":"nonpayable","name":"lockForCall","inputs":[{"name":"apiId","type":"bytes32"},{"name":"requestHash","type":"bytes32"},{"name":"expiresAtMs","type":"uint64"}],"outputs":[{"type":"bytes32"}]},
{"type":"function","stateMutability":"nonpayable","name":"purchaseSubscription","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[]},
{"type":"function","stateMutability":"view","name":"withdrawableOf","inputs":[{"name":"account","type":"address"}],"outputs":[{"type":"uint256"}]},
{"type":"function","stateMutability":"nonpayable","name":"withdraw","inputs":[],"outputs":[]}
]Integration tips
- Approve first: users must
approve(API_ESCROW, price)on SYL before lock/purchase (or use permit on SYL where supported). - Event‑driven UI: drive PPC progress with
RequestCreated(Registry) →Locked(Escrow) →RequestRegistered/ResponseSubmitted(Consensus) →RequestFinalized/Failed→Settled/Refunded. - Idempotency: it’s safe to retry settlement calls from an indexer; duplicates do nothing after
settled == true. - Subscriptions: your UI should read
AccessRegistry.subscriptionEndsAtandremainingCallsto display entitlement; Escrow does not hold per‑call funds under subscriptions.
Admin & security notes
- Keep
platformTreasuryandnodePoolupdated; both are pull‑payment recipients. - Prefer multisig owners for upgrades & parameter changes; surface paused state in UI.
- Fee changes and plan changes only affect future locks/purchases; existing PPC requests keep their original snapshots.
Out of scope for this page
- Vote tally & finalization → APIConsensus
- Registry data & request IDs → AccessRegistry
- Node slashing/rewards semantics → NodeRegistry
- Addresses & minimal ABIs → Architecture → Addresses & ABIs