Add AsterPay KYA Trust Score demo for ACK-ID integration#63
Add AsterPay KYA Trust Score demo for ACK-ID integration#63timolein74 wants to merge 1 commit intoagentcommercekit:mainfrom
Conversation
Made-with: Cursor
WalkthroughThis 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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
demos/README.mddemos/asterpay-kya/README.mddemos/asterpay-kya/package.jsondemos/asterpay-kya/src/asterpay-kya-ack-id.tsdemos/asterpay-kya/src/index.tsdemos/asterpay-kya/src/jwk-keys.tsdemos/asterpay-kya/src/kya-token.tsdemos/asterpay-kya/tsconfig.jsondemos/asterpay-kya/vitest.config.tspackage.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. | |||
There was a problem hiding this comment.
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.
| 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.
| 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") |
There was a problem hiding this comment.
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.
| 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], | ||
| }, | ||
| } |
There was a problem hiding this comment.
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.
| 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}`) |
There was a problem hiding this comment.
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.
| 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.
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.
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 configpackage.json— Addeddemo:asterpay-kyascriptdemos/README.md— Added demo listingTesting
The demo runs successfully with
pnpm demo:asterpay-kyaand all 5 steps complete without errors.