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.
| Permission | What it grants |
|---|---|
read:wallet | Your wallet identity — the EVM address, Solana address, and a stable account ID. Used by almost every app to know who's running it. |
read:portfolio | A full multi-chain token portfolio — balances on every supported chain, optional realized + unrealized PnL, optional NFT listings. |
read:positions | Your open positions on supported venues (perps, spot trading, prediction markets). |
read:tokens | Token metadata, prices, and search across the chains you use. |
read:chain | Direct on-chain reads — call any contract's read functions, multicall, fetch logs, look up balances by address. |
read:files | Read your file storage. The app can list folders, fetch file contents, and search files by name or content. |
read:appdata | Read the app's own key-value storage (its private state and any cached snapshots). |
read:secrets | Resolve 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.
| Permission | What it grants |
|---|---|
write:files | Create and update files in your file storage. Used by apps that save reports, snapshots, or generated artifacts. |
write:appdata | Write to the app's key-value storage. Required for any app that caches data, stores user preferences, or persists state between runs. |
prepare:transaction | Build 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:swap | Use Bankr's swap routing to assemble swap transactions. Still requires user confirmation in chat to broadcast. |
execute:transfer | Build a token transfer. Still requires user confirmation in chat to broadcast. |
execute:sign | Build an arbitrary signature request. Still requires user confirmation in chat to sign. |
Network and AI permissions
| Permission | What it grants |
|---|---|
fetch:http | Make outbound HTTP requests from the server-side runtime. Locked to public hostnames — private IPs are blocked. |
pay:x402 | Lets 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:x402 | Reserved 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:agent | Run 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 data →
read:wallet,read:portfolioand/orread:positions,read:appdata,write:appdata. - Public snapshot dashboard with scheduled-refresh content → adds
fetch:http, ofteninvoke: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:
| Trigger | Whose wallet drives the script |
|---|---|
Frontend — a button or bankr.scripts.run call from the app's HTML | Set by the manifest's frontendIdentity field — "owner" (default) or "viewer" |
| Scheduled run — fires automatically on the app's schedule | Always the owner (the wallet that installed the app) |
| Owner manually clicks Run in the Scripts drawer | The 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:
- List every key the iframe reads. Open
index.htmland find everybankr.appKV.get(...)andbankr.appKV.list(...)call — including the ones nested insidePromise.all, fallback branches, and event handlers that run before sign-in. - Each key must be in
manifest.publicDataKeys. If the iframe readsportfolio_snapshot,meta.balancesUpdatedAt, andlast_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 treatsmeta.balancesUpdatedAtas its own entry. - 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. - 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. - 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. - Wrap interactive buttons in
bankr.auth.requireSignIn(). Calls likebankr.invokeScript,bankr.appKV.set,bankr.askChat, andbankr.confirmTransactionalways require sign-in. Render those buttons disabled (or hidden) whenbankr.auth.isAuthenticatedis false, and callbankr.auth.requireSignIn()from the click handler so the visitor sees a sign-in modal instead of a thrown error. - Re-audit on every edit. Whenever you (or Bankr) add a new
appKV.getcall 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
- SDK Reference — the methods each permission unlocks.
- Apps Overview — building, refining, and sharing.