openzoo 0.51.2 → 0.51.3
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 +12 -8
- package/package.json +2 -2
- package/transmute/README.md +42 -9
- package/transmute/lib/build.js +72 -2
- package/transmute/lib/cli.js +73 -17
- package/transmute/lib/compile/bytecode.js +591 -0
- package/transmute/lib/compile/hostids.js +11 -0
- package/transmute/lib/compile/index.js +16 -3
- package/transmute/lib/deploy.js +149 -1
- package/transmute/lib/gateway.js +14 -8
- package/transmute/lib/hub.js +13 -3
- package/transmute/lib/solana.js +67 -21
- package/transmute/lib/wire.js +42 -1
- package/transmute/prebuilt/zoo_vm.v0.so +0 -0
- package/transmute/runtime/zoo-host/examples/vmrun.rs +43 -0
- package/transmute/runtime/zoo-host/src/assets.rs +53 -10
- package/transmute/runtime/zoo-host/src/ctx.rs +15 -0
- package/transmute/runtime/zoo-host/src/helpers.rs +245 -0
- package/transmute/runtime/zoo-host/src/kv.rs +29 -8
- package/transmute/runtime/zoo-host/src/lib.rs +13 -0
- package/transmute/runtime/zoo-host/src/site.rs +94 -0
- package/transmute/runtime/zoo-host/src/vm.rs +902 -0
- package/transmute/runtime/zoo-vm/Cargo.lock +596 -0
- package/transmute/runtime/zoo-vm/Cargo.toml +23 -0
- package/transmute/runtime/zoo-vm/src/lib.rs +92 -0
- package/transmute/site/package.json +6 -0
- package/transmute/site/public/index.html +4 -4
package/bin/openzoo.js
CHANGED
|
@@ -170,18 +170,21 @@ usage:
|
|
|
170
170
|
npx openzoo balance wallet balance on every rail — Solana (USDC/TOKEN/SOL),
|
|
171
171
|
Base (USDC/ETH), Robinhood Chain (USDG/memecoins/ETH)
|
|
172
172
|
npx openzoo build [dir] transmute a Vercel-shaped app (Next.js pages/api,
|
|
173
|
-
app/**/route, Vite + api/*)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
npx openzoo
|
|
179
|
-
|
|
173
|
+
app/**/route, Vite + api/*) for Solana. Default
|
|
174
|
+
--target shared: bytecode for the shared zoo-vm
|
|
175
|
+
runtime, no Rust toolchain, a site is data accounts
|
|
176
|
+
(~0.03-0.1 SOL). --target program: own Pinocchio
|
|
177
|
+
program per site (--arch v0|v3, ~1 SOL+)
|
|
178
|
+
npx openzoo deploy [dir] deploy the site to Solana accounts (--cluster mainnet,
|
|
179
|
+
--runtime <zoo-vm id> or OPENZOO_VM_PROGRAM, --yes to
|
|
180
|
+
accept the rent estimate; burner wallet)
|
|
181
|
+
npx openzoo serve [site] local gateway/explorer for a deployed site (--runtime,
|
|
182
|
+
--cluster, --port 4402); /api/* reads are free
|
|
180
183
|
simulations, writes are signed by the burner
|
|
184
|
+
npx openzoo runtime deploy deploy the shared zoo-vm runtime once per cluster
|
|
181
185
|
npx openzoo inspect [dir] print the app in Vercel terms + eligibility report
|
|
182
186
|
npx openzoo hub hosted explorer for every transmuted site on the
|
|
183
187
|
cluster (what sites.openzoo.fun runs)
|
|
184
|
-
npx openzoo hub hosted explorer for every transmuted site on the cluster
|
|
185
188
|
npx openzoo address print both funding addresses (Solana + EVM)
|
|
186
189
|
npx openzoo help this text
|
|
187
190
|
|
|
@@ -421,6 +424,7 @@ async function main() {
|
|
|
421
424
|
case 'serve':
|
|
422
425
|
case 'inspect':
|
|
423
426
|
case 'hub':
|
|
427
|
+
case 'runtime':
|
|
424
428
|
case 'transmute': {
|
|
425
429
|
// VERCEL-SHAPED APP -> SOLANA MAINNET. `openzoo build|deploy|serve` hand
|
|
426
430
|
// the argv to openzoo-transmute (a sibling npm package): it reads the
|
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 \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
|
|
3
|
+
"version": "0.51.3",
|
|
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|runtime` transmute a Vercel-shaped app (Next.js / Vite + /api) into Solana accounts on the shared zoo-vm Pinocchio runtime (~0.05 SOL a site) or a dedicated program, mainnet.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
package/transmute/README.md
CHANGED
|
@@ -63,17 +63,48 @@ Requirements: Node ≥ 18, and for `build`/`deploy` the Solana toolchain
|
|
|
63
63
|
(`cargo build-sbf`): `sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)"`.
|
|
64
64
|
`build --skip-cargo` works without it (you get the crate and the cost sheet, no `.so`).
|
|
65
65
|
|
|
66
|
-
## Quickstart
|
|
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.
|
|
67
74
|
|
|
68
75
|
```sh
|
|
69
76
|
cd my-next-app # or a Vite repo with api/*.js, or a .vercel/output
|
|
70
77
|
npx openzoo-transmute inspect # the app in Vercel terms + eligibility report
|
|
71
|
-
npx openzoo-transmute build # → .zoo-out/.zoo/{
|
|
72
|
-
npx openzoo-transmute deploy --cluster
|
|
73
|
-
npx openzoo-transmute serve
|
|
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
|
|
74
81
|
open http://127.0.0.1:4402/ # explorer at /.zoo/, manifest at /.zoo/manifest.json
|
|
75
82
|
```
|
|
76
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
|
+
|
|
77
108
|
`inspect` prints one line per function with its route, style, runtime,
|
|
78
109
|
`maxDuration`, `memory` and methods, marks each `✓`/`✗` and says why:
|
|
79
110
|
|
|
@@ -115,14 +146,16 @@ POST /api/counter → 402 payment required (gateway started without
|
|
|
115
146
|
|
|
116
147
|
| command | what it does |
|
|
117
148
|
|---|---|
|
|
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; `
|
|
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 |
|
|
121
152
|
| `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 |
|
|
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 |
|
|
123
156
|
| `help`, `--version` | |
|
|
124
157
|
|
|
125
|
-
Environment: `OPENZOO_CLUSTER` (default `mainnet` for `deploy`/`status`, `localnet` for `serve`), `OPENZOO_RPC` (mainnet RPC
|
|
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
|
|
126
159
|
URL), `OPENZOO_KEYPAIR` / `OPENZOO_WALLET` (signer path), `OPENZOO_DEBUG=1`
|
|
127
160
|
(stack traces). Signer discovery order (`lib/wallet.js`): `--keypair` →
|
|
128
161
|
`OPENZOO_KEYPAIR` → `OPENZOO_WALLET` or `~/.openzoo/wallet.json` (the openzoo
|
package/transmute/lib/build.js
CHANGED
|
@@ -251,8 +251,10 @@ export async function build(root = '.', opts = {}) {
|
|
|
251
251
|
log(`reading ${root} → ${deployment.source}${deployment.framework ? ` (${deployment.framework})` : ''}: ${deployment.functions.length} function(s), ${deployment.staticFiles.length} static file(s)`);
|
|
252
252
|
for (const n of deployment.notes) { log(`note: ${n}`); notes.push(n); }
|
|
253
253
|
|
|
254
|
+
const target = opts.target === 'shared' ? 'shared' : 'program';
|
|
254
255
|
const transmute = opts.transmute || await loadTransmute();
|
|
255
|
-
const result = await transmute(deployment, { name, runtimePath, arch: opts.arch, root, outDir });
|
|
256
|
+
const result = await transmute(deployment, { name, runtimePath, arch: opts.arch, root, outDir, target });
|
|
257
|
+
if (target === 'shared') return buildShared({ root, outDir, zooDir, deployment, name, result, notes, log, opts });
|
|
256
258
|
if (!result || !result.crate || !result.crate['Cargo.toml'] || !result.crate['src/lib.rs']) throw new Error('transmute() returned no crate (expected {crate:{"Cargo.toml","src/lib.rs"}, manifest, report})');
|
|
257
259
|
const manifest = { name, ...(result.manifest || {}) };
|
|
258
260
|
if (!manifest.version) manifest.version = 1;
|
|
@@ -295,7 +297,7 @@ export async function build(root = '.', opts = {}) {
|
|
|
295
297
|
const soSize = soPath ? fs.statSync(soPath).size : null;
|
|
296
298
|
writeJson(path.join(zooDir, 'build.json'), {
|
|
297
299
|
name, crateName, root, outDir, arch, soPath, soSize, soName: soNameFor(crateName), builtAt: new Date().toISOString(),
|
|
298
|
-
runtimePath, cargo: cargo ? { ok: cargo.ok, missing: !!cargo.missing } : { skipped: true },
|
|
300
|
+
runtimePath, cargo: cargo ? { ok: cargo.ok, missing: !!cargo.missing } : { skipped: true }, target: 'program',
|
|
299
301
|
});
|
|
300
302
|
|
|
301
303
|
// Cost sheet.
|
|
@@ -309,3 +311,71 @@ export async function build(root = '.', opts = {}) {
|
|
|
309
311
|
log(`next: openzoo-transmute deploy ${path.relative(process.cwd(), outDir) || outDir} --cluster <localnet|devnet|mainnet>`);
|
|
310
312
|
return { outDir, zooDir, crateDir, manifest, report, soPath, arch, costEstimate, deployment, notes, staticPlan };
|
|
311
313
|
}
|
|
314
|
+
|
|
315
|
+
// ---------------------------------------------------------------- shared runtime
|
|
316
|
+
|
|
317
|
+
/** Bytes of the site account the shared runtime creates per site. */
|
|
318
|
+
export const SITE_ACCOUNT_BYTES = 66;
|
|
319
|
+
|
|
320
|
+
/** Rent for a shared-runtime site: site account + code asset + assets + manifest (no program). */
|
|
321
|
+
export async function estimateSharedCost({ staticFiles = [], codeSize = 0, manifestBytes = 0, connection = null }) {
|
|
322
|
+
const est = await estimateCost({ staticFiles, soSize: null, manifestBytes, connection });
|
|
323
|
+
est.items = est.items.filter((i) => i.kind !== 'program');
|
|
324
|
+
const rent = async (n) => { if (connection) { try { return await connection.getMinimumBalanceForRentExemption(n); } catch {} } return (ACCOUNT_OVERHEAD_BYTES + n) * LAMPORTS_PER_BYTE; };
|
|
325
|
+
const codeBytes = accountBytesFor('asset', codeSize, 'application/x-zoo-bytecode');
|
|
326
|
+
const extra = [
|
|
327
|
+
{ label: 'site account (shared runtime)', kind: 'site', bytes: SITE_ACCOUNT_BYTES, lamports: await rent(SITE_ACCOUNT_BYTES) },
|
|
328
|
+
{ label: `/.zoo/code.bin (bytecode = ${codeSize.toLocaleString()} B)`, kind: 'asset', bytes: codeBytes, lamports: await rent(codeBytes) },
|
|
329
|
+
];
|
|
330
|
+
for (const it of extra) it.sol = it.lamports / LAMPORTS_PER_SOL;
|
|
331
|
+
est.items = [...extra, ...est.items];
|
|
332
|
+
est.lamports += extra.reduce((n, i) => n + i.lamports, 0);
|
|
333
|
+
est.sol = est.lamports / LAMPORTS_PER_SOL;
|
|
334
|
+
est.feeLamports = 5000 * (2 + Math.ceil(codeSize / 900) + staticFiles.reduce((n, f) => n + 1 + Math.ceil(f.size / 900), 0) + 2);
|
|
335
|
+
est.feeSol = est.feeLamports / LAMPORTS_PER_SOL;
|
|
336
|
+
est.totalLamports = est.lamports + est.transientLamports + est.feeLamports;
|
|
337
|
+
est.totalSol = est.totalLamports / LAMPORTS_PER_SOL;
|
|
338
|
+
est.unknownProgram = false;
|
|
339
|
+
est.shared = true;
|
|
340
|
+
return est;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** `build --target shared`: bytecode module instead of a crate; nothing to compile. */
|
|
344
|
+
async function buildShared({ root, outDir, zooDir, deployment, name, result, notes, log, opts }) {
|
|
345
|
+
if (!result || !result.code || !result.code.length) throw new Error('transmute() returned no bytecode module for --target shared');
|
|
346
|
+
const manifest = { name, ...(result.manifest || {}), target: 'shared' };
|
|
347
|
+
if (!manifest.version) manifest.version = 1;
|
|
348
|
+
if (!manifest.framework) manifest.framework = deployment.framework;
|
|
349
|
+
if (!Array.isArray(manifest.routes)) manifest.routes = [];
|
|
350
|
+
if (!Array.isArray(manifest.static)) manifest.static = deployment.staticFiles.map((f) => ({ path: f.path, contentType: f.contentType, size: f.size }));
|
|
351
|
+
if (!manifest.config) manifest.config = deployment.config;
|
|
352
|
+
const runtime = opts.runtime || process.env.OPENZOO_VM_PROGRAM || null;
|
|
353
|
+
if (runtime) manifest.runtime = runtime;
|
|
354
|
+
const report = { eligible: [], ineligible: [], warnings: [], ...(result.report || {}) };
|
|
355
|
+
|
|
356
|
+
fs.rmSync(path.join(zooDir, 'crate'), { recursive: true, force: true });
|
|
357
|
+
fs.mkdirSync(zooDir, { recursive: true });
|
|
358
|
+
const codePath = path.join(zooDir, 'code.bin');
|
|
359
|
+
fs.writeFileSync(codePath, result.code);
|
|
360
|
+
const byPath = new Map(deployment.staticFiles.map((f) => [f.path, f]));
|
|
361
|
+
const staticPlan = manifest.static.map((s) => ({ ...s, file: byPath.get(s.path)?.file || s.file || null }));
|
|
362
|
+
writeJson(path.join(zooDir, 'manifest.json'), manifest);
|
|
363
|
+
writeJson(path.join(zooDir, 'report.json'), report);
|
|
364
|
+
writeJson(path.join(zooDir, 'static-plan.json'), staticPlan);
|
|
365
|
+
const codeSize = result.code.length;
|
|
366
|
+
writeJson(path.join(zooDir, 'build.json'), { name, root, outDir, target: 'shared', codePath, codeSize, runtime, builtAt: new Date().toISOString(), cargo: { skipped: true } });
|
|
367
|
+
log(`wrote ${path.relative(process.cwd(), zooDir) || zooDir}/{code.bin (${codeSize.toLocaleString()} B bytecode), manifest.json, report.json, static-plan.json}`);
|
|
368
|
+
log(`functions: ${report.eligible.length} eligible, ${report.ineligible.length} ineligible${report.warnings.length ? `, ${report.warnings.length} warning(s)` : ''}`);
|
|
369
|
+
for (const i of report.ineligible) log(` ✗ ${i.name}: ${i.reason}${i.file ? ` (${path.relative(root, i.file)}${i.line ? ':' + i.line : ''})` : ''}`);
|
|
370
|
+
for (const w of report.warnings) log(` ! ${typeof w === 'string' ? w : w.message || JSON.stringify(w)}`);
|
|
371
|
+
if (!runtime) { const n = 'no --runtime given: pass --runtime <zoo-vm program id> (or OPENZOO_VM_PROGRAM) at deploy time'; notes.push(n); log(`note: ${n}`); }
|
|
372
|
+
|
|
373
|
+
const connection = opts.connection !== undefined ? opts.connection : await probeConnection(opts.cluster || 'localnet');
|
|
374
|
+
const manifestBytes = Buffer.byteLength(JSON.stringify({ ...manifest, runtime: 'x'.repeat(44), site: 'x'.repeat(44), deployedAt: new Date().toISOString() }));
|
|
375
|
+
const costEstimate = await estimateSharedCost({ staticFiles: staticPlan, codeSize, manifestBytes, connection });
|
|
376
|
+
log('');
|
|
377
|
+
log(formatCostTable(costEstimate));
|
|
378
|
+
log('');
|
|
379
|
+
log(`next: openzoo-transmute deploy ${path.relative(process.cwd(), outDir) || outDir} --cluster mainnet${runtime ? '' : ' --runtime <id>'}`);
|
|
380
|
+
return { outDir, zooDir, crateDir: null, manifest, report, soPath: null, codePath, codeSize, arch: null, target: 'shared', costEstimate, deployment, notes, staticPlan };
|
|
381
|
+
}
|
package/transmute/lib/cli.js
CHANGED
|
@@ -14,20 +14,25 @@ import path from 'node:path';
|
|
|
14
14
|
import { PublicKey } from '@solana/web3.js';
|
|
15
15
|
import { readDeployment } from './vercel.js';
|
|
16
16
|
import { build } from './build.js';
|
|
17
|
-
import { deploy, resolveOutDir, clusterLabel } from './deploy.js';
|
|
17
|
+
import { deploy, deployRuntime, resolveOutDir, clusterLabel } from './deploy.js';
|
|
18
18
|
import { startGateway, DEFAULT_PORT } from './gateway.js';
|
|
19
19
|
import { startHub, DEFAULT_HUB_PORT } from './hub.js';
|
|
20
|
-
import { connect, getProgramInfo, readManifest } from './solana.js';
|
|
20
|
+
import { connect, getProgramInfo, getSiteInfo, readManifest } from './solana.js';
|
|
21
21
|
import { loadWallet, rpcUrl } from './wallet.js';
|
|
22
22
|
import { MANIFEST_PATH } from './wire.js';
|
|
23
23
|
|
|
24
24
|
export const USAGE = `openzoo-transmute — Vercel app → Solana program + asset accounts
|
|
25
25
|
|
|
26
26
|
usage:
|
|
27
|
-
openzoo-transmute build [dir] [--out .zoo-out] [--name <crate>] [--arch v0|v3] [--cluster <c>] [--skip-cargo]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
openzoo-transmute
|
|
27
|
+
openzoo-transmute build [dir] [--target shared|program] [--out .zoo-out] [--name <crate>] [--arch v0|v3] [--cluster <c>] [--skip-cargo]
|
|
28
|
+
--target shared (default): bytecode for the shared zoo-vm runtime — a site is data accounts only (≈0.05–0.1 SOL)
|
|
29
|
+
--target program: your own Pinocchio program per site (≈1 SOL+)
|
|
30
|
+
openzoo-transmute deploy [dir|outDir] [--cluster mainnet|devnet|localnet|<url>] [--keypair <path>] [--yes] [--runtime <zoo-vm id>]
|
|
31
|
+
[--program <id>] [--concurrency 4] [--skip-assets] [--force] [--headroom 1.5]
|
|
32
|
+
openzoo-transmute serve <siteId|programId> [--runtime <zoo-vm id>] [--cluster <c>] [--port ${DEFAULT_PORT}] [--keypair <path>] [--host 127.0.0.1] [--quiet]
|
|
33
|
+
openzoo-transmute runtime deploy [--cluster <c>] [--keypair <path>] [--yes] [--program-keypair zoo-vm-keypair.json] [--so <path>]
|
|
34
|
+
deploy (or upgrade) the shared zoo-vm runtime once per cluster
|
|
35
|
+
openzoo-transmute runtime status <zoo-vm id> [--cluster <c>]
|
|
31
36
|
openzoo-transmute hub [--cluster mainnet] [--port 8080] [--host 0.0.0.0] [--public-url <https://…>]
|
|
32
37
|
hosted explorer for EVERY program on the cluster (/s/<programId>), read-only
|
|
33
38
|
openzoo-transmute inspect [dir] print the Vercel model + eligibility report
|
|
@@ -35,7 +40,7 @@ usage:
|
|
|
35
40
|
openzoo-transmute help
|
|
36
41
|
|
|
37
42
|
clusters: mainnet (default, needs --yes to deploy), devnet, testnet, localnet, or an RPC URL.
|
|
38
|
-
env: OPENZOO_CLUSTER, OPENZOO_RPC (mainnet RPC), OPENZOO_KEYPAIR / OPENZOO_WALLET (signer).
|
|
43
|
+
env: OPENZOO_CLUSTER, OPENZOO_RPC (mainnet RPC), OPENZOO_KEYPAIR / OPENZOO_WALLET (signer), OPENZOO_VM_PROGRAM (shared runtime id).
|
|
39
44
|
`;
|
|
40
45
|
|
|
41
46
|
/** `--k v`, `--k=v`, `--flag`, `--no-flag`; everything else is positional. */
|
|
@@ -114,26 +119,49 @@ async function tryTransmute(deployment) {
|
|
|
114
119
|
}
|
|
115
120
|
}
|
|
116
121
|
|
|
122
|
+
export const DEFAULT_TARGET = 'shared';
|
|
123
|
+
|
|
124
|
+
/** `--runtime` is the zoo-vm program id (shared) — or, for `--target program`, a zoo-host checkout path. */
|
|
125
|
+
function runtimeFlag(f) {
|
|
126
|
+
const v = f.runtime;
|
|
127
|
+
if (typeof v !== 'string') return { runtime: process.env.OPENZOO_VM_PROGRAM || null, runtimePath: f.runtimePath || undefined };
|
|
128
|
+
if (fs.existsSync(v) && fs.existsSync(path.join(v, 'Cargo.toml'))) return { runtime: process.env.OPENZOO_VM_PROGRAM || null, runtimePath: v };
|
|
129
|
+
new PublicKey(v);
|
|
130
|
+
return { runtime: v, runtimePath: f.runtimePath || undefined };
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
async function cmdBuild(p, f, log) {
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
134
|
+
const target = f.target || process.env.OPENZOO_TARGET || DEFAULT_TARGET;
|
|
135
|
+
if (!['shared', 'program'].includes(target)) throw new Error(`--target must be shared or program (got ${target})`);
|
|
136
|
+
const { runtime, runtimePath } = runtimeFlag(f);
|
|
137
|
+
const r = await build(p[0] || '.', { out: f.out, name: f.name, arch: f.arch, cluster: f.cluster, target, runtime, runtimePath, skipCargo: !!f.skipCargo, log });
|
|
138
|
+
if (f.json) log(JSON.stringify({ outDir: r.outDir, target: r.target || 'program', soPath: r.soPath, codePath: r.codePath || null, codeSize: r.codeSize || null, arch: r.arch, report: r.report, costEstimate: { sol: r.costEstimate.sol, totalSol: r.costEstimate.totalSol, source: r.costEstimate.source } }, null, 2));
|
|
120
139
|
return r.report.ineligible.length && !r.report.eligible.length && r.manifest.routes.length === 0 && r.deployment.functions.length ? 2 : 0;
|
|
121
140
|
}
|
|
122
141
|
|
|
123
142
|
async function cmdDeploy(p, f, log) {
|
|
124
|
-
const
|
|
143
|
+
const { runtime } = runtimeFlag(f);
|
|
144
|
+
const r = await deploy({ outDir: p[0] || '.', cluster: f.cluster, keypair: f.keypair, programId: f.program, runtime, yes: !!f.yes, concurrency: f.concurrency ? Number(f.concurrency) : undefined, skipAssets: !!f.skipAssets, force: !!f.force, headroom: f.headroom, log });
|
|
125
145
|
if (f.json) log(JSON.stringify(r, null, 2));
|
|
126
146
|
return 0;
|
|
127
147
|
}
|
|
128
148
|
|
|
129
149
|
async function cmdServe(p, f, log) {
|
|
130
150
|
let programId = p[0];
|
|
151
|
+
let { runtime } = runtimeFlag(f);
|
|
131
152
|
if (!programId) {
|
|
132
153
|
// Fall back to the last deploy in ./ or ./.zoo-out.
|
|
133
|
-
try {
|
|
134
|
-
|
|
154
|
+
try {
|
|
155
|
+
const d = JSON.parse(fs.readFileSync(path.join(resolveOutDir('.'), '.zoo', 'deploy.json'), 'utf8'));
|
|
156
|
+
programId = d.target === 'shared' ? d.site : d.programId;
|
|
157
|
+
if (d.target === 'shared' && !runtime) runtime = d.runtime;
|
|
158
|
+
} catch { /* none */ }
|
|
159
|
+
if (!programId) throw new Error('usage: serve <siteId|programId> [--runtime <zoo-vm id>] (no .zoo/deploy.json found to infer it from)');
|
|
135
160
|
}
|
|
136
161
|
new PublicKey(programId); // validate
|
|
162
|
+
// Shared runtime: the positional is the site id, the program is zoo-vm.
|
|
163
|
+
let site = null;
|
|
164
|
+
if (runtime) { site = programId; programId = runtime; }
|
|
137
165
|
let keypair = null, walletPath = null;
|
|
138
166
|
if (f.keypair !== false) {
|
|
139
167
|
try { const w = loadWallet({ keypair: typeof f.keypair === 'string' ? f.keypair : undefined }); keypair = w.keypair; walletPath = w.path; }
|
|
@@ -142,10 +170,10 @@ async function cmdServe(p, f, log) {
|
|
|
142
170
|
const port = f.port != null ? Number(f.port) : DEFAULT_PORT;
|
|
143
171
|
const host = f.host || '127.0.0.1';
|
|
144
172
|
if (keypair && !/^(127\.0\.0\.1|localhost|::1|\[::1\])$/.test(host)) log(`warning: binding ${host} with a signer: anyone who can reach this gateway can send transactions paid by ${keypair.publicKey.toBase58()}; use --no-keypair unless that is intended`);
|
|
145
|
-
const gw = await startGateway({ programId, cluster: f.cluster, port, host, keypair, log, quiet: !!f.quiet });
|
|
173
|
+
const gw = await startGateway({ programId, site, cluster: f.cluster, port, host, keypair, log, quiet: !!f.quiet });
|
|
146
174
|
const m = gw.state.manifest;
|
|
147
175
|
log(`openzoo-transmute gateway → ${gw.url}/ (explorer ${gw.url}/.zoo/, manifest ${gw.url}${MANIFEST_PATH})`);
|
|
148
|
-
log(`program ${programId} on ${gw.state.cluster} · ${m.routes.length} function(s), ${m.static ? m.static.length : '?'} asset(s) · signer ${keypair ? keypair.publicKey.toBase58() + (walletPath ? ` (${walletPath})` : '') : 'none (reads only)'}`);
|
|
176
|
+
log(`${site ? `site ${site} on runtime ${programId}` : `program ${programId}`} on ${gw.state.cluster} · ${m.routes.length} function(s), ${m.static ? m.static.length : '?'} asset(s) · signer ${keypair ? keypair.publicKey.toBase58() + (walletPath ? ` (${walletPath})` : '') : 'none (reads only)'}`);
|
|
149
177
|
for (const r of m.routes) log(` ${(r.methods && r.methods.length ? r.methods.join('|') : 'ANY').padEnd(16)} ${r.routePath}`);
|
|
150
178
|
const stop = () => gw.close().then(() => process.exit(0));
|
|
151
179
|
process.once('SIGINT', stop);
|
|
@@ -165,10 +193,24 @@ async function cmdInspect(p, f, log) {
|
|
|
165
193
|
}
|
|
166
194
|
|
|
167
195
|
async function cmdStatus(p, f, log) {
|
|
168
|
-
if (!p[0]) throw new Error('usage: status <programId> [--cluster <c>]');
|
|
169
|
-
const
|
|
196
|
+
if (!p[0]) throw new Error('usage: status <siteId|programId> [--runtime <zoo-vm id>] [--cluster <c>]');
|
|
197
|
+
const { runtime } = runtimeFlag(f);
|
|
170
198
|
const rpc = rpcUrl(f.cluster);
|
|
171
199
|
const connection = connect(rpc);
|
|
200
|
+
if (runtime) {
|
|
201
|
+
const site = new PublicKey(p[0]);
|
|
202
|
+
const [info, manifest] = await Promise.all([getSiteInfo(connection, new PublicKey(runtime), site), readManifest(connection, new PublicKey(runtime), site).catch(() => null)]);
|
|
203
|
+
const out = { site: site.toBase58(), runtime, cluster: clusterLabel(f.cluster), rpc: rpc.replace(/\?.*$/, ''), exists: info.exists, authority: info.authority ? info.authority.toBase58() : null, account: info.pda.toBase58(), manifest: manifest ? { name: manifest.name, routes: manifest.routes?.length ?? 0, static: manifest.static?.length ?? 0, deployedAt: manifest.deployedAt } : null };
|
|
204
|
+
if (f.json) { log(JSON.stringify(out, null, 2)); return info.exists ? 0 : 1; }
|
|
205
|
+
log(`site ${out.site} on runtime ${runtime} (${out.cluster})`);
|
|
206
|
+
if (!info.exists) { log('status NOT DEPLOYED'); return 1; }
|
|
207
|
+
log(`authority ${out.authority}`);
|
|
208
|
+
log(`account ${out.account}`);
|
|
209
|
+
if (manifest) { log(`manifest ${manifest.name || ''} · ${out.manifest.routes} function(s), ${out.manifest.static} asset(s) · deployed ${manifest.deployedAt || '?'}`); for (const r of manifest.routes || []) log(` ${(r.methods && r.methods.length ? r.methods.join('|') : 'ANY').padEnd(16)} ${r.routePath}`); }
|
|
210
|
+
else log(`manifest none at ${MANIFEST_PATH}`);
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
const programId = new PublicKey(p[0]);
|
|
172
214
|
const [info, slot, manifest] = await Promise.all([getProgramInfo(connection, programId), connection.getSlot('confirmed'), readManifest(connection, programId).catch(() => null)]);
|
|
173
215
|
const out = {
|
|
174
216
|
programId: programId.toBase58(), cluster: clusterLabel(f.cluster), rpc: rpc.replace(/\?.*$/, ''), slot,
|
|
@@ -215,13 +257,27 @@ async function cmdHub(p, f, log) {
|
|
|
215
257
|
return 0;
|
|
216
258
|
}
|
|
217
259
|
|
|
260
|
+
async function cmdRuntime(p, f, log) {
|
|
261
|
+
const sub = p[0];
|
|
262
|
+
if (sub === 'deploy') {
|
|
263
|
+
const r = await deployRuntime({ cluster: f.cluster, keypair: f.keypair, yes: !!f.yes, programKeypair: f.programKeypair, so: f.so, headroom: f.headroom, log });
|
|
264
|
+
if (f.json) log(JSON.stringify(r, null, 2));
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
if (sub === 'status') {
|
|
268
|
+
if (!p[1]) throw new Error('usage: runtime status <zoo-vm id> [--cluster <c>]');
|
|
269
|
+
return cmdStatus([p[1]], { ...f, runtime: undefined }, log);
|
|
270
|
+
}
|
|
271
|
+
throw new Error('usage: runtime deploy|status');
|
|
272
|
+
}
|
|
273
|
+
|
|
218
274
|
export async function run(argv = process.argv.slice(2), io = {}) {
|
|
219
275
|
const log = io.log ?? console.log;
|
|
220
276
|
const error = io.error ?? console.error;
|
|
221
277
|
const { cmd, positionals, flags } = parseArgs(argv);
|
|
222
278
|
if (flags.version) { log(JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version); return 0; }
|
|
223
279
|
if (flags.help || cmd === 'help' || cmd === '--help') { log(USAGE); return 0; }
|
|
224
|
-
const commands = { build: cmdBuild, deploy: cmdDeploy, serve: cmdServe, inspect: cmdInspect, status: cmdStatus, hub: cmdHub };
|
|
280
|
+
const commands = { build: cmdBuild, deploy: cmdDeploy, serve: cmdServe, inspect: cmdInspect, status: cmdStatus, hub: cmdHub, runtime: cmdRuntime };
|
|
225
281
|
const fn = commands[cmd];
|
|
226
282
|
if (!fn) { error(`unknown command: ${cmd}\n`); error(USAGE); process.exitCode = 1; return 1; }
|
|
227
283
|
try {
|