Menu

State Attestation Specification

Formal specification for the attestation format, signing scheme, and verification algorithm used by InsumerAPI.

Version 1.0 Status Stable Published 2026-02-28 Last updated 2026-07-28 Author Douglas Borthwick

Abstract

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.

1. Terminology

TermDefinition
AttestationA signed statement about on-chain state produced by InsumerAPI
VerifierAny party that checks the cryptographic validity of an attestation
ConditionA predicate over on-chain state (e.g., "balance >= threshold")
Condition hashSHA-256 digest of the canonical JSON of an evaluated condition
Block anchorThe block number and timestamp at which state was read
Merkle proofA 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.

2. Overview

                                   ┌──────────────┐
                                   │  Blockchain   │
                                   │    State      │
                                   └──────┬───────┘
                                          │
                                          │
┌──────────┐    conditions + wallet    ┌──┴───────────┐    signed attestation    ┌──────────┐
│  Caller  │ ────────────────────────▶ │  InsumerAPI  │ ──────────────────────▶ │ Verifier │
└──────────┘                           └──────────────┘                         └──────────┘
                                                                                     │
                                                                              fetch JWKS
                                                                                     │
                                                                              ┌──────┴───────┐
                                                                              │  JWKS        │
                                                                              │  Endpoint    │
                                                                              └──────────────┘
  1. A caller submits a wallet address and one or more conditions to InsumerAPI (POST /v1/attest).
  2. InsumerAPI reads chain state at a recent block, evaluates each condition, and returns a signed attestation.
  3. The verifier (which may be the caller or a downstream system) validates the attestation using InsumerAPI's public key, condition hashes, and block anchoring, without contacting the API again.

3. Signing

3.1 Algorithm

All InsumerAPI responses are signed using ECDSA with the P-256 curve (secp256r1) and SHA-256 (JOSE algorithm identifier ES256).

3.2 Signature format

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.

3.3 Signed payload

For an attestation, the signed payload is the JSON serialization of the following fields in this order:

JSON
{"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.

3.3.1 v2 signed payload (domain-separated)

Keys created on or after the v2 rollout sign a domain-separated preimage. For these keys the signature is computed over:

Preimage (v2)
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.

3.4 Key identification

Every signed response includes a kid (Key ID) string. Verifiers use this value to select the correct public key from the JWKS endpoint.

4. Key Distribution

4.1 JWKS endpoints

InsumerAPI publishes its signing key as a JSON Web Key Set (RFC 7517) at two locations:

The JWKS document contains the public key used to verify all attestation signatures:

FieldValueDescription
kty"EC"Key type
crv"P-256"Curve
xbase64urlx-coordinate of the public key
ybase64urly-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)

4.2 Key rotation

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.

5. Attestation Object

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.

5.1 Top-level structure

JSON
{
  "id": "ATST-A7C3E1B2D4F56789",
  "pass": true,
  "results": [ ... ],
  "passCount": 2,
  "failCount": 0,
  "attestedAt": "2026-02-26T12:34:57.000Z",
  "expiresAt": "2026-02-26T13:04:57.000Z"
}
FieldTypeRequiredDescription
idstringREQUIREDUnique identifier. Format: ATST- followed by 16 uppercase hex characters.
passbooleanREQUIREDtrue if and only if ALL conditions are met.
resultsarrayREQUIREDPer-condition results (Section 5.2).
passCountintegerREQUIREDCount of conditions where met is true.
failCountintegerREQUIREDCount of conditions where met is false.
attestedAtstringREQUIREDISO 8601 timestamp when the attestation was created.
expiresAtstringREQUIREDISO 8601 timestamp after which the attestation SHOULD be considered stale. Default: 30 minutes after attestedAt.

5.2 Result object

Each entry in the results array describes the evaluation of one condition:

FieldTypeRequiredDescription
conditionintegerREQUIREDZero-based index of this condition in the request.
labelstringOPTIONALHuman-readable label provided by the caller.
typestringREQUIREDCondition type (Section 6).
chainIdinteger or stringREQUIREDChain identifier.
metbooleanREQUIREDWhether the condition was satisfied.
evaluatedConditionobjectREQUIREDThe exact predicate that was evaluated (Section 7).
conditionHashstringREQUIRED0x-prefixed SHA-256 hex digest of the canonical JSON of evaluatedCondition (Section 8).
blockNumberstringCONDITIONALHex-encoded block number. Present for RPC-connected chains.
blockTimestampstringCONDITIONALISO 8601 timestamp of the block. Present if and only if blockNumber is present.
ledgerIndexintegerCONDITIONALXRPL ledger index. Present only for XRPL conditions.
ledgerHashstringCONDITIONALXRPL validated ledger hash. Present only for XRPL conditions. Enables independent snapshot verification.
trustLineStateobjectCONDITIONALTrust line state flags. Present only for non-native XRPL token_balance conditions. Contains frozen (boolean). A frozen trust line causes met: false.
proofobjectOPTIONALMerkle storage proof (Section 10). Present only when requested.

5.3 Signed response envelope

The complete response pairs the attestation object with its signature:

JSON
{
  "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.

5.4 JWT bearer token format

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:

FieldValue
algES256
typJWT
kidinsumer-attest-v1

Claims:

ClaimTypeDescription
issstringhttps://api.insumermodel.com
substringThe wallet address (EVM, Solana, or XRPL) that was attested.
jtistringThe attestation ID (e.g. ATST-A7C3E).
iatintegerIssued-at timestamp (Unix seconds).
expintegerExpiration timestamp (Unix seconds). Default: iat + 1800.
passbooleanAggregate pass/fail; same as attestation.pass.
resultsarrayPer-condition results; same as attestation.results.
conditionHasharrayArray of 0x-prefixed SHA-256 hex strings; one per condition in results.
blockNumberstringHex-encoded block number from the first result (when available). For multi-chain attestations, per-result block info is inside the results array.
blockTimestampstringISO 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.

6. Condition Types

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.

6.1 token_balance

Asserts whether a wallet's ERC-20 token balance meets a threshold.

ParameterTypeRequiredDescription
type"token_balance"REQUIRED
contractAddressstringREQUIREDERC-20 contract address
chainIdintegerREQUIREDEVM chain ID
thresholdnumberREQUIREDMinimum balance in human-readable units
decimalsintegerOPTIONALToken decimals (default: 18)
labelstringOPTIONALHuman-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"

6.2 nft_ownership

Asserts whether a wallet holds at least one NFT from a collection.

ParameterTypeRequiredDescription
type"nft_ownership"REQUIRED
contractAddressstringREQUIREDERC-721 contract address
chainIdintegerREQUIREDEVM chain ID
labelstringOPTIONALHuman-readable label

Semantics: met is true when the wallet holds one or more tokens from the collection.

Comparison (operator field): "gt". Evaluated threshold: 0

6.3 eas_attestation

Asserts whether a wallet has received a valid Ethereum Attestation Service attestation matching a schema.

ParameterTypeRequiredDescription
type"eas_attestation"REQUIRED
schemaIdstringCONDITIONALBytes32 hex EAS schema ID. Required unless template is provided.
attesterstringOPTIONALExpected attester address. If provided, only attestations from this address are accepted.
templatestringOPTIONALNamed compliance template (pre-configured shorthand).
chainIdintegerCONDITIONALRequired when using raw schemaId.
labelstringOPTIONALHuman-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)

6.4 farcaster_id

Asserts whether a wallet is registered on Farcaster.

ParameterTypeRequiredDescription
type"farcaster_id"REQUIRED
labelstringOPTIONALHuman-readable label

Semantics: met is true when the wallet has a registered Farcaster ID.

Comparison (operator field): "registered"

6.5 evm_view_call

Asserts 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.

ParameterTypeRequiredDescription
type"evm_view_call"REQUIRED
contractAddressstringREQUIREDThe contract to call. A real deployed contract; "native" is not valid for this type.
chainIdintegerREQUIREDAn RPC-supported EVM chain. Non-EVM chains are not supported.
selectorstringREQUIREDThe 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\)$.
labelstringOPTIONALHuman-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.

7. Evaluated Conditions

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:

FieldPresent whenDescription
typeAlwaysCondition type identifier
chainIdAlwaysChain where state was read
contractAddresstoken_balance, nft_ownership, evm_view_callContract that was queried
operatorAlwaysComparison operator: gte, gt, valid, decoder, registered, view_call_true (evm_view_call), owner_or_bound_wallet (erc8004_agent), authorized_by_principal (erc7710_delegation)
thresholdtoken_balance, nft_ownershipNumeric threshold used (human-readable units)
decimalstoken_balanceToken decimals applied
selectorevm_view_callCanonical view-function signature the 4-byte selector derives from (Section 6.5)
schemaIdeas_attestationSchema that was checked
attestereas_attestation (when filtered)Attester address filter
decodereas_attestation (template-specific)Decoder function used for template evaluation (e.g., Gitcoin Passport)
currencyXRPL trust line conditionsCurrency code for XRPL trust line checks
taxonXRPL nft_ownership (when specified)NFT taxon filter for XRPL NFT conditions

The evaluated condition serves two purposes:

  1. Transparency: The verifier can see exactly what was checked.
  2. Tamper-evidence: The condition hash (Section 8) binds the evaluated condition to the signed attestation.

7.1 erc8004_agent layout

The evaluatedCondition for an erc8004_agent condition carries exactly:

evaluatedCondition: erc8004_agent
{
  "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.

7.2 erc7710_delegation layout

The evaluatedCondition for an erc7710_delegation condition carries exactly:

evaluatedCondition: erc7710_delegation
{
  "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.

7.2.1 The verdict and the declared limits

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:

declaredLimits[0]: one decoded entry
{
  "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.

7.2.2 Omitting the declared limits

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.

8. Condition Hashes

Each result MUST include a conditionHash field computed as:

Formula
conditionHash = "0x" + hex(SHA-256(canonical_json(evaluatedCondition)))

Where canonical_json produces a JSON string with:

8.1 Verification

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.

8.2 Example

Given an evaluated condition:

Canonical JSON (sorted keys)
{"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.

9. Block Anchoring

9.1 Purpose

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.

9.2 Fields

FieldFormatDescription
blockNumber0x-prefixed hex stringThe block at which state was read
blockTimestampISO 8601 datetimeThe timestamp of that block

9.3 Requirements

9.4 Freshness verification

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.

9.5 Anchor selection

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.

10. Merkle Storage Proofs

10.1 Overview

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.

10.2 Availability

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:

  1. The chain supports Merkle storage proofs (currently 28 of 32 EVM chains).
  2. The wallet has a non-zero balance at the queried contract.

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.

10.3 Proof object

JSON
{
  "available": true,
  "type": "merkle",
  "blockNumber": "0x12a05f200",
  "mappingSlot": 3,
  "storageKey": "0x...",
  "accountProof": ["0x...", "0x...", ...],
  "storageProof": [
    {
      "key": "0x...",
      "value": "0x3B9ACA00",
      "proof": ["0x...", "0x...", ...]
    }
  ],
  "storageHash": "0x..."
}
FieldTypeDescription
availablebooleantrue if proof was generated
type"merkle"Proof type identifier
subjectstringOPTIONAL. 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.
blockNumberstringHex block number at which the proof was generated. MUST match the result's blockNumber.
contractAddressstringOPTIONAL. The contract whose storage was proven. Present on erc7710_delegation proofs, where it is the DelegationManager.
mappingSlotintegerStorage slot of the proven mapping: the ERC-20 balanceOf mapping on a balance proof, the disabledDelegations mapping on a revocation proof
storageKeystringKeccak-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
accountProofstring[]Merkle-Patricia trie proof nodes from state root to the contract's account
storageProofobject[]Proof nodes from the contract's storage root to the proven slot
storageProof[].keystringStorage key being proven
storageProof[].valuestringThe 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[].proofstring[]Merkle proof nodes
storageHashstringStorage root hash of the contract account

InsumerAPI may include additional fields in the proof object. Verifiers SHOULD ignore fields they do not recognize.

10.4 Verification

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.

10.5 Privacy note

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.

10.6 Unavailable proof

When the proof cannot be generated:

JSON
{
  "type": "merkle",
  "available": false,
  "reason": "Merkle proofs not available for this chain"
}

The reason field is informational and not part of any signed payload.

10.7 Delegation revocation proofs

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.

JSON: revocation proof
{
  "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:

  1. Recompute 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.
  2. Verify 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.
  3. Interpret the proven value: 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.

11. Trust Profiles

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.

11.1 Structure

JSON
{
  "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"
}

11.2 ID format

Trust profile IDs use the format TRST- followed by 5 uppercase hex characters.

11.3 Dimensions

A dimension groups related checks. Each dimension contains:

FieldTypeDescription
checksarrayArray of check objects (same structure as attestation results)
passCountintegerChecks where met is true
failCountintegerChecks where met is false
totalintegerTotal 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.

11.4 Condition set versioning

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.

11.5 Signing

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.

12. Verification Algorithm

A conforming verifier MUST implement checks 1 and 2. Checks 3 and 4 are RECOMMENDED.

Check 1: Signature verification

  1. Fetch the JWKS document from https://api.insumermodel.com/v1/jwks.
  2. Select the key matching the response's kid.
  3. Reconstruct the JSON payload from the attestation object: {"id":"...","pass":...,"results":[...],"attestedAt":"..."} with fields in that exact order (see Section 3.3).
  4. Compute SHA-256(json_bytes).
  5. Verify the ECDSA P-256 signature against the digest using the public key.
  6. If verification fails, REJECT the attestation.

The simplest approach is to use insumer-verify, which handles payload reconstruction and verification in a single call.

Check 2: Condition hash integrity

For each result in results:

  1. Compute canonical_json(evaluatedCondition) (sorted keys, no whitespace).
  2. Compute SHA-256 of the resulting bytes.
  3. Compare "0x" + hex(digest) to the claimed conditionHash.
  4. If any hash does not match, REJECT the attestation.

Check 3: Block freshness (RECOMMENDED)

If blockTimestamp is present:

  1. Parse blockTimestamp as a UTC datetime.
  2. Compute age = now() - blockTimestamp.
  3. If 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.

Check 4: Expiry (RECOMMENDED)

  1. Parse expiresAt as a UTC datetime.
  2. If now() > expiresAt, REJECT the attestation as expired.

Check 5: Merkle proof (OPTIONAL)

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.

13. Security Considerations

13.1 Trust model

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.

13.2 Replay protection

Attestation IDs and attestedAt timestamps provide replay detection. Verifiers SHOULD reject attestations they have seen before (by ID) or that exceed their acceptable age.

13.3 Privacy

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.

13.4 Condition hash as tamper seal

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.

13.5 Freshness

Verifiers SHOULD enforce freshness bounds (Check 3) to ensure attestations reflect recent chain state.

14. Versioning

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.

14.1 Signature scheme version

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.

14.2 Revision history

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.

DateAddition
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.

15. References

Appendix A: JSON Serialization

This specification uses two different JSON serialization approaches:

Condition hashes (Section 8) use sorted-key canonical JSON:

  1. All object keys are sorted lexicographically (Unicode code point order).
  2. Sorting is applied recursively to nested objects.
  3. No whitespace between tokens.

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):

JSON (unsorted)
{"threshold": 1000, "type": "token_balance", "chainId": 1, "contractAddress": "0xA0b8..."}

Sorted for hashing:

Canonical JSON (sorted)
{"chainId":1,"contractAddress":"0xA0b8...","threshold":1000,"type":"token_balance"}

Appendix B: Verification Library

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:

JavaScript
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 passed

As 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.

Available on npm. Source on GitHub.

Appendix C: Getting Started

InsumerAPI is in production and ready to integrate. To start verifying wallets:

  1. Get a free API key at insumermodel.com/developers
  2. Call POST /v1/attest with a wallet and conditions (add "format": "jwt" for gateway integration)
  3. Verify the response with insumer-verify (npm, zero dependencies)
ResourceURL
API basehttps://api.insumermodel.com
OpenAPI specinsumermodel.com/openapi.yaml
JWKShttps://api.insumermodel.com/v1/jwks
Verification libraryinsumer-verify (npm)
MCP servermcp-server-insumer (npm)
LangChain toolkitlangchain-insumer (PyPI)
Full documentationinsumermodel.com/developers
← API Reference Developer Hub →