insumer-verify 1.3.10 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -133,8 +133,8 @@ XRPL attestation results use `ledgerIndex` (integer) and `ledgerHash` (string) i
133
133
  "evaluatedCondition": {
134
134
  "type": "token_balance",
135
135
  "chainId": "xrpl",
136
- "currency": "524C555344...",
137
- "issuer": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
136
+ "contractAddress": "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De",
137
+ "currency": "524C555344000000000000000000000000000000",
138
138
  "operator": "gte",
139
139
  "threshold": 100,
140
140
  "decimals": 6
@@ -271,6 +271,25 @@ Each attestation result includes an `evaluatedCondition` object and a `condition
271
271
 
272
272
  This ensures the condition that was actually evaluated on-chain can be independently verified by any third party.
273
273
 
274
+ ## Examples
275
+
276
+ The [`examples/`](./examples/) directory contains runnable scripts covering common patterns:
277
+
278
+ | Example | What it shows |
279
+ |---------|--------------|
280
+ | [`basic-attest.mjs`](./examples/basic-attest.mjs) | Single token balance check + verification |
281
+ | [`multi-condition.mjs`](./examples/multi-condition.mjs) | Multiple conditions across different chains |
282
+ | [`jwt-format.mjs`](./examples/jwt-format.mjs) | JWT format for gateway integration (Kong, Nginx, Cloudflare Access) |
283
+ | [`verify-manual.mjs`](./examples/verify-manual.mjs) | DIY verification with Web Crypto — no library, proving the format is open |
284
+ | [`express-gate.mjs`](./examples/express-gate.mjs) | Express middleware: verify attestation before granting access |
285
+ | [`xrpl-trustline.mjs`](./examples/xrpl-trustline.mjs) | XRPL trust line tokens (RLUSD, USDC) |
286
+
287
+ ```bash
288
+ INSUMER_API_KEY=insr_live_... node examples/basic-attest.mjs
289
+ ```
290
+
291
+ The attestation format is an open standard — `verify-manual.mjs` demonstrates full verification using only the Web Crypto API with no dependencies. See the [State Attestation Spec](https://insumermodel.com/state-attestation-spec) for the complete format definition.
292
+
274
293
  ## Pricing
275
294
 
276
295
  **Tiers:** Free (10 credits) | Pro $9/mo (10,000/day) | Enterprise $29/mo (100,000/day)
@@ -0,0 +1,58 @@
1
+ # insumer-verify examples
2
+
3
+ Runnable examples showing how to request, verify, and gate access with InsumerAPI attestations.
4
+
5
+ ## Philosophy
6
+
7
+ InsumerAPI standardizes the **format and verification model** of eligibility attestations, not the underlying computation method. Any conforming implementation may use direct reads, proof systems, or delegated verification, but it must emit a privacy-preserving, signed, time-bounded attestation that can be independently verified by relying parties without real-time issuer communication.
8
+
9
+ What this means in practice: every example below produces or consumes the same signed attestation format. The verification side never needs to know *how* the attestation was produced — only that it conforms to the [State Attestation Spec](https://insumermodel.com/state-attestation-spec).
10
+
11
+ ## Examples
12
+
13
+ | File | What it shows |
14
+ |------|--------------|
15
+ | `basic-attest.mjs` | Single token balance check + verification |
16
+ | `multi-condition.mjs` | Multiple conditions across different chains |
17
+ | `jwt-format.mjs` | Request JWT format for gateway integration (Kong, Nginx, Cloudflare Access) |
18
+ | `verify-manual.mjs` | DIY verification with Web Crypto — no library, proving the format is open |
19
+ | `express-gate.mjs` | Express middleware: verify attestation before granting access |
20
+ | `xrpl-trustline.mjs` | XRPL trust line tokens (RLUSD, USDC) with issuer addressing |
21
+
22
+ ## Prerequisites
23
+
24
+ ```bash
25
+ npm install insumer-verify # for library examples
26
+ ```
27
+
28
+ Get a free API key:
29
+
30
+ ```bash
31
+ curl -s -X POST https://api.insumermodel.com/v1/keys/create \
32
+ -H "Content-Type: application/json" \
33
+ -d '{"email": "you@example.com", "appName": "examples"}' | jq .
34
+ ```
35
+
36
+ Set it as an environment variable:
37
+
38
+ ```bash
39
+ export INSUMER_API_KEY=insr_live_your_key_here
40
+ ```
41
+
42
+ ## Run
43
+
44
+ ```bash
45
+ node examples/basic-attest.mjs
46
+ node examples/verify-manual.mjs # no library needed
47
+ ```
48
+
49
+ ## Verification spec
50
+
51
+ All examples verify attestations against the [State Attestation Specification](https://insumermodel.com/state-attestation-spec). The spec defines:
52
+
53
+ - **Attestation format** — signed boolean assertions over on-chain state
54
+ - **Signing scheme** — ECDSA P-256 (ES256) with JWKS key distribution
55
+ - **Condition hashes** — SHA-256 tamper seals over evaluated predicates
56
+ - **Verification algorithm** — 4 checks any relying party can run offline
57
+
58
+ The format is the standard. The computation behind it is an implementation detail.
@@ -0,0 +1,64 @@
1
+ /**
2
+ * basic-attest.mjs — Single token balance attestation + verification.
3
+ *
4
+ * Demonstrates the core flow:
5
+ * 1. Send conditions to InsumerAPI
6
+ * 2. Receive a signed attestation (boolean, not balance)
7
+ * 3. Verify it cryptographically — offline, no API callback needed
8
+ *
9
+ * Usage: INSUMER_API_KEY=insr_live_... node examples/basic-attest.mjs
10
+ */
11
+
12
+ import { verifyAttestation } from "insumer-verify";
13
+
14
+ const API_KEY = process.env.INSUMER_API_KEY;
15
+ if (!API_KEY) {
16
+ console.error("Set INSUMER_API_KEY environment variable");
17
+ process.exit(1);
18
+ }
19
+
20
+ // 1. Request an attestation — "does this wallet hold >= 1000 USDC on Ethereum?"
21
+ const res = await fetch("https://api.insumermodel.com/v1/attest", {
22
+ method: "POST",
23
+ headers: {
24
+ "Content-Type": "application/json",
25
+ "X-API-Key": API_KEY,
26
+ },
27
+ body: JSON.stringify({
28
+ wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
29
+ conditions: [
30
+ {
31
+ type: "token_balance",
32
+ chainId: 1,
33
+ contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
34
+ threshold: 1000,
35
+ decimals: 6,
36
+ label: "USDC >= 1000 on Ethereum",
37
+ },
38
+ ],
39
+ }),
40
+ });
41
+
42
+ const apiResponse = await res.json();
43
+
44
+ // Check for RPC failure (retryable, not a verification failure)
45
+ if (!apiResponse.ok && apiResponse.error?.code === "rpc_failure") {
46
+ console.log("RPC failure — retry after 2-5 seconds:", apiResponse.error.failedConditions);
47
+ process.exit(1);
48
+ }
49
+
50
+ // 2. Verify the attestation — signature, condition hashes, freshness, expiry
51
+ const result = await verifyAttestation(apiResponse, {
52
+ maxAge: 120, // reject if block data is older than 2 minutes
53
+ });
54
+
55
+ console.log("Verification:", result.valid ? "PASSED" : "FAILED");
56
+ console.log("Checks:", JSON.stringify(result.checks, null, 2));
57
+
58
+ if (result.valid) {
59
+ const { pass, results } = apiResponse.data.attestation;
60
+ console.log(`\nAttestation: ${pass ? "ALL MET" : "NOT ALL MET"}`);
61
+ for (const r of results) {
62
+ console.log(` ${r.label}: ${r.met ? "met" : "not met"} (block ${r.blockNumber})`);
63
+ }
64
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * express-gate.mjs — Express middleware: verify attestation before granting access.
3
+ *
4
+ * Pattern: the client calls InsumerAPI, gets an attestation, and sends it
5
+ * to your server. Your server verifies it cryptographically — no API
6
+ * callback, no real-time issuer communication. The attestation is
7
+ * self-contained proof that the wallet meets the conditions.
8
+ *
9
+ * This is the relying party pattern: your server never touches the
10
+ * blockchain or the attestation issuer. It only needs the public key
11
+ * (via JWKS) and the verification algorithm (4 checks).
12
+ *
13
+ * Usage: node examples/express-gate.mjs
14
+ * Then: curl -X POST http://localhost:3000/api/premium \
15
+ * -H "Content-Type: application/json" \
16
+ * -d '{"attestation": <full API response or JWT string>}'
17
+ *
18
+ * Requires: npm install express insumer-verify
19
+ */
20
+
21
+ import express from "express";
22
+ import { verifyAttestation } from "insumer-verify";
23
+
24
+ const app = express();
25
+ app.use(express.json());
26
+
27
+ /**
28
+ * Middleware: require a valid InsumerAPI attestation.
29
+ * Accepts either a raw attestation object or a JWT string.
30
+ */
31
+ function requireAttestation(options = {}) {
32
+ const { maxAge = 120 } = options;
33
+
34
+ return async (req, res, next) => {
35
+ const attestationData = req.body.attestation;
36
+
37
+ if (!attestationData) {
38
+ return res.status(401).json({ error: "Missing attestation" });
39
+ }
40
+
41
+ try {
42
+ const result = await verifyAttestation(attestationData, { maxAge });
43
+
44
+ if (!result.valid) {
45
+ return res.status(403).json({
46
+ error: "Attestation verification failed",
47
+ checks: result.checks,
48
+ });
49
+ }
50
+
51
+ // Attach verified attestation to request for downstream use
52
+ if (typeof attestationData === "string") {
53
+ // JWT — decode claims
54
+ const [, payloadB64] = attestationData.split(".");
55
+ req.attestation = JSON.parse(
56
+ Buffer.from(payloadB64, "base64url").toString()
57
+ );
58
+ } else {
59
+ req.attestation = attestationData.data.attestation;
60
+ }
61
+
62
+ next();
63
+ } catch (err) {
64
+ return res.status(400).json({ error: "Invalid attestation format" });
65
+ }
66
+ };
67
+ }
68
+
69
+ // Protected route — only accessible with a valid attestation
70
+ app.post("/api/premium", requireAttestation({ maxAge: 120 }), (req, res) => {
71
+ const { pass, results, id } = req.attestation;
72
+
73
+ if (!pass) {
74
+ return res.status(403).json({
75
+ error: "Conditions not met",
76
+ attestationId: id,
77
+ });
78
+ }
79
+
80
+ res.json({
81
+ message: "Welcome, verified holder",
82
+ attestationId: id,
83
+ conditionsMet: results.filter((r) => r.met).map((r) => r.label),
84
+ });
85
+ });
86
+
87
+ // Health check
88
+ app.get("/health", (req, res) => res.json({ ok: true }));
89
+
90
+ const PORT = process.env.PORT || 3000;
91
+ app.listen(PORT, () => {
92
+ console.log(`Server running on http://localhost:${PORT}`);
93
+ console.log("POST /api/premium with { attestation: <response or JWT> }");
94
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * jwt-format.mjs — Request attestation as a JWT for gateway integration.
3
+ *
4
+ * Adding "format": "jwt" to the request returns a standard ES256 JWT
5
+ * alongside the raw attestation. The JWT is verifiable by any standard
6
+ * JWT library or gateway (Kong, Nginx, Cloudflare Access, AWS API Gateway)
7
+ * using the JWKS endpoint — no custom code needed on the gateway side.
8
+ *
9
+ * This is the bridge between InsumerAPI and existing auth infrastructure:
10
+ * the attestation format is standard, so it plugs into standard tools.
11
+ *
12
+ * Usage: INSUMER_API_KEY=insr_live_... node examples/jwt-format.mjs
13
+ */
14
+
15
+ import { verifyAttestation } from "insumer-verify";
16
+
17
+ const API_KEY = process.env.INSUMER_API_KEY;
18
+ if (!API_KEY) {
19
+ console.error("Set INSUMER_API_KEY environment variable");
20
+ process.exit(1);
21
+ }
22
+
23
+ // Request JWT format
24
+ const res = await fetch("https://api.insumermodel.com/v1/attest", {
25
+ method: "POST",
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ "X-API-Key": API_KEY,
29
+ },
30
+ body: JSON.stringify({
31
+ wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
32
+ conditions: [
33
+ {
34
+ type: "token_balance",
35
+ chainId: 1,
36
+ contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
37
+ threshold: 100,
38
+ decimals: 6,
39
+ label: "USDC >= 100 on Ethereum",
40
+ },
41
+ ],
42
+ format: "jwt",
43
+ }),
44
+ });
45
+
46
+ const apiResponse = await res.json();
47
+
48
+ if (!apiResponse.ok) {
49
+ console.error("API error:", apiResponse.error);
50
+ process.exit(1);
51
+ }
52
+
53
+ // The JWT is in the response data
54
+ const jwt = apiResponse.data.jwt;
55
+ console.log("JWT token:", jwt);
56
+ console.log();
57
+
58
+ // Decode and inspect JWT claims (without verification, for display)
59
+ const [, payloadB64] = jwt.split(".");
60
+ const claims = JSON.parse(Buffer.from(payloadB64, "base64url").toString());
61
+ console.log("JWT claims:");
62
+ console.log(` iss: ${claims.iss}`);
63
+ console.log(` sub: ${claims.sub} (wallet)`);
64
+ console.log(` jti: ${claims.jti} (attestation ID)`);
65
+ console.log(` pass: ${claims.pass}`);
66
+ console.log(` iat: ${new Date(claims.iat * 1000).toISOString()}`);
67
+ console.log(` exp: ${new Date(claims.exp * 1000).toISOString()}`);
68
+ console.log();
69
+
70
+ // Verify the JWT — insumer-verify auto-detects string input as JWT
71
+ const result = await verifyAttestation(jwt);
72
+
73
+ console.log("JWT verification:", result.valid ? "PASSED" : "FAILED");
74
+ console.log("Checks:", JSON.stringify(result.checks, null, 2));
75
+
76
+ // You can also verify the raw attestation from the same response
77
+ const rawResult = await verifyAttestation(apiResponse);
78
+ console.log("\nRaw attestation verification:", rawResult.valid ? "PASSED" : "FAILED");
@@ -0,0 +1,79 @@
1
+ /**
2
+ * multi-condition.mjs — Multiple conditions across different chains.
3
+ *
4
+ * A single attestation request can check up to 10 conditions across any
5
+ * combination of the 32 supported chains. The response is one signed
6
+ * attestation covering all conditions — pass is true only if ALL are met.
7
+ *
8
+ * Usage: INSUMER_API_KEY=insr_live_... node examples/multi-condition.mjs
9
+ */
10
+
11
+ import { verifyAttestation } from "insumer-verify";
12
+
13
+ const API_KEY = process.env.INSUMER_API_KEY;
14
+ if (!API_KEY) {
15
+ console.error("Set INSUMER_API_KEY environment variable");
16
+ process.exit(1);
17
+ }
18
+
19
+ // Check multiple conditions in a single call:
20
+ // - USDC on Ethereum (EVM, chainId 1)
21
+ // - UNI governance token on Ethereum
22
+ // - USDC on Base (EVM, chainId 8453)
23
+ const res = await fetch("https://api.insumermodel.com/v1/attest", {
24
+ method: "POST",
25
+ headers: {
26
+ "Content-Type": "application/json",
27
+ "X-API-Key": API_KEY,
28
+ },
29
+ body: JSON.stringify({
30
+ wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
31
+ conditions: [
32
+ {
33
+ type: "token_balance",
34
+ chainId: 1,
35
+ contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
36
+ threshold: 100,
37
+ decimals: 6,
38
+ label: "USDC >= 100 on Ethereum",
39
+ },
40
+ {
41
+ type: "token_balance",
42
+ chainId: 1,
43
+ contractAddress: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
44
+ threshold: 1,
45
+ decimals: 18,
46
+ label: "UNI >= 1 on Ethereum",
47
+ },
48
+ {
49
+ type: "token_balance",
50
+ chainId: 8453,
51
+ contractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
52
+ threshold: 50,
53
+ decimals: 6,
54
+ label: "USDC >= 50 on Base",
55
+ },
56
+ ],
57
+ }),
58
+ });
59
+
60
+ const apiResponse = await res.json();
61
+
62
+ if (!apiResponse.ok) {
63
+ console.error("API error:", apiResponse.error);
64
+ process.exit(1);
65
+ }
66
+
67
+ // Verify once — covers all conditions in the attestation
68
+ const result = await verifyAttestation(apiResponse, { maxAge: 120 });
69
+
70
+ console.log("Verification:", result.valid ? "PASSED" : "FAILED");
71
+
72
+ const { attestation } = apiResponse.data;
73
+ console.log(`\nAttestation ${attestation.id}:`);
74
+ console.log(` Overall: ${attestation.pass ? "ALL MET" : "NOT ALL MET"} (${attestation.passCount} passed, ${attestation.failCount} failed)`);
75
+ console.log(` Expires: ${attestation.expiresAt}`);
76
+
77
+ for (const r of attestation.results) {
78
+ console.log(` [${r.met ? "PASS" : "FAIL"}] ${r.label} (chain ${r.chainId}, block ${r.blockNumber})`);
79
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * verify-manual.mjs — DIY attestation verification with Web Crypto.
3
+ *
4
+ * No library. No dependencies. Just the Web Crypto API and the spec.
5
+ *
6
+ * This example proves that the attestation format is open and independently
7
+ * verifiable. You don't need insumer-verify, you don't need to trust any
8
+ * library — you can verify directly against the JWKS public key using
9
+ * standard cryptographic primitives available in every modern runtime.
10
+ *
11
+ * The protocol standardizes the format and verification model, not the
12
+ * implementation. This file IS a conforming verifier.
13
+ *
14
+ * Implements all 4 verification checks from the State Attestation Spec:
15
+ * 1. Signature — ECDSA P-256 over {id, pass, results, attestedAt}
16
+ * 2. Condition hashes — SHA-256 of canonical sorted-key JSON
17
+ * 3. Freshness — blockTimestamp age vs maxAge
18
+ * 4. Expiry — attestation validity window
19
+ *
20
+ * Usage: INSUMER_API_KEY=insr_live_... node examples/verify-manual.mjs
21
+ */
22
+
23
+ const API_KEY = process.env.INSUMER_API_KEY;
24
+ if (!API_KEY) {
25
+ console.error("Set INSUMER_API_KEY environment variable");
26
+ process.exit(1);
27
+ }
28
+
29
+ // ── Step 1: Fetch the signing key from JWKS ──────────────────────
30
+
31
+ const jwksRes = await fetch("https://api.insumermodel.com/v1/jwks");
32
+ const jwks = await jwksRes.json();
33
+ const keyData = jwks.keys[0]; // or match by kid
34
+
35
+ const publicKey = await crypto.subtle.importKey(
36
+ "jwk",
37
+ { kty: keyData.kty, crv: keyData.crv, x: keyData.x, y: keyData.y },
38
+ { name: "ECDSA", namedCurve: "P-256" },
39
+ false,
40
+ ["verify"]
41
+ );
42
+
43
+ console.log("Imported public key (kid:", keyData.kid, ")");
44
+
45
+ // ── Step 2: Request an attestation ───────────────────────────────
46
+
47
+ const res = await fetch("https://api.insumermodel.com/v1/attest", {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ "X-API-Key": API_KEY,
52
+ },
53
+ body: JSON.stringify({
54
+ wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
55
+ conditions: [
56
+ {
57
+ type: "token_balance",
58
+ chainId: 1,
59
+ contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
60
+ threshold: 100,
61
+ decimals: 6,
62
+ label: "USDC >= 100 on Ethereum",
63
+ },
64
+ ],
65
+ }),
66
+ });
67
+
68
+ const apiResponse = await res.json();
69
+ if (!apiResponse.ok) {
70
+ console.error("API error:", apiResponse.error);
71
+ process.exit(1);
72
+ }
73
+
74
+ const { attestation, sig, kid } = apiResponse.data;
75
+ console.log("Received attestation:", attestation.id);
76
+
77
+ // ── Check 1: Signature verification ─────────────────────────────
78
+ // The signed payload is JSON.stringify({id, pass, results, attestedAt})
79
+ // in that exact field order.
80
+
81
+ const signedPayload = JSON.stringify({
82
+ id: attestation.id,
83
+ pass: attestation.pass,
84
+ results: attestation.results,
85
+ attestedAt: attestation.attestedAt,
86
+ });
87
+
88
+ const payloadBytes = new TextEncoder().encode(signedPayload);
89
+ const sigBytes = Uint8Array.from(atob(sig), (c) => c.charCodeAt(0));
90
+
91
+ const sigValid = await crypto.subtle.verify(
92
+ { name: "ECDSA", hash: "SHA-256" },
93
+ publicKey,
94
+ sigBytes,
95
+ payloadBytes
96
+ );
97
+
98
+ console.log("\nCheck 1 — Signature:", sigValid ? "PASSED" : "FAILED");
99
+
100
+ // ── Check 2: Condition hash integrity ────────────────────────────
101
+ // Each result's conditionHash must equal SHA-256 of its evaluatedCondition
102
+ // serialized with sorted keys.
103
+
104
+ let hashesValid = true;
105
+ for (const r of attestation.results) {
106
+ const sortedKeys = Object.keys(r.evaluatedCondition).sort();
107
+ const canonical = JSON.stringify(r.evaluatedCondition, sortedKeys);
108
+ const hashBuffer = await crypto.subtle.digest(
109
+ "SHA-256",
110
+ new TextEncoder().encode(canonical)
111
+ );
112
+ const computed =
113
+ "0x" +
114
+ Array.from(new Uint8Array(hashBuffer))
115
+ .map((b) => b.toString(16).padStart(2, "0"))
116
+ .join("");
117
+
118
+ if (computed !== r.conditionHash) {
119
+ console.log(`Check 2 — Condition hash MISMATCH at index ${r.condition}`);
120
+ console.log(` Expected: ${r.conditionHash}`);
121
+ console.log(` Computed: ${computed}`);
122
+ hashesValid = false;
123
+ }
124
+ }
125
+ console.log("Check 2 — Condition hashes:", hashesValid ? "PASSED" : "FAILED");
126
+
127
+ // ── Check 3: Freshness ───────────────────────────────────────────
128
+ // Reject if blockTimestamp is older than maxAge seconds.
129
+
130
+ const MAX_AGE_SECONDS = 120;
131
+ let freshnessValid = true;
132
+ for (const r of attestation.results) {
133
+ if (!r.blockTimestamp) continue; // skip chains without block timestamps
134
+ const ageMs = Date.now() - new Date(r.blockTimestamp).getTime();
135
+ if (ageMs > MAX_AGE_SECONDS * 1000) {
136
+ console.log(
137
+ `Check 3 — Result ${r.condition} is ${Math.round(ageMs / 1000)}s old (max: ${MAX_AGE_SECONDS}s)`
138
+ );
139
+ freshnessValid = false;
140
+ }
141
+ }
142
+ console.log("Check 3 — Freshness:", freshnessValid ? "PASSED" : "FAILED");
143
+
144
+ // ── Check 4: Expiry ──────────────────────────────────────────────
145
+ // Reject if the attestation's validity window has elapsed.
146
+
147
+ const expiryValid = Date.now() <= new Date(attestation.expiresAt).getTime();
148
+ console.log("Check 4 — Expiry:", expiryValid ? "PASSED" : "FAILED");
149
+
150
+ // ── Summary ──────────────────────────────────────────────────────
151
+
152
+ const allPassed = sigValid && hashesValid && freshnessValid && expiryValid;
153
+ console.log("\nOverall:", allPassed ? "VALID" : "INVALID");
154
+ console.log(`Attestation ${attestation.id}: ${attestation.pass ? "conditions met" : "conditions not met"}`);
@@ -0,0 +1,100 @@
1
+ /**
2
+ * xrpl-trustline.mjs — XRPL trust line token attestation.
3
+ *
4
+ * XRPL uses a different addressing model than EVM chains:
5
+ * - Tokens are identified by issuer address + currency code
6
+ * - The contractAddress field holds the issuer's r-address
7
+ * - Results include ledgerIndex/ledgerHash instead of blockNumber/blockTimestamp
8
+ * - Trust line tokens include trustLineState with frozen status
9
+ *
10
+ * The attestation format is the same — signed, verifiable, boolean.
11
+ * The chain-specific details are in the evaluatedCondition and result fields.
12
+ *
13
+ * Usage: INSUMER_API_KEY=insr_live_... node examples/xrpl-trustline.mjs
14
+ */
15
+
16
+ import { verifyAttestation } from "insumer-verify";
17
+
18
+ const API_KEY = process.env.INSUMER_API_KEY;
19
+ if (!API_KEY) {
20
+ console.error("Set INSUMER_API_KEY environment variable");
21
+ process.exit(1);
22
+ }
23
+
24
+ // XRPL token issuers and currency codes
25
+ const RLUSD_ISSUER = "rMxCKbEDwqr76QuheSUMdEGf4B9xJ8m5De";
26
+ const RLUSD_CURRENCY = "524C555344000000000000000000000000000000";
27
+
28
+ const USDC_ISSUER = "rGm7WCVp9gb4jZHWTEtGUr4dd74z2XuWhE";
29
+ const USDC_CURRENCY = "5553444300000000000000000000000000000000";
30
+
31
+ // Check RLUSD and USDC on XRPL
32
+ // Note: XRPL uses xrplWallet parameter (not wallet, which is EVM)
33
+ const res = await fetch("https://api.insumermodel.com/v1/attest", {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ "X-API-Key": API_KEY,
38
+ },
39
+ body: JSON.stringify({
40
+ xrplWallet: "ra8xqX4QhcogFfxpMxMByvFnXyxw9E8rzY",
41
+ conditions: [
42
+ {
43
+ type: "token_balance",
44
+ chainId: "xrpl",
45
+ contractAddress: RLUSD_ISSUER,
46
+ currency: RLUSD_CURRENCY,
47
+ threshold: 10,
48
+ decimals: 6,
49
+ label: "RLUSD >= 10 on XRPL",
50
+ },
51
+ {
52
+ type: "token_balance",
53
+ chainId: "xrpl",
54
+ contractAddress: USDC_ISSUER,
55
+ currency: USDC_CURRENCY,
56
+ threshold: 5,
57
+ decimals: 6,
58
+ label: "USDC >= 5 on XRPL",
59
+ },
60
+ ],
61
+ }),
62
+ });
63
+
64
+ const apiResponse = await res.json();
65
+
66
+ if (!apiResponse.ok) {
67
+ console.error("API error:", apiResponse.error);
68
+ process.exit(1);
69
+ }
70
+
71
+ // Verify — same function, same checks. XRPL results use ledgerIndex
72
+ // instead of blockTimestamp, so freshness check skips them gracefully.
73
+ const result = await verifyAttestation(apiResponse, { maxAge: 120 });
74
+
75
+ console.log("Verification:", result.valid ? "PASSED" : "FAILED");
76
+
77
+ const { attestation } = apiResponse.data;
78
+ console.log(`\nAttestation ${attestation.id}:`);
79
+
80
+ for (const r of attestation.results) {
81
+ console.log(`\n ${r.label}: ${r.met ? "MET" : "NOT MET"}`);
82
+ console.log(` Chain: ${r.chainId}`);
83
+
84
+ // XRPL-specific fields
85
+ if (r.ledgerIndex) {
86
+ console.log(` Ledger index: ${r.ledgerIndex}`);
87
+ }
88
+ if (r.ledgerHash) {
89
+ console.log(` Ledger hash: ${r.ledgerHash}`);
90
+ }
91
+
92
+ // Trust line state (non-native tokens only)
93
+ if (r.trustLineState) {
94
+ console.log(` Trust line frozen: ${r.trustLineState.frozen}`);
95
+ }
96
+
97
+ // The evaluatedCondition uses contractAddress for the issuer
98
+ console.log(` Issuer (contractAddress): ${r.evaluatedCondition.contractAddress}`);
99
+ console.log(` Currency: ${r.evaluatedCondition.currency}`);
100
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "insumer-verify",
3
- "version": "1.3.10",
3
+ "version": "1.4.0",
4
4
  "description": "Client-side verifier for InsumerAPI attestations. ECDSA P-256 signatures, condition hashes, block freshness, expiry. Zero dependencies. Used by DJD Agent Score (Coinbase x402).",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "build",
16
+ "examples",
16
17
  "README.md"
17
18
  ],
18
19
  "scripts": {