Chained validation
The four-signature RequireValidation → respond → Allow trace that proves the third ERC-8004 leg actually closes the loop on Solana devnet.
Four signatures prove the third ERC-8004 leg closes the loop end to end. Subject = Quantu tier-3 agent the Pay.sh smoke also used; capability = usdc-payment-policy.v1; attestor identity reused from examples/attestor-demo/attestor-keypair.json (Phase D). Captured 2026-05-06.
Source script: examples/attestor-demo/scripts/devnet-chained-validation.ts. Trace JSON: examples/attestor-demo/devnet-chained-validation.json.
The four signatures
| Step | Decision | Tx |
|---|---|---|
| 1 | gate_payment (no attestation account) → RequireValidation | 3oKW7QugBLJ7kH2QbLLWEuEn3MyNmLWCj3XovCSdDQNmq5HriwNKvPMUR9TQByZPBAPbvprDfdeYDZvh7ofntRRh |
| 2 | request_validation opens the request | 2KbXYCF67D2f2fKHk5yTzrkFBr1mV47Q3Yb1veH5e3PX4PuLa66suodAUc7uTBnr6Y44NGV1TfHHMtAZiFSnbbRF |
| 3 | respond_to_validation (creates ValidationAttestation PDA 8YKq…xt2q) | 67CzMS9GEtUBesNznKpT2UWqvjEBzhgZd7AVkhXKQ5SoqRoBotcaYf1sTF8sHxj55TNT9k847nj7FQdrwAqKussp |
| 4 | gate_payment (with attestation) → Allow | dEXkCEeSn8uiVAa14u7EusdFufSuUQttmcTdLHMSq5J3VSARM4KMRCfwpRSkVmYBc1yRQuyvPMCebifCf1dmrmC |
On-chain artefacts
| Artefact | Address |
|---|---|
Subject agent_account (Quantu tier-3 demo agent) | 5PfaofvEUf3adtJwMho7zzbfvgxwxbvp2V5moqhtLK8y |
CapabilityNamespace for usdc-payment-policy.v1 | 34gonn86FjxzXZMGd43RSvQVyH1r6PrGV9xnHXjjkEwR |
Attestor's AttestorProfile | GTzWJzV5htNi1Ntqwq2e2ydu9h4rArnKQwzv2sJjC9zP |
ValidationRequest (step 2's output) | GnbrSzWsDw1rehCrFJ4ckiM9JJJeAHdjfNDt7QQy7vhV |
ValidationAttestation (step 3's output — the headline read target) | C6Yr7oKcZ6sDVibR35SWbFnGCXyfQjLeRCiPbjxYq6vY |
The ValidationAttestation PDA is what PolicyVault's RequireValidation policy reads at fixed byte offsets via policy-vault/src/ext/validation_registry.rs. After step 3 creates this PDA for (subject, capability, attestor) = (5PfaofvE…, sha256(usdc-payment-policy.v1), GTzWJzV5…), a subsequent gate_payment call that passes this attestation account through the validation_attestation slot turns a RequireValidation decision into Allow.
What the trace proves
- The third ERC-8004 leg is real on Solana. Identity (Quantu) + Reputation (Quantu) + Validation (AgentTrust) all wire together.
- The complete
RequireValidationround-trip works. PolicyVault emitscapabilityHash→ attestor responds → policy now allows. Each step is a separate on-chain tx; AgentTrust composes the PDAs without trusting any off-chain coordination. - The byte-offset parser reads the right fields. PolicyVault's
RequireValidationpolicy readssubject_assetat byte 8,capability_hashat byte 40,attestorat byte 72,expires_atat byte 208,revokedat byte 216 — exactly the offsetspolicy-vault/src/ext/validation_registry.rsdeclares. The Phase F evidence reports verified each parsed value matches the on-chain raw bytes. - The Phase D demo capability hash is consistent end to end.
sha256("usdc-payment-policy.v1") = a968ecd0b93d9bfe57aa62c56d1e439717cf51d1dfe0ec413267834f2ca08375— the same digest that policy-vault'srequired_capability_hashstores, thatCapabilityNamespacekeys against, thatValidationAttestation.capability_hashcarries.
Reproduce
git clone https://github.com/agenttrust-labs/agenttrust && cd agenttrust
pnpm install
pnpm --filter ./examples/attestor-demo run chainedCost: ~0.012 SOL devnet total across the four txs. Output JSON: examples/attestor-demo/devnet-chained-validation.json.
For the full lifecycle including revocation:
REVOKE=1 pnpm --filter ./examples/attestor-demo run chainedREVOKE=1 runs the optional fifth step — revoke_validation sets revoked=true on the attestation PDA in place. A subsequent gate_payment call returns Deny(AttestationRevoked) (DenyReason code 13).
How a facilitator drives this in production
The Pay.sh adapter (trustgate/server/src/facilitators/pay-sh/) already surfaces capabilityHash in its formatChallenge response when the policy gate returns RequireValidation. A facilitator that hits this branch:
- Sees
decision.kind = "RequireValidation"+capabilityHashin the x402/verifyresponse body (headerX-Capability-Required: <hex>). - Looks up the attestor service that handles that capability (typically discovered out-of-band via a registry or hard-coded contract).
- Submits the claim payload off chain; the attestor responds on chain via
respond_to_validation. - Re-submits the payment to the SERVICE; the new
/verifycall includes thevalidation_attestationPDA in the gate_payment account list, the policy reads it, and the decision flips toAllow.
The script demonstrates steps 3-4 working end to end on devnet. Steps 1-2 are business logic the SERVICE owns. Walkthrough: Custom attestor.
Validated by Kani
validation_expiry_correct— an expired attestation cannot produceAllow.gate_payment_strict_correctness— strict handler returnsOk(())if and only if the lazy composer returnsAllow.
If a future change broke the policy's expiry semantics or the RequireValidation arm slipped into Ok, both proofs fail loud. Plus the Anchor end-to-end suite covers the full lifecycle in tests/validation-registry.spec.ts.
Read next
Devnet smoke
The Pay.sh + AgentTrust atomic-settlement trace on Solana devnet — gate, transfer, emit_feedback in one transaction. Reproducible from a clean checkout.
Atomic-tx invariant
gate_payment + transfer + emit_feedback execute as one Solana transaction or none. Three SDK layers + the on-chain Kani anchor + localnet runtime proof.