openzoo 0.50.99 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/bin/openzoo.js +33 -0
  2. package/lib/cursorbackend.js +6 -1
  3. package/lib/cursorbackend.js.bak-hiddenbots +4072 -0
  4. package/lib/models.js +6 -1
  5. package/package.json +7 -4
  6. package/transmute/README.md +352 -0
  7. package/transmute/bin/openzoo-transmute.js +7 -0
  8. package/transmute/docs/OPENZOO_CLI_PATCH.md +139 -0
  9. package/transmute/docs/VERCEL_TO_SOLANA.md +381 -0
  10. package/transmute/lib/build.js +311 -0
  11. package/transmute/lib/cli.js +231 -0
  12. package/transmute/lib/compile/index.js +146 -0
  13. package/transmute/lib/compile/ir.js +1634 -0
  14. package/transmute/lib/compile/parse.js +753 -0
  15. package/transmute/lib/compile/rust.js +777 -0
  16. package/transmute/lib/deploy.js +209 -0
  17. package/transmute/lib/eligibility.js +211 -0
  18. package/transmute/lib/gateway.js +704 -0
  19. package/transmute/lib/hub.js +249 -0
  20. package/transmute/lib/solana.js +339 -0
  21. package/transmute/lib/vercel.js +296 -0
  22. package/transmute/lib/wallet.js +41 -0
  23. package/transmute/lib/wire.js +167 -0
  24. package/transmute/runtime/zoo-host/Cargo.lock +588 -0
  25. package/transmute/runtime/zoo-host/Cargo.toml +28 -0
  26. package/transmute/runtime/zoo-host/src/assets.rs +170 -0
  27. package/transmute/runtime/zoo-host/src/ctx.rs +322 -0
  28. package/transmute/runtime/zoo-host/src/json.rs +257 -0
  29. package/transmute/runtime/zoo-host/src/kv.rs +182 -0
  30. package/transmute/runtime/zoo-host/src/lib.rs +123 -0
  31. package/transmute/runtime/zoo-host/src/val.rs +1044 -0
  32. package/transmute/runtime/zoo-host/src/wire.rs +239 -0
  33. package/transmute/site/.env.production +2 -0
  34. package/transmute/site/api/echo.js +10 -0
  35. package/transmute/site/api/hello.js +12 -0
  36. package/transmute/site/api/hits.js +13 -0
  37. package/transmute/site/api/time.js +5 -0
  38. package/transmute/site/public/app.js +16 -0
  39. package/transmute/site/public/index.html +62 -0
package/lib/models.js CHANGED
@@ -305,6 +305,11 @@ export const ANTHROPIC_NATIVE_ALIASES = {
305
305
  'claude-opus-4-8': 'anthropic/claude-opus-4.8',
306
306
  'claude-opus-4.8': 'anthropic/claude-opus-4.8',
307
307
  'claude-fable-5': 'anthropic/claude-fable-5',
308
+ // Both spellings the catalog serves for 5.1 (`claude-fable-5.1` and the
309
+ // Anthropic-native `claude-fable-5-1`). Missing here meant Claude Code's
310
+ // pick of Fable 5.1 fell through as an unknown id.
311
+ 'claude-fable-5.1': 'anthropic/claude-fable-5.1',
312
+ 'claude-fable-5-1': 'anthropic/claude-fable-5.1',
308
313
  'claude-haiku-4.5': 'anthropic/claude-haiku-4.5',
309
314
  };
310
315
 
@@ -336,7 +341,7 @@ export const ALIAS_IDS = [
336
341
  'gpt-5', 'gpt-5-mini', 'chatgpt-4o-latest', 'o1', 'o3', 'o3-mini', 'o4-mini',
337
342
  'claude-3-5-sonnet-latest', 'claude-sonnet-4-0', 'claude-opus-4-1',
338
343
  'claude-opus-5', 'claude-opus-5-fast', 'claude-3-5-opus', 'claude-sonnet-5',
339
- 'claude-opus-4-8', 'claude-fable-5',
344
+ 'claude-opus-4-8', 'claude-fable-5', 'claude-fable-5.1', 'claude-fable-5-1',
340
345
  'gemini-2.5-pro', 'gemini-2.5-flash', 'grok-4', 'grok-3',
341
346
  'deepseek-chat', 'deepseek-reasoner', 'qwen-max', 'llama-3.3-70b',
342
347
  ];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.50.99",
4
- "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
3
+ "version": "0.51.0",
4
+ "description": "Local x402-paying proxy + MCP server for openzoo.fun \u2014 point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental. `openzoo build|deploy|serve|hub` transmute a Vercel-shaped app (Next.js / Vite + /api) into a Pinocchio program + asset accounts on Solana mainnet.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "files": [
11
11
  "bin",
12
12
  "lib",
13
- "README.md"
13
+ "README.md",
14
+ "transmute"
14
15
  ],
15
16
  "engines": {
16
17
  "node": ">=18"
@@ -25,7 +26,9 @@
25
26
  "dugite": "^3.2.3",
26
27
  "selfsigned": "^5.5.0",
27
28
  "viem": "^2.21.0",
28
- "zod": "^3.24.0"
29
+ "zod": "^3.24.0",
30
+ "acorn": "^8.14.0",
31
+ "acorn-walk": "^8.3.4"
29
32
  },
30
33
  "repository": {
31
34
  "type": "git",
@@ -0,0 +1,352 @@
1
+ # openzoo-transmute
2
+
3
+ Transmute a Vercel-shaped app — Next.js `pages/api`, app-router `route.ts`, or
4
+ a Vite repo with `api/*.js` — into **(a)** a Rust [Pinocchio](https://github.com/anza-xyz/pinocchio)
5
+ Solana program that hosts the `/api/*` Lambdas as instruction routes and
6
+ **(b)** the static build stored in program-derived accounts; deploy it to
7
+ Solana; serve it through a local gateway that speaks the same routes Vercel
8
+ would.
9
+
10
+ ```
11
+ vercel build ─► .vercel/output/{config.json, static/, functions/*.func}
12
+ │
13
+ openzoo build ─► .zoo-out/.zoo/{crate/ (Rust), manifest.json, deploy/<site>.so, static-plan.json}
14
+ openzoo deploy ─► program id + one asset account per file + /.zoo/manifest.json
15
+ openzoo serve ─► http://127.0.0.1:4402/ (GET = free simulation, POST = signed transaction)
16
+ ```
17
+
18
+ The whole reverse-engineering — what `vercel build` emits, what the Lambda
19
+ receives from `@vercel/node-bridge`, what Fluid Compute changes, the term-by-term
20
+ mapping, the limits, the cost model and the security notes — is in
21
+ [`docs/VERCEL_TO_SOLANA.md`](docs/VERCEL_TO_SOLANA.md). The patch that mounts
22
+ these commands in `npx openzoo` is [`docs/OPENZOO_CLI_PATCH.md`](docs/OPENZOO_CLI_PATCH.md).
23
+
24
+ ## How it works
25
+
26
+ * **The Lambda contract becomes instruction data.** Vercel's bridge hands a
27
+ function `{method, path, headers, body}` and expects `{statusCode, headers,
28
+ body}`. `lib/wire.js` and `runtime/zoo-host/src/wire.rs` are that pair as
29
+ bytes: `[tag][route][method][path][query][headers][body]` in, `[status][headers][body]`
30
+ out via `set_return_data` plus `sol_log_data` chunks.
31
+ * **Each handler becomes a Rust route.** The compiler (`lib/compile/`) lowers
32
+ the JS subset it accepts onto `zoo_host::{Ctx, Val}` — a JS-semantics dynamic
33
+ value with `req.query`/`req.body`/`res.json`/`Response.json`/`kv.*`/`process.env`
34
+ implemented in `runtime/zoo-host/src/{ctx,val,json,kv}.rs`. Anything outside
35
+ the subset (network, fs, timers, regex, classes…) is reported with `file:line`
36
+ and the rest of the app still builds.
37
+ * **`static/` becomes asset PDAs.** One rent-exempt account per file, written in
38
+ 900-byte chunks, writes gated by the program's upgrade authority
39
+ (`runtime/zoo-host/src/assets.rs`). `@vercel/kv` becomes KV PDAs the program
40
+ discovers by dry run.
41
+ * **Reads are free, writes are transactions.** `GET/HEAD/OPTIONS` run as
42
+ `simulateTransaction` (no signature, no fee); everything else is a signed
43
+ transaction paid by the gateway wallet. Without a wallet the gateway answers
44
+ `402` — the seam where openzoo's x402 flow plugs in.
45
+
46
+ ## Install
47
+
48
+ ```sh
49
+ # from this repository — the package is not on npm yet (`npm view openzoo-transmute` → 404)
50
+ cd openzoo-transmute && npm install --no-audit --no-fund
51
+ node bin/openzoo-transmute.js help # or: npm link && openzoo-transmute help
52
+
53
+ # once published: npx openzoo-transmute help
54
+ # through the openzoo CLI once docs/OPENZOO_CLI_PATCH.md is applied:
55
+ npx openzoo build | deploy | serve | inspect
56
+ ```
57
+
58
+ Until it is published, read `npx openzoo-transmute` in the examples below as
59
+ `node <repo>/openzoo-transmute/bin/openzoo-transmute.js` (or the `npm link`ed
60
+ `openzoo-transmute`).
61
+
62
+ Requirements: Node ≥ 18, and for `build`/`deploy` the Solana toolchain
63
+ (`cargo build-sbf`): `sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"`.
64
+ `build --skip-cargo` works without it (you get the crate and the cost sheet, no `.so`).
65
+
66
+ ## Quickstart
67
+
68
+ ```sh
69
+ cd my-next-app # or a Vite repo with api/*.js, or a .vercel/output
70
+ npx openzoo-transmute inspect # the app in Vercel terms + eligibility report
71
+ npx openzoo-transmute build # → .zoo-out/.zoo/{crate,manifest.json,deploy/*.so}
72
+ npx openzoo-transmute deploy --cluster devnet
73
+ npx openzoo-transmute serve <programId> --cluster devnet
74
+ open http://127.0.0.1:4402/ # explorer at /.zoo/, manifest at /.zoo/manifest.json
75
+ ```
76
+
77
+ `inspect` prints one line per function with its route, style, runtime,
78
+ `maxDuration`, `memory` and methods, marks each `✓`/`✗` and says why:
79
+
80
+ ```
81
+ functions (2):
82
+ ✓ /api/hello pages nodejs20.x 10s 1024MB any
83
+ pages/api/hello.js env=GREETING
84
+ ✓ /api/counter app nodejs20.x 10s 1024MB POST
85
+ app/api/counter/route.js env=GREETING
86
+ ```
87
+
88
+ `deploy` prints the rent sheet before spending anything and refuses mainnet
89
+ without `--yes` (this one was measured on a local validator for the two-route
90
+ sample in [The generated crate](#the-generated-crate), `.so` built with `--arch v3`):
91
+
92
+ ```
93
+ item bytes SOL
94
+ program account 36 0.0011
95
+ program data (2×.so = 416,672 B) 416,717 2.9012
96
+ /app.js 52 0.0013
97
+ /index.html 44 0.0012
98
+ /.zoo/manifest.json (manifest) 939 0.0074
99
+ rent total 2.9123
100
+ + tx fees (approx.) 0.0012
101
+ TOTAL 2.9135
102
+ (rent per http://127.0.0.1:8899)
103
+ ```
104
+
105
+ Then `serve` maps HTTP onto the program:
106
+
107
+ ```
108
+ GET / → 200 text/html (asset PDA, etag, 304 on If-None-Match)
109
+ GET /api/hello?name=zoo&n=21 → 200 {"hello":"zoo","n":42,"greeting":"hi"} x-zoo-simulated: true, x-zoo-cu: 11067
110
+ POST /api/counter → 200 {"hits":1} x-zoo-signature: <tx>, x-zoo-simulated: false, x-zoo-cu: 10634
111
+ POST /api/counter → 402 payment required (gateway started without a keypair)
112
+ ```
113
+
114
+ ## CLI reference
115
+
116
+ | command | what it does |
117
+ |---|---|
118
+ | `build [dir] [--out .zoo-out] [--name <crate>] [--arch v0\|v3] [--cluster <c>] [--skip-cargo] [--json]` | read the app (`lib/vercel.js`), transmute (`lib/compile/`), write `<out>/.zoo/{crate/, manifest.json, report.json, static-plan.json, build.json}`, run `cargo build-sbf --arch <arch>` → `.zoo/deploy/<name>.so`, print the cost sheet (`--cluster` only chooses the RPC whose rent parameters price it, default `localnet`; unreachable → the 6.96 SOL/MB rule). Default arch is `v0` (mainnet); `deploy` re-detects the cluster's SBPF version and rebuilds if needed. Exit code 2 when nothing was eligible |
119
+ | `deploy [dir\|outDir] [--cluster mainnet\|devnet\|testnet\|localnet\|<url>] [--keypair <path>] [--yes] [--program <id>] [--concurrency 4] [--skip-assets] [--force] [--json]` | deploy or upgrade the program (keypair kept at `.zoo/program-keypair.json`, so redeploys upgrade in place), upload every static file (unchanged ones skipped by comparing bytes on chain), write `/.zoo/manifest.json`, record `.zoo/deploy.json` |
120
+ | `serve [programId] [--cluster <c>] [--port 4402] [--host 127.0.0.1] [--keypair <path>\|--no-keypair] [--quiet]` | the local gateway; `programId` defaults to the `.zoo/deploy.json` under the current directory (or its `.zoo-out/`). Reads are simulated, writes are signed by the wallet; without a wallet (or with `--no-keypair`) writes answer 402. Without `--cluster`/`OPENZOO_CLUSTER` this command assumes `localnet`, unlike the others |
121
+ | `inspect [dir] [--json]` | the Vercel model (functions, static files, routes, crons) plus the eligibility report |
122
+ | `status <programId> [--cluster <c>] [--json]` | program account, authority, `maxDataLen`, deploy slot, and the on-chain manifest |
123
+ | `help`, `--version` | |
124
+
125
+ Environment: `OPENZOO_CLUSTER` (default `mainnet` for `deploy`/`status`, `localnet` for `serve`), `OPENZOO_RPC` (mainnet RPC
126
+ URL), `OPENZOO_KEYPAIR` / `OPENZOO_WALLET` (signer path), `OPENZOO_DEBUG=1`
127
+ (stack traces). Signer discovery order (`lib/wallet.js`): `--keypair` →
128
+ `OPENZOO_KEYPAIR` → `OPENZOO_WALLET` or `~/.openzoo/wallet.json` (the openzoo
129
+ burner, `{solana:[64 bytes]}` or a bare `solana-keygen` array) → `~/.config/solana/id.json`.
130
+
131
+ Gateway responses carry `x-zoo-program`, `x-zoo-route`, `x-zoo-simulated`,
132
+ `x-zoo-cu`, `x-zoo-signature` (writes) and `x-zoo-asset` (static); route params
133
+ reach the handler as `x-zoo-param-<name>` headers. `413` above 900 body bytes,
134
+ `405` when an app-router file does not export the method, `502` with the last
135
+ program log lines when the instruction fails, `404` JSON (or the app's
136
+ `handle: error` route) otherwise.
137
+
138
+ ## The generated crate
139
+
140
+ `build` writes a crate that depends on the runtime by path. For this two-route
141
+ sample —
142
+
143
+ ```js
144
+ // pages/api/hello.js
145
+ export default function handler(req, res) {
146
+ res.status(200).json({ hello: req.query.name || 'world', n: Number(req.query.n) * 2, greeting: process.env.GREETING })
147
+ }
148
+ // app/api/counter/route.js
149
+ import { kv } from '@vercel/kv'
150
+ export async function POST() { const n = await kv.incr('hits'); return Response.json({ hits: n }) }
151
+ ```
152
+
153
+ — with `GREETING=hi` in `.env.production`, the compiler (`lib/compile/index.js`,
154
+ `transmute()`) emits exactly this; only the ~240-line prelude of helper
155
+ functions between the entrypoint and the routes is elided:
156
+
157
+ ```toml
158
+ # .zoo-out/.zoo/crate/Cargo.toml (crate name = the app directory, underscored)
159
+ [package]
160
+ name = "my_site"
161
+ version = "0.1.0"
162
+ edition = "2021"
163
+ description = "Generated by openzoo-transmute: Vercel Lambdas re-hosted as a Solana program"
164
+ publish = false
165
+
166
+ [lib]
167
+ crate-type = ["cdylib", "lib"]
168
+
169
+ [dependencies]
170
+ pinocchio = "0.11.2"
171
+ libm = "0.2"
172
+ zoo-host = { path = "<openzoo-transmute>/runtime/zoo-host" }
173
+
174
+ [profile.release]
175
+ overflow-checks = false
176
+ lto = "fat"
177
+ codegen-units = 1
178
+ opt-level = 3
179
+ ```
180
+
181
+ ```rust
182
+ // .zoo-out/.zoo/crate/src/lib.rs
183
+ //! Generated by openzoo-transmute — do not edit.
184
+ //! nextjs (nextjs) — 2 route(s), 2 static file(s)
185
+ //! route 0: /api/hello ← api/hello
186
+ //! route 1: /api/counter [POST] ← api/counter
187
+ #![no_std]
188
+ #![allow(unused_mut, unused_variables, unused_assignments, dead_code, unused_parens, unreachable_code, unused_braces, unused_labels, unused_imports, non_snake_case, non_upper_case_globals, unused_unsafe, clippy::all)]
189
+ extern crate alloc;
190
+ use alloc::{format, string::String, vec::Vec};
191
+ use pinocchio::{AccountView, Address, ProgramResult};
192
+ use zoo_host::{Ctx, Route, Val};
193
+ use zoo_host::val as zv;
194
+ use zoo_host::json as zjson;
195
+
196
+ pinocchio::program_entrypoint!(process_instruction);
197
+ pinocchio::default_allocator!();
198
+ pinocchio::nostd_panic_handler!();
199
+
200
+ /// The Lambda environment table, baked at build time (public on chain).
201
+ const ENV: &[(&str, &str)] = &[("GREETING", "hi")];
202
+ /// Route table: instruction byte 1 indexes it.
203
+ const ROUTES: &[Route] = &[route_0, route_1];
204
+
205
+ pub fn process_instruction(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
206
+ zoo_host::dispatch(program_id, accounts, data, ROUTES, ENV)
207
+ }
208
+
209
+ // … prelude: __zoo_query (req.query + route params), __zoo_resp / __zoo_send (Response objects),
210
+ // __zoo_cookies, __zoo_num, __zoo_set_headers, … …
211
+
212
+ // ---- route 0: api/hello (pages/api/hello.js)
213
+ const PARAMS_0: &[(&str, bool)] = &[];
214
+ fn route_0(cx: &mut Ctx) -> Result<(), Val> {
215
+ let _ = { let __a1 = Val::Num(200.0); cx.res_status(&__a1); Val::Undef };
216
+ let _ = { let __a2 = { let mut __o3 = Val::obj(); __o3.set_str("hello", { let __x4 = (__zoo_query(cx, PARAMS_0)).get_str("name"); if !__x4.truthy() { Val::str("world") } else { __x4 } }); __o3.set_str("n", ({ let __a: Vec<Val> = alloc::vec![(__zoo_query(cx, PARAMS_0)).get_str("n")]; zv::global_call("Number", &__a)? }).mul(&(Val::Num(2.0)))); __o3.set_str("greeting", { let __a5 = Val::str("GREETING"); cx.env(&__a5) }); __o3 }; cx.res_json(&__a2); Val::Undef };
217
+ Ok(())
218
+ }
219
+
220
+ // ---- route 1: api/counter (app/api/counter/route.js)
221
+ const PARAMS_1: &[(&str, bool)] = &[];
222
+ fn route_1(cx: &mut Ctx) -> Result<(), Val> {
223
+ let __m = cx.req_method();
224
+ match __m.as_str() {
225
+ Some("POST") => route_1_post(cx),
226
+ Some("OPTIONS") => { cx.res_status(&Val::Num(204.0)); cx.res_header(&Val::str("allow"), &Val::str("POST, OPTIONS")); cx.res_end(&Val::Undef); Ok(()) }
227
+ _ => { cx.res_status(&Val::Num(405.0)); cx.res_header(&Val::str("allow"), &Val::str("POST, OPTIONS")); cx.res_end(&Val::Undef); Ok(()) }
228
+ }
229
+ }
230
+ fn route_1_post(cx: &mut Ctx) -> Result<(), Val> {
231
+ let mut v_n: Val = { let __a6 = Val::str("hits"); let __a7 = Val::Num(1.0); cx.kv_incrby(&__a6, &__a7)? };
232
+ { let __rv = { let __b = { let mut __o8 = Val::obj(); __o8.set_str("hits", v_n.clone()); __o8 }; let __i = Val::Undef; __zoo_resp("json", __b, __i) }; __zoo_send(cx, &__rv)?; }
233
+ return Ok(());
234
+ Ok(())
235
+ }
236
+ ```
237
+
238
+ `Route = fn(&mut Ctx) -> Result<(), Val>`; `Err(v)` is a thrown JS value and
239
+ answers 500 like a crashed Lambda; returning without responding answers 504 like
240
+ a Lambda that never ended its response. The `?` after `kv_incrby` is the KV
241
+ account-discovery path: a missing account is logged as `ZOOK` and the gateway
242
+ retries with it. Built with `--arch v3` this crate is a 208,336-byte `.so`
243
+ (a 416,717-byte program data account, 2.90 SOL of rent); on a local validator
244
+ `GET /api/hello?name=zoo&n=21` costs 11,067 CU and `POST /api/counter`
245
+ 10,634 CU — nowhere near the 400 k limit.
246
+
247
+ ## Tests
248
+
249
+ ```sh
250
+ cd openzoo-transmute
251
+ npm install --no-audit --no-fund
252
+ npm test # node --test test/*.test.js
253
+ ```
254
+
255
+ The unit tests need no chain. The end-to-end test in `test/gateway.test.js`
256
+ deploys a pre-built site program and drives the gateway against a **local
257
+ validator**; it skips itself with a reason when either is missing:
258
+
259
+ ```sh
260
+ # 1. toolchain (once)
261
+ sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
262
+ export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
263
+
264
+ # 2. a validator with a funded wallet (keep it running in another shell)
265
+ solana-test-validator --reset
266
+ solana airdrop 100 --url http://127.0.0.1:8899 # ~/.config/solana/id.json
267
+
268
+ # 3. a site program with route 0 = /api/hello (pages style) and route 1 =
269
+ # /api/counter (POST, kv key "hits") — e.g. the two-route sample below
270
+ # (test validator = SBPF v3; mainnet = v0):
271
+ openzoo-transmute build <sample> --arch v3 # → <sample>/.zoo-out/.zoo/deploy/<name>.so
272
+ # then point the SAMPLE_SO constant in test/gateway.test.js at that .so
273
+ # (it is a fixed path today; no sample crate ships with the package yet)
274
+
275
+ # 4. run
276
+ OPENZOO_TEST_RPC=http://127.0.0.1:8899 npm test
277
+ ```
278
+
279
+ `test/gateway.test.js` looks for the `.so` at `SAMPLE_SO` and a validator at
280
+ `OPENZOO_TEST_RPC` (default `http://127.0.0.1:8899`); `test/build.test.js`
281
+ probes `localnet` for real rent numbers and falls back to the 6.96 SOL/MB rule.
282
+ The runtime crate also builds on the host (`cd runtime/zoo-host && cargo build`;
283
+ its `Cargo.toml` pulls the sha2/curve25519 fallbacks for non-SBF targets), and
284
+ for the chain: `cargo build-sbf --arch v3` (test validator) or `--arch v0` (mainnet).
285
+
286
+ ## Repository layout
287
+
288
+ ```
289
+ bin/openzoo-transmute.js the executable (→ lib/cli.js run())
290
+ lib/cli.js build · deploy · serve · inspect · status
291
+ lib/vercel.js the Vercel deployment model (Build Output API v3 reader / synthesizer)
292
+ lib/eligibility.js what can run on chain, with reasons
293
+ lib/compile/ JS → Rust: parse.js (acorn + TS stripping) → ir.js (lowering) → rust.js (printer, crate skeleton, prelude)
294
+ lib/build.js crate + manifest + cargo build-sbf + cost sheet
295
+ lib/deploy.js deploy / upgrade + assets + manifest
296
+ lib/gateway.js the local HTTP front (routes, static, 402/413 seams, explorer)
297
+ lib/solana.js loader (deploy/upgrade in pure JS), assets, KV, invoke + discovery
298
+ lib/wire.js the bridge contract as bytes, PDAs
299
+ lib/wallet.js signer discovery, cluster URLs
300
+ runtime/zoo-host/ the no_std pinocchio runtime the generated crate links
301
+ docs/VERCEL_TO_SOLANA.md the reverse-engineering document
302
+ docs/OPENZOO_CLI_PATCH.md mounting build/deploy/serve in npx openzoo
303
+ ```
304
+
305
+ ## Limits at a glance
306
+
307
+ | | |
308
+ |---|---|
309
+ | request body | 900 B (one transaction is 1232 B) |
310
+ | response | ≤ 1024 B in return data, ≈ 7 KB through log chunks |
311
+ | KV value | 10 000 B JSON per key |
312
+ | compute | 400 k CU requested, 1.4 M max |
313
+ | clock | seconds |
314
+ | rent | 6.96 SOL per MB of static + program bytes, 2 × the `.so` reserved |
315
+ | not supported | network, fs, timers, randomness, regex, classes, streaming, `waitUntil`, ISR, crons, middleware, local imports |
316
+
317
+ ## The hosted explorer (`hub`)
318
+
319
+ `serve` fronts one program on localhost. `hub` fronts every program on a
320
+ cluster from one public host, read-only:
321
+
322
+ ```
323
+ openzoo-transmute hub --cluster mainnet --port 8080 # or: fly deploy (Dockerfile + fly.toml here)
324
+ https://<host>/s/<programId> pins that site (cookie) and opens its /
325
+ https://<host>/s/<programId>/api/x one page of it, absolute
326
+ https://<host>/.hub paste a program id; recently served sites
327
+ ```
328
+
329
+ Reads are free simulations, exactly as in `serve`. Writes answer 402 on a
330
+ public host (a shared signer would be drained); use `serve` with your wallet
331
+ locally, or the x402-paid lane once the openzoo proxy fronts it. The site's
332
+ own root-relative links keep working because the pinned program rides a
333
+ cookie, so a transmuted app needs no base-path changes.
334
+
335
+ ## Roadmap
336
+
337
+ * **Browser fork / `sol://` scheme** — a browser that resolves
338
+ `sol://<programId>/path` straight from the RPC (assets from PDAs, `/api/*`
339
+ through `simulateTransaction`), so a site needs no gateway and no domain.
340
+ * **x402-paid writes through the openzoo proxy** — the gateway already answers
341
+ `402` with an `x402` stub for mutating requests when it has no signer; next is
342
+ letting the openzoo proxy pay per request from the burner wallet so a public
343
+ gateway never holds keys.
344
+ * **Chunked request bodies** — bodies > 900 B staged into a request account over
345
+ several transactions (the same mechanism assets already use), then referenced
346
+ by the invoke.
347
+ * **Response accounts** — responses > ~7 KB written into a per-request account
348
+ and read back by the gateway, instead of the log buffer.
349
+ * Compiler coverage: local imports, `RegExp`, base64 (`atob`/`btoa`), `cookies()`,
350
+ `URLSearchParams`, more of `@vercel/kv` (`hget`, lists, `expire`).
351
+
352
+ License: MIT.
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ // `openzoo-transmute build|deploy|serve|inspect|status|help` — see lib/cli.js.
3
+ import { run } from '../lib/cli.js';
4
+
5
+ const code = await run(process.argv.slice(2));
6
+ // `serve` returns 0 while its server keeps the loop alive; only bail on failure.
7
+ if (code) process.exit(code);
@@ -0,0 +1,139 @@
1
+ # Mounting `build` / `deploy` / `serve` in `npx openzoo`
2
+
3
+ The openzoo CLI (`staccDOTsol/openzoo`, `bin/openzoo.js`) gains four
4
+ subcommands that delegate to this package. Nothing is duplicated: `openzoo`
5
+ forwards its argv to `openzoo-transmute/lib/cli.js`, whose `run(argv)` resolves
6
+ to the exit code, sets `process.exitCode` when non-zero and never calls
7
+ `process.exit` itself (so `serve` keeps the event loop — and its HTTP server — alive).
8
+
9
+ Two files change.
10
+
11
+ **Status.** openzoo's `transmute-cli` branch carries hunk 1 (`bin/openzoo.js`,
12
+ byte-identical to the diff below) and nothing else. Hunk 2 waits for
13
+ `openzoo-transmute` to be published: `npm view openzoo-transmute` is a 404
14
+ today, so both the `"openzoo-transmute": "^0.1.0"` dependency and the
15
+ `npx --yes openzoo-transmute@latest` fallback fail on a fresh machine. Until
16
+ then, install the package from its path so the `import('openzoo-transmute/lib/cli.js')`
17
+ branch resolves: `npm install /path/to/leCore/openzoo-transmute` inside the
18
+ openzoo checkout (or `npm link` in that directory, then `npm link openzoo-transmute`).
19
+
20
+ ## 1. `bin/openzoo.js`
21
+
22
+ ```diff
23
+ --- a/bin/openzoo.js
24
+ +++ b/bin/openzoo.js
25
+ @@ -169,6 +169,16 @@
26
+ npx openzoo contexts --forget <hash|all> drop manifest entries
27
+ npx openzoo balance wallet balance on every rail — Solana (USDC/TOKEN/SOL),
28
+ Base (USDC/ETH), Robinhood Chain (USDG/memecoins/ETH)
29
+ + npx openzoo build [dir] transmute a Vercel-shaped app (Next.js pages/api,
30
+ + app/**/route, Vite + api/*) into a Pinocchio Rust
31
+ + program + asset plan (--out .zoo-out --arch v0|v3)
32
+ + npx openzoo deploy [dir] deploy the program and the static frontend to
33
+ + Solana accounts (--cluster mainnet|devnet|localnet,
34
+ + --yes to accept the rent estimate; burner wallet)
35
+ + npx openzoo serve <program> local gateway/explorer for a deployed site
36
+ + (--cluster, --port 4402); /api/* reads are free
37
+ + simulations, writes are signed by the burner
38
+ + npx openzoo inspect [dir] print the app in Vercel terms + eligibility report
39
+ npx openzoo address print both funding addresses (Solana + EVM)
40
+ npx openzoo help this text
41
+
42
+ @@ -403,6 +413,33 @@
43
+ if (bal <= 0) console.log('buy some with: npx openzoo topup 10');
44
+ break;
45
+ }
46
+ + case 'build':
47
+ + case 'deploy':
48
+ + case 'serve':
49
+ + case 'inspect':
50
+ + case 'transmute': {
51
+ + // VERCEL-SHAPED APP -> SOLANA MAINNET. `openzoo build|deploy|serve` hand
52
+ + // the argv to openzoo-transmute (a sibling npm package): it reads the
53
+ + // app the way `vercel build` would (pages/api, app/**/route, api/*,
54
+ + // .vercel/output), transmutes each Lambda into a Pinocchio Rust program
55
+ + // route, stores the static build in asset accounts, deploys with the
56
+ + // burner wallet at ~/.openzoo/wallet.json, and `serve` runs the local
57
+ + // gateway/explorer that maps http://localhost:4402/... onto the program.
58
+ + // Dynamic import so a machine without the package still runs every other
59
+ + // command; the fallback shells out to npx so the very first `openzoo
60
+ + // deploy` works before the dependency is pinned in package.json.
61
+ + const argv = process.argv.slice(2).map((a, i) => (i === 0 && a === 'transmute' ? 'help' : a));
62
+ + let cli = null;
63
+ + try { cli = await import('openzoo-transmute/lib/cli.js'); } catch { /* not installed alongside */ }
64
+ + if (cli) {
65
+ + await cli.run(argv);
66
+ + } else {
67
+ + const { spawnSync } = await import('child_process');
68
+ + const r = spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['--yes', 'openzoo-transmute@latest', ...argv], { stdio: 'inherit' });
69
+ + process.exit(r.status === null ? 1 : r.status);
70
+ + }
71
+ + break;
72
+ + }
73
+ case 'address':
74
+ (await import('../lib/info.js')).printAddress();
75
+ break;
76
+ ```
77
+
78
+ The first hunk lands inside the `HELP` template literal, right after the
79
+ `balance` lines and before `address`; the second goes into `main()`'s `switch
80
+ (cmd)` between the `credits` and `address` cases. `openzoo transmute` with no
81
+ further arguments is rewritten to `help` so it prints this package's usage.
82
+
83
+ ## 2. `package.json`
84
+
85
+ ```diff
86
+ --- a/package.json
87
+ +++ b/package.json
88
+ @@ -21,9 +21,10 @@
89
+ "dependencies": {
90
+ "@modelcontextprotocol/sdk": "^1.12.0",
91
+ "@solana/spl-token": "^0.4.14",
92
+ "@solana/web3.js": "^1.98.4",
93
+ "dugite": "^3.2.3",
94
+ + "openzoo-transmute": "^0.1.0",
95
+ "selfsigned": "^5.5.0",
96
+ "viem": "^2.21.0",
97
+ "zod": "^3.24.0"
98
+ },
99
+ ```
100
+
101
+ `openzoo-transmute`'s `package.json` has no `exports` map, so the deep import
102
+ `openzoo-transmute/lib/cli.js` resolves as a plain file; both packages already
103
+ depend on `@solana/web3.js ^1.98.4`, so one copy is installed. Until the
104
+ dependency is pinned, the `catch` branch above shells out to
105
+ `npx --yes openzoo-transmute@latest …`, so `npx openzoo deploy` works on a
106
+ fresh machine either way — once the package is on npm (see *Status* above).
107
+
108
+ ## The wallet
109
+
110
+ Nothing to configure. `openzoo-transmute/lib/wallet.js::loadWallet()` looks, in order, at
111
+
112
+ 1. `--keypair <path>`
113
+ 2. `$OPENZOO_KEYPAIR`
114
+ 3. `$OPENZOO_WALLET`, else **`~/.openzoo/wallet.json`** — the openzoo burner
115
+ wallet, in either of its shapes: `{ "solana": [64 bytes], ... }` (what
116
+ `npx openzoo address` creates) or a bare `solana-keygen` array
117
+ 4. `~/.config/solana/id.json` (the Solana CLI keypair)
118
+
119
+ so a user who has run `npx openzoo address` already has the signer `deploy` and
120
+ `serve` will use, and the same `OPENZOO_WALLET` / `OPENZOO_RPC` variables
121
+ listed in openzoo's `HELP` apply. `OPENZOO_CLUSTER` selects the cluster
122
+ (`mainnet` by default; `deploy` refuses mainnet without `--yes` after printing
123
+ the rent sheet). The mainnet RPC default is the one the openzoo proxy uses
124
+ (`lib/config.js` `rpcUrl`), overridable with `OPENZOO_RPC`.
125
+
126
+ ## Checking the patch
127
+
128
+ ```sh
129
+ npm install # pulls openzoo-transmute (or: npm install /path/to/leCore/openzoo-transmute)
130
+ node bin/openzoo.js help | grep -A1 'openzoo build'
131
+ node bin/openzoo.js transmute # prints openzoo-transmute's usage
132
+ node bin/openzoo.js inspect ./my-app # Vercel model + eligibility, no chain access
133
+ node bin/openzoo.js build ./my-app --skip-cargo # crate + cost sheet without the Solana toolchain
134
+ node bin/openzoo.js deploy ./my-app --cluster devnet
135
+ node bin/openzoo.js serve --cluster devnet # program id from ./my-app/.zoo-out/.zoo/deploy.json
136
+ ```
137
+
138
+ Exit codes come back from `run()`: `0` ok, `1` error (message on stderr,
139
+ `OPENZOO_DEBUG=1` for the stack), `2` when `build` found nothing eligible.