Menu

On-Chain Attestation

Privacy-preserving boolean attestation across 33 blockchains. Read token balances, NFT ownership, EAS credentials, and Farcaster identity — get signed credentials back.

Core primitive. Trust profiles, compliance gating, and commerce all extend this signed attestation.

Building this in Claude Code? Install the wallet auth skill and Claude will write correct, signature-verifying integration code on the first try.

smithery skill add douglasborthwick/insumer-skill

View on Smithery · GitHub repo

What this does

Check if a wallet meets conditions without exposing balances. The API returns an ECDSA-signed true/false result with condition hashes and block anchoring, so any party can independently verify the attestation without trusting the caller. Trust profiles, compliance gating, and commerce endpoints all build on this same signed attestation model.

Verify a wallet in one call

Check if a wallet holds at least 1,000 USDC on Ethereum.

Node.js
const res = await fetch("https://api.insumermodel.com/v1/attest", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "x-api-key": "YOUR_API_KEY"
  },
  body: JSON.stringify({
    wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    conditions: [{
      type: "token_balance",
      contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
      chainId: 1,         // Ethereum mainnet
      threshold: 1000,     // 1000 USDC (human-readable)
      decimals: 6          // USDC has 6 decimals
    }]
  })
});

const response = await res.json();
console.log(response.data.attestation.pass); // true or false
Python
import requests

res = requests.post(
    "https://api.insumermodel.com/v1/attest",
    headers={"x-api-key": "YOUR_API_KEY"},
    json={
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "conditions": [{
            "type": "token_balance",
            "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",  # USDC on Ethereum
            "chainId": 1,         # Ethereum mainnet
            "threshold": 1000,     # 1000 USDC (human-readable)
            "decimals": 6          # USDC has 6 decimals
        }]
    }
)

response = res.json()
print(response["data"]["attestation"]["pass"])  # True or False
curl
curl -X POST https://api.insumermodel.com/v1/attest \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "conditions": [{
      "type": "token_balance",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "chainId": 1,
      "threshold": 1000,
      "decimals": 6,
      "label": "USDC >= 1000"
    }]
  }'

# contractAddress: USDC on Ethereum (chainId 1, 6 decimals)

Four ways to verify

Each condition type maps to a different on-chain primitive. Combine multiple conditions in a single call.

token_balance

Check if a wallet holds at least a threshold amount of any ERC-20 token. Works on all 30 EVM chains, Solana, and XRP Ledger.

  • contractAddress - Token contract
  • chainId - Network ID
  • threshold - Minimum amount (human-readable)
  • decimals - Token decimals (e.g. 6 for USDC)

nft_ownership

Verify ownership of ERC-721 or ERC-1155 NFTs. Checks that the wallet holds at least one NFT from the collection.

  • contractAddress - NFT contract
  • chainId - Network ID
  • threshold - Min count, defaults to any (optional)

eas_attestation

Verify on-chain EAS attestations by schema ID or use pre-configured compliance templates. See compliance docs.

  • schemaId - EAS schema UID
  • template - e.g. coinbase_verified_account
  • Works with Coinbase, Gitcoin Passport

farcaster_id

Check if a wallet has a registered Farcaster ID. Uses the IdRegistry contract on Optimism. Returns a boolean indicating registration status.

  • No contractAddress needed
  • No chainId needed
  • Automatic IdRegistry lookup

POST /v1/attest

Boolean attestation. Returns an ECDSA-signed pass/fail for one or more conditions.

Request Parameters

Parameter Type Required Description
wallet string Yes* EVM wallet address (0x...)
solanaWallet string Yes* Solana wallet address (base58)
xrplWallet string Yes* XRPL wallet address (r-address)
bitcoinWallet string Yes* Bitcoin address (P2PKH, P2SH, bech32, or Taproot)
conditions array Yes Array of condition objects
proof string No Set to "merkle" for storage proofs
format string No Set to "jwt" for a Wallet Auth JWT (ES256-signed). No additional cost

* Provide wallet for EVM chains, solanaWallet for Solana, xrplWallet for XRP Ledger, or bitcoinWallet for Bitcoin. Use one or more as needed.

Condition Object Fields

Field Type Description
type string One of: token_balance, nft_ownership, eas_attestation, farcaster_id
contractAddress string Token or NFT contract address. XRPL: "native" for XRP, or issuer r-address for trust line tokens
chainId number | string Network chain ID (e.g. 1 for Ethereum, "solana", "xrpl")
threshold number Minimum amount (human-readable units)
decimals number Token decimals (e.g. 18 for ETH, 6 for USDC). Auto-detected if omitted
currency string XRPL currency code (e.g. "RLUSD"). Required for XRPL trust line tokens
taxon integer XRPL NFT taxon filter. Optional — if omitted, matches any NFT from the issuer
template string Compliance template name (EAS only)
schemaId string EAS schema UID (EAS only)
attester string Expected attester address (EAS only, optional)
indexer string EAS indexer contract address (EAS only, optional)
label string Optional human-readable label

Response

200 OK
{
  "ok": true,
  "data": {
    "attestation": {
      "id": "ATST-E365DA8B1C2F4790",
      "pass": true,
      "results": [
        {
          "condition": 0,
          "label": "USDC >= 1000",
          "type": "token_balance",
          "chainId": 1,
          "met": true,
          "evaluatedCondition": {
            "type": "token_balance",
            "chainId": 1,
            "contractAddress": "0xA0b8...eB48",
            "operator": "gte",
            "threshold": 1000,
            "decimals": 6
          },
          "conditionHash": "0x448ddd3e...",
          "blockNumber": "0x1772dde",
          "blockTimestamp": "2026-03-05T00:41:11.000Z"
        }
      ],
      "passCount": 1,
      "failCount": 0,
      "attestedAt": "2026-03-05T00:41:14.934Z",
      "expiresAt": "2026-03-05T01:11:14.934Z"
    },
    "sig": "rHObYHqV...",
    "kid": "insumer-attest-v1"
  },
  "meta": {
    "creditsRemaining": 99,
    "creditsCharged": 1,
    "version": "1.0",
    "timestamp": "2026-03-05T00:41:14.934Z"
  }
}

pass is true only when ALL conditions are met.

sig is an ECDSA P-256 signature over the attestation payload.

kid identifies the signing key. Fetch the public key from /.well-known/jwks.json.

Standard attestation: 1 credit ($0.04). With Merkle proofs: 2 credits ($0.08).

XRPL Response Fields

XRPL Trust Line Token Result
{
  "condition": 0,
  "label": "RLUSD >= 50",
  "type": "token_balance",
  "chainId": "xrpl",
  "met": true,
  "evaluatedCondition": {
    "type": "token_balance",
    "chainId": "xrpl",
    "contractAddress": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
    "currency": "RLUSD",
    "threshold": 50
  },
  "conditionHash": "0x7f2a...",
  "ledgerIndex": 95482163,
  "ledgerHash": "BB9023D447285923...",
  "trustLineState": { "frozen": false }
}

ledgerIndex + ledgerHash replace blockNumber/blockTimestamp for XRPL conditions. They identify the validated ledger at verification time.

trustLineState is present only for non-native XRPL token conditions. Shows whether the trust line is frozen.

Frozen trust lines return met: false regardless of balance — frozen tokens are not spendable.

Three layers of independent verification

Every attestation can be verified without trusting us. One API call, three verification layers.

1

ECDSA Signature Verification

Every attestation is signed with ECDSA P-256. The algorithm is swappable via JWKS without breaking verifiers. Verify the signature using the insumer-verify library or raw Web Crypto.

Node.js
// npm install insumer-verify
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://insumermodel.com/.well-known/jwks.json"
});

console.log(result.valid); // true
Web Crypto API
// Fetch the public key from JWKS
const jwks = await fetch("https://insumermodel.com/.well-known/jwks.json")
  .then(r => r.json());
const key = jwks.keys.find(k => k.kid === data.kid);

// Import the public key
const publicKey = await crypto.subtle.importKey(
  "jwk", key,
  { name: "ECDSA", namedCurve: "P-256" },
  false, ["verify"]
);

// Verify the signature (canonical JSON — sorted keys)
const sorted = Object.keys(data.attestation).sort();
const payload = new TextEncoder().encode(
  JSON.stringify(data.attestation, sorted)
);
const sigBytes = Uint8Array.from(
  atob(data.sig), c => c.charCodeAt(0)
);

const valid = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  publicKey, sigBytes, payload
);
console.log(valid); // true
Cryptographic proof
2

Condition Hash Integrity

Each result includes a conditionHash computed from evaluatedCondition. Recompute it yourself to confirm the attestation matches the condition you requested.

Recompute conditionHash
const condition = result.evaluatedCondition;
const sortedKeys = Object.keys(condition).sort();
const canonical = JSON.stringify(condition, sortedKeys);
const hashBuffer = await crypto.subtle.digest(
  "SHA-256",
  new TextEncoder().encode(canonical)
);
const hashHex = "0x" + [...new Uint8Array(hashBuffer)]
  .map(b => b.toString(16).padStart(2, "0"))
  .join("");

console.log(hashHex === result.conditionHash); // true
Audit trail
3

Merkle Storage Proofs

Add proof: "merkle" to get an EIP-1186 storage proof anchored to a block state root. Verify the balance against the Ethereum state trie without any intermediary. Available on 26 of 30 EVM chains.

curl
curl -X POST https://api.insumermodel.com/v1/attest \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "proof": "merkle",
    "conditions": [{
      "type": "token_balance",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "chainId": 1,
      "threshold": 1000,
      "decimals": 6,
      "label": "USDC >= 1000"
    }]
  }'

# Response includes a proof object with storageProof, storageHash,
# and blockNumber alongside the standard attestation fields

Merkle proofs cost 2 credits ($0.08) per attestation. The response includes a proof object containing the raw storageProof array, storageHash, and blockNumber, allowing full trustless verification against the chain state.

Trustless verification
4

Wallet Auth JWT

Add format: "jwt" to receive an ES256-signed JWT alongside the standard response. Verifiable by any standard JWT library using the JWKS at /.well-known/jwks.json. See the full Wallet Auth section below.

Standard JWT verification

Gate any API on wallet state

Every existing auth system proves who you are. Wallet Auth proves what you own. It is a new category of API access control where access is gated on wallet state — what a wallet holds, what it has staked, what credentials it carries — rather than identity.

Call POST /v1/attest with format: "jwt". InsumerAPI reads the blockchain, evaluates the conditions, and returns an ES256-signed JWT alongside the standard attestation response. Point any standard API gateway at the JWKS endpoint. No blockchain infrastructure needed on the verifying side. No balance exposure. No additional cost — same 1 credit as a standard attestation.

Gateway compatible. Wallet Auth JWTs are standard ES256 JWTs backed by a JWKS endpoint. Compatible with any system that accepts JWKS-backed JWTs:

Kong · Nginx (ngx_http_auth_jwt) · Cloudflare Access · AWS API Gateway · Azure API Management · Traefik · Envoy · any OAuth 2.0 middleware

1. Request a Wallet Auth JWT

Add "format": "jwt" to any attestation request. The response includes a jwt field alongside the standard sig and kid.

curl
curl -X POST https://api.insumermodel.com/v1/attest \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "format": "jwt",
    "conditions": [{
      "type": "token_balance",
      "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "chainId": 1,
      "threshold": 1000,
      "decimals": 6,
      "label": "USDC >= 1000"
    }]
  }'

# Response includes data.jwt alongside data.sig and data.kid
# The jwt field is the Wallet Auth token — an ES256-signed JWT string

2. Verify with insumer-verify

Pass either the full response object or the JWT string directly — insumer-verify v1.3.0 auto-detects the format.

Node.js
// npm install insumer-verify
import { verifyAttestation } from "insumer-verify";

// Option A: pass the full API response envelope
const response = await res.json();
const result = await verifyAttestation(response, {
  jwksUrl: "https://insumermodel.com/.well-known/jwks.json"
});

// Option B: pass just the JWT string
const result = await verifyAttestation(response.data.jwt, {
  jwksUrl: "https://insumermodel.com/.well-known/jwks.json"
});

console.log(result.valid);   // true
console.log(result.payload); // the verified attestation data

3. Decode JWT claims

The JWT is a standard three-part token. Decode the payload to see the claims structure. Any JWT library in any language can read these claims.

JWT Payload (decoded)
{
  "iss": "https://api.insumermodel.com",       // issuer
  "sub": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",                       // wallet address
  "jti": "ATST-A7C3E1B2D4F56789",                           // attestation ID
  "iat": 1741089600,                              // issued at (unix)
  "exp": 1741091400,                              // expires in 30 min
  "pass": true,                                   // all conditions met
  "conditionHash": ["0x3a7f..."],              // array of all hashes
  "blockNumber": "0x12a3b4f",                    // from first result
  "blockTimestamp": "2026-03-04T12:00:00.000Z", // from first result
  "results": [                                    // per-condition results
    {
      "condition": 0,
      "label": "USDC >= 1000",
      "type": "token_balance",
      "chainId": 1,
      "met": true,
      "evaluatedCondition": { "type": "token_balance", ... },
      "conditionHash": "0x3a7f...",
      "blockNumber": "0x12a3b4f",
      "blockTimestamp": "2026-03-04T12:00:00Z"
    }
  ]
}

4. Portable credential

A Wallet Auth JWT can be forwarded from Service A to Service B and verified without re-querying the chain. The receiving service fetches the JWKS public key once, checks the ES256 signature, reads the claims, and makes an access decision — all without any blockchain infrastructure, any Insumer SDK, or any network call back to InsumerAPI. The JWT is self-contained proof of wallet state at a specific block height, valid for 30 minutes.

Service A
Calls POST /v1/attest
with format: "jwt"
JWT
Signed credential
ES256 · 30 min TTL
Service B
Verifies via JWKS
No chain query needed

Zero dependencies on the verifying side. Service B needs no blockchain node, no Insumer SDK, and no API key. Standard JWT verification with any language or gateway.

Use case: An AI agent obtains a Wallet Auth JWT from InsumerAPI and presents it as a bearer token when calling a downstream API. The downstream API verifies the JWT against the JWKS endpoint and grants access based on the pass claim — no blockchain interaction required.

JWKS endpoint: https://insumermodel.com/.well-known/jwks.json — also available at GET /v1/jwks. Key ID: insumer-attest-v1.

Handling rpc_failure errors

If we can’t verify, we don’t sign. When a data source is unavailable after retries, the API returns 503 with error code rpc_failure. No attestation signed, no JWT issued, no credits charged.

503 — rpc_failure
{
  "ok": false,
  "error": {
    "code": "rpc_failure",
    "message": "Unable to verify all conditions — data source unavailable after retries",
    "failedConditions": [
      { "chainId": 1, "contractAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", "message": "fetch failed" }
    ]
  },
  "meta": { "version": "1.0", "timestamp": "..." }
}

This is NOT a verification failure. Do not treat it as pass: false. It means the data source was temporarily unavailable and the API refused to sign an unverified result.

Retryable. Retry after 2–5 seconds. The failedConditions array tells you exactly which data source and chain failed.

Applies to: POST /v1/attest, POST /v1/trust, POST /v1/trust/batch, POST /v1/verify, POST /v1/acp/discount, POST /v1/ucp/discount.

Related articles

See how this endpoint fits the full API → API Topology