Skip to content

Add AsterPay KYA Trust Score demo for ACK-ID integration#63

Open
timolein74 wants to merge 1 commit intoagentcommercekit:mainfrom
AsterPay:feat/asterpay-kya-demo
Open

Add AsterPay KYA Trust Score demo for ACK-ID integration#63
timolein74 wants to merge 1 commit intoagentcommercekit:mainfrom
AsterPay:feat/asterpay-kya-demo

Conversation

@timolein74
Copy link

@timolein74 timolein74 commented Mar 12, 2026

Summary

Adds an AsterPay KYA (Know Your Agent) Trust Score demo, similar to the existing Skyfire KYA demo, showing how AsterPay's trust scoring framework integrates with ACK-ID.

  • Trust Score (0-100) with 7 scoring components converted to W3C Verifiable Credentials
  • Bidirectional JWT <-> VC conversion with full cryptographic integrity (lossless round-trip)
  • Trust-score-aware ACK-ID verification with configurable minimum thresholds
  • ERC-8183 IACPHook simulation demonstrating 5-shield agent gate (VERIFY -> SCREEN -> SCORE -> ATTEST -> COMPLY)
  • InsumerAPI attestations (Coinbase KYC, country verification, Gitcoin Passport, USDC balance)

What is AsterPay?

AsterPay provides trust infrastructure for AI agent payments — trust scoring, sanctions screening, and EUR settlement via SEPA Instant. We're building the compliance and trust layer for agent commerce.

Files added

  • demos/asterpay-kya/ — Full demo with README, source, and config
  • package.json — Added demo:asterpay-kya script
  • demos/README.md — Added demo listing

Testing

The demo runs successfully with pnpm demo:asterpay-kya and all 5 steps complete without errors.

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Walkthrough

This PR introduces a comprehensive demo for AsterPay KYA Trust Score integration with ACK-ID, including new demo directory structure, documentation, configuration files, and TypeScript modules for KYA token issuance, JWT-to-Verifiable Credential conversion, verification, and IACPHook simulation. All changes are additive with no modifications to existing code.

Changes

Cohort / File(s) Summary
Documentation
demos/README.md, demos/asterpay-kya/README.md
Added overview section to root demos README and comprehensive guide covering demo flow, VC structure, verification, trust score components, and production considerations.
Configuration
demos/asterpay-kya/package.json, demos/asterpay-kya/tsconfig.json, demos/asterpay-kya/vitest.config.ts
Added workspace package configuration, TypeScript library config with path alias, and Vitest test runner configuration with no-tests pass-through.
Implementation
demos/asterpay-kya/src/jwk-keys.ts, demos/asterpay-kya/src/kya-token.ts, demos/asterpay-kya/src/asterpay-kya-ack-id.ts, demos/asterpay-kya/src/index.ts
Implemented JWKS generation, mock KYA token creation with zod schemas, bidirectional JWT\u2194️VC conversion with verification logic, and orchestrated demo script exercising token creation, VC conversion, validation gates, and IACPHook simulation.
Root Script
package.json
Added demo:asterpay-kya npm script to execute the demo via pnpm filter.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

  • Use pnpm catalogs for shared versions #42 — Related through the use of catalog-style dependency placeholders (workspace:* entries) in the new package.json, aligning with the migration to pnpm catalog format.

Suggested reviewers

  • pitluga
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly and clearly summarizes the main change: adding an AsterPay KYA Trust Score demo for ACK-ID integration, which matches the primary objective of the changeset.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@demos/asterpay-kya/README.md`:
- Line 3: The README contains inconsistent terminology: the top summary uses
"VERIFY → SCREEN → SCORE → SETTLE → COMPLY" while later text and the demo use
"VERIFY → SCREEN → SCORE → ATTEST → COMPLY"; update the README to use a single
term across the file (choose either SETTLE or ATTEST) and replace all
occurrences of the alternate phrase so the phrases "VERIFY → SCREEN → SCORE →
SETTLE → COMPLY" and "VERIFY → SCREEN → SCORE → ATTEST → COMPLY" no longer
conflict; ensure the demo description and any headings match the selected term.

In `@demos/asterpay-kya/src/asterpay-kya-ack-id.ts`:
- Around line 51-84: The syntheticVC currently embeds the original JWT in
proof.originalPayload and jws but builds credentialSubject separately, so
credentialSubject can diverge from the signed payload; to fix, either (A) bind
the proof to the generated credentialSubject by producing a real signature over
the final VC (i.e., compute the proof.jws from the canonicalized
credentialSubject/VC and set proof.originalPayload to that canonicalized
payload) using the issuer key, or (B) derive credentialSubject directly from the
decoded jwtParts[1] payload (so credentialSubject exactly matches
proof.originalPayload) and add a runtime check in any consumer/read path to
verify credentialSubject === decoded proof.originalPayload before trusting the
VC; update the syntheticVC.proof (fields jws and originalPayload) accordingly
and ensure verification logic checks this equality.
- Around line 29-45: The code currently only enforces iat, exp, jti, and sub on
the verified JWT payload but does not reject tokens missing the trust-critical
claims, allowing undefined trustScore or sanctioned to slip through; update the
verification in asterpay-kya-ack-id.ts (where payload is extracted in
verifyAsterPayKyaAsAckId / the verification block referencing payload,
jwtSignature) to also require presence and valid types for trustScore (number)
and sanctioned (boolean) before creating the VC, and throw an explicit Error
(e.g., "Missing or invalid trust-critical claims: trustScore/sanctioned") if
they are absent or of the wrong type.

In `@demos/asterpay-kya/src/index.ts`:
- Around line 240-275: The demo currently always approves the IACPHook path
regardless of the converted credential; update the logic around
convertAsterPayKyaToVerifiableCredential/ vc (and att) to evaluate the actual
verification gates (check vc.credentialSubject.sanctioned flag, numeric
vc.credentialSubject.trustScore against the minimum threshold, and required
insumerAttestation fields like att.coinbaseKyc.met, att.gitcoinPassport.met,
att.tokenBalance.met) and branch: if any gate fails, log appropriate FAILED
messages (instead of the hardcoded success lines), emit a rejection outcome
(e.g., failure message instead of successMessage("IACPHook: APPROVED...")), and
avoid emitting ReputationPositive; otherwise proceed with the existing approved
logs and ReputationPositive emission. Ensure the printed
VERIFY/SCREEN/SCORE/ATTEST/COMPLY lines reflect the actual pass/fail of each
check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f46841a5-dc61-47b1-b5f8-6edff0e26177

📥 Commits

Reviewing files that changed from the base of the PR and between f0e8c6c and db25f9b.

📒 Files selected for processing (10)
  • demos/README.md
  • demos/asterpay-kya/README.md
  • demos/asterpay-kya/package.json
  • demos/asterpay-kya/src/asterpay-kya-ack-id.ts
  • demos/asterpay-kya/src/index.ts
  • demos/asterpay-kya/src/jwk-keys.ts
  • demos/asterpay-kya/src/kya-token.ts
  • demos/asterpay-kya/tsconfig.json
  • demos/asterpay-kya/vitest.config.ts
  • package.json

@@ -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.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor terminology inconsistency with demo implementation.

Line 3 describes the framework as "VERIFY → SCREEN → SCORE → SETTLE → COMPLY", but line 77 and the demo implementation use "VERIFY → SCREEN → SCORE → ATTEST → COMPLY" (ATTEST instead of SETTLE). Consider aligning the terminology for consistency.

📝 Suggested fix
-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.
+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 → ATTEST → COMPLY.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.
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 → ATTEST → COMPLY.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demos/asterpay-kya/README.md` at line 3, The README contains inconsistent
terminology: the top summary uses "VERIFY → SCREEN → SCORE → SETTLE → COMPLY"
while later text and the demo use "VERIFY → SCREEN → SCORE → ATTEST → COMPLY";
update the README to use a single term across the file (choose either SETTLE or
ATTEST) and replace all occurrences of the alternate phrase so the phrases
"VERIFY → SCREEN → SCORE → SETTLE → COMPLY" and "VERIFY → SCREEN → SCORE →
ATTEST → COMPLY" no longer conflict; ensure the demo description and any
headings match the selected term.

Comment on lines +29 to +45
const { payload } = await jose.jwtVerify<AsterPayKyaJwtPayload>(
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")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject tokens with missing trust-critical claims before creating the VC.

Only iat, exp, jti, and sub are enforced here. A signed token that omits trustScore or sanctioned will still be converted, and verifyAsterPayKyaAsAckId() can treat those missing values as acceptable (undefined < minTrustScore and undefined sanctioned both fall through).

Suggested guard
-  if (!payload.iat || !payload.exp || !payload.jti || !payload.sub) {
+  if (
+    payload.iat == null ||
+    payload.exp == null ||
+    !payload.jti ||
+    !payload.sub ||
+    !payload.agentAddress ||
+    payload.trustScore == null ||
+    !payload.tier ||
+    !payload.components ||
+    !payload.insumerAttestation ||
+    payload.sanctioned == null
+  ) {
     throw new Error("Invalid JWT payload")
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demos/asterpay-kya/src/asterpay-kya-ack-id.ts` around lines 29 - 45, The code
currently only enforces iat, exp, jti, and sub on the verified JWT payload but
does not reject tokens missing the trust-critical claims, allowing undefined
trustScore or sanctioned to slip through; update the verification in
asterpay-kya-ack-id.ts (where payload is extracted in verifyAsterPayKyaAsAckId /
the verification block referencing payload, jwtSignature) to also require
presence and valid types for trustScore (number) and sanctioned (boolean) before
creating the VC, and throw an explicit Error (e.g., "Missing or invalid
trust-critical claims: trustScore/sanctioned") if they are absent or of the
wrong type.

Comment on lines +51 to +84
const syntheticVC: Verifiable<W3CCredential<AsterPayKyaCredentialSubject>> = {
"@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],
},
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bind the generated VC body to the proof.

The stored proof preserves the original JWT, but it never signs the generated credentialSubject. After conversion, credentialSubject.trustScore/sanctioned can be edited and the proof still reconstructs the old JWT, so this VC is not verifiable on its own.

Either issue a real signed VC here, or verify on read that the current credentialSubject exactly matches the signed payload before any consumer trusts the VC.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demos/asterpay-kya/src/asterpay-kya-ack-id.ts` around lines 51 - 84, The
syntheticVC currently embeds the original JWT in proof.originalPayload and jws
but builds credentialSubject separately, so credentialSubject can diverge from
the signed payload; to fix, either (A) bind the proof to the generated
credentialSubject by producing a real signature over the final VC (i.e., compute
the proof.jws from the canonicalized credentialSubject/VC and set
proof.originalPayload to that canonicalized payload) using the issuer key, or
(B) derive credentialSubject directly from the decoded jwtParts[1] payload (so
credentialSubject exactly matches proof.originalPayload) and add a runtime check
in any consumer/read path to verify credentialSubject === decoded
proof.originalPayload before trusting the VC; update the syntheticVC.proof
(fields jws and originalPayload) accordingly and ensure verification logic
checks this equality.

Comment on lines +240 to +275
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}`)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the IACPHook simulation enforce the gate.

This path never uses the verification result to decide access. It prints VERIFY/SCREEN/SCORE/ATTEST/COMPLY as successful and always ends with APPROVED, so a low-score or sanctioned token would still “pass” the demo.

Suggested direction
 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("   → Resolving agent identity via ACK-ID...")
+  const verification = await verifyAsterPayKyaAsAckId(
+    jwks,
+    kyaToken,
+    ["did:web:api.asterpay.io"],
+    50,
+  )
+  if (!verification.valid) {
+    log(errorMessage(`   IACPHook: BLOCKED — ${verification.reason}`))
+    return
+  }
+
+  const vc = await convertAsterPayKyaToVerifiableCredential(jwks, kyaToken)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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}`)
const vc = await convertAsterPayKyaToVerifiableCredential(jwks, kyaToken)
log(" → Resolving agent identity via ACK-ID...")
const verification = await verifyAsterPayKyaAsAckId(
jwks,
kyaToken,
["did:web:api.asterpay.io"],
50,
)
if (!verification.valid) {
log(errorMessage(` IACPHook: BLOCKED — ${verification.reason}`))
return
}
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}`)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@demos/asterpay-kya/src/index.ts` around lines 240 - 275, The demo currently
always approves the IACPHook path regardless of the converted credential; update
the logic around convertAsterPayKyaToVerifiableCredential/ vc (and att) to
evaluate the actual verification gates (check vc.credentialSubject.sanctioned
flag, numeric vc.credentialSubject.trustScore against the minimum threshold, and
required insumerAttestation fields like att.coinbaseKyc.met,
att.gitcoinPassport.met, att.tokenBalance.met) and branch: if any gate fails,
log appropriate FAILED messages (instead of the hardcoded success lines), emit a
rejection outcome (e.g., failure message instead of successMessage("IACPHook:
APPROVED...")), and avoid emitting ReputationPositive; otherwise proceed with
the existing approved logs and ReputationPositive emission. Ensure the printed
VERIFY/SCREEN/SCORE/ATTEST/COMPLY lines reflect the actual pass/fail of each
check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant