xark-cli 0.2.1

The xark toolchain CLI
//! `xark client` — scaffold a small TypeScript starter that verifies this
//! circuit's proofs with the `xark-client` library.
//!
//! It copies the snarkjs verifying key (`xark setup`) and writes an `example.ts`
//! that builds a client from it and verifies a proof bundle from `xark prove`.
//! Proving stays in the CLI — snarkjs can verify an xark proof but can't
//! generate one — so the client verifies and hands you the on-chain calldata.

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 {
    /// Circuit crate directory (or its `target/xark/` output dir). Defaults to
    /// the current directory; paths are inferred from `target/xark/`.
    #[arg(value_hint = clap::ValueHint::DirPath)]
    pub path: Option<PathBuf>,

    /// Output directory for the generated client. Inferred as
    /// `target/xark/<name>/client`.
    #[arg(long, value_hint = clap::ValueHint::DirPath)]
    pub out: Option<PathBuf>,
}

pub fn run(args: ClientArgs) -> Result<()> {
    let project = XarkProject::resolve(args.path.clone())?;

    // The client verifies with snarkjs, which needs the verifying key. Guide the
    // user to `xark setup` rather than scaffolding a client that can't verify.
    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`.
"#;