Documentation
Guides and reference for OutLayer verifiable compute and agent custody.
Docs navigation
Building a Connector
For what a connector costs to call — operation prices, the author's share, subscriptions, trials and the per-wallet quota — see Connectors & Subscriptions. This page is about publishing one.
What makes a project a connector#
A connector is an ordinary project that was curated and priced. There is no separate runtime, no special deployment and no second API. You write a WASI module and publish it like any project; two structural facts make it a connector:
- it is published under the curated namespace —
connectors.outlayer.nearon mainnet,connectors.outlayer.testneton testnet; and - its wasm carries a manifest declaring a
connector_id.
Membership is a comparison against the owner account of the project id, so “is this a connector” is a fact about where it lives rather than something a project claims about itself.
Why the category exists
An ordinary project runs your code for you. A connector runs your code for somebody else's agent, inside a TEE that holds custody keys, and lets it reach the internet. Three constraints follow, and they are the whole difference:
| ordinary project | connector | |
|---|---|---|
| outbound network | open | only the hosts your manifest declares |
| operation naming | your business | a required top-level operation |
| pricing | per call | per operation, on chain |
It is not a plugin or anything running inside another program: your module is a normal WASI guest with a normal entry point, and it cannot see other calls, other agents' secrets, or the wallet's keys.
The shape of a call#
Every connector call names its operation in one place:
{ "operation": "send", "to": "[email protected]", "subject": "hello" }Over HTTPS that object is the input of POST /call/{owner}/{project}; on chain it is input_data in request_execution. The same bytes either way, and four readers take the operation out of them: the contract prices the call, the coordinator bills it and picks which limit applies, the worker refuses a connector call that names none, and your guest dispatches on it.
Fail-closed, before your code runs. Absent, blank, not a string, nested, or spelled op — all refused. None of them defaults, because a defaulted operation is a defaulted price and the cheapest one is what an attacker would pick. An operation with no on-chain price is refused too: unpriced is not free.
Two format constraints: a priced project's request must be a JSON object, and on chain it must be at most 10 KB — the contract parses it and the caller's gas pays for that. A connector that moves more than that takes a reference, not the bytes.
Answering
Return JSON on stdout:
{ "success": true, "output": { }, "logs": [], "error": null }The field is error, not error_message.
Network: you declare it, the worker enforces it#
A connector reaches only the hosts listed in capabilities.network in its manifest. Exact hostnames, case-insensitive, no implicit subdomain wildcard: example.com does not permit evil.example.com.
The manifest lives in a wasm custom section, so it is covered by the SHA256 the contract records for that version. Nobody — not you after publishing, not the operator — can widen it without publishing a new version that users have to move to.
A connector with no manifest section reaches nothing. That is the fail-closed direction, and build.sh in connector-probe fails the build when the section is missing, so you find out at your desk rather than in production.
Every outbound attempt is reported to the coordinator with whether the allowlist permitted it. The coordinator stores that trail and decides nothing — enforcement happens inside the worker, where the keys are.
Three secrets, three owners#
A connector reads secrets exactly as any other project does. What trips connectors up is that three credentials belonging to different people meet in one run.
Yours (the author's). Your SMTP password or upstream API key — the same for every caller. Store it under your own account with store_secrets for the project you publish, and name the profile in the manifest embedded in the wasm; the worker decrypts it into every run, and the call carries nothing:
{ "connector_id": "<id>", "author_secrets": { "profile": "prod" } }Its access condition is judged against the real caller, so it is also who may run your connector: AllowAll for everyone, a whitelist or DAO role for a circle. Not a connector feature: any project declares one the same way — see The author's secret.
The caller's. A row the call names with secrets_ref, gated by the condition its owner stored. An owner hands a credential to their agents by storing it once under their own account and whitelisting the agents' wallet accounts (optionally until a date); each agent names it:
{
"input": { "operation": "send" },
"secrets_ref": { "account_id": "owner.near", "profile": "gmail" }
}The agent's. A credential stored FOR a custody wallet under the wallet's account and fetched only when the call asks for it with x-use-owner-secret: true and names nothing else. You never hold it — see Secrets left FOR an agent.
What can refuse a call to you#
Four independent mechanisms, ANDed — every applicable one must pass, and none of them can raise another.
- Price (the contract). Per operation, with the author's share and the account it pays to.
request_executionrefuses a call that does not attach the operation's exact price. You do not set this in your manifest — a manifest may state a recommended price; the on-chain one is what is charged. - Operation limits (the coordinator).
(operation, window, max, who it applies to). Windows areday/week/month, rolling from first use and not calendar-aligned — a calendar month resets for everybody at midnight on the 1st, which turns a monthly cap into a stampede. - The per-wallet connector quota, which grows with the wallet's age.
- The caller's own money — balance, allowance, or per-call cap.
Operation names in the coordinator's own rules carry the connector id (near-email:send, or a whole-segment wildcard near-email:* — no general globbing). In your manifest you write them without it.
Before you ship#
- The manifest section is present in the built wasm, and declares every host you reach.
- Every operation you dispatch on has a price on chain — an unpriced one can never run.
- Your answer uses
error, noterror_message. - On-chain input stays under 10 KB.
- Nothing in your output echoes a secret — yours or the caller's.
wasi-examples/connector-probe in the near-offshore repo is a working connector kept deliberately boring: it does nothing useful so that everything around a connector can be tested against it.