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:
- Identifies required credential types from all queries (e.g.
EssentialIdCredential,AMLCTFCredential). - 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.
- Generates proofs for all queries in a single batch via
requestProof. - 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).
- Returns results — the
onSuccesscallback receives aproofsarray (one entry per query, in the same order) and aneligibilityobject 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 Proof —
credentialSubjectuses an operator ($eq,$lt,$gt,$in,$ne). No data is revealed. - Selective Disclosure —
credentialSubjectuses 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
idvalues — every query in the array must have a distinctid. The SDK and verifier contracts use this to correlate proofs with requests. - Order matters for result mapping —
proofs[i]in the response corresponds tosdkQuery[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
submitZKPResponseV2for 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: truewhen revocation checks are not required for your use case, to improve proof generation performance.