Capabilities & permissions
A function can do nothing beyond pure computation unless its deployment declares the capability — and for anything sensitive, the caller must additionally grant it. Both checks happen server-side in the host broker, against server-held state, on every single call.
The 8 capabilities
| capability | unlocks | purpose | needs caller grant |
|---|---|---|---|
AI_INFERENCE | animica.ai.infer / animica.ai.chat | AI inference via the miner network, metered per token | no |
CALL_FUNCTION | animica.call("owner/slug", …) | nested execution of another published function | yes |
CALL_APP | animica.call into an app’s functions | like CALL_FUNCTION, scoped to app targets | yes |
READ_CHAIN | animica.chain.head / balance | read-only Animica chain access | no |
SPEND_ANM | animica.wallet.pay / balance | pay ANM from the CALLER’s balance, inside their granted caps | yes |
PERSIST_STATE | animica.state.get / set / delete | per-function encrypted key/value store | no |
SCHEDULE | CloudSchedule | the function is allowed to be driven by schedules | no |
HTTP_FETCH | animica.http.fetch | host-mediated outbound HTTPS with SSRF guards | yes |
Two layers of consent
- The developer declares capabilities on the function at deploy time. The deploy-time validator infers which capabilities the source appears to use (advisory, to pre-fill the UI) — but enforcement never trusts the source: the host broker checks the declared list on every host call and refuses anything undeclared with
CapabilityDenied. - The caller grants the sensitive subset (SPEND_ANM, CALL_APP, CALL_FUNCTION, HTTP_FETCH) explicitly, per app/agent/function, with hard budget bounds. No grant, no spend — even if the capability is declared.
grants API
# a user authorizes an app once, with hard bounds; revocable any time
POST /api/cloud/v1/grants
{
"subjectKind": "app", "subjectId": "…",
"capabilities": ["SPEND_ANM"],
"maxPerCallNanm": "1000000",
"maxPerExecNanm": "5000000",
"dailyCapNanm": "50000000",
"allowedPayees": ["anim1…"]
}
DELETE /api/cloud/v1/grants?id=… # takes effect on the very next host callHow SPEND_ANM actually moves money
- User code never touches a key. It asks the host to pay; the host verifies everything.
- Checks on every payment, atomically: grant exists, not revoked/expired, includes
SPEND_ANM; amount withinmaxPerCallNanm; recipient inallowedPayees(when set); running total withinmaxPerExecNanm; UTC-day total withindailyCapNanm(conditional-update claim — concurrent executions cannot double-spend the last allowance); and the whole call tree's budget not exceeded. - The transfer itself is two balanced ledger postings (payer debit, payee credit) in one transaction — nothing is minted, nothing is lost.
Why this is real security, not advisory: the sandbox has no network, no credentials and no keys — every privileged operation must pass through the broker, and the broker authorizes against the deployment row, the grant row and the live budgets, never against anything the guest sent. Revocation is read fresh at every spend, so it takes effect immediately.
Failure codes your code will see
animica.CapabilityDenied— capability not declared, or the caller's grant is missing / revoked / doesn't cover it.animica.BudgetExceeded— a spend/AI/call budget, quota or per-day cap would be exceeded.animica.AnimicaError— any other host-side failure (upstream unavailable, bad request…).