AccessRegistry
Source of truth for API listings: descriptors, plans (PPC/Subscription), per‑API consensus/freshness parameters, and provider signer. It also manages request IDs, a simple subscription ledger, and emits events that UIs/indexers can follow.
Interact with the proxy address published under Architecture → Addresses & ABIs. This contract is UUPS‑upgradeable, ownable, and pausable.
Responsibilities
- Register APIs (
apiId) with providerOwner, providerSigner, plan, and timing caps. - Store descriptor integrity anchors (
uri,contentHash,version,updatedAt). - Expose plan fields:
accessType,price,duration,callLimit,active. - Expose per‑API consistency/freshness params:
seqMonotonic,maxSkewMs,maxTtlMs, and the effectiveproviderSigner. - Guard request creation with expiry windows and per‑consumer nonces (derives
requestId). - Maintain a subscription ledger (
subscriptionEndsAt,remainingCalls).
Data structures
enum AccessType { Subscription, PayPerCall }
struct APIPlan {
AccessType accessType; // 0=Subscription, 1=PayPerCall
uint256 price; // in SYL (18d)
uint256 duration; // seconds for subscriptions; 0 for PPC
uint256 callLimit; // max calls per period (0 = unlimited)
bool active; // listing on/off
}
struct Descriptor {
string uri; // e.g. ipfs://CID
bytes32 contentHash; // keccak256(canonical descriptor)
uint64 updatedAt; // block.timestamp (s)
uint32 version; // 1,2,3...
}
struct ApiMeta {
address providerOwner; // who can update this API's settings
address providerSigner;// EIP‑712 signer for Snapshots
bool seqMonotonic; // enforce non‑decreasing seqNo at finalize
uint64 maxSkewMs; // allowed future skew for providerTs
uint64 maxTtlMs; // cap applied to Snapshot.ttl (0 = no cap)
bool active; // listing active flag
}
struct ConsensusParams { uint8 placeholder; } // reservedGlobal state
paymentToken()→ SYL address (discoverability)apiEscrow()/apiConsensus()/nodeRegistry()→ wired contract addressesmaxRequestExpiryMs(owner‑set; default 60_000 ms; max 10 minutes)enforceSignerTimelock(owner toggle) andsignerUpdateUnlockAt[apiId]
Views (read model)
- API registry
apiMeta(apiId) → ApiMetadescriptorOf(apiId) → DescriptorapiPlan(apiId) → APIPlanconsensusParams(apiId) → ConsensusParams
- Per‑API convenience
providerSignerOf(apiId) → address- If signer timelock is active and not yet elapsed, returns address(0) (snapshots should be rejected during the window).
seqMonotonic(apiId) → boolmaxSkewMs(apiId) → uint64maxTtlMs(apiId) → uint64isApiActive(apiId) → bool
- Requests & subscriptions
consumerNonce(consumer, apiId) → uint256(increments on request creation)subscriptionEndsAt(consumer, apiId) → uint64(unix seconds)hasActiveSubscription(consumer, apiId) → boolremainingCalls(consumer, apiId) → uint256
Writes (by role)
Owner (contract admin)
setApiEscrow(address)/setApiConsensus(address)/setNodeRegistry(address)setMaxRequestExpiryMs(uint64)(cap ≤ 10 minutes)setSignerTimelock(bool)(enforce signer rotation delay)pause()/unpause()
Provider owner (per‑API admin)
- Register
registerApi(apiId, providerOwner, providerSigner, seqMonotonic, maxSkewMs, maxTtlMs, plan)registerApiAndDescriptor(..., plan, descriptorUri, descriptorHash)(helper)- Emits
ApiRegistered
- Descriptor
setDescriptor(apiId, uri, contentHash)→ bumpsversion, setsupdatedAt- Emits
DescriptorSet
- Signer
setProviderSigner(apiId, newSigner)→ if timelock enforced and both old/new non‑zero, setssignerUpdateUnlockAt- Emits
ProviderSignerUpdated
- Plan & timing
setPlan(apiId, APIPlan)(validates PPCduration==0, Subduration>0,price>0)setTimingCaps(apiId, maxSkewMs, maxTtlMs)- Emits
PlanUpdated,TimingCapsUpdated
- Activation
setApiActive(apiId, bool)→ EmitsApiActiveSet
Escrow (protocol hook)
recordSubscription(consumer, apiId, startTs, endTs, amountPaid)- Extends
subscriptionEndsAtmonotonically; ifcallLimit>0, resetsremainingCalls. - Emits
SubscriptionRecorded.
- Extends
Requests (consumers / escrow)
createRequest(apiId, requestHash, expiresAtMs)— uses msg.sender asconsumer.createRequestFor(consumer, apiId, requestHash, expiresAtMs)— for Escrow to mirror PPC locks.
Request rules
expiresAtMsmust be > now and withinmaxRequestExpiryMs.- If plan is Subscription: require
hasActiveSubscription(consumer, apiId)and, whencallLimit>0, decrementremainingCalls. - Nonce increments per
(consumer, apiId); see Request ID derivation. - Emits
RequestCreated.
Request ID derivation
Deterministic and collision‑resistant:
requestId = keccak256(
abi.encodePacked(
bytes1(0x01), // domain tag
address(this),
block.chainid,
apiId,
consumer,
nonce // ++consumerNonce[consumer][apiId]
)
);UIs/indexers should join events by requestId across AccessRegistry, APIConsensus, and APIEscrow.
Events
ApiRegistered(bytes32 apiId, address providerOwner, address providerSigner)DescriptorSet(bytes32 apiId, string uri, bytes32 contentHash, uint32 version)ProviderSignerUpdated(bytes32 apiId, address oldSigner, address newSigner)ApiActiveSet(bytes32 apiId, bool active)TimingCapsUpdated(bytes32 apiId, uint64 maxSkewMs, uint64 maxTtlMs)PlanUpdated(bytes32 apiId, AccessType accessType, uint256 price, uint256 duration, uint256 callLimit, bool active)SubscriptionRecorded(bytes32 apiId, address consumer, uint64 startTs, uint64 endTs, uint256 amountPaid)RequestCreated(bytes32 requestId, bytes32 apiId, address consumer, bytes32 requestHash, uint64 expiresAtMs, uint256 nonce)
Some builds also declare
SubscriptionRequestfor UI analytics; it’s ancillary to the core flow.
Minimal ABI (client‑facing reads)
[
{"type":"function","stateMutability":"view","name":"descriptorOf","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"tuple","components":[{"name":"uri","type":"string"},{"name":"contentHash","type":"bytes32"},{"name":"updatedAt","type":"uint64"},{"name":"version","type":"uint32"}]}]},
{"type":"function","stateMutability":"view","name":"apiPlan","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"tuple","components":[{"name":"accessType","type":"uint8"},{"name":"price","type":"uint256"},{"name":"duration","type":"uint256"},{"name":"callLimit","type":"uint256"},{"name":"active","type":"bool"}]}]},
{"type":"function","stateMutability":"view","name":"providerSignerOf","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"address"}]},
{"type":"function","stateMutability":"view","name":"seqMonotonic","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"bool"}]},
{"type":"function","stateMutability":"view","name":"maxSkewMs","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"uint64"}]},
{"type":"function","stateMutability":"view","name":"maxTtlMs","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"uint64"}]},
{"type":"function","stateMutability":"view","name":"isApiActive","inputs":[{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"bool"}]},
{"type":"function","stateMutability":"view","name":"consumerNonce","inputs":[{"name":"consumer","type":"address"},{"name":"apiId","type":"bytes32"}],"outputs":[{"type":"uint256"}]}
]Integration tips
- Signer rotation: while
signerUpdateUnlockAt[apiId]is in the future,providerSignerOf(apiId)returns 0x0. Nodes should treat snapshots as unauthorized until the window elapses. - Expiry windows: clients must set
expiresAtMswithinmaxRequestExpiryMs; the default is 60s, owner‑configurable up to 10 minutes. - Subscriptions: after
recordSubscription,remainingCallsresets tocallLimit(if > 0). UIs can callcreateRequest(...)for per‑call analytics under subscriptions—no additional funds move.
Admin & security notes
- Provider mutations are gated by providerOwner; rotate with care. Keep
providerSignerin HSM/HW wallet. - Owner can pause the registry; UIs should disable writes while paused.
- Only Escrow should call
recordSubscription/createRequestFor(enforced by modifiers).
Out of scope for this page
- Pricing/escrow semantics → APIEscrow
- Vote/settlement logic → APIConsensus
- Node eligibility/slashing → NodeRegistry
- Addresses & minimal ABIs → Architecture → Addresses & ABIs
Last updated on