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.
- package/bin/openzoo.js +33 -0
- package/lib/cursorbackend.js +6 -1
- package/lib/cursorbackend.js.bak-hiddenbots +4072 -0
- package/lib/models.js +6 -1
- package/package.json +7 -4
- package/transmute/README.md +352 -0
- package/transmute/bin/openzoo-transmute.js +7 -0
- package/transmute/docs/OPENZOO_CLI_PATCH.md +139 -0
- package/transmute/docs/VERCEL_TO_SOLANA.md +381 -0
- package/transmute/lib/build.js +311 -0
- package/transmute/lib/cli.js +231 -0
- package/transmute/lib/compile/index.js +146 -0
- package/transmute/lib/compile/ir.js +1634 -0
- package/transmute/lib/compile/parse.js +753 -0
- package/transmute/lib/compile/rust.js +777 -0
- package/transmute/lib/deploy.js +209 -0
- package/transmute/lib/eligibility.js +211 -0
- package/transmute/lib/gateway.js +704 -0
- package/transmute/lib/hub.js +249 -0
- package/transmute/lib/solana.js +339 -0
- package/transmute/lib/vercel.js +296 -0
- package/transmute/lib/wallet.js +41 -0
- package/transmute/lib/wire.js +167 -0
- package/transmute/runtime/zoo-host/Cargo.lock +588 -0
- package/transmute/runtime/zoo-host/Cargo.toml +28 -0
- package/transmute/runtime/zoo-host/src/assets.rs +170 -0
- package/transmute/runtime/zoo-host/src/ctx.rs +322 -0
- package/transmute/runtime/zoo-host/src/json.rs +257 -0
- package/transmute/runtime/zoo-host/src/kv.rs +182 -0
- package/transmute/runtime/zoo-host/src/lib.rs +123 -0
- package/transmute/runtime/zoo-host/src/val.rs +1044 -0
- package/transmute/runtime/zoo-host/src/wire.rs +239 -0
- package/transmute/site/.env.production +2 -0
- package/transmute/site/api/echo.js +10 -0
- package/transmute/site/api/hello.js +12 -0
- package/transmute/site/api/hits.js +13 -0
- package/transmute/site/api/time.js +5 -0
- package/transmute/site/public/app.js +16 -0
- 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.
|
|
4
|
-
"description": "Local x402-paying proxy + MCP server for openzoo.fun
|
|
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.
|