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 v1 signed payload uses insertion-order JSON serialization, not alphabetically sorted keys. This is distinct from condition hashes (Section 8), which use sorted keys.
Keys created on or after the v2 rollout sign a domain-separated preimage. For these keys the signature is computed over:
domain + "\n" + canonical_json({"v":2,"id":"...","pass":true,"results":[...],"attestedAt":"..."})where domain is insumer.attestation.v2 for attestations and insumer.trust.v2 for trust profiles, and canonical_json is the JCS / RFC 8785 canonical form (recursively sorted keys, Section 8) rather than insertion order. The domain tag binds each signature to its message type, so an attestation signature cannot be reinterpreted under another context. Under v2 each evaluatedCondition.threshold (and the ratio quantities) is a canonical decimal string with no decimals field, so the signed bytes are exact and reproducible by any verifier in any language.
v1 keys continue to sign the insertion-order payload in Section 3.3 with no domain tag (see Section 14, Versioning). The kid on the response selects both the key and the scheme.
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-v2" | Key identifier. The JWKS publishes three IDs over the same key: insumer-attest-v1 (legacy attest + trust), insumer-attest-v2 (v2 attest), and insumer-trust-v2 (v2 trust). The kid on each response selects both the key and the signing scheme (Section 3.3.1) |
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 nine condition types. The four core types are documented below. evm_view_call (an arbitrary bool-returning view function, named by a canonical selector of the form functionName(address), on RPC-supported EVM chains) is an additive type. ratio_to_amount and ratio_to_supply (dimensionless ratio conditions, where the threshold is derived from a multiple of a reference amount or a share of total supply) are additive v2 types; see the signature-scheme section for their canonical decimal-string quantity encoding. erc8004_agent and erc7710_delegation (agent conditions: ERC-8004 agent registration and ERC-7710 delegation validity, Base only at launch) are additive types, generally available on any API key and also reachable with no key via x402 pay-per-call. Their evaluatedCondition layouts are given in Section 7.
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"
evm_view_callAsserts whether an arbitrary contract's own view function answers true for the wallet. This is the general-purpose escape hatch: any predicate a contract already exposes (hasAccess(address), isMember(address), an allowlist, a custom policy) becomes attestable without a dedicated condition type.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | "evm_view_call" | REQUIRED | |
contractAddress | string | REQUIRED | The contract to call. A real deployed contract; "native" is not valid for this type. |
chainId | integer | REQUIRED | An RPC-supported EVM chain. Non-EVM chains are not supported. |
selector | string | REQUIRED | The canonical signature of the view function, in the form functionName(address). v1 supports single-address-argument view functions only; the signature must match ^[a-zA-Z_$][a-zA-Z0-9_$]*\(address\)$. |
label | string | OPTIONAL | Human-readable label |
Semantics: The verifier derives the 4-byte function selector as the first 4 bytes of keccak256 of the canonical signature, forms calldata as that selector followed by the wallet address left-padded to 32 bytes, and executes eth_call against contractAddress at the anchored block. met is true when the call returns a nonzero 256-bit word (the ABI encoding of true). A revert or an empty return is an on-chain fact and produces a signed false; a transport failure refuses to sign rather than guessing.
Comparison (operator field): "view_call_true"
Reproducing the verdict: The evaluatedCondition carries the canonical selector string, so a verifier can recompute the 4-byte selector, replay the same eth_call at the anchored blockNumber, and compare. No information beyond the signed result is needed.
Scope of the claim: The attestation states what the named contract returned at the anchored block, and nothing more. The contract's own correctness, semantics, and upgradeability are outside the signature's scope: the signed bytes commit to the contract, the selector, the chain, and the block. Merkle storage proofs are not available for this type, since the predicate is the contract's computation rather than a single storage slot.
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, evm_view_call | Contract that was queried |
operator | Always | Comparison operator: gte, gt, valid, decoder, registered, view_call_true (evm_view_call), owner_or_bound_wallet (erc8004_agent), authorized_by_principal (erc7710_delegation) |
threshold | token_balance, nft_ownership | Numeric threshold used (human-readable units) |
decimals | token_balance | Token decimals applied |
selector | evm_view_call | Canonical view-function signature the 4-byte selector derives from (Section 6.5) |
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:
erc8004_agent layoutThe evaluatedCondition for an erc8004_agent condition carries exactly:
{
"type": "erc8004_agent",
"chainId": 8453,
"registry": "0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
"agentId": "123",
"wallet": "0x<attested wallet>",
"operator": "owner_or_bound_wallet"
}met is true iff the attested wallet either owns the agent NFT (ownerOf) or is the registry's on-chain signature-verified agentWallet binding (getAgentWallet). The result object additionally carries agentExists (boolean) and matchedVia ("owner" | "agent_wallet" | "none") inside the signed results array, outside conditionHash. ERC-8004 registration is permissionless NFT minting: the signed statement is exactly “registered in registry R at agentId N, owned by / bound to this wallet”. It implies no vetting, no reputation, no endorsement. The registry clears the agentWallet binding automatically when the agent NFT transfers, so a block-anchored verdict reflects binding truth at that block.
erc7710_delegation layoutThe evaluatedCondition for an erc7710_delegation condition carries exactly:
{
"type": "erc7710_delegation",
"chainId": 8453,
"delegationManager": "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3",
"domainVersion": "1",
"frameworkVersion": "1.3.0",
"delegationHash": "0x<hash>",
"delegator": "0x<principal>",
"delegate": "0x<agent wallet>",
"wallet": "0x<attested wallet>",
"authority": "root",
"expectedDelegator": "0x<principal>",
"signatureType": "eoa",
"caveatCoverage": "full",
"caveatCount": 1,
"unevaluatedCaveats": 0,
"operator": "authorized_by_principal"
}signatureType is one of "eoa", "erc1271", or "none". "eoa" means an EOA principal signed the delegation; "erc1271" means a smart-contract principal asserts the delegation is valid. That is a different claim, and both appear distinctly in the signed bytes. caveatCoverage is "full" or "partial". The result object additionally carries declaredLimits (decoded caveat limits with decimal-string amounts in base units), and on failure unrecognizedEnforcers and failReason (delegate_mismatch, principal_mismatch, invalid_signature, delegator_not_deployed, revoked, unknown_caveat_enforcer, outside_time_window). delegator_not_deployed is reported when the principal address holds no contract code at the anchored block, which is the state a smart-contract wallet is in until its first transaction. It is distinct from invalid_signature because no signature was evaluated: there was no principal on chain to evaluate one against. The verdict is still a signed false, since redemption calls into the delegator account and an undeployed principal cannot exercise the delegation. Declared limits are reported, not simulated: on-chain redemption enforces them, and the attestation states what the principal signed. Attestations containing a delegation condition expire in 5 minutes rather than the standard 30: revocation is one transaction away, so the verdict window stays tight, and the verdict states “not revoked as of block N” (blockNumber is in the signed result). That clause is itself provable: a delegation condition requested with proof: "merkle" returns an EIP-1186 storage proof of the revocation slot, specified in Section 10.7.
met is the verdict, and it is exactly this boolean: the attested wallet is the delegate, the declared delegator matches the required expectedDelegator, the EIP-712 signature verifies, the delegation is unrevoked as of the anchored block, every caveat uses a recognized enforcer, and any time-window caveat is currently satisfied. declaredLimits is not an input to that boolean. It is a signed decode of the caveat terms the caller itself submitted, and it sits as a sibling of evaluatedCondition, inside the signed results payload but outside conditionHash. A verifier reading a delegation attestation should treat met as the statement and declaredLimits as signed context riding alongside it. That context rides on affirmative and negative verdicts alike: the decode is computed once the signature and revocation checks pass, so a delegation failing on unknown_caveat_enforcer or outside_time_window still carries the decode of its recognized caveats — for a time-window failure, the decoded timestamp caveat is the evidence for the failure. Failures at or before the signature and revocation checks (delegate_mismatch, principal_mismatch, invalid_signature, delegator_not_deployed, revoked) carry no decode.
Each entry carries the exact hex bytes it was decoded from, so the decode is independently checkable:
{
"kind": "erc20_transfer_amount",
"enforcer": "0xf100b0819427117ecf76ed94b358b1a5b5c6d2fc",
"terms": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913000000000000000000000000000000000000000000000000000000003b9aca00",
"token": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
"maxAmount": "1000000000"
}kind is one of timestamp (fields timestampAfter, timestampBefore), erc20_transfer_amount (token, maxAmount), native_transfer_amount (maxAmount), allowed_targets (targets), or limited_calls (maxCalls). All numeric values are decimal strings in base units. Because the caller supplied terms in its own request, verification requires no external lookup: re-run the enforcer contract's public getTermsInfo byte layout against terms and compare it to the decoded values, then recompute delegationHash (the EIP-712 struct hash inside evaluatedCondition, which commits to every caveat's enforcer and terms) from the delegation object held locally and confirm it matches. The hash pins which caveats were evaluated; the byte layout pins what each one says.
A delegation attestation in JWT form is portable, so by default the principal's declared limits travel to anyone the attestation is forwarded to. That is usually desirable, since a relying party needs the limits to act on them, but not always. The top-level request modifier declaredLimits (a sibling of proof and format) takes "include" (the default, also the behavior when the field is absent) or "omit". Under "omit" the decoded limits are not included and the result carries declaredLimitsOmitted: true in their place. The caller loses nothing: it submitted the caveats and already holds them.
The modifier changes only what rides alongside the verdict. met, delegationHash, and conditionHash are byte-identical whether or not the limits are omitted, since declaredLimits was never inside evaluatedCondition to begin with. A forwarded attestation therefore still commits to exactly which delegation was checked, and a holder can verify a limit shown to them out-of-band without learning one from the attestation itself.
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.
v2: under the v2 scheme (Section 3.3.1) the evaluatedCondition carries threshold (and the ratio quantities multiple / amount / minFraction) as a canonical decimal string and omits decimals: the decimal string is exact and self-describing, so the hash needs no separate scale. The hashing algorithm (recursive sorted keys, SHA-256) is otherwise identical.
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.
The anchor is chosen by the API, not by the caller. Standard attestations anchor at the chain tip. Attestations requesting proof: "merkle" MAY anchor a small number of blocks behind the tip: a Merkle proof requires the state trie for the anchored block, and nodes commonly serve a block before its state is retrievable, so anchoring at the tip would intermittently make proofs unavailable. The lag is a few blocks (on the order of seconds) and applies to the whole request, so the verdict and the proof always describe the same block, as required by Section 9.3.
Verifiers MUST NOT assume the anchor equals the chain tip at the time of the response, and MUST NOT treat a small gap between the anchor and the tip as evidence of a stale or replayed attestation. Freshness is evaluated as specified in Section 9.4, against blockTimestamp and expiresAt, not against the distance from the tip.
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.
Two condition types produce proofs, and each proves a different slot.
A token_balance condition produces a balance proof when ALL of the following are true:
An erc7710_delegation condition produces a revocation proof (Section 10.7) when the DelegationManager the delegation was signed against is a deployment whose storage layout has been verified on-chain. Currently that is the v1.3.0 manager on Base, 0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3. Other recognized managers return available: false with a reason naming the unverified layout.
No other condition type produces a proof. erc8004_agent in particular does not.
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 |
subject | string | OPTIONAL. What the proof is about. Omitted on balance proofs; "delegation_revocation" on erc7710_delegation proofs (Section 10.7). Its presence is how the two are distinguished. |
blockNumber | string | Hex block number at which the proof was generated. MUST match the result's blockNumber. |
contractAddress | string | OPTIONAL. The contract whose storage was proven. Present on erc7710_delegation proofs, where it is the DelegationManager. |
mappingSlot | integer | Storage slot of the proven mapping: the ERC-20 balanceOf mapping on a balance proof, the disabledDelegations mapping on a revocation proof |
storageKey | string | Keccak-256 storage key used for the proof: keccak256(abi.encode(key, uint256(mappingSlot))), where key is the wallet address on a balance proof and delegationHash on a revocation 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 proven slot |
storageProof[].key | string | Storage key being proven |
storageProof[].value | string | The proven slot value (hex BigInt). Raw balance before decimal division on a balance proof; 1 (revoked) or 0 (not revoked) on a revocation proof |
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.
An erc7710_delegation verdict (Section 7.2) has three parts, and they are not equally checkable by default. The EIP-712 signature verification and the caveat decode are reproducible by the caller without assistance, because the caller holds the delegation object and submitted the raw caveat terms (Section 7.2.1). The remaining clause, “not revoked as of block N”, was an assertion by the API that a verifier could not independently confirm. A revocation proof closes that gap, making the verdict checkable rather than partly trusted.
When a delegation condition is requested with proof: "merkle" and the manager's layout has been verified (Section 10.2), the proof object proves the value of disabledDelegations[delegationHash] in the DelegationManager's storage against the anchored block's state root.
{
"available": true,
"type": "merkle",
"subject": "delegation_revocation",
"blockNumber": "0x12a05f200",
"contractAddress": "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3",
"mappingSlot": 1,
"storageKey": "0x...",
"accountProof": ["0x...", "0x...", ...],
"storageProof": [
{
"key": "0x...",
"value": "0x0",
"proof": ["0x...", "0x...", ...]
}
],
"storageHash": "0x..."
}A verifier checks a revocation proof as follows:
storageKey = keccak256(abi.encode(delegationHash, mappingSlot)), taking delegationHash from the signed evaluatedCondition, and confirm it equals the returned storageKey. This step is REQUIRED: it is what binds the proof to this specific delegation. Without it, a proof of an unrelated storage slot would still verify correctly against the block header.accountProof against the state root of the block header for blockNumber, then verify storageProof against the account's storage root, as in Section 10.4.1 means the delegation was revoked, 0 means it was not. Note that this proves absence as firmly as presence: a proven 0 is positive proof that no revocation exists at that slot, not merely a report that none was found.Proofs are produced only for DelegationManager deployments whose storage layout has been verified on-chain, and the restriction is deliberate rather than provisional. An inferred slot number would yield a well-formed proof of the wrong storage location, and that proof would verify against the block header exactly as a correct one does. A verifier would have no way to detect the mistake. That outcome is worse than returning no proof, so managers without a verified layout return available: false and the boolean verdict is computed and signed as normal.
Unlike a balance proof (Section 10.5), a revocation proof reveals no balance and no wallet holdings: the proven slot is a single revocation flag keyed by a delegation hash the caller already holds.
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": 23, "total": 26 },
"governance": { "checks": [...], "passCount": 2, "failCount": 2, "total": 4 },
"nfts": { "checks": [...], "passCount": 1, "failCount": 2, "total": 3 },
"staking": { "checks": [...], "passCount": 0, "failCount": 3, "total": 3 },
"institutional_stablecoins": { "checks": [...], "passCount": 0, "failCount": 8, "total": 8 },
"solana": { "checks": [...], "passCount": 1, "failCount": 0, "total": 1 },
"xrpl": { "checks": [...], "passCount": 0, "failCount": 2, "total": 2 }
},
"summary": {
"totalChecks": 47,
"totalPassed": 7,
"totalFailed": 40,
"dimensionsWithActivity": 4,
"dimensionsChecked": 7
},
"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. If subject is "delegation_revocation", the verifier MUST also recompute storageKey from the delegationHash in evaluatedCondition before relying on the proven value, as specified in Section 10.7.
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.
The signature scheme is versioned independently of meta.version and selected by the response kid. v1 (insumer-attest-v1) signs the bare insertion-order payload (Section 3.3) and carries threshold as a JSON number with a decimals field. v2 (insumer-attest-v2, insumer-trust-v2) signs the domain-separated canonical preimage (Section 3.3.1) and carries quantities as canonical decimal strings. Both verify against the same key published at the JWKS endpoint. All keys created on or after the v2 rollout are v2; keys issued earlier remain v1 and stay verifiable unchanged. Verifiers MUST branch on kid and MUST NOT assume a single scheme.
Version 1.0 of this specification has been extended additively since publication. Every change below adds capability; none alters or invalidates a previously specified format. Attestations signed under any earlier revision remain valid and verify unchanged.
| Date | Addition |
|---|---|
| 2026-07-28 | evm_view_call specified (Section 6.5): request parameters, calldata construction, the view_call_true operator, signed-false semantics for reverts, and the reproduction procedure. The type itself has been live and additive; this documents it at specification depth alongside the other condition types. |
| 2026-07-28 | Additional failReason value delegator_not_deployed for erc7710_delegation (Section 7.2): reported when the principal address holds no contract code at the anchored block, the state a smart-contract wallet is in until its first transaction. Previously such a principal was reported as invalid_signature, which overstated what had been evaluated. Verdicts are unchanged: the condition still fails, and no other failReason changes meaning. |
| 2026-07-27 | Revocation proofs for erc7710_delegation (Section 10.7): with proof: "merkle", an EIP-1186 storage proof of disabledDelegations[delegationHash] against the anchored block's state root, carrying subject: "delegation_revocation" and contractAddress. Returned for DelegationManagers whose storage layout has been verified on-chain. |
| 2026-07-27 | Anchor selection documented (Section 9.5): proof-mode attestations may anchor slightly behind the chain tip so the state trie is retrievable, and verifiers are instructed not to infer staleness from the distance to the tip. |
| 2026-07-27 | Agent standing condition types erc8004_agent and erc7710_delegation (Sections 7.1, 7.2), with the owner_or_bound_wallet and authorized_by_principal operators. Delegation attestations carry a 5-minute expiry. |
| 2026-07-27 | Declared-limit semantics for erc7710_delegation (Sections 7.2.1, 7.2.2): each decoded limit carries the raw terms bytes it was decoded from, and the optional declaredLimits: "omit" request modifier withholds decoded limits from forwarded attestations while leaving the verdict, delegationHash and conditionHash byte-identical. |
| 2026-06-10 | v2 signature scheme (Sections 3.3.1, 14.1): domain-separated canonical preimage, quantities as decimal strings, distinct kid values for attestations and trust profiles. v1 keys and their signatures are unaffected. |
| 2026-06-01 | Ratio condition types ratio_to_amount and ratio_to_supply: dimensionless rules that need no re-tuning across chains, token prices, or transaction sizes. |
| 2026-05-12 | Condition type evm_view_call for arbitrary boolean view functions on RPC EVM chains. |
| 2026-02-25 | EIP-1186 Merkle storage proofs (Section 9), enabling trustless verification against block headers without re-querying a chain. |
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 |