Synsemadocsv0.6.xENES

Operate

Vela (Horizen)

Vela is Horizen's confidential coprocessor: an application runs as a WebAssembly module inside a TEE (an AWS Nitro Enclave in production, an isolated process in the local kit), its state travels encrypted, and every result is settled on-chain as a signed state root. The official toolchain is Go + TinyGo. Since the guests package, the application can be a .syn file, and the side outside the enclave — keys, deploys, encrypted requests, events, reports, meta-transactions — can be Synsema too.

This page is the whole picture. The repository directory is the contract: packages/guests/vela (README, adapter, three example apps, a client, a trigger contract, an ERC-20, two probes). Nothing here is a guess: the ABI was read from Horizen's Go code (vela, vela-common-go, vela-nova, vela-starterkit, v0.2.0) and every flow below ran against the starter kit in Docker.

Start from the kit. synsema/vela-app is a template repository: the app with its tests, the client, scripts/build.sh (your program into the release's guest module: no compiler, a few seconds), scripts/smoke.mjs, scripts/devnet.sh (Horizen's starter kit in Docker) and scripts/e2e.sh (keys → deploy → register → deposit → an encrypted request → your events), plus a CI workflow that builds app.wasm on every push. "Use this template", then the four commands in its README. No Docker? synsema run vela_client.syn -- devnet writes a token of your own on the public devnet (below) into client/.env.

A complete app to fork. synsema/vela-payroll is private payroll on Vela, built on the kit: an employer funds the app with a stablecoin and runs payroll from a CSV; each person sees only their own payslips; the chain sees deposits, withdrawals and one public receipt per run (run number, head count, a hash of the items); people withdraw as pull-payments and need no ETH, because the employer's facilitator submits for them; an allowed auditor gets the plain picture from the enclave. Its client adds fund, payrun <csv>, payslips, withdraw, pending and claim-for, with amounts in tokens converted by text with the token's decimals(); it ships the test ERC-20 with permit that its scripts/devnet.sh deploys and allowlists locally; and its scripts/e2e.sh runs the whole cycle. Verified end to end on the public devnet.

Policy inside, LLM outside. synsema/vela-treasury is an agent treasury: an agent (any process holding a proposer key — a worker on the Synsema platform, a laptop) proposes payments; the enclave applies the owner's policy — who may propose, who may be paid and up to how much, the automatic limit per payment, the allowance the owner granted — and either pays at once through a trigger contract (TreasuryTrigger.sol, one per app, ERC-20 or ETH) or holds the proposal with every reason for the owner's approve / reject; the trigger's answer settles the payment or refunds it. The standalone agent worker (agent.syn) turns the invoices in inbox/ into proposals — JSON as is, free text through an LLM when a provider is configured — and holds no funds: the worst a bug or a prompt injection can do is a proposal the policy stops. The client protocol is a module (client/vela_lib.syn) both the worker and the CLI use. Verified end to end on the public devnet, trigger cycle included.

Bids nobody sees. synsema/vela-auction is a sealed-bid auction: a seller offers a block of one token for another; bidders deposit the payment token and send their bids encrypted to the enclave — nobody, not even the seller, sees a bid before the close; the matching runs inside (uniform price or pay-as-bid, bids ranked by cross-multiplication of (quantity, total) pairs, exact integers, a long division of its own because / goes through a float); winners and losers are settled from their escrow as pull-payments, each bidder learns only its own result and the clearing price, the losing bids are never revealed, and the chain sees opened and cleared receipts with no bidder in them. Three parties, one auction, two sealed bids, claims checked on-chain: scripts/e2e.sh, verified on the public devnet.

Each kit is a web app. The recipe's entry in all four is a console (web.syn, kind = web): the payroll console (fund, onboard people, pay runs, each person's payslips and payouts), the treasury console (deploy with a trigger made through a factory contract on the stack, policy, payees, the agent's proposals, approve or reject, receipts), the auction console (the seller's desk and a desk per bidder), and the starter kit's workbench (deploy your app.syn, register, deposit, any payload encrypted to the Executor, your events decrypted, users through the facilitator, reports). Every action is one request to the enclave; the console's state lives on the project's volume. Deployed from synsema.com, a project's environment is provisioned from the public devnet at creation ([provision] env_url in syn.toml: a token of your own, the addresses, the keys); locally, synsema serve web.syn with the same lines in .env. The consoles custody keys for the accounts they make (a person a portal holds keys for, a demo bidder, a demo agent): the library signs as any key it holds (send_tx_as, submit_as) or submits for it through the facilitator (submit_for_with); an account with a wallet of its own uses the command-line client. All four verified end to end on the public devnet from the browser.

Where it sits: a guest, not a new artifact§

Vela does not load a WASI command (no _start, no argv) and does not provide the synsema_host imports of the embeddable artifact: its Executor instantiates a module once and calls its own exportsallocate, deallocate, load_module, deploy, deposit, process_request, trusted_request — with pointers into the module's memory. So the engine stays as it is, and a thin adapter in packages/guests/vela/ maps those exports onto the generic synsema_call ABI, with your program embedded: one .wasm is exactly one app, and the SHA-256 Vela verifies on-chain covers the interpreter and your logic together.

That is the host axis of the two-axis rule. On the client axis, what Vela needed and was generic — WebCrypto primitives (ecdh_, hkdf_sha256, aes_gcm_), --deterministic, steps() — entered the language under generic names. No company's name is in the engine.

Your program → a module (no compiler)§

The module is the interpreter plus your program. Every Synsema release publishes the interpreter as a Vela guest — synsema-vela-guest.wasm, an asset of the release — with an app slot inside: a fixed block of data with a header. embed.syn (in the kit as scripts/embed.syn, in the repository as packages/guests/vela/tools/embed.syn) finds the slot in the file and overwrites it with your .syn. The result is a module that is exactly your app, and the SHA-256 Vela verifies on-chain covers interpreter and program together. Programs up to 512 KB; the embed takes a second.

synsema test app/app.syn                                   # the app, natively — the same code runs in the enclave
sh scripts/build.sh                                        # downloads the release's guest once, embeds app/app.syn → build/app.wasm (+ .sha256)
synsema run scripts/embed.syn -- synsema-vela-guest.wasm app/app.syn build/app.wasm   # the same, by hand
node scripts/smoke.mjs build/app.wasm                      # optional: Node 20 or 24+ (not 22, see below) — imports, exports, load_module, deploy, determinism

The guest always targets wasm32-wasip1: Vela's linker defines WASI and nothing else, so print becomes the Executor's log (INF …). Vela's upload limit is 50 MB; the released guest carries the example ledger, so it can be deployed as it comes.

The adapter itself — Rust, only if you change the adapter (packages/guests/vela): rustup target add wasm32-wasip1 and cargo build --profile wasm (→ engine/target/wasm32-wasip1/wasm/synsema_vela_guest.wasm; SYNSEMA_VELA_APP=… cargo build fills the slot at build time), cargo test --target <your host triple> for its unit tests, node tests/vela_guest.probe.mjs <the .wasm> and cd tests/wasmtime-go && go run . <the .wasm> for the two probes (Node's WASI, and wasmtime-go v1.0.0 — the Executor's exact runtime).

Run the Node probe on Node 20 or Node 24+, not 22: Node 22.x crashes intermittently inside V8 (a segmentation fault from the concurrent tier-up race) while executing this module — reproduced with 22.23.2 on Linux, one run in three; node --no-wasm-dynamic-tiering … avoids it. The bug is in that host, not in the guest: wasmtime-go v1.0.0, the Executor's runtime, is unaffected, and CI runs both probes.

The contract of your .syn§

The adapter calls one task per Vela entry point; each receives one map and returns one map. Tasks you don't define fall back as noted.

Vela exporttaskctx the task receivesmap it returns
deploy(appId, params)deploy(ctx){app_id, kind: "deploy", params}params is the constructor JSON already decoded (nothing when empty){state, fuel?}state required
load_module(appId)load_module(ctx) → falls back to deploy with params: nothing{app_id, kind: "load_module", params: nothing}{state, fuel?} — the Executor calls this only to warm its module cache after a restart and discards the state; if the fallback deploy fails (it needed params) the adapter answers an empty state with a warning rather than an error, which would leave the app unloadable
deposit(appId, sender, token, value, state)deposit(ctx){app_id, kind: "deposit", sender, token, value, value_hex, state} — addresses 0x + 40 hex, lowercase; value as exact decimal text, value_hex as 0x…; state decoded from JSON (text if it isn't JSON){state?, events?, app_events?, fuel?, error?}
process_request(…, requestType = 1)process(ctx){app_id, kind: "process", request_type: 1, sender, payload, payload_hex, state}payload decoded from JSON, or text; payload_hex the raw bytes as 0x…{state?, events?, app_events?, withdrawals?, fuel?, error?}
process_request(…, requestType = 2) (deanonymization)deanonymize(ctx) → falls back to processsame, kind: "deanonymize", request_type: 2{report, state?, fuel?}report required (Vela refuses a type-2 result without it; on any other type a report is dropped with a warning)
trusted_request(appId, payload, state) (TRUSTPROCESS from a trigger contract)trusted(ctx) → falls back to process{app_id, kind: "trusted", request_type: 4, sender: nothing, payload, payload_hex, state} — the payload is what the trigger's getTrustProcessPayload returned: ABI bytes, in clear; read payload_hex and abi_decode itlike process, and emit no app_events (an app event fires the trigger again; an empty one ends the loop)

Two request types never reach the program: AssociateKey (3, a user registering a P-521 key and seed — the Executor handles it) and a PROCESS with an empty payload (the Executor returns the state untouched without calling the module; a deposit on that request still runs deposit). A deposit and a process from the same on-chain request run back to back, the second on the state the first returned.

Shapes inside the returned map§

program built (deterministic). Omitted (or nothing) on deposit/process: the state stays as it came, byte for byte.

user must have registered a P-521 key** (novaw registeruser, or the client's register): an event for an unregistered address makes the Executor fail the whole request (CodePubKeyNotRegistered, code 9) — don't emit to a recipient you can't vouch for. A missing subtype is 32 zero bytes (for a user who registered a seed the Executor overwrites it with an HMAC of the seed anyway).

to a trigger contract during stateUpdate.

("execute_requested") that lands left-aligned and zero-padded — the starter kit's subtypeToBytes32 convention, what a trigger contract compares against.

<field>_hex ("0x…", exact bytes — what a contract abi.decodes; build them with abi_encode and decode(b, "hex")), <field>_base64, or <field> (a text as-is, a map or list as compact JSON). A Synsema bytes value inside data would be JSON-encoded as base64 text — use data_hex when the bytes matter.

(ETH). Pull-payment: the recipient later calls claim(token, payee) on the ProcessorEndpoint. In a trigger app, to is the trigger contract and the matching app event carries the call.

JSON integer or a 0x… text is accepted too. The adapter renders them as Vela's Uint256 hex.

applied). A runtime error in the program does the same, with the engine's message. Never a trap.

deterministic count of executed statements. Vela charges fuel × EXECUTOR_FUEL_PRICE_PER_UNIT (at least MIN_FEE_PER_REQUEST, 10 wei in the starter kit) against the request's maxFeeValue and fails the request if it doesn't cover it — with steps() a handler is typically a few hundred units and novaw reserves 100 wei by default, so declare a constant like the reference app (5/20/35/50) or tell your users to reserve more.

print inside the program goes to the Executor's log; it is not a data channel. The result travels only through the returned map.

Determinism, by construction§

The program runs under the ceiling stdout: no now(), no random()/token(), no network, no files, no LLM. Vela signs the state root, so the same inputs must produce the same bytes — the ceiling makes that a property of the runtime, not of discipline (the Go reference app stamps time.Now() on every transaction; here that line cannot compile in). Maps keep insertion order and JSON is emitted in that order, so the state bytes are the same across runs and across instances (verified: five processes, identical hashes). Everything pure is available: types, JSON, decimal (exact, 28 digits), bytes_to_int/int_to_bytes (exact 256-bit integers), keccak256, sha256, abi_encode/abi_decode, match, try/recover, and test blocks that run natively.

Two things to know about numbers. A decimal or big integer is written as a bare JSON number, and a bare number above 2⁵³ comes back as a float when the state is decoded on the next call — keep amounts as text in the state (text(n), or the Uint256 hex the payment app uses) and convert on use. And bytes(h, "hex") takes no 0x prefix and needs an even number of digits, while Vela's Uint256 hex strips leading zeros — every app writes the same two helpers (see hex_to_int/int_to_hex in examples/payment_app.syn).

The example apps§

ProgramWhat it showsTests
app.synA private ledger: balances per account and token, transfers, withdrawals with a public ABI receipt (app_events + data_hex + a label subtype), a deanonymization report, a trusted task decoding (address,address,uint256) from payload_hex. The Node probe drives it.8
examples/payment_app.synHorizen's own Private Transfer app (vela-nova's payment_app) in Synsema, wire-compatible with the novaw wallet: the same payload instructions, event bodies (balance in Uint256 hex), allowedTokens deploy params, balances/tx_history reports, keccak invoice receipts — novaw deployapp / registeruser / deposit / privatetransfer / withdraw / getprivatebalance / requestreport drive it unchanged. No timestamps (no clock in the enclave): tx_history filters by address.9
examples/trigger_app.synAn execution pool for the trigger cycle (below): execute locks funds, withdraws to the trigger and emits execute_requested with abi.encode(bytes16,address,uint256,bytes); trusted decodes (bytes16,uint256,uint8), credits the remainder, clears the lock and emits no app event.6

The client: the other side of the enclave, in Synsema§

novaw only speaks the payment app's payload; every other app needs its own client. examples/client/vela_client.syn does everything a client can do, natively, with the builtins that arrived in v0.6.20 — no Go, no TypeScript. Copy .env.example to .env: VELA_RPC_URL, VELA_PROCESSOR, VELA_TEE_AUTHENTICATOR, VELA_AUTHORITY_URL, VELA_SUBGRAPH_URL, VELA_APP_ID, VELA_MAX_FEE (wei), VELA_SECP_KEY (signs transactions, pays), VELA_P521_KEY / VELA_P521_PUB (from keys), VELA_USER_KEY (facilitator commands only).

synsema run vela_client.syn -- …What it does
keysa fresh P-521 pair, printed for .env (ecdh_keypair + reveal)
addressthe signing address (VELA_SECP_KEY)
teethe Executor's P-521 communication key, read from TeeAuthenticator.getPubSecp521r1()
deploy <wasm> [params-json|-] [trigger]multipart upload to <authority>/deploy/upload (multipart_encode), then submitDeployRequest(0, descriptor) or submitDeployRequestWithTrigger(0, descriptor, trigger); applicationId and requestId from the DeployRequestSubmitted log; waits on the subgraph; prints the VELA_APP_ID to set
registerAssociateKey: your P-521 public key ‖ the privacy seed encrypted for the Executor (226 bytes); the seed is secp256k1_sign(keccak256("subtype-key-v1")), exactly like novaw
deposit <amount> [token]ETH (wei) through submitRequest with an empty payload, or an allowlisted ERC-20 after approve(processor, amount)
send '<json>' [wei]PROCESS with the payload encrypted for the Executor; an optional ETH deposit rides on the same request
report '<json>', report-download <id>DEANONYMIZATION (the caller must be an allowed authority), then the report fetched from the authority service — GET /nonce, an EIP-191 signature of chainId(8) ‖ appId(8) ‖ reportId(32) ‖ nonce(32), POST /getreport — and decrypted
events [n], app-events [n]your events, filtered by your 50 HMAC subtypes and decrypted; the app's public events, with the label decoded when the subtype is one
status <requestId>the subgraph's requestCompleteds / deployRequestCompleteds row
user, register-for, send-for '<json>' [amount token], events-for [n]facilitator / meta-transactions (submitRequestFor): the user (VELA_USER_KEY, needs no ETH) signs an EIP-712 RequestAuthorization (domain Vela / version "0" / chainId / the endpoint, nonce = getFacilitatorNonce(user)) and, for an ERC-20 deposit, an EIP-2612 Permit under the token's domain; VELA_SECP_KEY submits and pays gas and fee. Only ASSOCIATEKEY and PROCESS; the deposit can only be an ERC-20, never ETH

Every request waits for RequestCompleted through the subgraph and reports the error code and message when status ≠ 0. The pure parts have tests (synsema test vela_client.syn): the cipher round trip, seed and subtypes, log parsing, calldata, the EIP-712 digests.

The wire formats, so nobody has to re-read Go§

key = HKDF-SHA256(ECDH-P-521 shared X, no salt, no info, 32 bytes) between the user's P-521 key and the Executor's communication key (133 bytes, uncompressed). Reports are encrypted for the requester's registered key. In Synsema: ecdh_shared_secrethkdf_sha256aes_gcm_encrypt.

(133 alone is accepted). The seed is a 65-byte secp256k1 signature; the user's event subtypes are HMAC-SHA256(seed, byte(i)), i = 1..50 — the Executor stamps one on each event, and a client filters the subgraph by that set.

DeployRequestSubmitted and its requestId the event's data.

"wasmSha256": "…", "constructorParams": {…}} after POST /deploy/upload (multipart field wasm`).

msg.value = maxFeeValue and the endpoint pulls the tokens (allowance, or the permit in submitRequestFor).

Trigger contracts: on-chain calls from inside the enclave§

A trigger contract is the only autonomy an app has today: no cron, no oracle, no network. The cycle, from the starter kit's design doc and verified end to end:

1. A user sends process (here {"command": "execute", "execute": {target, value, data}}). The app locks the amount, returns a withdrawal to the trigger contract and one app event whose data_hex is abi.encode(bytes16 lockId, address target, uint256 value, bytes data) with the label subtype execute_requested. 2. During stateUpdate the ProcessorEndpoint claims the withdrawn ETH into the trigger, calls execute(appEventData) — the call runs from the pool's address — then withdraw() sweeps what is left back, and getTrustProcessPayload(...) returns abi.encode(bytes16 lockId, uint256 remain, uint8 outcome). A non-empty payload enqueues a TRUSTPROCESS (priority queue, no sender, no fee). 3. The Executor calls trusted_request: the app decodes payload_hex, credits remain back to the lock's owner, clears the lock, emits an encrypted execution_outcome to the owner — and no app event, which is what ends the loop (the trigger's events.length == 0 guard returns "").

examples/trigger/src/PoolTrigger.sol is the companion contract (it extends Vela's AbstractTrigger; build.sh clones HorizenOfficial/vela v0.2.0 — those contracts are BSL and are not vendored — and deploys it with forge). The app is deployed with {"triggerContract": "0x…"} in the constructor params and the same address in submitDeployRequestWithTrigger (vela_client.syn -- deploy pool.wasm '{"triggerContract":"0x…"}' 0x…). Measured: the target received exactly 0.01 ETH during stateUpdate, and the TRUSTPROCESS came back about 25 s later.

ERC-20 and the facilitator§

Tokens must be in the TokenAllowlist (addAllowedToken, ADMIN role) and, for an app, in its allowedTokens at deploy. examples/erc20/src/TestToken.sol is a minimal ERC-20 with EIP-2612 permit; build.sh deploys and allowlists it. From the client: deploy payment.wasm '{"allowedTokens":["0x…"]}', deposit 1000000 0x… (approve + pull), then transfers with "tokenAddress" in the payload; the events carry tokenAddress and the endpoint's custody holds the tokens.

The facilitator pattern is a client-side flow, not a platform limitation: submitRequestFor lets someone else pay. The user signs typed data only; a user with 0 ETH registered its key (register-for) and deposited 2 TST plus a transfer in one request (send-for … 2000000 0x…), the facilitator paying gas and 85 wei of fees; the user's events decrypt with the user's own seed (events-for). One detail you will hit: OpenZeppelin's ECDSA.recover and permit want v = 27/28, while secp256k1_sign returns the recovery id 0/1 — add 27.

The public devnet§

devnet.synsema.app runs Horizen's starter kit v0.2.0 (Anvil, the Vela contracts, the subgraph, the Executor as an emulated TEE, the Manager, the Authority Service) behind HTTPS, for anyone who wants to deploy a Synsema app on Vela without nine containers on their machine. A token of your own is one command away; the client writes the lines into client/.env:

synsema run vela_client.syn -- devnet            # or: curl -X POST https://devnet.synsema.app/token
VELA_RPC_URL=https://devnet.synsema.app/<your token>/rpc
VELA_AUTHORITY_URL=https://devnet.synsema.app/<your token>/authority
VELA_SUBGRAPH_URL=https://devnet.synsema.app/<your token>/subgraph/subgraphs/name/hcce
VELA_PROCESSOR=0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9
VELA_TEE_AUTHENTICATOR=0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0
VELA_TOKEN_ALLOWLIST=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
VELA_DEFAULT_AUTHORITY=0x5FbDB2315678afecb367f032d93F642f64180aa3
VELA_TEST_TOKEN=0x610178dA211FEF7D417bC0e6FeD39F05609AD788
VELA_TRIGGER_FACTORY=0xC9a43158891282A2B1475592D5719c001986Aaec
VELA_TOKEN=0x610178dA211FEF7D417bC0e6FeD39F05609AD788
VELA_SECP_KEY=…   VELA_P521_KEY=…   VELA_P521_PUB=…   (Anvil #0 and a fresh P-521 pair: everything a client needs)

It is a devnet: the keys are Anvil's public ones, there is no attestation, it is reset from time to time (the contract addresses come back the same; app ids, balances and registered keys do not), and nothing you put there is private from whoever runs the machine — a place to iterate, not to keep value. The admin is Anvil account #0 (its private key is the well-known ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80, the kit's default VELA_SECP_KEY), so everything an admin does is yours to do: deploying (DEPLOYER_ROLE), allowlisting an ERC-20 (vela_client.syn -- allow-token <address>), allowing an auditor for your app (vela_client.syn -- allow-authority <appId> <address>). A test ERC-20 with permit (TST, 6 decimals, the whole supply in Anvil #0) is already allowlisted: VELA_TEST_TOKEN. https://devnet.synsema.app/status shows the block number and usage. The token is a name in the path that keeps scanners out and lets usage be counted per user, not a secret worth anything. Ten tokens an hour per address, except for the Synsema platform's control, which asks for one per project it creates from a recipe. The kit's ProcessorEndpoint deploys with room for ten applications; the devnet raises that to 100 000 (devnet/admin.syn in the platform's repository), so a MaxNumOfApplicationsExceeded revert means a stack at its default, not a bug in your deploy.

The local stack (starter kit v0.2.0) — a recipe that works§

compose up -d in dockerfiles/ (nine images, about 1.5 GB). The chain is published on localhost:8545, the authority service on :8081, the subgraph on :8000; the internal network is dockerfiles_pes_network. The deployer writes the addresses to the deploy-data volume: ProcessorEndpoint 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9, TeeAuthenticator 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0, TokenAllowlist 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9, DefaultAuthority 0x5FbDB2315678afecb367f032d93F642f64180aa3`. Anvil account #0 is deployer and admin.

DefaultAuthority.addAllowedAuthority(appId, caller) is called by the admin (cast is inside the vela-skit-chain container). Multi-app works: each deploy gets its own applicationId.

mount — copy it into a container's filesystem first: docker run --rm --network dockerfiles_pes_network --entrypoint sh -v <wallet dir>:/wallet -w /wallet postgres:14 -c 'cp /wallet/novaw-linux /usr/local/bin/novaw && chmod +x /usr/local/bin/novaw && novaw <cmd>', with MSYS_NO_PATHCONV=1 in Git Bash and a wallet.conf pointing at 10.10.40.30:8545, 10.10.40.40:8081, vela-skit-subgraph-node:8000.

container on Docker's default network with RPC_URL=http://host.docker.internal:8545 (the running chain container has no DNS for the solc and GitHub downloads).

25 s. Executor limits: wasmtime-go v1.0.0, one request per block, a 30 s communication timeout, no fuel metering (fuel is self-declared), memory under 2 GB.

Gotchas met while writing guests and clients§

(write 171). index_of takes a list, not text. Maps are shared by reference: a task that sets inside the state it received mutates the caller's map — it matters in tests that reuse a state.

from .env is as_secret(bytes(env("K"), "hex"), "K") (a text secret() is the hex characters, not the key). hmac_sha256(data, key) stringifies a bytes value — wrap both in as_secret. The private key from ecdh_keypair is labelled ecdh_keypair.private (require reveal("ecdh_keypair.private") to print it once).

has status, ok, body, json, headers. multipart_encode(parts){body, content_type}. require file.read("*") for a path given on the command line; args() needs no capability; sleep needs time; secp256k1_sign needs sign("<the secret's label>").

Adding another host§

Copy packages/guests/vela/, rename the crate, and read the host's contract from its source: which exports it calls, their signatures, how it writes inputs and reads results, which imports it provides, what it forbids. Write that at the top of src/lib.rs with the file and version it came from. Keep the shape — decode inputs → one ctx map → run_app(task, fallback, ctx) → encode the task's map into the host's exact result — pick wasm32-wasip1 when the host links WASI and wasm32-unknown-unknown only if it provides the synsema_host imports, treat the ceiling as the determinism contract, write the probe, wire CI, and document the .syn contract in the guest's README. If the generic ABI lacks something, add a documented synsema_call operation useful to every host — never a special export for one of them.