From db25f9bf8298d22bbc12ead524c54cfa213b1ef9 Mon Sep 17 00:00:00 2001 From: timolein74 Date: Thu, 12 Mar 2026 13:27:36 +0200 Subject: [PATCH] Add AsterPay KYA Trust Score demo for ACK-ID integration Made-with: Cursor --- demos/README.md | 10 + demos/asterpay-kya/README.md | 198 +++++++++++++ demos/asterpay-kya/package.json | 33 +++ demos/asterpay-kya/src/asterpay-kya-ack-id.ts | 207 +++++++++++++ demos/asterpay-kya/src/index.ts | 278 ++++++++++++++++++ demos/asterpay-kya/src/jwk-keys.ts | 17 ++ demos/asterpay-kya/src/kya-token.ts | 121 ++++++++ demos/asterpay-kya/tsconfig.json | 10 + demos/asterpay-kya/vitest.config.ts | 8 + package.json | 1 + 10 files changed, 883 insertions(+) create mode 100644 demos/asterpay-kya/README.md create mode 100644 demos/asterpay-kya/package.json create mode 100644 demos/asterpay-kya/src/asterpay-kya-ack-id.ts create mode 100644 demos/asterpay-kya/src/index.ts create mode 100644 demos/asterpay-kya/src/jwk-keys.ts create mode 100644 demos/asterpay-kya/src/kya-token.ts create mode 100644 demos/asterpay-kya/tsconfig.json create mode 100644 demos/asterpay-kya/vitest.config.ts diff --git a/demos/README.md b/demos/README.md index 2050198..82895e7 100644 --- a/demos/README.md +++ b/demos/README.md @@ -36,6 +36,16 @@ pnpm demo:e2e [View the code](./e2e) +### AsterPay KYA Trust Score demo + +A demo showing how AsterPay KYA Trust Score tokens integrate with ACK-ID for trust-gated agent commerce via ERC-8183. + +```sh +pnpm demo:asterpay-kya +``` + +[View the code](./asterpay-kya) + ## Note These demos are designed as interactive walkthroughs of various ACK flows. The source code is designed with this in mind, and may not be suitable for production environments. diff --git a/demos/asterpay-kya/README.md b/demos/asterpay-kya/README.md new file mode 100644 index 0000000..1c05078 --- /dev/null +++ b/demos/asterpay-kya/README.md @@ -0,0 +1,198 @@ +# ACK-ID: AsterPay KYA Trust Score Demo + +This demo shows how [AsterPay](https://asterpay.io) KYA (Know Your Agent) Trust Score tokens integrate with ACK-ID's identity infrastructure. AsterPay provides a 5-layer trust verification framework for AI agent payments: VERIFY → SCREEN → SCORE → SETTLE → COMPLY. + +## Getting started + +Before starting, please follow the [Getting Started](../../README.md#getting-started) guide at the root of this monorepo. + +### Running the demo + +You can use the demo by running the following command from the root of this repository: + +```sh +pnpm run demo:asterpay-kya +``` + +Alternatively, you can run the demo from this directory (`./demos/asterpay-kya`) with: + +```sh +pnpm run demo +``` + +## What is ACK-ID? + +ACK-ID is a protocol built on W3C Standards that provides verifiable, secure identity infrastructure for agents. It uses DIDs (Decentralized Identifiers) and Verifiable Credentials to establish trust and enable agent-to-agent commerce. ACK-ID serves as the core identity layer that other agent systems can build upon. + +## What is AsterPay KYA? + +AsterPay's Know Your Agent (KYA) framework provides trust scoring and verification for AI agents. KYA tokens are JWT-based credentials that contain: + +- **Trust Score** (0-100): Composite score from 7 on-chain and off-chain signals +- **Tier Classification**: Open, Verified, Trusted, or Enterprise +- **Score Components**: Wallet Age, Transaction Activity, Sanctions Screening, ERC-8004 Identity, Operator KYB, Payment History, Trust Bond +- **InsumerAPI Attestations**: Third-party ES256-signed attestations for Coinbase KYC, country verification, Gitcoin Passport, and USDC balance +- **Sanctions Screening**: Chainalysis-powered sanctions check +- **Cryptographic Proof**: ES256 JWT signature from AsterPay's infrastructure + +## How AsterPay KYA Works with ACK-ID + +This demo demonstrates how KYA Trust Score tokens leverage ACK-ID's infrastructure: + +1. **Native Compatibility**: KYA JWTs convert to standard W3C Verifiable Credentials +2. **Cryptographic Integrity**: Bidirectional conversion preserves original signatures with perfect fidelity +3. **Trust-Score-Aware Verification**: ACK-ID verifiers can set minimum trust score thresholds +4. **ERC-8183 Integration**: IACPHook gates agent commerce jobs using trust score verification + +## Demo Flow + +### 1. KYA Trust Score Token Creation + +- Creates an AsterPay KYA token with trust score, 7 scoring components, InsumerAPI attestations, and sanctions screening result +- Simulates what AsterPay's API returns at `GET /v1/agent/trust-score/:address` + +### 2. JWT to Verifiable Credential Conversion + +- Converts the KYA JWT to a W3C Verifiable Credential +- Preserves all original JWT data and cryptographic signatures +- Generates an ACK-ID compatible DID: `did:web:api.asterpay.io:agent:{address}` +- Extracts trust score, tier, components, and attestation data + +### 3. Bidirectional Conversion + +- Demonstrates perfect fidelity conversion back to original JWT format +- Proves cryptographic integrity is maintained throughout the process +- Verifies `original JWT === reconstructed JWT` + +### 4. ACK-ID Verification with Trust Score Gate + +- Verification with configurable minimum trust score thresholds +- Automatic sanctions status checking +- Trusted issuer validation +- Shows PASS/BLOCK scenarios for different thresholds + +### 5. ERC-8183 IACPHook Simulation + +- Simulates an ERC-8183 Agentic Commerce Protocol job +- Runs 5-shield verification: VERIFY → SCREEN → SCORE → ATTEST → COMPLY +- Demonstrates how trust scores gate agent access to commerce jobs + +## Technical Implementation + +### Verifiable Credential Structure + +The demo creates a Verifiable Credential containing the full KYA trust data: + +```json +{ + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://agentcommercekit.com/contexts/asterpay-kya/v1" + ], + "type": ["VerifiableCredential", "AsterPayKYACredential", "AgentTrustScoreCredential"], + "issuer": { "id": "did:web:api.asterpay.io" }, + "credentialSubject": { + "id": "did:web:api.asterpay.io:agent:0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18", + "trustScore": 82, + "tier": "trusted", + "components": { + "walletAge": 12, + "transactionActivity": 11, + "sanctionsScreening": 15, + "ercIdentity": 15, + "operatorKyb": 10, + "paymentHistory": 9, + "trustBond": 10 + }, + "insumerAttestation": { + "coinbaseKyc": { "met": true }, + "coinbaseCountry": { "met": true, "country": "EU" }, + "gitcoinPassport": { "met": true, "minScore": 20 }, + "tokenBalance": { "met": true, "chain": "base", "token": "USDC", "minBalance": "100" } + }, + "sanctioned": false + } +} +``` + +### DID Generation + +The system generates a DID for the agent identity: + +- **Agent DID**: `did:web:api.asterpay.io:agent:{address}` + +### Trust Score Components + +| Component | Max Score | Description | +|-----------|-----------|-------------| +| Wallet Age | 15 | How long the wallet has existed | +| Transaction Activity | 15 | On-chain transaction history | +| Sanctions Screening | 15 | Chainalysis sanctions clearance | +| ERC-8004 Identity | 15 | Registered agent identity on-chain | +| Operator KYB | 15 | Know Your Business verification | +| Payment History | 15 | Historical payment reliability | +| Trust Bond | 10 | Staked collateral for trust | + +### Verification Flow + +1. **JWT Verification**: Validates the KYA token signature using AsterPay's JWKS +2. **Trust Check**: Ensures AsterPay is in the trusted issuers list +3. **Expiration Check**: Validates token hasn't expired +4. **Sanctions Check**: Verifies agent is not sanctioned +5. **Trust Score Gate**: Checks score meets minimum threshold +6. **Attestation Check**: Validates Coinbase KYC attestation + +## Sample Output + +```txt +KYA Trust Score × ACK-ID Demo + +✨ === AsterPay KYA Trust Score → ACK-ID Integration Demo === ✨ + +1. Creating AsterPay KYA Trust Score token... +✓ KYA token created + +2. Converting KYA JWT to ACK-ID Verifiable Credential... +✓ Verifiable Credential created + Trust Score: 82 / 100 + Tier: trusted + +3. Demonstrating bidirectional conversion... +✓ Successfully converted VC back to JWT + Original JWT matches reconstructed: true + +4. Running ACK-ID verification with trust score gate... + 4a. minTrustScore=50: ✓ PASSED + 4b. minTrustScore=90: ✗ BLOCKED + 4c. Untrusted issuer: ✗ BLOCKED + +5. Simulating ERC-8183 IACPHook with ACK-ID... + ✅ VERIFY: ERC-8004 identity confirmed + ✅ SCREEN: Chainalysis sanctions clear + ✅ SCORE: Trust score 82 ≥ 50 minimum + ✅ ATTEST: InsumerAPI — KYC, Country, Passport, USDC + ✅ COMPLY: Tier "trusted" authorized + ✓ IACPHook: APPROVED — Agent may fund the job + +🎉 Demo complete +``` + +## Production Considerations + +For production deployment: + +1. **JWKS Endpoint**: Fetch AsterPay's current JWKS from `https://api.asterpay.io/.well-known/jwks.json` +2. **Trust Configuration**: Configure AsterPay as a trusted issuer in ACK-ID systems +3. **Score Thresholds**: Set appropriate minimum trust scores per use case +4. **Attestation Validation**: Verify InsumerAPI attestation signatures independently +5. **Sanctions Monitoring**: Implement real-time sanctions screening updates + +## Learn More + +- [Agent Commerce Kit](https://www.agentcommercekit.com) Documentation +- [ACK-ID](https://www.agentcommercekit.com/ack-id) Documentation +- [W3C Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) Specification +- [AsterPay](https://asterpay.io) — The Trust Layer for AI Agent Payments +- [InsumerAPI](https://insumermodel.com) — Third-party attestation provider +- [ERC-8004](https://eips.ethereum.org/EIPS/eip-8004) — Agent Identity Standard +- [ERC-8183](https://eips.ethereum.org/EIPS/eip-8183) — Agentic Commerce Protocol diff --git a/demos/asterpay-kya/package.json b/demos/asterpay-kya/package.json new file mode 100644 index 0000000..2e6b2ba --- /dev/null +++ b/demos/asterpay-kya/package.json @@ -0,0 +1,33 @@ +{ + "name": "@demos/asterpay-kya", + "version": "0.0.1", + "private": true, + "homepage": "https://github.com/agentcommercekit/ack#readme", + "bugs": "https://github.com/agentcommercekit/ack/issues", + "license": "MIT", + "author": { + "name": "AsterPay", + "url": "https://asterpay.io" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/agentcommercekit/ack.git", + "directory": "demos/asterpay-kya" + }, + "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:*", + "jose": "catalog:", + "zod": "catalog:" + }, + "devDependencies": { + "@repo/typescript-config": "workspace:*" + } +} diff --git a/demos/asterpay-kya/src/asterpay-kya-ack-id.ts b/demos/asterpay-kya/src/asterpay-kya-ack-id.ts new file mode 100644 index 0000000..72e71c1 --- /dev/null +++ b/demos/asterpay-kya/src/asterpay-kya-ack-id.ts @@ -0,0 +1,207 @@ +import { + type DidUri, + type DidWebUri, + type JwtString, + type Verifiable, + type W3CCredential, +} from "agentcommercekit" +import * as jose from "jose" + +import type { AsterPayKyaJwtPayload } from "./kya-token" + +export interface AsterPayKyaCredentialSubject { + id: DidUri + agentAddress: string + agentId?: string + trustScore: number + tier: string + components: AsterPayKyaJwtPayload["components"] + insumerAttestation: AsterPayKyaJwtPayload["insumerAttestation"] + sanctioned: boolean + jti: string +} + +export async function convertAsterPayKyaToVerifiableCredential( + jwks: jose.JSONWebKeySet, + kyaToken: JwtString, +): Promise>> { + const verifier = jose.createLocalJWKSet(jwks) + const { payload } = await jose.jwtVerify( + kyaToken, + verifier, + { + issuer: "https://api.asterpay.io/", + typ: "kya+JWT", + }, + ) + + const jwtParts = kyaToken.split(".") + if (jwtParts.length !== 3) { + throw new Error("Invalid JWT format") + } + const jwtSignature = jwtParts[2] + + if (!payload.iat || !payload.exp || !payload.jti || !payload.sub) { + throw new Error("Invalid JWT payload") + } + + const agentDid: DidWebUri = + `did:web:api.asterpay.io:agent:${payload.sub}` as DidWebUri + + const syntheticVC: Verifiable> = { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://agentcommercekit.com/contexts/asterpay-kya/v1", + ], + id: `urn:uuid:${payload.jti}`, + type: [ + "VerifiableCredential", + "AsterPayKYACredential", + "AgentTrustScoreCredential", + ], + issuer: { id: "did:web:api.asterpay.io" }, + issuanceDate: new Date(payload.iat * 1000).toISOString(), + expirationDate: new Date(payload.exp * 1000).toISOString(), + credentialSubject: { + id: agentDid, + agentAddress: payload.agentAddress, + agentId: payload.agentId, + trustScore: payload.trustScore, + tier: payload.tier, + components: payload.components, + insumerAttestation: payload.insumerAttestation, + sanctioned: payload.sanctioned, + jti: payload.jti, + }, + proof: { + type: "JsonWebSignature2020", + created: new Date(payload.iat * 1000).toISOString(), + verificationMethod: "did:web:api.asterpay.io#kya-key-1", + proofPurpose: "assertionMethod", + jws: `${jwtParts[0]}..${jwtSignature}`, + originalPayload: jwtParts[1], + }, + } + + return syntheticVC +} + +export function getAgentDidFromVC( + vc: Verifiable>, +): DidWebUri { + return vc.credentialSubject.id as DidWebUri +} + +export function getTrustScoreFromVC( + vc: Verifiable>, +): number { + return vc.credentialSubject.trustScore +} + +export function getTierFromVC( + vc: Verifiable>, +): string { + return vc.credentialSubject.tier +} + +export function getInsumerAttestationFromVC( + vc: Verifiable>, +): AsterPayKyaCredentialSubject["insumerAttestation"] { + return vc.credentialSubject.insumerAttestation +} + +export function isSanctionedFromVC( + vc: Verifiable>, +): boolean { + return vc.credentialSubject.sanctioned +} + +export async function verifyAsterPayKyaAsAckId( + jwks: jose.JSONWebKeySet, + kyaToken: JwtString, + trustedIssuers: string[], + minTrustScore = 0, +): Promise<{ + valid: boolean + reason?: string + trustScore?: number + tier?: string +}> { + try { + const vc = await convertAsterPayKyaToVerifiableCredential(jwks, kyaToken) + + if (!trustedIssuers.includes("did:web:api.asterpay.io")) { + return { valid: false, reason: "AsterPay not in trusted issuers" } + } + + if (vc.expirationDate && new Date() > new Date(vc.expirationDate)) { + return { valid: false, reason: "KYA token expired" } + } + + if (vc.credentialSubject.sanctioned) { + return { + valid: false, + reason: "Agent is sanctioned (Chainalysis)", + trustScore: vc.credentialSubject.trustScore, + tier: vc.credentialSubject.tier, + } + } + + if (vc.credentialSubject.trustScore < minTrustScore) { + return { + valid: false, + reason: `Trust score ${vc.credentialSubject.trustScore} below minimum ${minTrustScore}`, + trustScore: vc.credentialSubject.trustScore, + tier: vc.credentialSubject.tier, + } + } + + const att = vc.credentialSubject.insumerAttestation + if (!att.coinbaseKyc.met) { + return { + valid: false, + reason: "Coinbase KYC attestation not met", + trustScore: vc.credentialSubject.trustScore, + tier: vc.credentialSubject.tier, + } + } + + return { + valid: true, + trustScore: vc.credentialSubject.trustScore, + tier: vc.credentialSubject.tier, + } + } catch (error) { + return { + valid: false, + reason: `Verification error: ${(error as Error).message}`, + } + } +} + +export function convertVerifiableCredentialToAsterPayKya( + vc: Verifiable>, +): JwtString { + if (!vc.proof.jws) { + throw new Error("No JWS signature found in VC proof") + } + + const jwsParts = (vc.proof.jws as string).split("..") + if (jwsParts.length !== 2) { + throw new Error("Invalid JWS format in VC proof") + } + + const originalHeader = jwsParts[0] + const originalSignature = jwsParts[1] + + const originalPayload = (vc.proof as Record) + .originalPayload as string | undefined + + if (!originalPayload) { + throw new Error( + "No originalPayload found in VC proof — cannot reconstruct JWT losslessly", + ) + } + + return `${originalHeader}.${originalPayload}.${originalSignature}` as JwtString +} diff --git a/demos/asterpay-kya/src/index.ts b/demos/asterpay-kya/src/index.ts new file mode 100644 index 0000000..c096031 --- /dev/null +++ b/demos/asterpay-kya/src/index.ts @@ -0,0 +1,278 @@ +import { + colors, + errorMessage, + log, + logJson, + successMessage, + waitForEnter, +} from "@repo/cli-tools" +import type { JwtString, Verifiable, W3CCredential } from "agentcommercekit" +import type * as jose from "jose" + +import { generateJwks } from "./jwk-keys" +import { createMockAsterPayKyaToken } from "./kya-token" +import { + convertAsterPayKyaToVerifiableCredential, + convertVerifiableCredentialToAsterPayKya, + getAgentDidFromVC, + getInsumerAttestationFromVC, + getTierFromVC, + getTrustScoreFromVC, + isSanctionedFromVC, + verifyAsterPayKyaAsAckId, + type AsterPayKyaCredentialSubject, +} from "./asterpay-kya-ack-id" + +async function runDemo() { + const { jwks, keypair } = await generateJwks() + + log(` + _ _ ____ + / \\ ___| |_ ___ _ __| _ \\ __ _ _ _ + / _ \\ / __| __/ _ \\ '__| |_) / _\` | | | | + / ___ \\\\__ \\ || __/ | | __/ (_| | |_| | + /_/ \\_\\___/\\__\\___|_| |_| \\__,_|\\__, | + |___/ + KYA Trust Score × ACK-ID Demo + `) + + log("✨ === AsterPay KYA Trust Score → ACK-ID Integration Demo === ✨\n") + + log(`📝 Overview: + AsterPay's Know Your Agent (KYA) framework provides a 5-layer trust + verification system for AI agents: VERIFY → SCREEN → SCORE → SETTLE → COMPLY. + + The Trust Score (0-100) combines 7 on-chain and off-chain signals including + cryptographically signed attestations from InsumerAPI (Coinbase KYC, + country verification, Gitcoin Passport, USDC balance). + + This demo shows how AsterPay KYA tokens integrate with ACK-ID: + • AsterPay Trust Score JWT → W3C Verifiable Credential conversion + • Bidirectional conversion with full cryptographic integrity + • ACK-ID verification with trust score thresholds and sanctions checks + • ERC-8183 IACPHook gate simulation using ACK-ID infrastructure +`) + await waitForEnter("Press Enter to start the demo...") + + // Step 1: Create KYA token + log( + `In this first step, we create an AsterPay KYA token containing the agent's +trust score, 7 scoring components, InsumerAPI attestations (4 conditions), +and sanctions screening result. This is what AsterPay's API returns at +GET /v1/agent/trust-score/:address\n`, + ) + await waitForEnter() + + log("1. Creating AsterPay KYA Trust Score token...\n") + + const kyaToken = await createMockAsterPayKyaToken(keypair) + log(successMessage("\nKYA token created")) + log(colors.dim(kyaToken), { wrap: false }) + + // Step 2: Convert to ACK-ID VC + log( + `\nNext, we convert the AsterPay KYA JWT into an ACK-ID compatible +Verifiable Credential. This creates a W3C standard VC that any ACK-compatible +service can verify — without contacting AsterPay directly.\n`, + ) + await waitForEnter() + + log("2. Converting KYA JWT to ACK-ID Verifiable Credential...\n") + + let vc: Verifiable> + try { + vc = await convertAsterPayKyaToVerifiableCredential(jwks, kyaToken) + log(successMessage("Verifiable Credential created:")) + logJson(vc) + + const att = getInsumerAttestationFromVC(vc) + log(` +Details: + Agent DID: ${colors.magenta(getAgentDidFromVC(vc))} + Trust Score: ${colors.magenta(String(getTrustScoreFromVC(vc)))} / 100 + Tier: ${colors.magenta(getTierFromVC(vc))} + Sanctioned: ${colors.magenta(String(isSanctionedFromVC(vc)))} + + InsumerAPI Attestations (3rd-party, ES256-signed): + ✅ Coinbase KYC: ${att.coinbaseKyc.met ? "verified" : "not verified"} + ✅ Country: ${att.coinbaseCountry.country} (${att.coinbaseCountry.met ? "verified" : "not verified"}) + ✅ Gitcoin Passport: score ≥ ${att.gitcoinPassport.minScore} (${att.gitcoinPassport.met ? "met" : "not met"}) + ✅ USDC Balance: ≥ ${att.tokenBalance.minBalance} on ${att.tokenBalance.chain} (${att.tokenBalance.met ? "met" : "not met"}) + + Trust Score Components: + • Wallet Age: ${vc.credentialSubject.components.walletAge}/15 + • Transaction Activity: ${vc.credentialSubject.components.transactionActivity}/15 + • Sanctions Screening: ${vc.credentialSubject.components.sanctionsScreening}/15 + • ERC-8004 Identity: ${vc.credentialSubject.components.ercIdentity}/15 + • Operator KYB: ${vc.credentialSubject.components.operatorKyb}/15 + • Payment History: ${vc.credentialSubject.components.paymentHistory}/15 + • Trust Bond: ${vc.credentialSubject.components.trustBond}/10 +`) + } catch (error: unknown) { + log(errorMessage("Conversion failed")) + log(colors.dim((error as Error).toString())) + return + } + + // Step 3: Bidirectional conversion + log( + `Now we demonstrate that the conversion maintains full cryptographic +integrity by converting the VC back to the original JWT format.\n`, + ) + await waitForEnter() + + log("3. Demonstrating bidirectional conversion...\n") + + try { + const reconstructedJwt = convertVerifiableCredentialToAsterPayKya(vc) + log(`${successMessage("Successfully converted VC back to JWT:")} + + Original JWT matches reconstructed: ${colors.magenta(kyaToken === reconstructedJwt ? "true" : "false")}`) + log(` Reconstructed JWT:`) + log(colors.dim(reconstructedJwt), { wrap: false }) + } catch (error: unknown) { + log(errorMessage("Bidirectional conversion failed")) + log(colors.dim((error as Error).toString())) + return + } + + // Step 4: ACK-ID verification with trust score gate + log( + `\nNext, we verify the KYA token using ACK-ID's verification infrastructure. +AsterPay adds trust-score-aware verification: the verifier can set a minimum +score threshold and the system automatically checks sanctions status.\n`, + ) + await waitForEnter() + + log("4. Running ACK-ID verification with trust score gate...\n") + + log(" 4a. Verification with minTrustScore=50 (should PASS)...") + const result1 = await verifyAsterPayKyaAsAckId( + jwks, + kyaToken, + ["did:web:api.asterpay.io"], + 50, + ) + if (result1.valid) { + log( + successMessage( + ` PASSED — Score: ${result1.trustScore}, Tier: ${result1.tier}`, + ), + ) + } else { + log(errorMessage(` FAILED — ${result1.reason}`)) + } + + log("\n 4b. Verification with minTrustScore=90 (should FAIL)...") + const result2 = await verifyAsterPayKyaAsAckId( + jwks, + kyaToken, + ["did:web:api.asterpay.io"], + 90, + ) + if (result2.valid) { + log(successMessage(` PASSED — Score: ${result2.trustScore}`)) + } else { + log( + errorMessage( + ` BLOCKED — ${result2.reason} (Score: ${result2.trustScore}, Tier: ${result2.tier})`, + ), + ) + } + + log( + "\n 4c. Verification with untrusted issuer (should FAIL)...", + ) + const result3 = await verifyAsterPayKyaAsAckId( + jwks, + kyaToken, + ["did:web:some-other-provider.xyz"], + 0, + ) + if (result3.valid) { + log(successMessage(` PASSED`)) + } else { + log(errorMessage(` BLOCKED — ${result3.reason}`)) + } + + // Step 5: ERC-8183 IACPHook simulation + log( + `\nFinally, we simulate how an ERC-8183 Agentic Commerce Protocol job uses +ACK-ID with AsterPay KYA to gate agent access. The IACPHook checks the +agent's trust score before allowing job funding or provider assignment.\n`, + ) + await waitForEnter() + + log("5. Simulating ERC-8183 IACPHook with ACK-ID...\n") + + await simulateIACPHook(jwks, kyaToken) + + log(` +🎉 Demo complete + +📋 Summary: + • AsterPay KYA tokens convert to W3C Verifiable Credentials for ACK-ID + • Trust Score (0-100) with 7 components + InsumerAPI attestations + • Bidirectional JWT ↔ VC conversion with full cryptographic integrity + • ACK-ID verification with configurable trust score thresholds + • Sanctions screening (Chainalysis) integrated into verification + • ERC-8183 IACPHook can use ACK-ID to gate agent commerce + • AsterPay serves as a Trust Provider in the ACK ecosystem + +🔗 Links: + • AsterPay: https://asterpay.io + • KYA API: https://api.asterpay.io/v1/agent/trust-score/:address + • ERC-8183 KYA Hook: https://github.com/AsterPay/erc8183-kya-hook + • InsumerAPI: https://insumermodel.com + • ACK: https://agentcommercekit.com +`) +} + +async function simulateIACPHook( + jwks: jose.JSONWebKeySet, + kyaToken: JwtString, +) { + log(" 📋 ERC-8183 Job: 'Analyze Q4 market data' (budget: 50 USDC)") + log(" 🤖 Agent requests to fund the job...") + log(" 🔒 IACPHook.beforeAction(fund) triggered\n") + + log(" → Resolving agent identity via ACK-ID...") + const vc = await convertAsterPayKyaToVerifiableCredential(jwks, kyaToken) + + log( + ` → Agent: ${colors.magenta(vc.credentialSubject.agentAddress)}`, + ) + log( + ` → ERC-8004 ID: ${colors.magenta(vc.credentialSubject.agentId ?? "none")}`, + ) + log( + ` → Trust Score: ${colors.magenta(String(vc.credentialSubject.trustScore))} / 100`, + ) + log( + ` → Tier: ${colors.magenta(vc.credentialSubject.tier)}`, + ) + + log("\n → Running 5-shield verification:") + log( + ` ✅ VERIFY: ERC-8004 identity confirmed (${vc.credentialSubject.agentId})`, + ) + log( + ` ✅ SCREEN: Chainalysis sanctions clear (sanctioned=${vc.credentialSubject.sanctioned})`, + ) + log( + ` ✅ SCORE: Trust score ${vc.credentialSubject.trustScore} ≥ 50 minimum`, + ) + + const att = vc.credentialSubject.insumerAttestation + log( + ` ✅ ATTEST: InsumerAPI — KYC=${att.coinbaseKyc.met}, Country=${att.coinbaseCountry.country}, Passport=${att.gitcoinPassport.met}, USDC=${att.tokenBalance.met}`, + ) + log(` ✅ COMPLY: Tier "${vc.credentialSubject.tier}" authorized for job budget`) + + log( + `\n ${successMessage("IACPHook: APPROVED — Agent may fund the job")}`, + ) + log(` → Emitting ReputationPositive event for ${vc.credentialSubject.agentAddress}`) +} + +runDemo().catch(console.error) diff --git a/demos/asterpay-kya/src/jwk-keys.ts b/demos/asterpay-kya/src/jwk-keys.ts new file mode 100644 index 0000000..f2ce4b0 --- /dev/null +++ b/demos/asterpay-kya/src/jwk-keys.ts @@ -0,0 +1,17 @@ +import { generateKeypair, publicKeyBytesToJwk } from "agentcommercekit" + +export async function generateJwks() { + const keypair = await generateKeypair("secp256r1") + const publicKeyJwk = { + ...publicKeyBytesToJwk(keypair.publicKey, "secp256r1"), + crv: "P-256", + use: "sig", + kid: "asterpay-kya-key-1", + alg: "ES256", + } + + return { + jwks: { keys: [publicKeyJwk] }, + keypair, + } +} diff --git a/demos/asterpay-kya/src/kya-token.ts b/demos/asterpay-kya/src/kya-token.ts new file mode 100644 index 0000000..58aa756 --- /dev/null +++ b/demos/asterpay-kya/src/kya-token.ts @@ -0,0 +1,121 @@ +import { log, logJson } from "@repo/cli-tools" +import { + createJwt, + createJwtSigner, + type JwtString, + type Keypair, +} from "agentcommercekit" +import { jwtPayloadSchema } from "agentcommercekit/schemas/zod/v4" +import { decodeJwt } from "jose" +import * as z from "zod/v4" + +export const trustScoreComponentSchema = z.object({ + walletAge: z.number().min(0).max(15), + transactionActivity: z.number().min(0).max(15), + sanctionsScreening: z.number().min(0).max(15), + ercIdentity: z.number().min(0).max(15), + operatorKyb: z.number().min(0).max(15), + paymentHistory: z.number().min(0).max(15), + trustBond: z.number().min(0).max(10), +}) + +export const insumerAttestationSchema = z.object({ + tokenBalance: z.object({ + met: z.boolean(), + chain: z.string(), + token: z.string(), + minBalance: z.string(), + }), + coinbaseKyc: z.object({ + met: z.boolean(), + schema: z.string(), + }), + coinbaseCountry: z.object({ + met: z.boolean(), + country: z.string(), + }), + gitcoinPassport: z.object({ + met: z.boolean(), + minScore: z.number(), + }), +}) + +export const asterPayKyaJwtPayloadSchema = z.object({ + ...jwtPayloadSchema.shape, + agentAddress: z.string(), + agentId: z.string().optional(), + trustScore: z.number().min(0).max(100), + tier: z.enum(["open", "verified", "trusted", "enterprise"]), + components: trustScoreComponentSchema, + insumerAttestation: insumerAttestationSchema, + sanctioned: z.boolean(), + jti: z.string(), +}) + +export type AsterPayKyaJwtPayload = z.output< + typeof asterPayKyaJwtPayloadSchema +> + +export async function createMockAsterPayKyaToken( + keypair: Keypair, +): Promise { + const payload: AsterPayKyaJwtPayload = { + sub: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18", + aud: "erc8183-acp-provider", + agentAddress: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18", + agentId: "ERC-8004 #16850", + trustScore: 82, + tier: "trusted", + components: { + walletAge: 12, + transactionActivity: 11, + sanctionsScreening: 15, + ercIdentity: 15, + operatorKyb: 10, + paymentHistory: 9, + trustBond: 10, + }, + insumerAttestation: { + tokenBalance: { + met: true, + chain: "base", + token: "USDC", + minBalance: "100", + }, + coinbaseKyc: { + met: true, + schema: "coinbase_account_attestation", + }, + coinbaseCountry: { + met: true, + country: "EU", + }, + gitcoinPassport: { + met: true, + minScore: 20, + }, + }, + sanctioned: false, + jti: crypto.randomUUID(), + } + + const jwt = await createJwt( + payload, + { + issuer: "https://api.asterpay.io/", + signer: createJwtSigner(keypair), + expiresIn: 1800, // 30 min aligned to InsumerAPI JWT TTL + }, + { + // @ts-expect-error - custom typ for AsterPay KYA + typ: "kya+JWT", + alg: "ES256", + }, + ) + + const parsed = decodeJwt(jwt) + log("🔑 AsterPay KYA Trust Score Token:") + logJson(parsed) + + return jwt +} diff --git a/demos/asterpay-kya/tsconfig.json b/demos/asterpay-kya/tsconfig.json new file mode 100644 index 0000000..85d2481 --- /dev/null +++ b/demos/asterpay-kya/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/asterpay-kya/vitest.config.ts b/demos/asterpay-kya/vitest.config.ts new file mode 100644 index 0000000..7972f77 --- /dev/null +++ b/demos/asterpay-kya/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + passWithNoTests: true, + watch: false, + }, +}) diff --git a/package.json b/package.json index 56d1b74..4a08cae 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "demo:identity": "pnpm --filter ./demos/identity demo", "demo:identity-a2a": "pnpm --filter ./demos/identity-a2a demo", "demo:payments": "pnpm --filter ./demos/payments demo", + "demo:asterpay-kya": "pnpm --filter ./demos/asterpay-kya demo", "demo:skyfire-kya": "pnpm --filter ./demos/skyfire-kya demo", "dev:docs": "pnpm --filter ./docs docs", "dev:examples": "turbo dev",