Formal specification for the attestation format, signing scheme, and verification algorithm used by InsumerAPI.
This document specifies the attestation format, signing scheme, and verification algorithm used by InsumerAPI — the on-chain attestation authority for AI agents, apps, and commerce.
An InsumerAPI attestation is a cryptographically signed boolean assertion ("wallet X satisfies condition Y at block Z") that any downstream system can independently verify without re-querying a blockchain node or learning the underlying state values.
This specification defines:
This document is intended for integrators and verifiers who consume InsumerAPI responses. It defines everything needed to parse, verify, and trust an attestation.
| Term | Definition |
|---|---|
| Attestation | A signed statement about on-chain state produced by InsumerAPI |
| Verifier | Any party that checks the cryptographic validity of an attestation |
| Condition | A predicate over on-chain state (e.g., "balance >= threshold") |
| Condition hash | SHA-256 digest of the canonical JSON of an evaluated condition |
| Block anchor | The block number and timestamp at which state was read |
| Merkle proof | A cryptographic storage proof binding a value to a block header |
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
┌──────────────┐
│ Blockchain │
│ State │
└──────┬───────┘
│
│
┌──────────┐ conditions + wallet ┌──┴───────────┐ signed attestation ┌──────────┐
│ Caller │ ────────────────────────▶ │ InsumerAPI │ ──────────────────────▶ │ Verifier │
└──────────┘ └──────────────┘ └──────────┘
│
fetch JWKS
│
┌──────┴───────┐
│ JWKS │
│ Endpoint │
└──────────────┘POST /v1/attest).All InsumerAPI responses are signed using ECDSA with the P-256 curve (secp256r1) and SHA-256 (JOSE algorithm identifier ES256).
The sig field contains a base64-encoded (standard alphabet) P1363-format signature (two 32-byte integers r || s, total 64 bytes). The encoded signature is 88 characters.
P1363 is the fixed-length format (not DER). Verifiers MUST use P1363 decoding.
For an attestation, the signed payload is the JSON serialization of the following fields in this order:
{"id":"...","pass":true,"results":[...],"attestedAt":"..."}The field order is fixed: id, pass, results, attestedAt. This is the exact output of JSON.stringify() on the attestation object as constructed by InsumerAPI.
For a trust profile, the signed payload is the JSON serialization of the trust object with fields in this order: id, wallet, conditionSetVersion, dimensions, summary, profiledAt, expiresAt.
The digest is SHA-256(json_bytes), and the signature is computed over this 32-byte digest.
Note: The signed payload uses insertion-order JSON serialization, not alphabetically sorted keys. This is distinct from condition hashes (Section 8), which use sorted keys.
Every signed response includes a kid (Key ID) string. Verifiers use this value to select the correct public key from the JWKS endpoint.
InsumerAPI publishes its signing key as a JSON Web Key Set (RFC 7517) at two locations:
GET https://api.insumermodel.com/v1/jwks (24-hour cache)https://insumermodel.com/.well-known/jwks.jsonThe JWKS document contains the public key used to verify all attestation signatures:
| Field | Value | Description |
|---|---|---|
kty | "EC" | Key type |
crv | "P-256" | Curve |
x | base64url | x-coordinate of the public key |
y | base64url | y-coordinate of the public key |
use | "sig" | Key usage |
alg | "ES256" | Algorithm |
kid | "insumer-attest-v1" | Current key identifier |
If a key rotation occurs, the previous key will remain in the JWKS document until all outstanding attestations signed with it have expired (minimum 30 minutes). Verifiers SHOULD select the key matching the response's kid rather than hardcoding a key.
An attestation is the core output of the protocol. It asserts whether a wallet satisfies a set of conditions at a specific point in time.
{
"id": "ATST-A7C3E1B2D4F56789",
"pass": true,
"results": [ ... ],
"passCount": 2,
"failCount": 0,
"attestedAt": "2026-02-26T12:34:57.000Z",
"expiresAt": "2026-02-26T13:04:57.000Z"
}| Field | Type | Required | Description |
|---|---|---|---|
id | string | REQUIRED | Unique identifier. Format: ATST- followed by 16 uppercase hex characters. |
pass | boolean | REQUIRED | true if and only if ALL conditions are met. |
results | array | REQUIRED | Per-condition results (Section 5.2). |
passCount | integer | REQUIRED | Count of conditions where met is true. |
failCount | integer | REQUIRED | Count of conditions where met is false. |
attestedAt | string | REQUIRED | ISO 8601 timestamp when the attestation was created. |
expiresAt | string | REQUIRED | ISO 8601 timestamp after which the attestation SHOULD be considered stale. Default: 30 minutes after attestedAt. |
Each entry in the results array describes the evaluation of one condition:
| Field | Type | Required | Description |
|---|---|---|---|
condition | integer | REQUIRED | Zero-based index of this condition in the request. |
label | string | OPTIONAL | Human-readable label provided by the caller. |
type | string | REQUIRED | Condition type (Section 6). |
chainId | integer or string | REQUIRED | Chain identifier. |
met | boolean | REQUIRED | Whether the condition was satisfied. |
evaluatedCondition | object | REQUIRED | The exact predicate that was evaluated (Section 7). |
conditionHash | string | REQUIRED | 0x-prefixed SHA-256 hex digest of the canonical JSON of evaluatedCondition (Section 8). |
blockNumber | string | CONDITIONAL | Hex-encoded block number. Present for RPC-connected chains. |
blockTimestamp | string | CONDITIONAL | ISO 8601 timestamp of the block. Present if and only if blockNumber is present. |
ledgerIndex | integer | CONDITIONAL | XRPL ledger index. Present only for XRPL conditions. |
ledgerHash | string | CONDITIONAL | XRPL validated ledger hash. Present only for XRPL conditions. Enables independent snapshot verification. |
trustLineState | object | CONDITIONAL | Trust line state flags. Present only for non-native XRPL token_balance conditions. Contains frozen (boolean). A frozen trust line causes met: false. |
proof | object | OPTIONAL | Merkle storage proof (Section 10). Present only when requested. |
The complete response pairs the attestation object with its signature:
{
"ok": true,
"data": {
"attestation": { ... },
"sig": "MEYCIQDx...base64...",
"kid": "insumer-attest-v1"
},
"meta": {
"version": "1.0",
"timestamp": "2026-02-26T12:34:57.000Z"
}
}The sig and kid fields MUST be siblings of the object they sign (not nested within it). The meta object is NOT part of the signed payload.
Callers MAY request the attestation as a standard JWT by including "format": "jwt" in the request body. When present, the response includes an additional jwt field alongside the existing data object.
The JWT is signed with ES256 (ECDSA P-256 + SHA-256) using the same key identified by kid. The header and claims are:
Header:
| Field | Value |
|---|---|
alg | ES256 |
typ | JWT |
kid | insumer-attest-v1 |
Claims:
| Claim | Type | Description |
|---|---|---|
iss | string | https://api.insumermodel.com |
sub | string | The wallet address (EVM, Solana, or XRPL) that was attested. |
jti | string | The attestation ID (e.g. ATST-A7C3E). |
iat | integer | Issued-at timestamp (Unix seconds). |
exp | integer | Expiration timestamp (Unix seconds). Default: iat + 1800. |
pass | boolean | Aggregate pass/fail — same as attestation.pass. |
results | array | Per-condition results — same as attestation.results. |
conditionHash | array | Array of 0x-prefixed SHA-256 hex strings — one per condition in results. |
blockNumber | string | Hex-encoded block number from the first result (when available). For multi-chain attestations, per-result block info is inside the results array. |
blockTimestamp | string | ISO 8601 block timestamp from the first result (when available). |
The JWT is verifiable by any standard JWT library (Kong, Nginx, Cloudflare Access, AWS API Gateway) using the JWKS endpoint at GET /v1/jwks or the static /.well-known/jwks.json.
A condition is a predicate evaluated against on-chain state. InsumerAPI supports four condition types.
token_balanceAsserts whether a wallet's ERC-20 token balance meets a threshold.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "token_balance" | REQUIRED | |
contractAddress | string | REQUIRED | ERC-20 contract address |
chainId | integer | REQUIRED | EVM chain ID |
threshold | number | REQUIRED | Minimum balance in human-readable units |
decimals | integer | OPTIONAL | Token decimals (default: 18) |
label | string | OPTIONAL | Human-readable label |
Semantics: met is true when the wallet's balance (adjusted for decimals) is greater than or equal to threshold.
Comparison (operator field): "gte"
nft_ownershipAsserts whether a wallet holds at least one NFT from a collection.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "nft_ownership" | REQUIRED | |
contractAddress | string | REQUIRED | ERC-721 contract address |
chainId | integer | REQUIRED | EVM chain ID |
label | string | OPTIONAL | Human-readable label |
Semantics: met is true when the wallet holds one or more tokens from the collection.
Comparison (operator field): "gt" — Evaluated threshold: 0
eas_attestationAsserts whether a wallet has received a valid Ethereum Attestation Service attestation matching a schema.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "eas_attestation" | REQUIRED | |
schemaId | string | CONDITIONAL | Bytes32 hex EAS schema ID. Required unless template is provided. |
attester | string | OPTIONAL | Expected attester address. If provided, only attestations from this address are accepted. |
template | string | OPTIONAL | Named compliance template (pre-configured shorthand). |
chainId | integer | CONDITIONAL | Required when using raw schemaId. |
label | string | OPTIONAL | Human-readable label |
Semantics: met is true when a valid, non-revoked attestation exists for the wallet matching the specified schema and attester constraints.
Comparison (operator field): "valid" (standard) or "decoder" (template-specific evaluation logic)
farcaster_idAsserts whether a wallet is registered on Farcaster.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "farcaster_id" | REQUIRED | |
label | string | OPTIONAL | Human-readable label |
Semantics: met is true when the wallet has a registered Farcaster ID.
Comparison (operator field): "registered"
Every result includes an evaluatedCondition object that captures the exact predicate that was applied. This is a statement of what was checked, not a repeat of the caller's input.
The evaluated condition MUST include:
| Field | Present when | Description |
|---|---|---|
type | Always | Condition type identifier |
chainId | Always | Chain where state was read |
contractAddress | token_balance, nft_ownership | Contract that was queried |
operator | Always | Comparison operator: gte, gt, valid, decoder, registered |
threshold | token_balance, nft_ownership | Numeric threshold used (human-readable units) |
decimals | token_balance | Token decimals applied |
schemaId | eas_attestation | Schema that was checked |
attester | eas_attestation (when filtered) | Attester address filter |
decoder | eas_attestation (template-specific) | Decoder function used for template evaluation (e.g., Gitcoin Passport) |
currency | XRPL trust line conditions | Currency code for XRPL trust line checks |
taxon | XRPL nft_ownership (when specified) | NFT taxon filter for XRPL NFT conditions |
The evaluated condition serves two purposes:
Each result MUST include a conditionHash field computed as:
conditionHash = "0x" + hex(SHA-256(canonical_json(evaluatedCondition)))
Where canonical_json produces a JSON string with:
, and :)A verifier SHOULD recompute the condition hash from the evaluatedCondition object and compare it to the claimed conditionHash. If they differ, the result MUST be rejected as tampered.
Because the conditionHash is embedded in the results array, which is part of the signed payload, a valid signature over the attestation transitively guarantees the integrity of every evaluated condition.
Given an evaluated condition:
{"chainId":1,"contractAddress":"0xA0b8...eB48","decimals":6,"operator":"gte","threshold":1000,"type":"token_balance"}The condition hash is 0x + the hex-encoded SHA-256 of the above byte string.
Block anchoring binds an attestation result to a specific point in blockchain history. Without it, a verifier cannot distinguish a result from 10 seconds ago from one from 10 minutes ago.
| Field | Format | Description |
|---|---|---|
blockNumber | 0x-prefixed hex string | The block at which state was read |
blockTimestamp | ISO 8601 datetime | The timestamp of that block |
blockNumber and blockTimestamp are included for every EVM chain. If the block anchor cannot be captured, the API returns 503 rather than signing a partial result.slot for Solana, ledgerIndex/ledgerHash for XRPL.blockNumber and blockTimestamp MUST correspond to the same block.Verifiers MAY enforce a maximum age by comparing blockTimestamp to their local clock. A verifier SHOULD allow for reasonable clock skew (RECOMMENDED: 60 seconds) and block propagation delay.
If blockTimestamp is absent, the verifier SHOULD fall back to attestedAt for freshness checks, with the understanding that this provides weaker guarantees.
When requested via proof: "merkle", InsumerAPI includes a Merkle storage proof alongside the boolean result. This allows a verifier to independently confirm the on-chain value against a block header without querying any RPC endpoint.
Merkle proofs are available only when ALL of the following are true:
token_balance.When unavailable, the response includes a proof object with available: false and a reason string.
{
"available": true,
"type": "merkle",
"blockNumber": "0x12a05f200",
"mappingSlot": 3,
"storageKey": "0x...",
"accountProof": ["0x...", "0x...", ...],
"storageProof": [
{
"key": "0x...",
"value": "0x3B9ACA00",
"proof": ["0x...", "0x...", ...]
}
],
"storageHash": "0x..."
}| Field | Type | Description |
|---|---|---|
available | boolean | true if proof was generated |
type | "merkle" | Proof type identifier |
blockNumber | string | Hex block number at which the proof was generated. MUST match the result's blockNumber. |
mappingSlot | integer | Discovered ERC-20 balanceOf mapping slot |
storageKey | string | Keccak-256 storage key used for the proof |
accountProof | string[] | Merkle-Patricia trie proof nodes from state root to the contract's account |
storageProof | object[] | Proof nodes from the contract's storage root to the balance slot |
storageProof[].key | string | Storage key being proven |
storageProof[].value | string | Raw balance value (hex BigInt, before decimal division) |
storageProof[].proof | string[] | Merkle proof nodes |
storageHash | string | Storage root hash of the contract account |
InsumerAPI may include additional fields in the proof object. Verifiers SHOULD ignore fields they do not recognize.
Merkle proofs can be verified against any Ethereum block header source (archive node, light client, block explorer). The insumer-verify library handles this automatically. For custom implementations, the proof structure follows standard Merkle-Patricia trie verification — verify accountProof against the block's state root, then verify storageProof against the account's storage root.
Standard attestations (without proof) return only a boolean and never reveal balances. Merkle proofs do reveal the raw on-chain balance. Callers MUST be aware that requesting proofs trades privacy for trustlessness.
When the proof cannot be generated:
{
"type": "merkle",
"available": false,
"reason": "Merkle proofs not available for this chain"
}The reason field is informational and not part of any signed payload.
A trust profile is a higher-level construct built on top of attestations. It evaluates a curated set of conditions across multiple dimensions to produce a fact-based wallet profile.
{
"id": "TRST-A1B2C",
"wallet": "0xd8dA...6045",
"conditionSetVersion": "v1",
"dimensions": {
"stablecoins": { "checks": [...], "passCount": 3, "failCount": 4, "total": 7 },
"governance": { "checks": [...], "passCount": 2, "failCount": 2, "total": 4 },
"nfts": { "checks": [...], "passCount": 1, "failCount": 2, "total": 3 },
"staking": { "checks": [...], "passCount": 0, "failCount": 3, "total": 3 },
"solana": { "checks": [...], "passCount": 1, "failCount": 1, "total": 2 },
"xrpl": { "checks": [...], "passCount": 0, "failCount": 2, "total": 2 }
},
"summary": {
"totalChecks": 21,
"totalPassed": 7,
"totalFailed": 14,
"dimensionsWithActivity": 4,
"dimensionsChecked": 6
},
"profiledAt": "2026-02-26T12:34:57.000Z",
"expiresAt": "2026-02-26T13:04:57.000Z"
}Trust profile IDs use the format TRST- followed by 5 uppercase hex characters.
A dimension groups related checks. Each dimension contains:
| Field | Type | Description |
|---|---|---|
checks | array | Array of check objects (same structure as attestation results) |
passCount | integer | Checks where met is true |
failCount | integer | Checks where met is false |
total | integer | Total checks in this dimension |
The core dimensions (stablecoins, governance, nfts, staking) are always present. The solana dimension is included when a solanaWallet parameter is provided, and the xrpl dimension is included when an xrplWallet parameter is provided.
The conditionSetVersion field identifies which curated condition set was used. When the condition set is updated, the version is incremented. Verifiers SHOULD treat profiles with different versions as incomparable.
The entire trust object (including id, wallet, conditionSetVersion, dimensions, summary, profiledAt, expiresAt) is signed as a single payload using the same ECDSA P-256 mechanism described in Section 3.
A conforming verifier MUST implement checks 1 and 2. Checks 3 and 4 are RECOMMENDED.
https://api.insumermodel.com/v1/jwks.kid.{"id":"...","pass":...,"results":[...],"attestedAt":"..."} with fields in that exact order (see Section 3.3).SHA-256(json_bytes).The simplest approach is to use insumer-verify, which handles payload reconstruction and verification in a single call.
For each result in results:
canonical_json(evaluatedCondition) (sorted keys, no whitespace).SHA-256 of the resulting bytes."0x" + hex(digest) to the claimed conditionHash.If blockTimestamp is present:
blockTimestamp as a UTC datetime.age = now() - blockTimestamp.age exceeds the verifier's configured maxAge, REJECT or flag the attestation as stale.If blockTimestamp is absent, the verifier MAY use attestedAt as a weaker freshness signal.
expiresAt as a UTC datetime.now() > expiresAt, REJECT the attestation as expired.If a Merkle proof is present and available is true, verify it against a trusted block header source. The insumer-verify library handles this automatically.
Without Merkle proofs, the verifier trusts InsumerAPI to have read chain state honestly. The signature guarantees InsumerAPI produced the attestation (non-repudiation) and that it has not been modified in transit (integrity). Merkle proofs provide an additional layer — they allow the verifier to independently confirm the on-chain value against a block header, without trusting anyone.
Attestation IDs and attestedAt timestamps provide replay detection. Verifiers SHOULD reject attestations they have seen before (by ID) or that exceed their acceptable age.
Standard attestations reveal only a boolean (met: true/false) and never expose raw balances or holdings quantities. This is a deliberate privacy property.
Merkle proofs reveal the raw balance. Callers requesting proofs MUST understand this tradeoff.
InsumerAPI does not log or store wallet addresses beyond the request-response cycle. See the Privacy Policy for details.
The condition hash chain (Section 8) ensures that modifying any aspect of the evaluated condition — the contract address, threshold, comparison, chain, or decimals — invalidates the signature. This prevents an intermediary from substituting conditions after signing.
Verifiers SHOULD enforce freshness bounds (Check 3) to ensure attestations reflect recent chain state.
New condition types, proof types, and trust dimensions may be introduced in future versions of this specification. Verifiers SHOULD handle unrecognized condition types gracefully (e.g., by skipping condition hash verification for unknown types rather than rejecting the entire attestation). Version changes will be reflected in the meta.version field of API responses.
This specification uses two different JSON serialization approaches:
Condition hashes (Section 8) use sorted-key canonical JSON:
Signature payloads (Section 3.3) use fixed insertion-order JSON — fields appear in the order specified in Section 3.3, not alphabetically. This is the standard output of JSON.stringify() with no replacer argument.
Condition hash example:
Input evaluatedCondition (unsorted):
{"threshold": 1000, "type": "token_balance", "chainId": 1, "contractAddress": "0xA0b8..."}Sorted for hashing:
{"chainId":1,"contractAddress":"0xA0b8...","threshold":1000,"type":"token_balance"}insumer-verify is the official verification library for InsumerAPI attestations. Zero dependencies. Runs all verification checks described in Section 12 in a single function call:
import { verifyAttestation } from "insumer-verify";
// Pass the full API response envelope, not response.data
const response = await res.json();
const result = await verifyAttestation(response, {
jwksUrl: "https://api.insumermodel.com/v1/jwks"
});
console.log(result.valid); // true — all checks passedAs of v1.3.0, insumer-verify auto-detects the input type: pass a JWT string and it verifies the ES256 signature via JWKS plus the same four checks; pass an attestation object and it uses the raw verification path.
InsumerAPI is in production and ready to integrate. To start verifying wallets:
POST /v1/attest with a wallet and conditions (add "format": "jwt" for gateway integration)insumer-verify (npm, zero dependencies)| Resource | URL |
|---|---|
| API base | https://api.insumermodel.com |
| OpenAPI spec | insumermodel.com/openapi.yaml |
| JWKS | https://api.insumermodel.com/v1/jwks |
| Verification library | insumer-verify (npm) |
| MCP server | mcp-server-insumer (npm) |
| LangChain toolkit | langchain-insumer (PyPI) |
| Full documentation | insumermodel.com/developers |