openzoo 0.51.4 → 0.51.5
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 +0 -37
- package/lib/config.js +34 -0
- package/lib/pay.js +60 -4
- package/package.json +4 -7
- package/transmute/README.md +0 -385
- package/transmute/bin/openzoo-transmute.js +0 -7
- package/transmute/docs/OPENZOO_CLI_PATCH.md +0 -139
- package/transmute/docs/VERCEL_TO_SOLANA.md +0 -381
- package/transmute/lib/build.js +0 -381
- package/transmute/lib/cli.js +0 -293
- package/transmute/lib/compile/bytecode.js +0 -591
- package/transmute/lib/compile/hostids.js +0 -11
- package/transmute/lib/compile/index.js +0 -159
- package/transmute/lib/compile/ir.js +0 -1634
- package/transmute/lib/compile/parse.js +0 -753
- package/transmute/lib/compile/rust.js +0 -781
- package/transmute/lib/deploy.js +0 -357
- package/transmute/lib/eligibility.js +0 -211
- package/transmute/lib/gateway.js +0 -710
- package/transmute/lib/hub.js +0 -290
- package/transmute/lib/solana.js +0 -387
- package/transmute/lib/vercel.js +0 -296
- package/transmute/lib/wallet.js +0 -41
- package/transmute/lib/wire.js +0 -208
- package/transmute/prebuilt/zoo_vm.v0.so +0 -0
- package/transmute/runtime/zoo-host/Cargo.lock +0 -588
- package/transmute/runtime/zoo-host/Cargo.toml +0 -30
- package/transmute/runtime/zoo-host/examples/vmrun.rs +0 -43
- package/transmute/runtime/zoo-host/src/assets.rs +0 -214
- package/transmute/runtime/zoo-host/src/ctx.rs +0 -350
- package/transmute/runtime/zoo-host/src/fmt.rs +0 -188
- package/transmute/runtime/zoo-host/src/helpers.rs +0 -245
- package/transmute/runtime/zoo-host/src/json.rs +0 -263
- package/transmute/runtime/zoo-host/src/kv.rs +0 -206
- package/transmute/runtime/zoo-host/src/lib.rs +0 -137
- package/transmute/runtime/zoo-host/src/site.rs +0 -94
- package/transmute/runtime/zoo-host/src/val.rs +0 -1050
- package/transmute/runtime/zoo-host/src/vm.rs +0 -902
- package/transmute/runtime/zoo-host/src/wire.rs +0 -239
- package/transmute/runtime/zoo-vm/Cargo.lock +0 -596
- package/transmute/runtime/zoo-vm/Cargo.toml +0 -23
- package/transmute/runtime/zoo-vm/src/lib.rs +0 -92
- package/transmute/site/.env.production +0 -2
- package/transmute/site/api/echo.js +0 -10
- package/transmute/site/api/hello.js +0 -12
- package/transmute/site/api/hits.js +0 -13
- package/transmute/site/api/time.js +0 -5
- package/transmute/site/package.json +0 -6
- package/transmute/site/public/app.js +0 -16
- package/transmute/site/public/index.html +0 -96
package/bin/openzoo.js
CHANGED
|
@@ -179,22 +179,6 @@ usage:
|
|
|
179
179
|
npx openzoo contexts --forget <hash|all> drop manifest entries
|
|
180
180
|
npx openzoo balance wallet balance on every rail — Solana (USDC/TOKEN/SOL),
|
|
181
181
|
Base (USDC/ETH), Robinhood Chain (USDG/memecoins/ETH)
|
|
182
|
-
npx openzoo build [dir] transmute a Vercel-shaped app (Next.js pages/api,
|
|
183
|
-
app/**/route, Vite + api/*) for Solana. Default
|
|
184
|
-
--target shared: bytecode for the shared zoo-vm
|
|
185
|
-
runtime, no Rust toolchain, a site is data accounts
|
|
186
|
-
(~0.03-0.1 SOL). --target program: own Pinocchio
|
|
187
|
-
program per site (--arch v0|v3, ~1 SOL+)
|
|
188
|
-
npx openzoo deploy [dir] deploy the site to Solana accounts (--cluster mainnet,
|
|
189
|
-
--runtime <zoo-vm id> or OPENZOO_VM_PROGRAM, --yes to
|
|
190
|
-
accept the rent estimate; burner wallet)
|
|
191
|
-
npx openzoo serve [site] local gateway/explorer for a deployed site (--runtime,
|
|
192
|
-
--cluster, --port 4402); /api/* reads are free
|
|
193
|
-
simulations, writes are signed by the burner
|
|
194
|
-
npx openzoo runtime deploy deploy the shared zoo-vm runtime once per cluster
|
|
195
|
-
npx openzoo inspect [dir] print the app in Vercel terms + eligibility report
|
|
196
|
-
npx openzoo hub hosted explorer for every transmuted site on the
|
|
197
|
-
cluster (what sites.openzoo.fun runs)
|
|
198
182
|
npx openzoo address print both funding addresses (Solana + EVM)
|
|
199
183
|
npx openzoo help this text
|
|
200
184
|
|
|
@@ -433,27 +417,6 @@ async function main() {
|
|
|
433
417
|
if (bal <= 0) console.log('buy some with: npx openzoo topup 10');
|
|
434
418
|
break;
|
|
435
419
|
}
|
|
436
|
-
case 'build':
|
|
437
|
-
case 'deploy':
|
|
438
|
-
case 'serve':
|
|
439
|
-
case 'inspect':
|
|
440
|
-
case 'hub':
|
|
441
|
-
case 'runtime':
|
|
442
|
-
case 'transmute': {
|
|
443
|
-
// VERCEL-SHAPED APP -> SOLANA MAINNET. `openzoo build|deploy|serve` hand
|
|
444
|
-
// the argv to openzoo-transmute (a sibling npm package): it reads the
|
|
445
|
-
// app the way `vercel build` would (pages/api, app/**/route, api/*,
|
|
446
|
-
// .vercel/output), transmutes each Lambda into a Pinocchio Rust program
|
|
447
|
-
// route, stores the static build in asset accounts, deploys with the
|
|
448
|
-
// burner wallet at ~/.openzoo/wallet.json, and `serve` runs the local
|
|
449
|
-
// gateway/explorer that maps http://localhost:4402/... onto the program.
|
|
450
|
-
const argv = process.argv.slice(2).map((a, i) => (i === 0 && a === 'transmute' ? 'help' : a));
|
|
451
|
-
// The transmuter ships inside this package (transmute/): lib/cli.js, the
|
|
452
|
-
// compiler, the gateway + hub and the on-chain runtime crate
|
|
453
|
-
// (transmute/runtime/zoo-host) all travel with npm — no second install.
|
|
454
|
-
await (await import('../transmute/lib/cli.js')).run(argv);
|
|
455
|
-
break;
|
|
456
|
-
}
|
|
457
420
|
case 'address':
|
|
458
421
|
(await import('../lib/info.js')).printAddress();
|
|
459
422
|
break;
|
package/lib/config.js
CHANGED
|
@@ -80,6 +80,40 @@ export const EVM_FUNDING_ASSETS = {
|
|
|
80
80
|
],
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* RPC endpoint BY CHAIN ID — the only lookup a 402 row can be resolved with.
|
|
85
|
+
*
|
|
86
|
+
* A row names its chain as `eip155:<id>` and nothing else. The rail NAMES
|
|
87
|
+
* above are a coarser bucket: railOf() collapses every chain that is not Base
|
|
88
|
+
* or Robinhood into the single name "evm", so a lookup keyed by rail name
|
|
89
|
+
* cannot tell Polygon from Arbitrum and answers with whatever the fallback
|
|
90
|
+
* branch happens to be.
|
|
91
|
+
*
|
|
92
|
+
* MEASURED 2026-09-09: the payment preflight did exactly that — for any
|
|
93
|
+
* non-Base EVM row it read the balance over the ROBINHOOD RPC, asked a Polygon
|
|
94
|
+
* USDC address that is not a contract there, threw, was swallowed by the
|
|
95
|
+
* preflight's catch, and signed an authorization for a chain the wallet held
|
|
96
|
+
* nothing on. The facilitator then reverted it and the caller was told
|
|
97
|
+
* "execution reverted" while 76 USDC sat on Base. Keyed by chain id, that
|
|
98
|
+
* cannot happen: either we can read the chain, or we say we cannot.
|
|
99
|
+
*/
|
|
100
|
+
export const EVM_RPC_BY_CHAIN = {
|
|
101
|
+
8453: () => config.baseRpcUrl,
|
|
102
|
+
4663: () => config.rhRpcUrl,
|
|
103
|
+
137: () => process.env.OPENZOO_POLYGON_RPC || 'https://polygon-rpc.com',
|
|
104
|
+
42161: () => process.env.OPENZOO_ARBITRUM_RPC || 'https://arb1.arbitrum.io/rpc',
|
|
105
|
+
10: () => process.env.OPENZOO_OPTIMISM_RPC || 'https://mainnet.optimism.io',
|
|
106
|
+
480: () => process.env.OPENZOO_WORLD_RPC || 'https://worldchain-mainnet.gateway.tenderly.co',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** RPC for a row's `eip155:<id>` network, or null when we cannot read that chain. */
|
|
110
|
+
export function evmRpcForNetwork(network) {
|
|
111
|
+
const m = /^eip155:(\d+)$/.exec(String(network || ''));
|
|
112
|
+
if (!m) return null;
|
|
113
|
+
const f = EVM_RPC_BY_CHAIN[Number(m[1])];
|
|
114
|
+
return f ? f() : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
83
117
|
/** RPC endpoint for an EVM rail name. */
|
|
84
118
|
export function evmRpcFor(rail) {
|
|
85
119
|
// A rail with no RPC here reports NO balance at all — silently. Every entry
|
package/lib/pay.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Connection } from '@solana/web3.js';
|
|
2
|
-
import { config, fundingLine, evmRpcFor } from './config.js';
|
|
2
|
+
import { config, fundingLine, evmRpcFor, evmRpcForNetwork } from './config.js';
|
|
3
3
|
import { loadOrCreateWallet } from './wallet.js';
|
|
4
4
|
import {
|
|
5
5
|
parse402, orderAccepts, railOf, buildPaymentOnline, tokenBalance,
|
|
@@ -123,6 +123,30 @@ const RAIL_MEMO_MS = Number(process.env.OPENZOO_RAIL_MEMO_MS || 15_000);
|
|
|
123
123
|
const underfundedUntil = new Map(); // asset -> epoch ms after which to re-try it
|
|
124
124
|
let lastGoodAsset = null;
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* One line to stderr about which rail was chosen or skipped. Quiet by default
|
|
128
|
+
* would defeat the point — this is the answer to "why did my deposit not get
|
|
129
|
+
* used", and it is only printed when a rail was actually passed over, so a
|
|
130
|
+
* normal single-rail call stays silent. OPENZOO_QUIET=1 turns it off.
|
|
131
|
+
*/
|
|
132
|
+
function railNote(msg) {
|
|
133
|
+
if (process.env.OPENZOO_QUIET === '1') return;
|
|
134
|
+
try { process.stderr.write(`openzoo: ${msg}\n`); } catch { /* stderr closed */ }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** "Base (eip155:8453)" — the name a human knows plus the id a log needs. */
|
|
138
|
+
function railLabel(accept) {
|
|
139
|
+
const n = chainName(accept?.network);
|
|
140
|
+
return n === accept?.network ? String(n) : `${n} (${accept.network})`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** A chain a human recognises, for funding instructions. */
|
|
144
|
+
const CHAIN_NAMES = { 1: 'Ethereum', 10: 'Optimism', 137: 'Polygon', 480: 'World Chain', 4663: 'Robinhood Chain', 8453: 'Base', 42161: 'Arbitrum' };
|
|
145
|
+
export function chainName(network) {
|
|
146
|
+
const m = /^eip155:(\d+)$/.exec(String(network || ''));
|
|
147
|
+
return (m && CHAIN_NAMES[Number(m[1])]) || network;
|
|
148
|
+
}
|
|
149
|
+
|
|
126
150
|
const memoKey = (a) => `${a?.network || ''}|${a?.asset || ''}`;
|
|
127
151
|
|
|
128
152
|
/** lastGood first, known-underfunded last (never dropped — only deprioritised). */
|
|
@@ -197,8 +221,12 @@ export class PayClient {
|
|
|
197
221
|
const b = await tokenBalance(this.connection, this.keypair.publicKey, accept.asset);
|
|
198
222
|
return BigInt(b.raw || 0);
|
|
199
223
|
}
|
|
224
|
+
// By chain id, for the reason spelled out in buildPaymentFor: the rail name
|
|
225
|
+
// cannot tell one non-Base EVM chain from another.
|
|
226
|
+
const rpcUrl = evmRpcForNetwork(accept.network) ?? evmRpcFor(rail);
|
|
227
|
+
if (!rpcUrl) return 0n;
|
|
200
228
|
const raw = await evmTokenBalance({
|
|
201
|
-
rpcUrl
|
|
229
|
+
rpcUrl, token: accept.asset, owner: this.evmAddress,
|
|
202
230
|
});
|
|
203
231
|
return BigInt(raw || 0);
|
|
204
232
|
}
|
|
@@ -253,8 +281,26 @@ export class PayClient {
|
|
|
253
281
|
// Preflight: check the settlement-asset balance so an unfundable payment
|
|
254
282
|
// fails HERE with funding instructions instead of at the facilitator.
|
|
255
283
|
// Advisory only — an unreachable RPC never blocks a payment attempt.
|
|
256
|
-
|
|
284
|
+
//
|
|
285
|
+
// THE RPC IS CHOSEN BY THE ROW'S CHAIN ID, NEVER BY ITS RAIL NAME.
|
|
286
|
+
// railOf() calls every chain that is not Base or Robinhood "evm", so the
|
|
287
|
+
// old `rail === 'base' ? baseRpc : rhRpc` sent a POLYGON row's balance
|
|
288
|
+
// read to Robinhood Chain, where that token address is not a contract.
|
|
289
|
+
// The read threw, the catch below swallowed it, and this signed an
|
|
290
|
+
// authorization for a chain holding nothing — the facilitator reverted
|
|
291
|
+
// it and the caller was told "execution reverted" with a funded Base
|
|
292
|
+
// wallet one row away. MEASURED 2026-09-09.
|
|
293
|
+
const rpcUrl = evmRpcForNetwork(accept.network);
|
|
257
294
|
const owner = this.evmAddress;
|
|
295
|
+
// A CHAIN WE CANNOT READ IS NOT A CHAIN WE SIGN FOR. Skipping the row
|
|
296
|
+
// costs a caller nothing — the walk moves to the next one and reports
|
|
297
|
+
// honestly if none is payable — while signing it blind spends the call
|
|
298
|
+
// on a revert and teaches the caller nothing about why.
|
|
299
|
+
if (owner && !rpcUrl) {
|
|
300
|
+
throw new UnderfundedError(accept, null, owner, {
|
|
301
|
+
line: `No RPC configured for ${accept.network}, so this wallet's balance there cannot be checked — skipping that rail rather than signing blind. Set OPENZOO_POLYGON_RPC (or the matching chain's RPC) to use it.`,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
258
304
|
if (owner) {
|
|
259
305
|
const bal = await evmTokenBalance({ rpcUrl, token: accept.asset, owner }).catch(() => null);
|
|
260
306
|
if (bal !== null && bal < BigInt(accept.maxAmountRequired)) {
|
|
@@ -263,7 +309,7 @@ export class PayClient {
|
|
|
263
309
|
// Paxos USDG, which implements EIP-3009 directly, so the only
|
|
264
310
|
// question left on every EVM rail is whether the wallet holds enough.
|
|
265
311
|
const sym = accept?.extra?.symbol || 'the quoted asset';
|
|
266
|
-
const where = rail === 'robinhood' ? 'Robinhood Chain' : rail === 'base' ? 'Base' : accept.network;
|
|
312
|
+
const where = rail === 'robinhood' ? 'Robinhood Chain' : rail === 'base' ? 'Base' : chainName(accept.network);
|
|
267
313
|
throw new UnderfundedError(accept, null, owner, {
|
|
268
314
|
line: await withOnrampLink(
|
|
269
315
|
`Send a few cents of ${sym} on ${where} to ${owner}.`,
|
|
@@ -340,6 +386,7 @@ export class PayClient {
|
|
|
340
386
|
}
|
|
341
387
|
try {
|
|
342
388
|
payment = await this.buildPaymentFor(cand, onStage, quote);
|
|
389
|
+
if (fundErrs.length) railNote(`paying on ${railLabel(cand)}`);
|
|
343
390
|
accept = cand;
|
|
344
391
|
lastGoodAsset = memoKey(cand);
|
|
345
392
|
underfundedUntil.delete(memoKey(cand));
|
|
@@ -352,6 +399,15 @@ export class PayClient {
|
|
|
352
399
|
// Remember it so the next call does not re-pay the discovery cost.
|
|
353
400
|
underfundedUntil.set(memoKey(cand), Date.now() + RAIL_MEMO_MS);
|
|
354
401
|
fundErrs.push({ sym: cand?.extra?.symbol || cand?.asset, err: e });
|
|
402
|
+
// SAY WHICH RAIL WAS PASSED OVER AND WHY.
|
|
403
|
+
//
|
|
404
|
+
// A caller whose wallet is funded on one chain and empty on five
|
|
405
|
+
// others sees only the LAST row's failure, and the chain that failed
|
|
406
|
+
// is not the chain they funded. Reading that as "my deposit is not
|
|
407
|
+
// seen" is the correct reading of what they were shown — the walk was
|
|
408
|
+
// silent about the five it skipped. One line per skip, on stderr,
|
|
409
|
+
// makes the choice inspectable without a flag or a debug build.
|
|
410
|
+
railNote(`skipping ${railLabel(cand)} — ${e.message.replace(/^openzoo wallet underfunded: /, '')}`);
|
|
355
411
|
}
|
|
356
412
|
}
|
|
357
413
|
if (!payment) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openzoo",
|
|
3
|
-
"version": "0.51.
|
|
4
|
-
"description": "Local x402-paying proxy + MCP server for openzoo.fun
|
|
3
|
+
"version": "0.51.5",
|
|
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.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"lib",
|
|
13
|
-
"README.md"
|
|
14
|
-
"transmute"
|
|
13
|
+
"README.md"
|
|
15
14
|
],
|
|
16
15
|
"engines": {
|
|
17
16
|
"node": ">=18"
|
|
@@ -26,9 +25,7 @@
|
|
|
26
25
|
"dugite": "^3.2.3",
|
|
27
26
|
"selfsigned": "^5.5.0",
|
|
28
27
|
"viem": "^2.21.0",
|
|
29
|
-
"zod": "^3.24.0"
|
|
30
|
-
"acorn": "^8.14.0",
|
|
31
|
-
"acorn-walk": "^8.3.4"
|
|
28
|
+
"zod": "^3.24.0"
|
|
32
29
|
},
|
|
33
30
|
"repository": {
|
|
34
31
|
"type": "git",
|
package/transmute/README.md
DELETED
|
@@ -1,385 +0,0 @@
|
|
|
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 (shared runtime — the default)
|
|
67
|
-
|
|
68
|
-
A site does not need its own program. The `zoo-vm` runtime
|
|
69
|
-
(`runtime/zoo-vm`, prebuilt at `prebuilt/zoo_vm.v0.so`) is deployed once per
|
|
70
|
-
cluster; every site is then just data accounts under it — a 66-byte site
|
|
71
|
-
account, its bytecode module (`/.zoo/code.bin`, ~1–2 KB for a handful of
|
|
72
|
-
routes), its static files and its manifest. Rent for a small site is
|
|
73
|
-
**≈0.03–0.1 SOL** instead of ≈1 SOL+ for a dedicated program.
|
|
74
|
-
|
|
75
|
-
```sh
|
|
76
|
-
cd my-next-app # or a Vite repo with api/*.js, or a .vercel/output
|
|
77
|
-
npx openzoo-transmute inspect # the app in Vercel terms + eligibility report
|
|
78
|
-
npx openzoo-transmute build # → .zoo-out/.zoo/{code.bin,manifest.json,…}; no cargo needed
|
|
79
|
-
npx openzoo-transmute deploy --cluster mainnet --runtime <zoo-vm id> --yes
|
|
80
|
-
npx openzoo-transmute serve # site id + runtime read from .zoo/deploy.json
|
|
81
|
-
open http://127.0.0.1:4402/ # explorer at /.zoo/, manifest at /.zoo/manifest.json
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The site id is the public key of `.zoo/site-keypair.json` (looks like a
|
|
85
|
-
program id, costs nothing; keep it — redeploys update the same site). The hub
|
|
86
|
-
serves it at `https://sites.openzoo.fun/s/<siteId>` once its
|
|
87
|
-
`OPENZOO_VM_PROGRAM` points at the same runtime.
|
|
88
|
-
|
|
89
|
-
Deploying the runtime itself (once, ≈2.2 SOL on mainnet for the 313 KB .so,
|
|
90
|
-
upgradeable by the deployer's key):
|
|
91
|
-
|
|
92
|
-
```sh
|
|
93
|
-
npx openzoo-transmute runtime deploy --cluster mainnet --yes # keypair → ./zoo-vm-keypair.json
|
|
94
|
-
export OPENZOO_VM_PROGRAM=<printed id>
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
`--target program` keeps the original path: one Pinocchio program per site,
|
|
98
|
-
compiled from the generated crate with `cargo build-sbf`.
|
|
99
|
-
|
|
100
|
-
## Quickstart (dedicated program)
|
|
101
|
-
|
|
102
|
-
```sh
|
|
103
|
-
npx openzoo-transmute build --target program # → .zoo-out/.zoo/{crate,manifest.json,deploy/*.so}
|
|
104
|
-
npx openzoo-transmute deploy --cluster mainnet --yes
|
|
105
|
-
npx openzoo-transmute serve <programId> --cluster mainnet
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
`inspect` prints one line per function with its route, style, runtime,
|
|
109
|
-
`maxDuration`, `memory` and methods, marks each `✓`/`✗` and says why:
|
|
110
|
-
|
|
111
|
-
```
|
|
112
|
-
functions (2):
|
|
113
|
-
✓ /api/hello pages nodejs20.x 10s 1024MB any
|
|
114
|
-
pages/api/hello.js env=GREETING
|
|
115
|
-
✓ /api/counter app nodejs20.x 10s 1024MB POST
|
|
116
|
-
app/api/counter/route.js env=GREETING
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`deploy` prints the rent sheet before spending anything and refuses mainnet
|
|
120
|
-
without `--yes` (this one was measured on a local validator for the two-route
|
|
121
|
-
sample in [The generated crate](#the-generated-crate), `.so` built with `--arch v3`):
|
|
122
|
-
|
|
123
|
-
```
|
|
124
|
-
item bytes SOL
|
|
125
|
-
program account 36 0.0011
|
|
126
|
-
program data (2×.so = 416,672 B) 416,717 2.9012
|
|
127
|
-
/app.js 52 0.0013
|
|
128
|
-
/index.html 44 0.0012
|
|
129
|
-
/.zoo/manifest.json (manifest) 939 0.0074
|
|
130
|
-
rent total 2.9123
|
|
131
|
-
+ tx fees (approx.) 0.0012
|
|
132
|
-
TOTAL 2.9135
|
|
133
|
-
(rent per http://127.0.0.1:8899)
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Then `serve` maps HTTP onto the program:
|
|
137
|
-
|
|
138
|
-
```
|
|
139
|
-
GET / → 200 text/html (asset PDA, etag, 304 on If-None-Match)
|
|
140
|
-
GET /api/hello?name=zoo&n=21 → 200 {"hello":"zoo","n":42,"greeting":"hi"} x-zoo-simulated: true, x-zoo-cu: 11067
|
|
141
|
-
POST /api/counter → 200 {"hits":1} x-zoo-signature: <tx>, x-zoo-simulated: false, x-zoo-cu: 10634
|
|
142
|
-
POST /api/counter → 402 payment required (gateway started without a keypair)
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## CLI reference
|
|
146
|
-
|
|
147
|
-
| command | what it does |
|
|
148
|
-
|---|---|
|
|
149
|
-
| `build [dir] [--target shared\|program] [--runtime <id>] [--out .zoo-out] [--name <crate>] [--arch v0\|v3] [--cluster <c>] [--skip-cargo] [--json]` | `--target shared` (default): transmute to a `ZOOB` bytecode module at `<out>/.zoo/code.bin` for the shared runtime, no cargo, site-sized cost sheet. `--target program`: 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 |
|
|
150
|
-
| `deploy [dir\|outDir] [--cluster mainnet\|devnet\|testnet\|localnet\|<url>] [--keypair <path>] [--yes] [--runtime <id>] [--program <id>] [--concurrency 4] [--skip-assets] [--force] [--json]` | shared build: create the site account under `--runtime` (or `OPENZOO_VM_PROGRAM`), upload `code.bin`, assets and manifest as PDAs namespaced by the site id (keypair at `.zoo/site-keypair.json`). Program build: 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` |
|
|
151
|
-
| `serve [siteId\|programId] [--runtime <id>] [--cluster <c>] [--port 4402] [--host 127.0.0.1] [--keypair <path>\|--no-keypair] [--quiet]` | the local gateway; with `--runtime` the positional is a site id on the shared runtime. It 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 |
|
|
152
|
-
| `inspect [dir] [--json]` | the Vercel model (functions, static files, routes, crons) plus the eligibility report |
|
|
153
|
-
| `status <siteId\|programId> [--runtime <id>] [--cluster <c>] [--json]` | program account, authority, `maxDataLen`, deploy slot, and the on-chain manifest; with `--runtime`, the site account and its manifest |
|
|
154
|
-
| `runtime deploy [--cluster <c>] [--keypair <path>] [--yes] [--program-keypair zoo-vm-keypair.json] [--so <path>]` | deploy (or upgrade in place) the shared `zoo-vm` runtime: `prebuilt/zoo_vm.<arch>.so`, or built from `runtime/zoo-vm` when no prebuilt matches the cluster's SBPF version |
|
|
155
|
-
| `runtime status <id> [--cluster <c>]` | the runtime program's account |
|
|
156
|
-
| `help`, `--version` | |
|
|
157
|
-
|
|
158
|
-
Environment: `OPENZOO_VM_PROGRAM` (shared runtime id; default for `--runtime`), `OPENZOO_TARGET` (`shared`/`program`), `OPENZOO_CLUSTER` (default `mainnet` for `deploy`/`status`, `localnet` for `serve`), `OPENZOO_RPC` (mainnet RPC
|
|
159
|
-
URL), `OPENZOO_KEYPAIR` / `OPENZOO_WALLET` (signer path), `OPENZOO_DEBUG=1`
|
|
160
|
-
(stack traces). Signer discovery order (`lib/wallet.js`): `--keypair` →
|
|
161
|
-
`OPENZOO_KEYPAIR` → `OPENZOO_WALLET` or `~/.openzoo/wallet.json` (the openzoo
|
|
162
|
-
burner, `{solana:[64 bytes]}` or a bare `solana-keygen` array) → `~/.config/solana/id.json`.
|
|
163
|
-
|
|
164
|
-
Gateway responses carry `x-zoo-program`, `x-zoo-route`, `x-zoo-simulated`,
|
|
165
|
-
`x-zoo-cu`, `x-zoo-signature` (writes) and `x-zoo-asset` (static); route params
|
|
166
|
-
reach the handler as `x-zoo-param-<name>` headers. `413` above 900 body bytes,
|
|
167
|
-
`405` when an app-router file does not export the method, `502` with the last
|
|
168
|
-
program log lines when the instruction fails, `404` JSON (or the app's
|
|
169
|
-
`handle: error` route) otherwise.
|
|
170
|
-
|
|
171
|
-
## The generated crate
|
|
172
|
-
|
|
173
|
-
`build` writes a crate that depends on the runtime by path. For this two-route
|
|
174
|
-
sample —
|
|
175
|
-
|
|
176
|
-
```js
|
|
177
|
-
// pages/api/hello.js
|
|
178
|
-
export default function handler(req, res) {
|
|
179
|
-
res.status(200).json({ hello: req.query.name || 'world', n: Number(req.query.n) * 2, greeting: process.env.GREETING })
|
|
180
|
-
}
|
|
181
|
-
// app/api/counter/route.js
|
|
182
|
-
import { kv } from '@vercel/kv'
|
|
183
|
-
export async function POST() { const n = await kv.incr('hits'); return Response.json({ hits: n }) }
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
— with `GREETING=hi` in `.env.production`, the compiler (`lib/compile/index.js`,
|
|
187
|
-
`transmute()`) emits exactly this; only the ~240-line prelude of helper
|
|
188
|
-
functions between the entrypoint and the routes is elided:
|
|
189
|
-
|
|
190
|
-
```toml
|
|
191
|
-
# .zoo-out/.zoo/crate/Cargo.toml (crate name = the app directory, underscored)
|
|
192
|
-
[package]
|
|
193
|
-
name = "my_site"
|
|
194
|
-
version = "0.1.0"
|
|
195
|
-
edition = "2021"
|
|
196
|
-
description = "Generated by openzoo-transmute: Vercel Lambdas re-hosted as a Solana program"
|
|
197
|
-
publish = false
|
|
198
|
-
|
|
199
|
-
[lib]
|
|
200
|
-
crate-type = ["cdylib", "lib"]
|
|
201
|
-
|
|
202
|
-
[dependencies]
|
|
203
|
-
pinocchio = "0.11.2"
|
|
204
|
-
libm = "0.2"
|
|
205
|
-
zoo-host = { path = "<openzoo-transmute>/runtime/zoo-host" }
|
|
206
|
-
|
|
207
|
-
[profile.release]
|
|
208
|
-
overflow-checks = false
|
|
209
|
-
lto = "fat"
|
|
210
|
-
codegen-units = 1
|
|
211
|
-
opt-level = 3
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
```rust
|
|
215
|
-
// .zoo-out/.zoo/crate/src/lib.rs
|
|
216
|
-
//! Generated by openzoo-transmute — do not edit.
|
|
217
|
-
//! nextjs (nextjs) — 2 route(s), 2 static file(s)
|
|
218
|
-
//! route 0: /api/hello ← api/hello
|
|
219
|
-
//! route 1: /api/counter [POST] ← api/counter
|
|
220
|
-
#![no_std]
|
|
221
|
-
#![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)]
|
|
222
|
-
extern crate alloc;
|
|
223
|
-
use alloc::{format, string::String, vec::Vec};
|
|
224
|
-
use pinocchio::{AccountView, Address, ProgramResult};
|
|
225
|
-
use zoo_host::{Ctx, Route, Val};
|
|
226
|
-
use zoo_host::val as zv;
|
|
227
|
-
use zoo_host::json as zjson;
|
|
228
|
-
|
|
229
|
-
pinocchio::program_entrypoint!(process_instruction);
|
|
230
|
-
pinocchio::default_allocator!();
|
|
231
|
-
pinocchio::nostd_panic_handler!();
|
|
232
|
-
|
|
233
|
-
/// The Lambda environment table, baked at build time (public on chain).
|
|
234
|
-
const ENV: &[(&str, &str)] = &[("GREETING", "hi")];
|
|
235
|
-
/// Route table: instruction byte 1 indexes it.
|
|
236
|
-
const ROUTES: &[Route] = &[route_0, route_1];
|
|
237
|
-
|
|
238
|
-
pub fn process_instruction(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
|
|
239
|
-
zoo_host::dispatch(program_id, accounts, data, ROUTES, ENV)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// … prelude: __zoo_query (req.query + route params), __zoo_resp / __zoo_send (Response objects),
|
|
243
|
-
// __zoo_cookies, __zoo_num, __zoo_set_headers, … …
|
|
244
|
-
|
|
245
|
-
// ---- route 0: api/hello (pages/api/hello.js)
|
|
246
|
-
const PARAMS_0: &[(&str, bool)] = &[];
|
|
247
|
-
fn route_0(cx: &mut Ctx) -> Result<(), Val> {
|
|
248
|
-
let _ = { let __a1 = Val::Num(200.0); cx.res_status(&__a1); Val::Undef };
|
|
249
|
-
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 };
|
|
250
|
-
Ok(())
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ---- route 1: api/counter (app/api/counter/route.js)
|
|
254
|
-
const PARAMS_1: &[(&str, bool)] = &[];
|
|
255
|
-
fn route_1(cx: &mut Ctx) -> Result<(), Val> {
|
|
256
|
-
let __m = cx.req_method();
|
|
257
|
-
match __m.as_str() {
|
|
258
|
-
Some("POST") => route_1_post(cx),
|
|
259
|
-
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(()) }
|
|
260
|
-
_ => { cx.res_status(&Val::Num(405.0)); cx.res_header(&Val::str("allow"), &Val::str("POST, OPTIONS")); cx.res_end(&Val::Undef); Ok(()) }
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
fn route_1_post(cx: &mut Ctx) -> Result<(), Val> {
|
|
264
|
-
let mut v_n: Val = { let __a6 = Val::str("hits"); let __a7 = Val::Num(1.0); cx.kv_incrby(&__a6, &__a7)? };
|
|
265
|
-
{ 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)?; }
|
|
266
|
-
return Ok(());
|
|
267
|
-
Ok(())
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
`Route = fn(&mut Ctx) -> Result<(), Val>`; `Err(v)` is a thrown JS value and
|
|
272
|
-
answers 500 like a crashed Lambda; returning without responding answers 504 like
|
|
273
|
-
a Lambda that never ended its response. The `?` after `kv_incrby` is the KV
|
|
274
|
-
account-discovery path: a missing account is logged as `ZOOK` and the gateway
|
|
275
|
-
retries with it. Built with `--arch v3` this crate is a 208,336-byte `.so`
|
|
276
|
-
(a 416,717-byte program data account, 2.90 SOL of rent); on a local validator
|
|
277
|
-
`GET /api/hello?name=zoo&n=21` costs 11,067 CU and `POST /api/counter`
|
|
278
|
-
10,634 CU — nowhere near the 400 k limit.
|
|
279
|
-
|
|
280
|
-
## Tests
|
|
281
|
-
|
|
282
|
-
```sh
|
|
283
|
-
cd openzoo-transmute
|
|
284
|
-
npm install --no-audit --no-fund
|
|
285
|
-
npm test # node --test test/*.test.js
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
The unit tests need no chain. The end-to-end test in `test/gateway.test.js`
|
|
289
|
-
deploys a pre-built site program and drives the gateway against a **local
|
|
290
|
-
validator**; it skips itself with a reason when either is missing:
|
|
291
|
-
|
|
292
|
-
```sh
|
|
293
|
-
# 1. toolchain (once)
|
|
294
|
-
sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"
|
|
295
|
-
export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"
|
|
296
|
-
|
|
297
|
-
# 2. a validator with a funded wallet (keep it running in another shell)
|
|
298
|
-
solana-test-validator --reset
|
|
299
|
-
solana airdrop 100 --url http://127.0.0.1:8899 # ~/.config/solana/id.json
|
|
300
|
-
|
|
301
|
-
# 3. a site program with route 0 = /api/hello (pages style) and route 1 =
|
|
302
|
-
# /api/counter (POST, kv key "hits") — e.g. the two-route sample below
|
|
303
|
-
# (test validator = SBPF v3; mainnet = v0):
|
|
304
|
-
openzoo-transmute build <sample> --arch v3 # → <sample>/.zoo-out/.zoo/deploy/<name>.so
|
|
305
|
-
# then point the SAMPLE_SO constant in test/gateway.test.js at that .so
|
|
306
|
-
# (it is a fixed path today; no sample crate ships with the package yet)
|
|
307
|
-
|
|
308
|
-
# 4. run
|
|
309
|
-
OPENZOO_TEST_RPC=http://127.0.0.1:8899 npm test
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
`test/gateway.test.js` looks for the `.so` at `SAMPLE_SO` and a validator at
|
|
313
|
-
`OPENZOO_TEST_RPC` (default `http://127.0.0.1:8899`); `test/build.test.js`
|
|
314
|
-
probes `localnet` for real rent numbers and falls back to the 6.96 SOL/MB rule.
|
|
315
|
-
The runtime crate also builds on the host (`cd runtime/zoo-host && cargo build`;
|
|
316
|
-
its `Cargo.toml` pulls the sha2/curve25519 fallbacks for non-SBF targets), and
|
|
317
|
-
for the chain: `cargo build-sbf --arch v3` (test validator) or `--arch v0` (mainnet).
|
|
318
|
-
|
|
319
|
-
## Repository layout
|
|
320
|
-
|
|
321
|
-
```
|
|
322
|
-
bin/openzoo-transmute.js the executable (→ lib/cli.js run())
|
|
323
|
-
lib/cli.js build · deploy · serve · inspect · status
|
|
324
|
-
lib/vercel.js the Vercel deployment model (Build Output API v3 reader / synthesizer)
|
|
325
|
-
lib/eligibility.js what can run on chain, with reasons
|
|
326
|
-
lib/compile/ JS → Rust: parse.js (acorn + TS stripping) → ir.js (lowering) → rust.js (printer, crate skeleton, prelude)
|
|
327
|
-
lib/build.js crate + manifest + cargo build-sbf + cost sheet
|
|
328
|
-
lib/deploy.js deploy / upgrade + assets + manifest
|
|
329
|
-
lib/gateway.js the local HTTP front (routes, static, 402/413 seams, explorer)
|
|
330
|
-
lib/solana.js loader (deploy/upgrade in pure JS), assets, KV, invoke + discovery
|
|
331
|
-
lib/wire.js the bridge contract as bytes, PDAs
|
|
332
|
-
lib/wallet.js signer discovery, cluster URLs
|
|
333
|
-
runtime/zoo-host/ the no_std pinocchio runtime the generated crate links
|
|
334
|
-
docs/VERCEL_TO_SOLANA.md the reverse-engineering document
|
|
335
|
-
docs/OPENZOO_CLI_PATCH.md mounting build/deploy/serve in npx openzoo
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
## Limits at a glance
|
|
339
|
-
|
|
340
|
-
| | |
|
|
341
|
-
|---|---|
|
|
342
|
-
| request body | 900 B (one transaction is 1232 B) |
|
|
343
|
-
| response | ≤ 1024 B in return data, ≈ 7 KB through log chunks |
|
|
344
|
-
| KV value | 10 000 B JSON per key |
|
|
345
|
-
| compute | 400 k CU requested, 1.4 M max |
|
|
346
|
-
| clock | seconds |
|
|
347
|
-
| rent | 6.96 SOL per MB of static + program bytes, 2 × the `.so` reserved |
|
|
348
|
-
| not supported | network, fs, timers, randomness, regex, classes, streaming, `waitUntil`, ISR, crons, middleware, local imports |
|
|
349
|
-
|
|
350
|
-
## The hosted explorer (`hub`)
|
|
351
|
-
|
|
352
|
-
`serve` fronts one program on localhost. `hub` fronts every program on a
|
|
353
|
-
cluster from one public host, read-only:
|
|
354
|
-
|
|
355
|
-
```
|
|
356
|
-
openzoo-transmute hub --cluster mainnet --port 8080 # or: fly deploy (Dockerfile + fly.toml here)
|
|
357
|
-
https://<host>/s/<programId> pins that site (cookie) and opens its /
|
|
358
|
-
https://<host>/s/<programId>/api/x one page of it, absolute
|
|
359
|
-
https://<host>/.hub paste a program id; recently served sites
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
Reads are free simulations, exactly as in `serve`. Writes answer 402 on a
|
|
363
|
-
public host (a shared signer would be drained); use `serve` with your wallet
|
|
364
|
-
locally, or the x402-paid lane once the openzoo proxy fronts it. The site's
|
|
365
|
-
own root-relative links keep working because the pinned program rides a
|
|
366
|
-
cookie, so a transmuted app needs no base-path changes.
|
|
367
|
-
|
|
368
|
-
## Roadmap
|
|
369
|
-
|
|
370
|
-
* **Browser fork / `sol://` scheme** — a browser that resolves
|
|
371
|
-
`sol://<programId>/path` straight from the RPC (assets from PDAs, `/api/*`
|
|
372
|
-
through `simulateTransaction`), so a site needs no gateway and no domain.
|
|
373
|
-
* **x402-paid writes through the openzoo proxy** — the gateway already answers
|
|
374
|
-
`402` with an `x402` stub for mutating requests when it has no signer; next is
|
|
375
|
-
letting the openzoo proxy pay per request from the burner wallet so a public
|
|
376
|
-
gateway never holds keys.
|
|
377
|
-
* **Chunked request bodies** — bodies > 900 B staged into a request account over
|
|
378
|
-
several transactions (the same mechanism assets already use), then referenced
|
|
379
|
-
by the invoke.
|
|
380
|
-
* **Response accounts** — responses > ~7 KB written into a per-request account
|
|
381
|
-
and read back by the gateway, instead of the log buffer.
|
|
382
|
-
* Compiler coverage: local imports, `RegExp`, base64 (`atob`/`btoa`), `cookies()`,
|
|
383
|
-
`URLSearchParams`, more of `@vercel/kv` (`hget`, lists, `expire`).
|
|
384
|
-
|
|
385
|
-
License: MIT.
|
|
@@ -1,7 +0,0 @@
|
|
|
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);
|