use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
use clap::Args;
use crate::xark_project::XarkProject;
#[derive(Args, Debug)]
pub struct ClientArgs {
#[arg(value_hint = clap::ValueHint::DirPath)]
pub path: Option<PathBuf>,
#[arg(long, value_hint = clap::ValueHint::DirPath)]
pub out: Option<PathBuf>,
}
pub fn run(args: ClientArgs) -> Result<()> {
let project = XarkProject::resolve(args.path.clone())?;
let vk_path = project.snarkjs_vk();
let vk_json = fs::read_to_string(&vk_path).map_err(|_| {
anyhow::anyhow!(
"no snarkjs verifying key at {} — run `xark setup {}` first",
vk_path.display(),
super::path_arg(&args.path)
)
})?;
let name = project.entry_name();
let out = args.out.clone().unwrap_or_else(|| project.client_dir());
fs::create_dir_all(&out).with_context(|| format!("creating {}", out.display()))?;
write(
&out.join("verification_key.json"),
&format!("{}\n", vk_json.trim()),
)?;
let subst = |t: &str| t.replace("__NAME__", &name);
write(&out.join("package.json"), &subst(PACKAGE_JSON))?;
write(&out.join("example.ts"), &subst(EXAMPLE_TS))?;
write(&out.join("README.md"), &subst(README_MD))?;
println!(
"Generated TypeScript client for `{name}` at {}",
out.display()
);
println!(
"{}",
crate::style::brand("✅ Client ready — verify (snarkjs) + on-chain calldata bytes.")
);
let dir = out.display();
println!(
"\n{}",
crate::style::next_steps(&[
(
format!("cd {dir}"),
"then install deps — see README (xark-client isn't on npm yet)",
),
(
"npx tsx example.ts <path-to-.proof.json>".to_string(),
"verify a proof produced by `xark prove`",
),
])
);
Ok(())
}
fn write(path: &Path, contents: &str) -> Result<()> {
fs::write(path, contents).with_context(|| format!("writing {}", path.display()))?;
Ok(())
}
const PACKAGE_JSON: &str = r#"{
"name": "__NAME__-client",
"version": "0.1.0",
"private": true,
"description": "Verify proofs for the __NAME__ xark circuit (built on xark-client).",
"scripts": {
"verify": "tsx example.ts"
},
"dependencies": {
"xark-client": "^0.1.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0"
}
}
"#;
const EXAMPLE_TS: &str = r#"// Verify a proof for the __NAME__ circuit with the xark-client library.
//
// npm install
// npx tsx example.ts <path-to-.proof.json>
//
// Produce a bundle first with: xark prove <circuit> --inputs <JSON|FILE>
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { XarkClient, type ProofBundle } from "xark-client";
// The snarkjs verifying key copied here by `xark client` (from `xark setup`).
const vk = JSON.parse(readFileSync(join(__dirname, "verification_key.json"), "utf8"));
async function main() {
const path = process.argv[2];
if (!path) {
console.error("usage: npx tsx example.ts <path-to-.proof.json>");
console.error(" produce one with: xark prove <circuit> --inputs <JSON|FILE>");
process.exit(2);
}
const client = new XarkClient(vk);
const bundle = JSON.parse(readFileSync(path, "utf8")) as ProofBundle;
const ok = await client.verify(bundle);
console.log(ok ? "✅ proof verified (snarkjs)" : "❌ proof INVALID");
// For on-chain use: `client.calldata(bundle)` returns the packed
// `proof ‖ public_inputs` bytes. Your program owns the accounts and any
// instruction discriminator, so build the transaction with your own client:
//
// const data = client.calldata(bundle); // Uint8Array
process.exit(ok ? 0 : 1);
}
main().catch((e) => {
console.error(e);
process.exit(1);
});
"#;
const README_MD: &str = r#"# __NAME__ — client
Generated by `xark client`. Built on the **xark-client** library: construct a
client from this circuit's snarkjs verifying key, then verify proofs and get the
on-chain calldata bytes.
## Install
`xark-client` isn't published to npm yet, so point the dependency at your local
checkout of the xark repo first, then install:
```bash
npm install /path/to/xark/clients/typescript # the xark-client library
npm install # tsx (+ snarkjs, via xark-client)
```
(Once `xark-client` is published, `npm install` alone will suffice.)
## Verify a proof
```bash
xark prove <circuit> --inputs <JSON|FILE> # writes <name>-<hash>.proof.json
npx tsx example.ts /path/to/<name>-<hash>.proof.json
```
## In your own app
```ts
import { XarkClient } from "xark-client";
import vk from "./verification_key.json";
const client = new XarkClient(vk);
await client.verify(bundle); // snarkjs verify
const data = client.calldata(bundle); // packed proof ‖ public bytes (Uint8Array)
```
`data` is the verifier calldata — you own the accounts and any discriminator, so
build the transaction with your own client. The on-chain verifier itself comes
from `xark export`.
Proving happens in the `xark` CLI: snarkjs can verify an xark proof but cannot
generate one. Flow: `xark prove …` → feed the `.proof.json` bundle here.
Files: `verification_key.json` (snarkjs VK), `example.ts`.
"#;