From 0738c7a05bc4d8fbee293034fea3ea377745da81 Mon Sep 17 00:00:00 2001 From: Douglas Borthwick <256362537+douglasborthwick-crypto@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:32:18 -0400 Subject: [PATCH 1/2] demo: wallet-state attestations (InsumerAPI) in ACK Adds demos/insumer-wallet-state, mirroring demos/skyfire-kya: consume an InsumerAPI wallet-state attestation (a signed boolean over on-chain conditions) and gate access on it. Verifies against the public JWKS via insumer-verify; maps into an ACK Verifiable Credential with the original ES256 signature preserved. No re-signing, no fork. --- demos/insumer-wallet-state/README.md | 83 ++++++++ demos/insumer-wallet-state/package.json | 31 +++ .../sample-attestation.json | 5 + demos/insumer-wallet-state/src/index.ts | 79 +++++++ .../src/insumer-ack-id.ts | 200 ++++++++++++++++++ demos/insumer-wallet-state/tsconfig.json | 10 + demos/insumer-wallet-state/vitest.config.ts | 8 + pnpm-lock.yaml | 22 ++ 8 files changed, 438 insertions(+) create mode 100644 demos/insumer-wallet-state/README.md create mode 100644 demos/insumer-wallet-state/package.json create mode 100644 demos/insumer-wallet-state/sample-attestation.json create mode 100644 demos/insumer-wallet-state/src/index.ts create mode 100644 demos/insumer-wallet-state/src/insumer-ack-id.ts create mode 100644 demos/insumer-wallet-state/tsconfig.json create mode 100644 demos/insumer-wallet-state/vitest.config.ts diff --git a/demos/insumer-wallet-state/README.md b/demos/insumer-wallet-state/README.md new file mode 100644 index 0000000..93c25f5 --- /dev/null +++ b/demos/insumer-wallet-state/README.md @@ -0,0 +1,83 @@ +# Insumer wallet-state attestations in ACK + +Consume an **InsumerAPI** wallet-state attestation as a verifiable claim — so an +ACK service can gate access on *live on-chain wallet state*, not just identity or +proof-of-payment. Mirrors [`demos/skyfire-kya`](../skyfire-kya). + +## Why + +ACK-ID attests *who* an agent is, and ACK-Pay confirms a payment cleared — but +nothing checks the **paying wallet's on-chain state** before settlement: no +balance, holdings, or compliance condition on the payer. + +InsumerAPI fills that gap. `POST /v1/attest` reads on-chain wallet state, +evaluates it against your conditions (token balance ≥ X, NFT ownership, EAS +attestation) across 37 chains, and returns an **ES256-signed boolean** — a +portable attestation anyone can verify offline with a public key. + +## The key property + +- **Verifying needs no secret.** Verification runs through the canonical + [`insumer-verify`](https://www.npmjs.com/package/insumer-verify) SDK — it checks + the ECDSA P-256 signature, the condition-hash binding, and expiry (and block + freshness when you pass `maxAge`) against the public JWKS at + `https://insumermodel.com/.well-known/jwks.json`. Verify offline; no key, nothing shared. +- **Only *minting* needs a key.** Requesting a fresh attestation calls + `POST /v1/attest` with your own `X-API-Key`. + +## Run + +```bash +# verify the bundled sample (no key) +pnpm demo + +# mint fresh + verify + gate green +INSUMER_API_KEY=insr_live_... pnpm demo +``` + +Get a free key (no signup, one call): + +```bash +curl -X POST https://api.insumermodel.com/v1/keys/create \ + -H "Content-Type: application/json" \ + -d '{"email":"you@example.com","appName":"ack-demo","tier":"free"}' +``` + +Attestations are valid 30 minutes by design; once the bundled sample's window +passes, the demo shows the expiry rejection (proving the expiry check). + +## What `granted` proves — and what it doesn't + +A granted attestation proves, cryptographically and offline, that **the subject +wallet satisfied the condition** at a recent block — signed by InsumerAPI, +verifiable with the public key alone. + +It is a **bearer** proof: it binds the *subject wallet* (`sub`), not whoever +presents it. Exactly like `skyfire-kya`, presenter-binding — proving the agent in +*this* session controls that wallet — is the job of the surrounding ACK session / +DID-auth layer (the same layer the native path below routes through). Treat +`granted: true` as *"a valid wallet-state attestation exists for this wallet, +inside its 30-minute window"* and compose it with your agent-session auth. A +holder-of-key (`cnf`) proof-of-possession binding is the upgrade path if you need +the attestation itself to be non-replayable. + +## Two integration paths + +1. **Adapter (this demo) — works today, no fork.** Verify the attestation against + the public JWKS and gate on the boolean, the way `skyfire-kya` does. +2. **Native verifier — an upstream option.** ACK's `verifyParsedCredential()` + resolves the issuer through a DID resolver and expects a `JwtProof2020` proof. + To flow natively through that path, InsumerAPI would publish a + `did:web:api.insumermodel.com` document and emit VC-shaped output; then + `getWalletStateClaimVerifier()` (in `src/insumer-ack-id.ts`) slots into + `verifyParsedCredential({ verifiers })` alongside ACK's own claim verifiers. + +## Boundary + +This demo is a **consumer of InsumerAPI's documented public surface** only +(`openapi.yaml` + the public JWKS). `conditionHash` is treated as an opaque +fingerprint and never recomputed. How state is sourced, how conditions are +evaluated, and how attestations are signed all stay server-side. + +It is in production today gating settlement for a Circle Alliance member building +payments for the agentic economy. diff --git a/demos/insumer-wallet-state/package.json b/demos/insumer-wallet-state/package.json new file mode 100644 index 0000000..8adb451 --- /dev/null +++ b/demos/insumer-wallet-state/package.json @@ -0,0 +1,31 @@ +{ + "name": "@demos/insumer-wallet-state", + "version": "0.0.1", + "private": true, + "homepage": "https://github.com/agentcommercekit/ack#readme", + "bugs": "https://github.com/agentcommercekit/ack/issues", + "license": "MIT", + "author": { + "name": "InsumerAPI" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/agentcommercekit/ack.git", + "directory": "demos/insumer-wallet-state" + }, + "type": "module", + "scripts": { + "check:types": "tsc --noEmit", + "clean": "git clean -fdX .turbo", + "demo": "tsx ./src/index.ts", + "test": "vitest" + }, + "dependencies": { + "@repo/cli-tools": "workspace:*", + "agentcommercekit": "workspace:*", + "insumer-verify": "1.5.1" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*" + } +} diff --git a/demos/insumer-wallet-state/sample-attestation.json b/demos/insumer-wallet-state/sample-attestation.json new file mode 100644 index 0000000..d75cc78 --- /dev/null +++ b/demos/insumer-wallet-state/sample-attestation.json @@ -0,0 +1,5 @@ +{ + "_note": "Historical sample from POST /v1/attest (format:jwt). Attestations are valid 30 minutes by design, so this will read as expired — set INSUMER_API_KEY and run the demo to mint a live one. Verifiable with the public JWKS at https://insumermodel.com/.well-known/jwks.json — no secret required.", + "kid": "insumer-attest-v2", + "jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imluc3VtZXItYXR0ZXN0LXYyIn0.eyJwYXNzIjp0cnVlLCJjb25kaXRpb25IYXNoIjpbIjB4ZTVjZGRhZmY4NjI2ZjY4MjE0ZTI1N2IwZmM5NDY1MmQ3NGNlMzI0NWFhNGMzZGIyN2ZkZDY4MWZhOThjMjMzYiJdLCJibG9ja051bWJlciI6IjB4MmQzMDc2MSIsImJsb2NrVGltZXN0YW1wIjoiMjAyNi0wNi0xNVQyMToxNjoyMS4wMDBaIiwicmVzdWx0cyI6W3siY29uZGl0aW9uIjowLCJsYWJlbCI6IiIsInR5cGUiOiJ0b2tlbl9iYWxhbmNlIiwiY2hhaW5JZCI6ODQ1MywibWV0Ijp0cnVlLCJldmFsdWF0ZWRDb25kaXRpb24iOnsidHlwZSI6InRva2VuX2JhbGFuY2UiLCJjaGFpbklkIjo4NDUzLCJjb250cmFjdEFkZHJlc3MiOiJuYXRpdmUiLCJvcGVyYXRvciI6Imd0ZSIsInRocmVzaG9sZCI6IjAuMDAwMSJ9LCJjb25kaXRpb25IYXNoIjoiMHhlNWNkZGFmZjg2MjZmNjgyMTRlMjU3YjBmYzk0NjUyZDc0Y2UzMjQ1YWE0YzNkYjI3ZmRkNjgxZmE5OGMyMzNiIiwiYmxvY2tOdW1iZXIiOiIweDJkMzA3NjEiLCJibG9ja1RpbWVzdGFtcCI6IjIwMjYtMDYtMTVUMjE6MTY6MjEuMDAwWiJ9XSwiaXNzIjoiaHR0cHM6Ly9hcGkuaW5zdW1lcm1vZGVsLmNvbSIsInN1YiI6IjB4ZDhkQTZCRjI2OTY0YUY5RDdlRWQ5ZTAzRTUzNDE1RDM3YUE5NjA0NSIsImp0aSI6IkFUU1QtNjg2N0RDQjc0MDA5QzI2NyIsImlhdCI6MTc4MTU1ODE4MSwiZXhwIjoxNzgxNTU5OTgxfQ.R2P-IBZwdw7pZH3pn54YqIDlaXWvKwNNv5RoqNpEf2oPG0TkTdhmFWOdcWFw4STwpa2WfLFe5vCfDLHGdt1UdQ" +} diff --git a/demos/insumer-wallet-state/src/index.ts b/demos/insumer-wallet-state/src/index.ts new file mode 100644 index 0000000..8da4970 --- /dev/null +++ b/demos/insumer-wallet-state/src/index.ts @@ -0,0 +1,79 @@ +// Demo: an ACK-Pay-style service gate backed by an InsumerAPI wallet-state +// attestation. +// +// ACK-Pay grants access on proof a payment cleared (a receipt). Its reference +// server never checks the PAYER's wallet first. This adds the missing step: +// before granting access, require a signed, live wallet-state condition. +// +// With a key: INSUMER_API_KEY=insr_live_... pnpm demo (mints fresh, goes green) +// Without: pnpm demo (verifies the bundled sample) + +import { readFile } from "node:fs/promises" +import { colors, errorMessage, log, logJson, successMessage } from "@repo/cli-tools" + +import { + INSUMER_ISSUER_DID, + convertInsumerAttestationToVerifiableCredential, + verifyInsumerAttestationAsAckId, +} from "./insumer-ack-id" + +const API = "https://api.insumermodel.com" + +async function getAttestationJwt(): Promise { + const key = process.env.INSUMER_API_KEY + if (key) { + const res = await fetch(`${API}/v1/attest`, { + method: "POST", + headers: { "X-API-Key": key, "Content-Type": "application/json" }, + body: JSON.stringify({ + wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + conditions: [ + { type: "token_balance", chainId: 8453, contractAddress: "native", threshold: "0.0001" }, + ], + format: "jwt", + }), + }) + const json = await res.json() + if (!json.ok) throw new Error(JSON.stringify(json.error)) + log("• Minted a fresh attestation with your key (1 credit).") + return json.data.jwt + } + const sample = JSON.parse( + await readFile(new URL("../sample-attestation.json", import.meta.url), "utf8"), + ) + log("• No INSUMER_API_KEY set — verifying the bundled sample attestation.") + log(colors.dim(" Get a free key: POST https://api.insumermodel.com/v1/keys/create")) + return sample.jwt +} + +async function main() { + log("🔐 InsumerAPI wallet-state attestation → ACK-ID gate\n") + log(`This gate checks the PAYER's live on-chain wallet state before access — +the step ACK-Pay's payment-receipt check leaves open.\n`) + + const token = await getAttestationJwt() + + // trustedIssuers is the service's allowlist — exactly ACK's model. + const trustedIssuers = [INSUMER_ISSUER_DID] + const result = await verifyInsumerAttestationAsAckId(token, trustedIssuers) + + log(colors.dim("\nVerification used ONLY the public JWKS — no API key, no secret.\n")) + log(JSON.stringify(result, null, 2)) + + if (result.granted) { + log(successMessage("\nAccess granted — wallet satisfies the signed, live condition.")) + const vc = await convertInsumerAttestationToVerifiableCredential(token) + log("\nSame attestation, as an ACK-compatible W3C Verifiable Credential:") + logJson(vc) + } else if (result.reason?.toLowerCase().includes("expir")) { + log(errorMessage("\nSample expired — attestations are valid 30 min by design.")) + log(colors.dim("Set INSUMER_API_KEY and re-run to mint a live one (goes green).")) + } else { + log(errorMessage(`\nAccess denied — ${result.reason ?? "condition not met"}.`)) + } +} + +main().catch((e: unknown) => { + log(errorMessage((e as Error).message)) + process.exit(1) +}) diff --git a/demos/insumer-wallet-state/src/insumer-ack-id.ts b/demos/insumer-wallet-state/src/insumer-ack-id.ts new file mode 100644 index 0000000..0b8b135 --- /dev/null +++ b/demos/insumer-wallet-state/src/insumer-ack-id.ts @@ -0,0 +1,200 @@ +// insumer-ack-id — map an InsumerAPI wallet-state attestation into ACK's +// Verifiable Credential envelope, so an ACK service can gate access on it. +// +// Mirrors demos/skyfire-kya: an external ES256/JWKS-signed attestation is +// verified out-of-band and surfaced as a boolean, then mapped into ACK's W3C +// Verifiable Credential so an ACK service can gate access on live wallet state. +// +// Verification is delegated to the canonical `insumer-verify` SDK (npm) — it +// checks the ECDSA P-256 signature, the condition-hash binding, and expiry +// (plus block freshness when a maxAge is supplied) against the public JWKS. +// This file adds ONLY the ACK-specific glue: a trusted-issuer gate and a W3C +// Verifiable Credential mapping. +// +// • Verifying needs ONLY the public JWKS — no API key, no shared secret. +// • Minting a fresh attestation needs your own key (POST /v1/attest). + +import type { Verifiable, W3CCredential } from "agentcommercekit" +import { verifyAttestation } from "insumer-verify" + +export const INSUMER_ISSUER = "https://api.insumermodel.com" + +// DID label used for ACK's trusted-issuer allowlist. Verification does NOT +// depend on DID resolution — insumer-verify checks the signature against the +// published JWKS directly, exactly as ACK's skyfire-kya demo does. +export const INSUMER_ISSUER_DID = "did:web:api.insumermodel.com" + +const DEFAULT_JWKS_URL = "https://insumermodel.com/.well-known/jwks.json" + +export interface InsumerVerifyOptions { + /** Public JWKS URL — the trust anchor. MUST be a constant you control, + * never a value derived from caller/agent input. */ + jwksUrl?: string + /** Optional: also enforce block freshness (max blockTimestamp age, seconds). */ + maxAge?: number +} + +export interface WalletStateCredentialSubject { + id: string + pass: boolean + conditionHash: string[] + results: unknown[] +} + +export interface WalletStateGateResult { + granted: boolean + reason?: string + pass?: boolean + subject?: string + wallet?: string + conditionHash?: string[] + results?: unknown[] + expiresAt?: string +} + +interface InsumerAttestationPayload { + pass: boolean + conditionHash: string[] + results: Array<{ chainId?: number | string }> + iss: string + sub: string + jti: string + iat: number + exp: number +} + +function decodeJwtPayload(token: string): InsumerAttestationPayload { + const part = token.split(".")[1] + if (!part) throw new Error("Malformed JWT: missing payload segment") + return JSON.parse(Buffer.from(part, "base64url").toString("utf8")) +} + +function firstFailedCheck(checks: Record): string { + for (const [name, c] of Object.entries(checks)) { + if (!c.passed) return `${name}: ${c.reason ?? "failed"}` + } + return "unknown" +} + +// did:pkh subject label for the verified wallet (EVM). Non-EVM subjects fall +// back to a urn carrying the raw address. +function subjectDid(payload: InsumerAttestationPayload): string { + const chainId = payload.results?.[0]?.chainId + if (typeof chainId === "number") { + return `did:pkh:eip155:${chainId}:${payload.sub}` + } + return `urn:insumer:wallet:${payload.sub}` +} + +/** + * Verify a raw InsumerAPI attestation (JWT string) with the canonical + * insumer-verify SDK. Returns its structured VerifyResult. + */ +export async function verifyInsumerAttestation(token: string, opts: InsumerVerifyOptions = {}) { + return verifyAttestation(token, { + jwksUrl: opts.jwksUrl ?? DEFAULT_JWKS_URL, + maxAge: opts.maxAge, + }) +} + +/** + * ACK-ID-style gate: fully verify the attestation, then confirm Insumer is a + * trusted issuer. `granted` is the boolean an ACK service would gate on. + * Mirrors demos/skyfire-kya's verifySkyfireKyaAsAckId. + */ +export async function verifyInsumerAttestationAsAckId( + token: string, + trustedIssuers: string[], + opts: InsumerVerifyOptions = {}, +): Promise { + const result = await verifyInsumerAttestation(token, opts) + if (!result.valid) { + return { granted: false, reason: firstFailedCheck(result.checks) } + } + + const payload = decodeJwtPayload(token) + if (payload.iss !== INSUMER_ISSUER) { + return { granted: false, reason: "issuer-mismatch" } + } + if (!trustedIssuers.includes(INSUMER_ISSUER_DID)) { + return { granted: false, reason: "issuer-not-trusted" } + } + + return { + granted: payload.pass === true, + pass: payload.pass, + subject: subjectDid(payload), + wallet: payload.sub, + conditionHash: payload.conditionHash, + results: payload.results, + expiresAt: new Date(payload.exp * 1000).toISOString(), + } +} + +/** + * Convert a verified InsumerAPI attestation into an ACK-compatible W3C + * Verifiable Credential, preserving the original ES256 signature as a detached + * JWS proof. conditionHash is carried through verbatim as an opaque + * fingerprint — never recomputed here (insumer-verify owns that check). + */ +export async function convertInsumerAttestationToVerifiableCredential( + token: string, + opts: InsumerVerifyOptions = {}, +): Promise>> { + const result = await verifyInsumerAttestation(token, opts) + if (!result.valid) { + throw new Error(`Attestation failed verification — ${firstFailedCheck(result.checks)}`) + } + + const payload = decodeJwtPayload(token) + const [encodedHeader, , signature] = token.split(".") + if (!encodedHeader || !signature) throw new Error("Malformed JWT") + const kid = JSON.parse(Buffer.from(encodedHeader, "base64url").toString("utf8")).kid as string + + return { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://agentcommercekit.com/contexts/insumer/v1", + ], + id: `urn:insumer:attestation:${payload.jti}`, + type: ["VerifiableCredential", "WalletStateCredential"], + issuer: { id: INSUMER_ISSUER_DID }, + issuanceDate: new Date(payload.iat * 1000).toISOString(), + expirationDate: new Date(payload.exp * 1000).toISOString(), + credentialSubject: { + id: subjectDid(payload), + pass: payload.pass, + conditionHash: payload.conditionHash, // opaque fingerprint, passthrough + results: payload.results, + }, + proof: { + type: "JsonWebSignature2020", + created: new Date(payload.iat * 1000).toISOString(), + verificationMethod: `${INSUMER_ISSUER_DID}#${kid}`, + proofPurpose: "assertionMethod", + jws: `${encodedHeader}..${signature}`, // detached JWS + }, + } as Verifiable> +} + +// --------------------------------------------------------------------------- +// NATIVE UPSTREAM PATH (not used by the runnable demo). +// +// ACK's generic verifyParsedCredential() runs verifyProof() FIRST, which +// resolves the credential issuer through a DID resolver (did:key/web/jwks/pkh) +// and only accepts a JwtProof2020 proof. To flow an Insumer attestation through +// that path natively — instead of this out-of-band adapter — InsumerAPI would: +// 1. publish a did:web:api.insumermodel.com DID document, and +// 2. emit VC-shaped output carrying a JwtProof2020 proof. +// Then this ClaimVerifier slots into verifyParsedCredential({ verifiers }), +// next to ACK's getControllerClaimVerifier() / getReceiptClaimVerifier(). +export function getWalletStateClaimVerifier() { + return { + accepts: (type: string[]) => type.includes("WalletStateCredential"), + verify: async (credentialSubject: { pass?: boolean }) => { + if (credentialSubject?.pass !== true) { + throw new Error("WalletStateCredential: on-chain condition not met") + } + }, + } +} diff --git a/demos/insumer-wallet-state/tsconfig.json b/demos/insumer-wallet-state/tsconfig.json new file mode 100644 index 0000000..85d2481 --- /dev/null +++ b/demos/insumer-wallet-state/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@repo/typescript-config/typescript-library.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["."], + "exclude": ["node_modules", "dist"] +} diff --git a/demos/insumer-wallet-state/vitest.config.ts b/demos/insumer-wallet-state/vitest.config.ts new file mode 100644 index 0000000..7972f77 --- /dev/null +++ b/demos/insumer-wallet-state/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + passWithNoTests: true, + watch: false, + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3197eec..24004c2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -180,6 +180,22 @@ importers: specifier: 5.0.6 version: 5.0.6 + demos/insumer-wallet-state: + dependencies: + '@repo/cli-tools': + specifier: workspace:* + version: link:../../tools/cli-tools + agentcommercekit: + specifier: workspace:* + version: link:../../packages/agentcommercekit + insumer-verify: + specifier: 1.5.1 + version: 1.5.1 + devDependencies: + '@repo/typescript-config': + specifier: workspace:* + version: link:../../tools/typescript-config + demos/payments: dependencies: '@hono/node-server': @@ -4582,6 +4598,10 @@ packages: peerDependencies: '@types/node': '>=18' + insumer-verify@1.5.1: + resolution: {integrity: sha512-7WaVBPfHZhROelHwg8moU819V7lNvqC1GHx6Kw99pnpDSNurMIGNXly3uor9SUsh1/Cj3wSVgGA2wZA8xTbYUA==} + engines: {node: '>=18.0.0'} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -11380,6 +11400,8 @@ snapshots: run-async: 3.0.0 rxjs: 7.8.2 + insumer-verify@1.5.1: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 From 2a47a925c1f2fbb2d08734f81f67a1941550ec43 Mon Sep 17 00:00:00 2001 From: Douglas Borthwick <256362537+douglasborthwick-crypto@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:44:43 -0400 Subject: [PATCH 2/2] demo(insumer-wallet-state): accurate sample note + docstrings --- demos/insumer-wallet-state/sample-attestation.json | 2 +- demos/insumer-wallet-state/src/index.ts | 2 ++ demos/insumer-wallet-state/src/insumer-ack-id.ts | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/demos/insumer-wallet-state/sample-attestation.json b/demos/insumer-wallet-state/sample-attestation.json index d75cc78..d551449 100644 --- a/demos/insumer-wallet-state/sample-attestation.json +++ b/demos/insumer-wallet-state/sample-attestation.json @@ -1,5 +1,5 @@ { - "_note": "Historical sample from POST /v1/attest (format:jwt). Attestations are valid 30 minutes by design, so this will read as expired — set INSUMER_API_KEY and run the demo to mint a live one. Verifiable with the public JWKS at https://insumermodel.com/.well-known/jwks.json — no secret required.", + "_note": "Sample from POST /v1/attest (format:jwt), issued 2026-06-15T21:16:21Z and valid until 2026-06-15T21:46:21Z — attestations live 30 minutes by design. Run after that window and the demo demonstrates the expiry rejection; set INSUMER_API_KEY to always mint a fresh one. Verifiable with the public JWKS at https://insumermodel.com/.well-known/jwks.json — no secret required.", "kid": "insumer-attest-v2", "jwt": "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Imluc3VtZXItYXR0ZXN0LXYyIn0.eyJwYXNzIjp0cnVlLCJjb25kaXRpb25IYXNoIjpbIjB4ZTVjZGRhZmY4NjI2ZjY4MjE0ZTI1N2IwZmM5NDY1MmQ3NGNlMzI0NWFhNGMzZGIyN2ZkZDY4MWZhOThjMjMzYiJdLCJibG9ja051bWJlciI6IjB4MmQzMDc2MSIsImJsb2NrVGltZXN0YW1wIjoiMjAyNi0wNi0xNVQyMToxNjoyMS4wMDBaIiwicmVzdWx0cyI6W3siY29uZGl0aW9uIjowLCJsYWJlbCI6IiIsInR5cGUiOiJ0b2tlbl9iYWxhbmNlIiwiY2hhaW5JZCI6ODQ1MywibWV0Ijp0cnVlLCJldmFsdWF0ZWRDb25kaXRpb24iOnsidHlwZSI6InRva2VuX2JhbGFuY2UiLCJjaGFpbklkIjo4NDUzLCJjb250cmFjdEFkZHJlc3MiOiJuYXRpdmUiLCJvcGVyYXRvciI6Imd0ZSIsInRocmVzaG9sZCI6IjAuMDAwMSJ9LCJjb25kaXRpb25IYXNoIjoiMHhlNWNkZGFmZjg2MjZmNjgyMTRlMjU3YjBmYzk0NjUyZDc0Y2UzMjQ1YWE0YzNkYjI3ZmRkNjgxZmE5OGMyMzNiIiwiYmxvY2tOdW1iZXIiOiIweDJkMzA3NjEiLCJibG9ja1RpbWVzdGFtcCI6IjIwMjYtMDYtMTVUMjE6MTY6MjEuMDAwWiJ9XSwiaXNzIjoiaHR0cHM6Ly9hcGkuaW5zdW1lcm1vZGVsLmNvbSIsInN1YiI6IjB4ZDhkQTZCRjI2OTY0YUY5RDdlRWQ5ZTAzRTUzNDE1RDM3YUE5NjA0NSIsImp0aSI6IkFUU1QtNjg2N0RDQjc0MDA5QzI2NyIsImlhdCI6MTc4MTU1ODE4MSwiZXhwIjoxNzgxNTU5OTgxfQ.R2P-IBZwdw7pZH3pn54YqIDlaXWvKwNNv5RoqNpEf2oPG0TkTdhmFWOdcWFw4STwpa2WfLFe5vCfDLHGdt1UdQ" } diff --git a/demos/insumer-wallet-state/src/index.ts b/demos/insumer-wallet-state/src/index.ts index 8da4970..8e32d57 100644 --- a/demos/insumer-wallet-state/src/index.ts +++ b/demos/insumer-wallet-state/src/index.ts @@ -19,6 +19,7 @@ import { const API = "https://api.insumermodel.com" +/** Get an attestation JWT — mint live with INSUMER_API_KEY, else load the bundled sample. */ async function getAttestationJwt(): Promise { const key = process.env.INSUMER_API_KEY if (key) { @@ -46,6 +47,7 @@ async function getAttestationJwt(): Promise { return sample.jwt } +/** Run the ACK-ID gate against an InsumerAPI wallet-state attestation. */ async function main() { log("🔐 InsumerAPI wallet-state attestation → ACK-ID gate\n") log(`This gate checks the PAYER's live on-chain wallet state before access — diff --git a/demos/insumer-wallet-state/src/insumer-ack-id.ts b/demos/insumer-wallet-state/src/insumer-ack-id.ts index 0b8b135..31c1132 100644 --- a/demos/insumer-wallet-state/src/insumer-ack-id.ts +++ b/demos/insumer-wallet-state/src/insumer-ack-id.ts @@ -63,12 +63,14 @@ interface InsumerAttestationPayload { exp: number } +/** Decode a JWT payload segment to JSON (call only after verification succeeds). */ function decodeJwtPayload(token: string): InsumerAttestationPayload { const part = token.split(".")[1] if (!part) throw new Error("Malformed JWT: missing payload segment") return JSON.parse(Buffer.from(part, "base64url").toString("utf8")) } +/** Short reason string for the first failing insumer-verify check. */ function firstFailedCheck(checks: Record): string { for (const [name, c] of Object.entries(checks)) { if (!c.passed) return `${name}: ${c.reason ?? "failed"}` @@ -76,8 +78,7 @@ function firstFailedCheck(checks: Record type.includes("WalletStateCredential"),