Quickstart
This page walks you through the shortest path to a successful zkVerify submission. You will learn the minimum inputs required, how Kurier and zkVerifyJS differ, and what a completed verification loop looks like. If you want the fastest start, Kurier is usually simpler; if you need direct on-chain control from the beginning, use zkVerifyJS.
To avoid getting stuck on environment setup, this page keeps only the minimum steps. You need three things: a proof, a vk, and public inputs. The Kurier path registers the vk first. The zkVerifyJS path submits the verification request directly on-chain.
If you want to look at a running example first, open the zkEscrow demo. It gives you a concrete feel for the end-to-end verification loop before you wire your own app.
Path A: Kurier (REST API)â
Kurier requires an API key. Request one first, then place it in .env.
API_KEY=your_kurier_api_key
The minimal request structure for registering a vk is shown below (Groth16 example):
const regParams = {proofType: "groth16",proofOptions: { library: "snarkjs", curve: "bn128" },vk: key}const regResponse = await axios.post(`${API_URL}/register-vk/${process.env.API_KEY}`, regParams)
The minimal structure for submitting a proof is shown below (UltraHonk example):
const params = {proofType: "ultrahonk",vkRegistered: true,chainId: 11155111,proofData: {proof: proof.proof,publicSignals: proof.pub_inputs,vk: vk.vkHash || vk.meta.vkHash}}const requestResponse = await axios.post(`${API_URL}/submit-proof/${process.env.API_KEY}`, params)
After submission, poll job-status until the status becomes Finalized:
const jobStatusResponse = await axios.get(`${API_URL}/job-status/${process.env.API_KEY}/${requestResponse.data.jobId}`)if (jobStatusResponse.data.status === "Finalized") {// verified}
Path B: zkVerifyJS (Direct Chain Interaction)â
zkVerifyJS requires a seed phrase to sign transactions, and the account must hold tVFY to pay transaction fees.
SEED_PHRASE="this is my seed phrase i should not share it with anyone"
Start a session and submit the verification request:
const session = await zkVerifySession.start().Volta().withAccount(process.env.SEED_PHRASE)await session.verify().groth16({ library: Library.snarkjs, curve: CurveType.bn128 }).execute({proofData: { vk: key, proof: proof, publicSignals: publicInputs },domainId: 0})
Common Failure Pointsâ
The most common issue is passing vk or public inputs in the wrong format, which causes the verification result to never appear. On the Kurier path, first confirm that register-vk successfully returned the vk hash before submitting the proof. On the zkVerifyJS path, confirm the account has tVFY, otherwise the transaction will not be accepted on-chain.