Skip to content

Multi-Query Verification

Multi-query verification lets you require users to satisfy multiple eligibility criteria in a single verification session. Instead of passing a single query, you provide an array of queries via the sdkQuery prop — the SDK handles credential issuance, proof generation, and result aggregation for all of them.

How it works

The SdkQuery type is an array:

type SdkQuery = Array<ZeroKnowledgeProofRequest & { subjectTitle: string }>;

Each entry in the array is an independent eligibility criterion. When the SDK receives multiple queries it:

  1. Identifies required credential types from all queries (e.g. EssentialIdCredential, AMLCTFCredential).
  2. Issues missing credentials — if the user doesn't yet hold a required credential, the SDK triggers the appropriate KYC/KYB flow. Credentials are issued one type at a time; the user is returned to the eligibility screen to continue with the next type.
  3. Generates proofs for all queries in a single batch via requestProof.
  4. Submits proofs — off-chain proofs are verified server-side; on-chain proofs are submitted to the verifier contract (V2 batches all proofs into a single transaction).
  5. Returns results — the onSuccess callback receives a proofs array (one entry per query, in the same order) and an eligibility object that summarises the overall outcome.

Defining multiple queries

Each query in the array must have a unique id. You can mix different credential types, operators, proof methods, and even on-chain and off-chain circuits in the same request.

For single-mode examples (off-chain only), see Individual Verification and Business Verification.

Mixed on-chain and off-chain

A single sdkQuery can combine on-chain and off-chain queries. The SDK separates them automatically — on-chain proofs are submitted to the verifier contract while off-chain proofs are verified server-side. You will need a deployed verifier contract for the on-chain queries — see On-chain verification for the full setup guide including contract deployment, registering ZKP requests, and configuring onChainMetaData.

import {
  AvererWebSdk,
  AvererSdkProvider,
  type SdkQuery,
  type SdkSuccessRes,
} from '@averer/averer-websdk';

const sdkQuery: SdkQuery = [
  // On-chain: ZKP age check — submitted to verifier contract
  {
    id: 3,
    circuitId: 'credentialAtomicQuerySigV2OnChain',
    subjectTitle: '18+ Years Old',
    query: {
      allowedIssuers: ['*'],
      type: 'EssentialIdCredential',
      context:
        'https://raw.githubusercontent.com/redbellynetwork/receptor-schema/refs/heads/main/schemas/json-ld/EssentialIdCredential.jsonld',
      credentialSubject: { birthDate: { $lt: 20070622 } },
    },
  },
  // Off-chain: ZKP AML/CTF — verified server-side
  {
    id: 6,
    circuitId: 'credentialAtomicQuerySigV2',
    subjectTitle: 'Pass AML & CTF Checks',
    query: {
      allowedIssuers: ['*'],
      type: 'AMLCTFCredential',
      context:
        'https://raw.githubusercontent.com/redbellynetwork/receptor-schema/refs/heads/main/schemas/json-ld/AMLCTFCredential.jsonld',
      skipClaimRevocationCheck: true,
      credentialSubject: { amlCheckStatus: { $eq: 'passed' } },
    },
  },
  // Off-chain: Selective Disclosure — reveals name to verifier
  {
    id: 8,
    circuitId: 'credentialAtomicQuerySigV2',
    subjectTitle: 'Full Name',
    query: {
      allowedIssuers: ['*'],
      type: 'EssentialIdCredential',
      context:
        'https://raw.githubusercontent.com/redbellynetwork/receptor-schema/refs/heads/main/schemas/json-ld/EssentialIdCredential.jsonld',
      skipClaimRevocationCheck: true,
      credentialSubject: { name: {} },
    },
  },
];

const onChainMetaData = {
  contract_address: '0x1320e9a482116A59BD8751388cceEB7f75160BA5', // must match your deployed verifier contract address
  method_id: 'ade09fcd', // submitZKPResponseV2
};

export default function MultiCriteriaPage() {
  const handleSuccess = (data: SdkSuccessRes) => {
    // data.proofs[0] → on-chain age check
    // data.proofs[1] → off-chain AML/CTF
    // data.proofs[2] → off-chain disclosed name
    if (data.eligibility.passed) {
      console.log('All criteria met', data.walletAddress);
      console.log('On-chain tx:', data.onChain?.txHash);
    }
  };

  return (
    <AvererSdkProvider configId="your-config-id">
      <AvererWebSdk
        appName="My Platform"
        sdkQuery={sdkQuery}
        onChainMetaData={onChainMetaData}
        onSuccess={handleSuccess}
        onError={(reason) => console.error(reason)}
      />
    </AvererSdkProvider>
  );
}

This example combines three criteria across both on-chain and off-chain in a single session:

# Query Method Circuit Verification
1 User is 18 or older Zero-Knowledge Proof ($lt) credentialAtomicQuerySigV2OnChain On-chain
2 AML/CTF check passed Zero-Knowledge Proof ($eq) credentialAtomicQuerySigV2 Off-chain
3 Disclose full name Selective Disclosure ({}) credentialAtomicQuerySigV2 Off-chain

The SDK automatically separates the queries by circuit type — on-chain proofs (*OnChain circuits) are submitted to the verifier contract, while off-chain proofs are verified server-side. Both are processed in the same verification session.

onChainMetaData reference

The onChainMetaData object tells the SDK where and how to submit on-chain proofs:

Field Description
contract_address Must match your deployed verifier contract address on Redbelly Network.
method_id Function selector for the submission method. Use 'ade09fcd' for V2 (recommended) or 'b68967e2' for V1.

The SDK uses submitZKPResponseV2 to batch all on-chain proofs into a single transaction. Each on-chain query's id must match the requestId registered on the contract via setZKPRequest.

For the full contract setup walkthrough (deploying the verifier, registering requests, V1 vs V2 differences), see On-chain verification.

Reading multi-query results

The onSuccess callback receives an SdkSuccessRes object. The proofs array maintains the same order as the input sdkQuery:

const handleSuccess = (data: SdkSuccessRes) => {
  // Overall pass/fail across all criteria
  console.log(data.eligibility.passed); // true | false
  console.log(data.eligibility.summary); // human-readable summary

  // Per-query results (same order as sdkQuery)
  data.proofs.forEach((proof) => {
    console.log(proof.subjectTitle); // e.g. "18+ Years Old"
    console.log(proof.status); // "valid" | "failed" | "pending"
    console.log(proof.proofData.credentialType); // e.g. "EssentialIdCredential"

    // For selective disclosure queries, access the revealed value:
    if (proof.proofData.rawProof?.vp) {
      const subject =
        proof.proofData.rawProof.vp.verifiableCredential?.credentialSubject;
      console.log(subject);
    }
  });

  // On-chain transaction data (when onChainMetaData was provided)
  if (data.onChain?.txHash) {
    console.log('Tx hash:', data.onChain.txHash);
  }
};

SdkSuccessRes shape

Field Type Description
walletAddress string Connected wallet address
user { email?, isAuthenticated, did? } Authenticated user details
proofs Array<{ id, circuitId, subjectTitle, status, proofData }> One entry per sdkQuery item, in order
eligibility.passed boolean true only when all queries pass
eligibility.summary string Human-readable result
onChain ContractInvokeTransactionData (optional) Transaction data for on-chain submissions

Mixing proof methods

A single sdkQuery array can combine Zero-Knowledge Proofs and Selective Disclosure queries. The SDK determines the method from the query shape:

  • Zero-Knowledge ProofcredentialSubject uses an operator ($eq, $lt, $gt, $in, $ne). No data is revealed.
  • Selective DisclosurecredentialSubject uses an empty object {} for the field. The field value is returned in the proof.
const sdkQuery: SdkQuery = [
  // ZKP: prove age without revealing birth date
  {
    id: 1,
    circuitId: 'credentialAtomicQuerySigV2',
    subjectTitle: '18+ Years Old',
    query: {
      allowedIssuers: ['*'],
      type: 'EssentialIdCredential',
      context: '...',
      credentialSubject: { birthDate: { $lt: 20070101 } },
    },
  },
  // Selective Disclosure: reveal the user's name
  {
    id: 2,
    circuitId: 'credentialAtomicQuerySigV2',
    subjectTitle: 'Full Name',
    query: {
      allowedIssuers: ['*'],
      type: 'EssentialIdCredential',
      context: '...',
      credentialSubject: { name: {} },
    },
  },
];

For more on the difference between these methods, see Proof Methods.

Best practices

  • Assign unique id values — every query in the array must have a distinct id. The SDK and verifier contracts use this to correlate proofs with requests.
  • Order matters for result mappingproofs[i] in the response corresponds to sdkQuery[i]. Use this when extracting specific proof results.
  • Reuse credential types across queries — you can reference the same credential type in multiple queries (e.g. one ZKP for age, one selective disclosure for name, both from EssentialIdCredential). Only one credential issuance is needed.
  • Prefer submitZKPResponseV2 for on-chain — the V2 method batches all proofs into a single transaction, reducing gas costs compared to V1's one-transaction-per-proof approach.
  • Set skipClaimRevocationCheck: true when revocation checks are not required for your use case, to improve proof generation performance.