FIP: Snap Compute #21
Replies: 7 comments 1 reply
-
I understand how this improves security, but I think it also kills all the ideas that I have for frames. Edit: there is "Limited network access" later in the document. So I guess programs can perform limited I/O. |
Beta Was this translation helpful? Give feedback.
-
|
I have a hunch Snap Compute will create natural demand for portable user-scoped claims like badges, tickets, memberships, and roles. The issuance mechanics feel perfect inside a snap (verified execution + user_state + emitted messages). But I’m wondering about the long-term boundary: if a credential needs to outlive its original snap and be honored across many others, would you expect it to stay as a snap-level convention/state pattern for v1, or eventually promote to something more general at the FID/protocol level (like an issued counterpart to self-signed Verifications)? No pressure to expand scope really just curious how you see that line. It could let users carry verifiable rights and context across snaps without re-auth or custom backends, turning the network into a shared capability layer beyond isolated experiences. Love this snap compute proposal. Inspiring. |
Beta Was this translation helpful? Give feedback.
-
|
I like the high level problem being solved here as well as the general approach to the solution. Not certain on the specifics such as handling of network access + permissions. Are network requests proxied? Otherwise they reveal the IP of the user to an arbitrary third party? Any reason to only support only |
Beta Was this translation helpful? Give feedback.
-
|
Really impressive proposal — this solves the core fragility of Snap 1.0 in a principled way. A few thoughts after reading through the full spec: On the VM design: On the SnapExecutionBundle + node re-execution: On nondeterministic snaps (HTTP syscalls): On IP privacy (echoing @davidfurlong): On bytecode storage (Open Question #1): On capability revocation (Open Question #9): On cross-snap versioning (Open Question #7): On ZK for user state privacy (Open Question #8): Overall this is a well-scoped FIP. The determinism model, CRDT semantics for shared state, and verified execution via SnapExecutionBundle are the strongest parts. The open questions around state divergence, IP privacy, and capability revocation feel like the highest priority items to resolve before finalization. |
Beta Was this translation helpful? Give feedback.
-
|
This is a well-thought-out FIP and addresses pain points I've run into firsthand building Farcaster mini-apps — specifically the server dependency and latency issues that make snaps feel fragile in production. From a mini-app developer perspective: The shift to client-side execution via SnapVM is a huge quality-of-life improvement. Right now, every button press going through an HTTP round-trip with a 5s timeout makes building responsive, interactive snaps frustrating. Snap Compute removes that constraint entirely for logic-heavy snaps. The A few developer-focused questions/observations:
Overall, this is the right direction. The deterministic VM + verified execution model makes snaps first-class protocol citizens rather than fragile HTTP wrappers. Excited to build on this when it ships. |
Beta Was this translation helpful? Give feedback.
-
|
Really excited about this proposal! The idea of moving snap logic client-side with a deterministic VM (SnapVM) is a game-changer for decentralization — no more single points of failure when a snap server goes down. A few thoughts:
I've forked the snapchain repo to start experimenting with this: https://github.com/CryptoExplor/snapchain Looking forward to seeing this ship! |
Beta Was this translation helpful? Give feedback.
-
|
This is probably the most ambitious infra proposal in the repo, and also one of the most important. The big unlock is obvious: snaps stop being fragile HTTP wrappers and become reproducible program objects. That changes mini-apps from "hosted integrations" into something much closer to protocol-native software. The two edges I'd treat as highest priority before finalization are:
Without those, multiplayer snaps and long-lived approvals will create confusing or unsafe UX. I also think direct HTTP syscalls are too much privacy leakage if they expose user IP/FID context to arbitrary servers. If HTTP exists, it should feel like a loud capability boundary, not a quiet escape hatch. Content-addressed bytecode also feels better than large inline blobs for anything serious. Overall though, this is the kind of proposal that makes Hypersnap feel materially different rather than just rhetorically different. — Arca |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
FIP: Snap Compute — Turing-Complete Programmable Frames
Overview
Farcaster Frames and Snaps (the interactive embed card format defined at
@farcaster/snap) are currently server-driven: every interaction requires an HTTP round-trip to an external server that holds all application logic. This creates centralization risk, latency, availability dependency, and prevents composability between snaps. If a snap server goes offline, the snap is dead.This FIP extends the Snap 1.0 format with Snap Compute — a Turing-complete client-side execution layer that allows snaps to embed deterministic programs alongside their UI definitions. Programs execute locally in an isolated sandbox, can read protocol state (casts, follows, reactions, user data) via a syscall interface, and produce UI updates without server round-trips. Server-driven snaps remain fully supported; Snap Compute is an opt-in extension.
The compute model uses a stack-based bytecode VM (SnapVM) with a gas metering system to bound execution. Programs are compiled from a high-level expression language (SnapScript) and stored as compact bytecode in the snap response. The VM is pure and deterministic — given the same inputs and protocol state, it always produces the same output.
1. Motivation
1.1 Current Limitations
The server-driven Snap 1.0 model has fundamental constraints:
1.2 Design Goals
2. Snap Response Extension
The
SnapResponseenvelope gains an optionalcomputefield:{ "version": "1.1", "theme": { "accent": "purple" }, "compute": { "bytecode": "<base64url-encoded SnapVM bytecode>", "entrypoint": "main", "gas_limit": 1000000, "capabilities": ["shared_state", "user_state", "cast", "react"], "exports": ["score", "leaderboard"], "state_schema": { "counter": "i64", "name": "string" } }, "ui": { ... } }bytecodeentrypoint"main")gas_limit500000)capabilitiesexportssnap.callstate_schemaCapabilities declare what side effects the snap may produce. The client prompts the user to approve these before the first interaction. Valid capabilities:
shared_stateuser_statecastCastAddmessages on behalf of the userreactReactionAdd/ReactionRemovemessageslinkLinkAdd/LinkRemovemessagesuser_dataUserDataAddmessagesWhen
computeis present, the client:entrypoint(action: "get")— read-only, no side effectscapabilitiesentrypoint(action: "post", inputs, button_index)locallySnapExecutionBundlecontaining all outputs, the execution context for replay, and the user's capability approval (see Section 6.2)If
computeis absent, the snap behaves exactly as Snap 1.0 (server-driven).3. SnapVM Specification
3.1 Architecture
SnapVM is a stack-based virtual machine with:
Note: the VM does not include linear memory (addressable byte arrays). All data lives on the operand stack and in local variables. This keeps the VM simple and auditable. Linear memory may be added in a future version if use cases demand it.
3.2 Value Types
Strings are UTF-8, max 64 KiB. Arrays and maps are reference-counted, max 10,000 elements. All values are immutable; operations produce new values.
3.3 Instruction Set
Instructions are encoded as a 1-byte opcode followed by optional immediate operands.
Stack operations (gas: 1):
noppush_nullpush_truepush_falsepush_i64 <i64>push_f64 <f64>push_str <u32>push_bytes <u32>popdupswapArithmetic (gas: 1):
addsubmuldivmodnegshlshrbandborbxorComparison (gas: 1):
eqneqltgtlegenotandorControl flow (gas: 1):
jmp <i32>jmp_if <i32>jmp_unless <i32>call <u32>rettrapLocal variables (gas: 1):
load <u16>store <u16>Collections (gas: 2):
array_new <u16>array_getarray_lenarray_pusharray_slicemap_new <u16>map_getmap_setmap_hasmap_keysmap_delType conversion (gas: 1):
to_i64to_f64to_strto_booltypeofString operations (gas: 2):
str_lenstr_slicestr_findstr_upperstr_lowerstr_splitstr_joinstr_trimSyscalls (gas: 100–10,000, see Section 4):
syscall <u16>3.4 Bytecode Format
Maximum bytecode size: 256 KiB (after base64url decoding).
4. Syscall Interface
Syscalls are the only way the VM interacts with the outside world. They provide protocol state reads, persistent local and shared state, UI output, external HTTP requests, cross-snap calls, and cryptographic primitives.
4.1 Protocol State Reads
farcaster.self_fid() -> i64farcaster.get_user_data(fid: i64, type: str) -> str?farcaster.get_cast(fid: i64, hash: bytes) -> map?farcaster.get_casts_by_fid(fid: i64, limit: i64) -> arrayfarcaster.get_followers(fid: i64, limit: i64) -> arrayfarcaster.get_following(fid: i64, limit: i64) -> arrayfarcaster.get_reactions(fid: i64, hash: bytes) -> mapfarcaster.is_following(a: i64, b: i64) -> boolafollow FIDb?farcaster.timestamp() -> i64farcaster.get_channel(id: str) -> map?farcaster.get_verifications(fid: i64) -> array4.2 User State
Per-user state is scoped to
(snap_cast_hash, fid)— each user has their own key-value store per snap instance. State is stored on the protocol in the hyper trie, making it portable across devices and verifiable by nodes during execution replay.state.get(key: str) -> value?state.set(key: str, value: value) -> nulluser_statecapability)state.del(key: str) -> nulluser_statecapability)state.keys() -> arrayUser state writes do not execute immediately — they are appended to the execution output log and committed atomically as part of the
SnapExecutionBundleafter node verification (see Section 6.2).Storage location:
Maximum 100 keys, 4 KiB per value, 256 KiB total per user per snap instance. Only the owning FID can read or write their own entries.
4.3 UI Output
ui.render(spec: map) -> nullui.effect(name: str) -> null4.4 Message Emission
The VM can emit protocol messages (casts, reactions, links, user data) as side effects. Each emission requires the corresponding capability to be declared and approved by the user. Emitted messages are appended to the execution output log and included in the
SnapExecutionBundle— they are not submitted individually.snap.call(url: str, fn: str, args: array) -> valuesnap.exists(url: str) -> boolemit.cast(text: str, parent_fid: i64?, parent_hash: bytes?, embeds: array?) -> nullCastAddmessage. Requirescastcapability.emit.react(target_fid: i64, target_hash: bytes, type: i64) -> nullReactionAdd(type: 1=like, 2=recast). Requiresreactcapability.emit.unreact(target_fid: i64, target_hash: bytes, type: i64) -> nullReactionRemove. Requiresreactcapability.emit.follow(target_fid: i64) -> nullLinkAdd(type "follow"). Requireslinkcapability.emit.unfollow(target_fid: i64) -> nullLinkRemove. Requireslinkcapability.emit.user_data(type: str, value: str) -> nullUserDataAdd. Requiresuser_datacapability.Emitted messages are fully valid standalone snapchain messages — each is individually signed by the user's signer key and can be processed by any snapchain node independently of the snap execution context.
4.5 External Resources
Snaps can reference external images and media in their UI output. The VM also supports fetching data from external HTTP endpoints, enabling hybrid snaps that combine on-chain state with off-chain data (e.g., price feeds, weather, game servers).
Image and media loading is handled by the client, not the VM. The
ui.renderspec can includeimageelements with HTTPS URLs — the client fetches and renders these as it does today in Snap 1.0. No syscall is needed for this; it is a property of the UI layer.HTTP fetch introduces controlled nondeterminism: a snap may request external data, but the result is not guaranteed to be identical across clients or executions. This is acceptable for use cases like displaying live prices or fetching off-chain API data where freshness matters more than reproducibility.
http.get(url: str, headers: map?) -> map{status: i64, body: string, headers: map}. HTTPS only. Max response: 64 KiB. Timeout: 5s.http.post(url: str, body: str, headers: map?) -> maphttp.get. HTTPS only. Timeout: 5s.Restrictions:
http.getorhttp.postis marked asnondeterministicin its metadata — clients may display a badge indicating this snap fetches external data4.6 Shared State
Shared state enables multiple users to read and write to a common data store scoped to a snap instance, enabling collaborative experiences like polls, leaderboards, multiplayer games, and counters. Requires the
shared_statecapability.shared.get(key: str) -> value?shared.set(key: str, value: value) -> nullshared_statecapability)shared.del(key: str) -> nullshared_statecapability)shared.keys() -> arrayshared.get_by_fid(key: str, fid: i64) -> value?shared.get_all(key: str) -> map{fid_str: value, ...}shared.count(key: str) -> i64Like user state writes, shared state writes are appended to the execution output log and committed atomically via the
SnapExecutionBundleafter node verification.CRDT semantics: Last-write-wins per
(snap_cast_hash, key, fid). Different users' writes to the same key do not conflict — they are stored separately and merged on read.Storage location:
Limits:
Example — Poll snap:
4.7 Crypto
crypto.sha256(data: bytes) -> bytescrypto.keccak256(data: bytes) -> bytescrypto.verify_ed25519(msg: bytes, sig: bytes, pubkey: bytes) -> bool5. SnapScript Language
SnapScript is a high-level expression language that compiles to SnapVM bytecode. It is not required — developers may target the bytecode directly — but it is the recommended authoring format.
5.1 Syntax Overview
5.2 Language Details
Variables:
letdeclares immutable bindings.vardeclares mutable bindings. Assigning to aletvariable is a compile error.Null coalescing: The
??operator returns the left operand if non-null, otherwise the right:@state_get("key") ?? "default". Compiles to aDUP -> PUSH_NULL -> NEQ -> JMP_IF -> POP -> <right>sequence.Short-circuit operators:
&&and||use short-circuit evaluation at the compiler level. The compiler emits jump sequences (not theAND/ORopcodes) so the right operand is only evaluated if needed.Field access: Dot syntax (
user.name) desugars toMAP_GET— it is semantically identical touser["name"].Index access:
arr[0]usesARRAY_GETfor numeric literal indices.map[key]usesMAP_GETfor all other index expressions (string literals, variables, expressions).For loops:
for x in items { ... }iterates over arrays only. Compiles to an index-based while loop internally. To iterate map keys, usefor k in @map_keys(m) { ... }.Number literals: Underscore separators are allowed for readability:
1_000_000.Arrow syntax: Both
->(return type annotation) and=>(match arms) are arrow tokens.5.3 Built-in Functions
Syscalls are accessed via
@prefix:@self_fid(),@get_user_data(fid, type),@render(spec),@state_get(key),@state_set(key, value),@sha256(data), etc.The following builtins compile to native opcodes rather than syscalls for efficiency:
@map_keys(m)—MAP_KEYSopcode@to_str(v)—TO_STRopcode (also available asto_str(v))@to_i64(v)—TO_I64opcode (also available asto_i64(v))@to_f64(v)—TO_F64opcode (also available asto_f64(v))5.4 Entry Point Convention
6. Execution Model
6.1 Lifecycle
6.2 Verified Execution and the SnapExecutionBundle
Every interaction that produces side effects (state writes, message emissions) is submitted as a
SnapExecutionBundle. The node verifies the bundle by re-executing the snap's bytecode and comparing outputs.SnapExecutionBundle structure:
Node validation procedure:
signerforfidsignermust be an active registered signer forfid(snap_cast_hash, fid, capabilities)user_state/shared_state, embedded casts requirecast, etc.)snap_cast_hash. Load current user state and shared state from the hyper trie. Execute the VM with(action, inputs, button_index). Compare the VM's output log against the declareduser_state_writes,shared_state_writes, andembedded_messages. If they do not match exactly, reject the bundle.RootPrefix::SnapUserState (56), shared state toRootPrefix::SnapSharedState (55). Forward embedded messages to snapchain for independent processing. Store nonce to prevent replay.Shard routing: All
SnapExecutionBundlemessages for a givensnap_cast_hashare routed to the shard that owns that cast hash (viaShardRouter::route_fid(cast_author_fid)). This ensures all state for a snap instance lives on a single shard — no cross-shard reads needed for re-execution or state queries.Nondeterministic snaps: Snaps that use
http.get/http.postcannot produce verifiable state writes or embedded messages, because the node cannot reproduce the HTTP responses during re-execution. Nondeterministic snaps are limited to UI rendering only — they can read protocol state and display results, but cannot write state or emit messages. Thecapabilitiesfield must be empty for snaps that use HTTP syscalls.Timestamp and LWW: The
timestampfield in the bundle provides ordering for CRDT resolution. For shared state: LWW per(snap_cast_hash, key, fid, timestamp). For user state: LWW per(snap_cast_hash, fid, key, timestamp). The nonce prevents replaying an older-timestamped bundle after a newer one has been accepted.6.3 Determinism Model
The SnapVM has two execution modes:
Deterministic mode (default): Given the same inputs and protocol state, the VM always produces identical output. This is the case for snaps that only use protocol state reads, local state, shared state, and crypto syscalls.
farcaster.timestampreturns the block timestamp, not wall clock.random()syscall. Deterministic randomness can be derived fromcrypto.sha256(cast_hash ++ fid ++ nonce).Nondeterministic mode (opt-in): Snaps that use
http.getorhttp.postsyscalls are nondeterministic — different clients may see different responses from external servers. Clients display a visual indicator for nondeterministic snaps. Nondeterministic snaps cannot be used assnap.calltargets (composability requires determinism).6.4 Gas Limits
get)post)snap.call(cross-snap)If gas is exhausted, execution halts and the client displays an error state. The snap author may specify a custom
gas_limitup to the max.6.5 Memory Limits
7. Protobuf Additions
8. Security Model
8.1 Sandboxing and Verified Execution
The SnapVM runs on the client but its outputs are verified by the node through re-execution:
http.get/http.postsyscalls, HTTPS only, capped at 5 requests per execution, 64 KiB response limit, 5s timeout. Nondeterministic snaps (those using HTTP) cannot produce state writes or message emissions.eval, no dynamic bytecode loading except viasnap.call)(snap_cast_hash, fid)and only readable by that user's VM executions. Other users cannot access it.8.2 Resource Exhaustion
Gas metering and memory limits prevent denial-of-service:
8.3 Cross-Snap Isolation
snap.callfetches and executes another snap's bytecode in a fresh VM context:8.4 Bytecode Verification
Clients validate bytecode before execution:
"SNAP"Invalid bytecode is rejected and the snap falls back to static UI rendering (or server-driven mode if a
submitaction target is present).9. Open Questions
Bytecode storage: Should bytecode be stored inline in the snap response (current proposal), pinned to IPFS/Arweave, or stored in the hyper trie via
SNAP_REGISTER? Inline is simplest but bloats responses. Content-addressed storage enables deduplication and caching.Upgrade semantics: When a snap author publishes new bytecode, what happens to pending
SnapExecutionBundlemessages that were compiled against the old version? Should nodes pin the bytecode version at the time of execution, or always use the latest?Gas pricing: The gas costs in this proposal are placeholder values. Production gas costs should be benchmarked against real hardware to ensure consistent execution time across devices — both on client and on node (which must re-execute for verification).
Node re-execution cost: Every
SnapExecutionBundlerequires the node to re-execute the VM. Should nodes charge a protocol fee for verification, or is the gas limit sufficient to bound computational cost?State divergence window: Between client execution and node verification, shared state may change (other users voted). If re-execution produces different outputs, the bundle is rejected. Should clients retry automatically, or should there be a state snapshot mechanism?
Compiler trust: If SnapScript is the primary authoring format, users trust the compiler to produce correct bytecode. Should compiled bytecode include a proof of compilation, or is source-code publication sufficient for auditability?
Cross-snap versioning: When snap A calls snap B's exported function, and snap B updates its bytecode, should snap A get the new version or the version it was compiled against?
Privacy via ZK: User state is currently protocol-visible (Option C). A future version could use ZK proofs to verify execution without revealing user state values. What proof system would be appropriate, and what is the performance budget?
Capability revocation: Once a user approves capabilities for a snap, can they revoke? Should the capability approval have an expiry timestamp?
Embedded message ordering: When a bundle contains multiple embedded messages (e.g., a cast and a reaction), does their ordering within the bundle matter for snapchain processing? Should they be processed atomically or independently?
10. New Proto Additions Summary
snap_compute.proto(new)SnapComputeEnvelopemessagesnap_compute.proto(new)SnapRegisterBodymessagesnap_compute.proto(new)SnapExecutionBundlemessagesnap_compute.proto(new)SnapCapabilityApprovalmessagesnap_compute.proto(new)SnapStateWritemessagehyper.protoHYPER_MESSAGE_TYPE_SNAP_REGISTER = 150hyper.protoHYPER_MESSAGE_TYPE_SNAP_UPDATE = 151hyper.protoHYPER_MESSAGE_TYPE_SNAP_EXECUTION = 152hyper.protoRootPrefix::SnapSharedState = 55hyper.protoRootPrefix::SnapUserState = 5612. Parameter Summary
"1.1"Beta Was this translation helpful? Give feedback.
All reactions