Skip to Content
⚠️ Alert: Sylan is under active development—only trust contract/wallet addresses announced on our official channels; we will never DM you, ask for funds, or run surprise airdrops/presales.
ContractsAPI Escrow

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 share
  • nodePool() → address receiving node share
  • defaultFeeBps → FeeBps
  • apiFeeOverride[apiId] → FeeBps (optional)
  • locks[requestId] → Lock
  • withdrawable[address] → uint256

Flow summaries

Pay‑Per‑Call

  1. Lock — Consumer calls lockForCall(apiId, requestHash, expiresAtMs):

    • Reads price & plan from AccessRegistry; requires plan PayPerCall and active.
    • Checks expiresAtMs is within Registry.maxRequestExpiryMs.
    • Pulls price SYL from msg.sender → escrow.
    • Creates requestId by calling Registry (createRequestFor), stores Lock, and emits Locked.
  2. FinalizeConsensus tallies snapshots and later calls:

    • settleSuccess(requestId) → distributes price according to snapshotted BPS.
    • settleFailure(requestId, reason) → credits full refund to consumer.
  3. Withdraw — Recipients call withdraw() to pull their balances. Emits Withdrawn.

Subscription

  1. Purchase — Consumer calls purchaseSubscription(apiId):

    • Requires plan Subscription, reads price, duration, callLimit.
    • Pulls price SYL from msg.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.)
  2. 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) external
  • function withdraw() external
  • function withdrawableOf(address account) external view returns (uint256)

Consensus‑only

  • function settleSuccess(bytes32 requestId) external
  • function settleFailure(bytes32 requestId, uint8 reason) external

Owner/admin

  • function setDefaultFeeBps(uint16 providerBps, uint16 nodeBps, uint16 platformBps) external
  • function setApiFeeBps(bytes32 apiId, uint16 providerBps, uint16 nodeBps, uint16 platformBps) external
  • function clearApiFeeBps(bytes32 apiId) external
  • function setPlatformTreasury(address) external
  • function setNodePool(address) external
  • function setAccessRegistry(address) external
  • function setApiConsensus(address) external
  • function setNodeRegistry(address) external
  • function pause()/unpause() and UUPS upgradeTo/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_000

On 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/settleFailure are guarded by locks[requestId].settled.
  • Active only: PPC locks require AccessRegistry.isApiActive(apiId) == true and plan PayPerCall.
  • Expiry bound: expiresAtMs must be future and within AccessRegistry.maxRequestExpiryMs.
  • BPS: provider+node+platform must equal 10,000.
  • Safety: nonReentrant on 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/FailedSettled/Refunded.
  • Idempotency: it’s safe to retry settlement calls from an indexer; duplicates do nothing after settled == true.
  • Subscriptions: your UI should read AccessRegistry.subscriptionEndsAt and remainingCalls to display entitlement; Escrow does not hold per‑call funds under subscriptions.

Admin & security notes

  • Keep platformTreasury and nodePool updated; 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
Last updated on