Menu

State Attestation Specification

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

Version 1.0 Status Draft Date 2026-02-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 signed payload uses insertion-order JSON serialization, not alphabetically sorted keys. This is distinct from condition hashes (Section 8), which use sorted keys.

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-v1"Current key identifier

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 four condition types.

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"

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_ownershipContract that was queried
operatorAlwaysComparison operator: gte, gt, valid, decoder, registered
thresholdtoken_balance, nft_ownershipNumeric threshold used (human-readable units)
decimalstoken_balanceToken decimals applied
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.

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.

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.

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

Merkle proofs are available only when ALL of the following are true:

  1. The condition type is token_balance.
  2. The chain supports Merkle storage proofs (currently 27 of 30 EVM chains).
  3. The wallet has a non-zero balance at the queried contract.

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
blockNumberstringHex block number at which the proof was generated. MUST match the result's blockNumber.
mappingSlotintegerDiscovered ERC-20 balanceOf mapping slot
storageKeystringKeccak-256 storage key used for the 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 balance slot
storageProof[].keystringStorage key being proven
storageProof[].valuestringRaw balance value (hex BigInt, before decimal division)
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.

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": 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"
}

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.

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.

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 →