Skip to main content

Permissions

Every app declares what it needs access to in its manifest. Permissions are checked at runtime — a script that calls a wallet read without read:wallet declared in the manifest fails immediately, with an error you'll see in the script output. This makes the surface area of any app explicit: glance at the permissions list and you know exactly what it can touch.

When you ask Bankr to build an app, the agent picks the minimum set of permissions the scripts actually use. If you want fewer, ask — Bankr will rewrite the scripts to drop the dependency, or tell you the feature can't work without it.

Permission catalog

Read permissions

These let the app see things. None of them can change state.

PermissionWhat it grants
read:walletYour wallet identity — the EVM address, Solana address, and a stable account ID. Used by almost every app to know who's running it.
read:portfolioA full multi-chain token portfolio — balances on every supported chain, optional realized + unrealized PnL, optional NFT listings.
read:positionsYour open positions on supported venues (perps, spot trading, prediction markets).
read:tokensToken metadata, prices, and search across the chains you use.
read:chainDirect on-chain reads — call any contract's read functions, multicall, fetch logs, look up balances by address.
read:filesRead your file storage. The app can list folders, fetch file contents, and search files by name or content.
read:appdataRead the app's own key-value storage (its private state and any cached snapshots).
read:secretsResolve manifest-declared secret values at runtime. Plaintext stays server-side — secrets never reach the rendered HTML.

Write permissions

These let the app change state. They're more sensitive — Bankr doesn't grant them unless the app actually needs them.

PermissionWhat it grants
write:filesCreate and update files in your file storage. Used by apps that save reports, snapshots, or generated artifacts.
write:appdataWrite to the app's key-value storage. Required for any app that caches data, stores user preferences, or persists state between runs.
prepare:transactionBuild a transaction blob (target address, calldata, value) for the user to confirm in chat. The app can't broadcast on its own — confirmation always goes through the chat flow.
execute:swapUse Bankr's swap routing to assemble swap transactions. Still requires user confirmation in chat to broadcast.
execute:transferBuild a token transfer. Still requires user confirmation in chat to broadcast.
execute:signBuild an arbitrary signature request. Still requires user confirmation in chat to sign.

Network and AI permissions

PermissionWhat it grants
fetch:httpMake outbound HTTP requests from the server-side runtime. Locked to public hostnames — private IPs are blocked.
pay:x402Lets the iframe call bankr.x402.fetch(url, options) to pay an x402 endpoint from the visitor's wallet (not the owner's). Each call pops a confirmation modal showing URL + max USD before any payment. Requires a companion x402 manifest block declaring allowedHosts and a per-call USD cap — without it the runtime rejects every fetch. Per-(visitor, app) rate limits: 10 calls/minute, $10/day.
fetch:x402Reserved for server-side, owner-paid x402 fetches (cron-driven premium feeds where the owner's wallet covers the bill once and visitors read the cached snapshot for free). The permission is declared today; the runtime client is not yet wired — scheduled scripts that need paid data should stick to fetch:http plus an upstream API key in secrets for now.
invoke:agentRun a curated, read-only Bankr agent turn from inside a script — useful for LLM-powered curation, scoring, or summaries. The agent runs as the app owner; cost lands on the owner's wallet.

Defaults Bankr picks

When the agent builds an app, it picks the smallest set that satisfies your request:

  • Read-only dashboard of your dataread:wallet, read:portfolio and/or read:positions, read:appdata, write:appdata.
  • Public snapshot dashboard with scheduled-refresh content → adds fetch:http, often invoke:agent.
  • Trade builder / order helper → adds one of prepare:transaction, execute:swap, execute:transfer.
  • Apps that store generated artifacts (charts, reports) → adds read:files, write:files.

You can audit the list anytime — every app's manifest is in your filesystem under /apps/{slug}/manifest.json.

Wallet identity

Apps run scripts under a single wallet identity. Which wallet depends on what triggered the script:

TriggerWhose wallet drives the script
Frontend — a button or bankr.scripts.run call from the app's HTMLSet by the manifest's frontendIdentity field — "owner" (default) or "viewer"
Scheduled run — fires automatically on the app's scheduleAlways the owner (the wallet that installed the app)
Owner manually clicks Run in the Scripts drawerThe owner

The default frontendIdentity: "owner" is the right choice for shared dashboards where you want every visitor to see the same data — your trades, your portfolio, your curated picks. Bankr leans on this default unless you specifically ask for visitor-scoped behavior.

frontendIdentity: "viewer" makes sense for per-visitor tools — a calculator, a "score MY positions" widget, a personal trade plan. Each visitor's wallet drives the script.

If a visitor on a shared dashboard wants the same thing pointed at their data, they don't need to argue with the manifest — they just click Fork and get their own copy under their wallet.

Anonymous viewers

Anyone visiting a public or unlisted app without signing in:

  • Can read any data the app explicitly publishes via publicDataKeys (snapshots populated by scheduled runs, curated content).
  • Cannot trigger scripts — anonymous viewers see a sign-in prompt for any button that runs server-side code.
  • Cannot read or write the app's private storage.

This is why public dashboards typically have a scheduled script that pre-computes the data into a public snapshot — anonymous viewers see the rendered snapshot instantly without authenticating.

Before publishing public or unlisted

The most common breakage when an app moves from private to public is reading an appKV key the manifest never declared. Anonymous viewers get a 403 with the exact error Public data key "<k>" is not exposed by this app, and the iframe shows an empty panel where data should be.

Walk this checklist before flipping make this app public or sharing the unlisted link:

  1. List every key the iframe reads. Open index.html and find every bankr.appKV.get(...) and bankr.appKV.list(...) call — including the ones nested inside Promise.all, fallback branches, and event handlers that run before sign-in.
  2. Each key must be in manifest.publicDataKeys. If the iframe reads portfolio_snapshot, meta.balancesUpdatedAt, and last_curated, all three names must be in the array. Meta and timestamp keys are separate keys, not sub-paths of the data key — the runtime treats meta.balancesUpdatedAt as its own entry.
  3. Public-facing keys must be file-backed. Keys that start with mongo: route to the per-viewer queryable backend and can never be exposed publicly. If you need a public snapshot, the writer must use a plain key (no prefix) so it lands under /apps/{slug}/data/{key}.json.
  4. The writer must be the owner. Schedule the script that populates the snapshot, or run it manually as the owner — viewers can't write file-backed appKV. Cron-triggered runs always execute as the owner, which is the right pattern for refreshable public data.
  5. Gate authenticated-only reads. If a key is intentionally private (per-viewer state, owner-only secrets), don't read it unconditionally. Wrap the read in if (bankr.ctx.isAuthenticated) { ... } and render a signed-out fallback so anonymous viewers see an empty state instead of an error.
  6. Wrap interactive buttons in bankr.auth.requireSignIn(). Calls like bankr.invokeScript, bankr.appKV.set, bankr.askChat, and bankr.confirmTransaction always require sign-in. Render those buttons disabled (or hidden) when bankr.auth.isAuthenticated is false, and call bankr.auth.requireSignIn() from the click handler so the visitor sees a sign-in modal instead of a thrown error.
  7. Re-audit on every edit. Whenever you (or Bankr) add a new appKV.get call or rename an existing one, recheck step 1 against the manifest. The cleanest way to ask Bankr is "audit publicDataKeys against every appKV.get in index.html and add any missing keys".

A working public-dashboard pattern looks like this:

// manifest.json
{
"permissions": ["read:portfolio", "read:appdata", "write:appdata"],
"publicDataKeys": [
"portfolio_snapshot",
"portfolio_history",
"meta.refreshedAt"
],
"schedule": [
{ "script": "refreshSnapshot", "cron": "*/15 * * * *", "enabled": true }
]
}
// scripts/refreshSnapshot.ts — runs as owner on the schedule
const portfolio = await bankr.wallet.balances({ include: { pnl: true } });
await appKV.set("portfolio_snapshot", { tokens: portfolio.tokens });
await appKV.set("portfolio_history", { points: portfolio.history });
await appKV.set("meta.refreshedAt", Date.now());
return { ok: true };
<!-- index.html — runs in the public iframe -->
<script>
bankr.on("ready", async () => {
const [snapshot, history, meta] = await Promise.all([
bankr.appKV.get("portfolio_snapshot"),
bankr.appKV.get("portfolio_history"),
bankr.appKV.get("meta.refreshedAt"),
]);
render(snapshot, history, meta);

// Owner-only "edit notes" — gated, never thrown for visitors.
if (bankr.ctx.isAuthenticated) {
const notes = await bankr.appKV.get("private_notes");
renderNotes(notes);
}
});
</script>

Every key the iframe reads (portfolio_snapshot, portfolio_history, meta.refreshedAt) is in publicDataKeys. The owner-only private_notes is gated on bankr.ctx.isAuthenticated, so anonymous viewers never trigger the read. The cron writer runs as the owner, and the keys are plain (file-backed), not mongo:-prefixed.

Asking Bankr to change permissions

Like everything else about an app, permissions are editable through chat:

this app doesn't need to read my files anymore — drop that permission
add fetch:http so you can pull market data from the upstream API
switch this to viewer mode so each user sees their own positions

Bankr updates the manifest, redeploys, and confirms what changed. The new permission set takes effect on the next script run — no reinstall, no fork.

Where to next