@agenttrust-sdk/mcp 0.2.6 → 0.3.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.
Files changed (49) hide show
  1. package/dist/embedded-data/devnet-smoke.json +4 -4
  2. package/dist/embedded-docs/architecture.mdx +174 -0
  3. package/dist/embedded-docs/getting-started/quickstart.mdx +79 -56
  4. package/dist/embedded-docs/index.mdx +54 -37
  5. package/dist/embedded-docs/integration-guides/capability-namespaces.mdx +135 -8
  6. package/dist/embedded-docs/integration-guides/custom-attestor.mdx +169 -8
  7. package/dist/embedded-docs/integration-guides/dexter-adapter.mdx +76 -0
  8. package/dist/embedded-docs/integration-guides/facilitator-adapters.mdx +85 -41
  9. package/dist/embedded-docs/integration-guides/pay-sh-adapter.mdx +90 -54
  10. package/dist/embedded-docs/integration-guides/x402-facilitator.mdx +55 -24
  11. package/dist/embedded-docs/mcp/hosted-endpoint.mdx +197 -0
  12. package/dist/embedded-docs/mcp/index.mdx +108 -0
  13. package/dist/embedded-docs/mcp/install.mdx +183 -0
  14. package/dist/embedded-docs/mcp/prompts.mdx +90 -0
  15. package/dist/embedded-docs/mcp/resources.mdx +115 -0
  16. package/dist/embedded-docs/mcp/tools.mdx +156 -0
  17. package/dist/embedded-docs/programs/policy-vault/composer.mdx +117 -0
  18. package/dist/embedded-docs/programs/policy-vault/counterparty-tier-policy.mdx +81 -9
  19. package/dist/embedded-docs/programs/policy-vault/index.mdx +77 -47
  20. package/dist/embedded-docs/programs/policy-vault/kill-switch-policy.mdx +65 -8
  21. package/dist/embedded-docs/programs/policy-vault/require-validation-policy.mdx +76 -8
  22. package/dist/embedded-docs/programs/policy-vault/spending-policy.mdx +83 -8
  23. package/dist/embedded-docs/programs/policy-vault/velocity-policy.mdx +85 -8
  24. package/dist/embedded-docs/programs/trustgate.mdx +112 -30
  25. package/dist/embedded-docs/programs/validation-registry.mdx +139 -32
  26. package/dist/embedded-docs/reference/byte-offset-reference.mdx +102 -13
  27. package/dist/embedded-docs/reference/capability-namespaces.mdx +56 -0
  28. package/dist/embedded-docs/reference/changelog.mdx +230 -13
  29. package/dist/embedded-docs/reference/deny-reason-codes.mdx +86 -0
  30. package/dist/embedded-docs/reference/devnet-program-ids.mdx +50 -8
  31. package/dist/embedded-docs/reference/discriminator-constants.mdx +104 -10
  32. package/dist/embedded-docs/reference/mainnet-program-ids.mdx +89 -5
  33. package/dist/embedded-docs/reference/quantu-agent-registry.mdx +104 -9
  34. package/dist/embedded-docs/sdk/exports-reference.mdx +239 -0
  35. package/dist/embedded-docs/sdk/gate-payment.mdx +99 -14
  36. package/dist/embedded-docs/sdk/index.mdx +141 -40
  37. package/dist/embedded-docs/sdk/mount-trustgate.mdx +178 -8
  38. package/dist/embedded-docs/verification/adversarial-harness.mdx +88 -0
  39. package/dist/embedded-docs/verification/atomic-tx-invariant.mdx +141 -0
  40. package/dist/embedded-docs/verification/chained-validation.mdx +87 -0
  41. package/dist/embedded-docs/verification/devnet-smoke.mdx +85 -0
  42. package/dist/embedded-docs/verification/index.mdx +31 -0
  43. package/dist/embedded-docs/verification/kani-proofs.mdx +144 -0
  44. package/dist/embedded-docs/verification/live-evidence.mdx +180 -0
  45. package/dist/tools/write/emit-feedback.d.ts +6 -0
  46. package/dist/tools/write/emit-feedback.js +12 -1
  47. package/dist/tools/write/emit-feedback.js.map +1 -1
  48. package/package.json +16 -15
  49. package/scripts/install-claude-desktop.sh +0 -0
@@ -1,15 +1,142 @@
1
1
  ---
2
2
  title: Capability namespaces
3
- description: Naming and hashing convention for validation capabilities.
3
+ description: Register a new namespace, derive its hash, gate against it from PolicyVault. Plus how the v1 sybil-resistance model devolves trust to PolicyVault's accepted_attestors[].
4
4
  ---
5
5
 
6
- `In progress`
6
+ Capability namespaces are the names PolicyVault gates against in `RequireValidation`. Each namespace is identified on chain by `SHA-256(name_utf8)` — that 32-byte hash is what `PolicyAccount.required_capability_hash` stores and what the `X-Capability-Required` x402 response header carries.
7
7
 
8
- Capability namespaces let facilitators ask for a specific validation artifact without coupling the payment route to one attestor. The ValidationRegistry stores the namespace and the attestation PDA stores the capability hash.
8
+ Source: [`programs/validation-registry/src/instructions/register_namespace.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/instructions/register_namespace.rs). State: [`state/capability_namespace.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/state/capability_namespace.rs).
9
9
 
10
- | Source | Path |
11
- | --- | --- |
12
- | namespace state | [`programs/validation-registry/src/state/capability_namespace.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/state/capability_namespace.rs) |
13
- | register instruction | [`programs/validation-registry/src/instructions/register_namespace.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/instructions/register_namespace.rs) |
14
- | x402 header | [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts) |
10
+ ## Register a namespace
15
11
 
12
+ ```ts
13
+ import { Keypair, PublicKey, sendAndConfirmTransaction, Transaction } from "@solana/web3.js";
14
+ import {
15
+ buildRegisterNamespaceIx,
16
+ computeCapabilityHash,
17
+ loadValidationRegistry,
18
+ makeProvider,
19
+ DEFAULT_DEVNET_PROGRAM_IDS,
20
+ } from "@agenttrust-sdk/trustgate";
21
+
22
+ const creator = Keypair.fromSecretKey(/* funded devnet keypair */);
23
+ const provider = makeProvider({
24
+ rpcUrl: "https://api.devnet.solana.com",
25
+ wallet: creator,
26
+ });
27
+
28
+ const validationRegistry = await loadValidationRegistry(
29
+ provider,
30
+ DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry,
31
+ );
32
+
33
+ const name = "my-org.attestation.v1"; // ≤ 32 bytes; min 3; no ':' allowed
34
+ const version = "v1"; // ≤ 16 bytes
35
+ const schemaUri = "https://my-org.example/schemas/attestation.v1.json";
36
+
37
+ const namespaceHash = computeCapabilityHash(name); // SHA-256(name)
38
+
39
+ const ix = await buildRegisterNamespaceIx({
40
+ program: validationRegistry,
41
+ creator: creator.publicKey,
42
+ namespaceHash,
43
+ name,
44
+ version,
45
+ schemaUri,
46
+ });
47
+
48
+ const tx = new Transaction().add(ix);
49
+ const sig = await sendAndConfirmTransaction(provider.connection, tx, [creator]);
50
+ console.log(`Namespace registered: ${sig}`);
51
+ ```
52
+
53
+ The `register_namespace` instruction enforces:
54
+
55
+ | Constraint | Limit | Error on violation |
56
+ |---|---|---|
57
+ | `name.len() >= 3` | min 3 | `NameTooShort` |
58
+ | `name.len() <= 32` | max 32 | `NameTooLong` |
59
+ | `':'` not in `name` | forbidden | `NamespaceColonForbidden` (so namespace strings pack into URIs without escaping) |
60
+ | `version.len() <= 16` | max 16 | `VersionTooLong` |
61
+ | `schema_uri.len() <= 160` | max 160 | `UriTooLong` |
62
+
63
+ Anyone can register a namespace — rent (~0.0023 SOL) is the economic deterrent. PolicyVault decides per-policy which attestors it trusts via `accepted_attestors[]`, so a namespace registration alone confers no trust.
64
+
65
+ ## Derive the PDA
66
+
67
+ ```ts
68
+ import { deriveCapabilityNamespacePda } from "@agenttrust-sdk/trustgate";
69
+
70
+ const namespacePda = deriveCapabilityNamespacePda(
71
+ DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry,
72
+ namespaceHash, // 32 bytes
73
+ );
74
+ ```
75
+
76
+ PDA seeds: `["capability", namespace_hash]`.
77
+
78
+ ## Gate a policy against it
79
+
80
+ Set `required_capability_hash` on `PolicyAccount` when calling `init_policy`:
81
+
82
+ ```ts
83
+ import { BN } from "@coral-xyz/anchor";
84
+
85
+ await policyVault.methods
86
+ .initPolicy({
87
+ policyId: 1,
88
+ enabledKindsBitmask: 0b11111, // all five kinds
89
+ requiredCapabilityHash: Array.from(namespaceHash),
90
+ acceptedAttestors: [/* up to 2 trusted attestor pubkeys, or both PublicKey.default for permissionless */],
91
+ /* … other policy fields … */
92
+ })
93
+ .accounts({
94
+ /* … */
95
+ })
96
+ .rpc();
97
+ ```
98
+
99
+ When PolicyVault's gate runs:
100
+
101
+ 1. If `required_capability_hash == [0; 32]` (zero) — policy is not enabled; pass-through.
102
+ 2. Else, look for a `ValidationAttestation` PDA at `["attestation", payee_asset, capability_hash, attestor]`. The `attestor` slot iterates against `accepted_attestors[]`.
103
+ 3. If no attestation exists → `RequireValidation(capability_hash)` is returned. The facilitator's `formatChallenge` includes `X-Capability-Required: <hex>` in the x402 response.
104
+ 4. After the user obtains an attestation (off-chain claim → attestor calls `respond_to_validation` → PDA written) and re-submits the payment, the gate flips to `Allow`.
105
+
106
+ Full decision flow: [PolicyVault → RequireValidation policy](/programs/policy-vault/require-validation-policy).
107
+
108
+ ## Naming conventions
109
+
110
+ The seeded v1 namespaces follow a `domain.subcategory.version` shape:
111
+
112
+ - `kyc.tier-1.v1`, `kyc.tier-2.v1`, `kyc.tier-3.v1`
113
+ - `audit.smart-contract.v1`, `audit.attestor-firm.v1`
114
+ - `model-card.v1`
115
+ - `jurisdiction.v1`
116
+ - `compliance.payments.v1`
117
+ - `agent-source.v1`
118
+ - `usdc-payment-policy.v1` (the Phase D demo capability)
119
+
120
+ Full lookup with PDAs and Explorer URLs: [Reference → Capability namespaces](/reference/capability-namespaces).
121
+
122
+ The playbook-level descriptive labels in [`docs/plan/research/06-validation-registry-class.md`](https://github.com/agenttrust-labs/agenttrust/blob/main/docs/plan/research/06-validation-registry-class.md) (e.g., `kyc.tier-1.v1.identity-verified`) decompose to these on-chain `name` strings plus the JSON `description` field. The chain stores the bounded name; richer metadata lives in the `schema_uri` document.
123
+
124
+ ## Sybil-resistance model — local trust, not global
125
+
126
+ Permissionless registration plus per-policy `accepted_attestors[]` filtering. PolicyVault decides which attestors it trusts for a given capability, not a central allow-list. A policy that gates against `audit.smart-contract.v1` might only accept attestations from Halborn or OtterSec; a policy that gates against `kyc.tier-1.v1` might accept any registered KYC attestor.
127
+
128
+ The trade-off is local trust over global gatekeeping — the only model that scales with the number of facilitators. Detailed rationale: [ValidationRegistry](/programs/validation-registry).
129
+
130
+ ## Read next
131
+
132
+ <Cards>
133
+ <Card title="Custom attestor" href="/integration-guides/custom-attestor">
134
+ The other side — register an attestor profile, respond to validation requests, revoke.
135
+ </Card>
136
+ <Card title="RequireValidation policy" href="/programs/policy-vault/require-validation-policy">
137
+ The PolicyVault read path that consumes the attestation PDA.
138
+ </Card>
139
+ <Card title="Capability namespaces (live)" href="/reference/capability-namespaces">
140
+ Every seeded v1 namespace with PDA + Explorer URL.
141
+ </Card>
142
+ </Cards>
@@ -1,15 +1,176 @@
1
1
  ---
2
2
  title: Custom attestor
3
- description: How third-party attestors plug into the ValidationRegistry path.
3
+ description: Register an AttestorProfile, respond to validation requests, revoke attestations. Full lifecycle with the live four-signature devnet trace.
4
4
  ---
5
5
 
6
- `In progress`
6
+ A custom attestor registers a profile, watches off-chain for validation requests for capabilities it can attest to, signs `respond_to_validation` to write the attestation PDA, and can later revoke any attestation it issued. PolicyVault then reads the resulting PDA at fixed byte offsets.
7
7
 
8
- A custom attestor registers a profile, responds to validation requests, and can revoke an attestation it issued. PolicyVault then reads the resulting PDA by fixed byte offsets.
8
+ Source: [`programs/validation-registry/`](https://github.com/agenttrust-labs/agenttrust/tree/main/programs/validation-registry). Devnet smoke: [`examples/attestor-demo/`](https://github.com/agenttrust-labs/agenttrust/tree/main/examples/attestor-demo).
9
9
 
10
- | Source | Path |
11
- | --- | --- |
12
- | attestor profile | [`programs/validation-registry/src/state/attestor_profile.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/state/attestor_profile.rs) |
13
- | response instruction | [`programs/validation-registry/src/instructions/respond_to_validation.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/instructions/respond_to_validation.rs) |
14
- | revoke instruction | [`programs/validation-registry/src/instructions/revoke_validation.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/validation-registry/src/instructions/revoke_validation.rs) |
10
+ ## Lifecycle
15
11
 
12
+ ```
13
+ 1. register_attestor → create AttestorProfile PDA
14
+ 2. <off-chain> → discover ValidationRequest events for capabilities you attest to
15
+ 3. respond_to_validation → create ValidationAttestation PDA (PolicyVault now reads it)
16
+ 4. revoke_validation → set revoked = true (PolicyVault now denies)
17
+ ```
18
+
19
+ ## Step 1 — register the profile
20
+
21
+ ```ts
22
+ import { Keypair, sendAndConfirmTransaction, Transaction } from "@solana/web3.js";
23
+ import {
24
+ buildRegisterAttestorIx,
25
+ loadValidationRegistry,
26
+ makeProvider,
27
+ DEFAULT_DEVNET_PROGRAM_IDS,
28
+ } from "@agenttrust-sdk/trustgate";
29
+
30
+ const attestor = Keypair.fromSecretKey(/* the attestor's signing key */);
31
+ const provider = makeProvider({
32
+ rpcUrl: "https://api.devnet.solana.com",
33
+ wallet: attestor,
34
+ });
35
+
36
+ const validationRegistry = await loadValidationRegistry(
37
+ provider,
38
+ DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry,
39
+ );
40
+
41
+ const ix = await buildRegisterAttestorIx({
42
+ program: validationRegistry,
43
+ attestor: attestor.publicKey,
44
+ displayNameUri: "https://my-org.example/attestor.json", // ≤ 100 bytes
45
+ });
46
+
47
+ const tx = new Transaction().add(ix);
48
+ const sig = await sendAndConfirmTransaction(provider.connection, tx, [attestor]);
49
+ ```
50
+
51
+ Constraints: `display_name_uri.len() <= 100` (`UriTooLong` on overflow). Self-registered (the signer is the attestor). PDA: `["attestor", attestor_pubkey]`. Account size includes counters for total attestations + total revoked, all initialised to zero.
52
+
53
+ ## Step 2 — discover validation requests
54
+
55
+ `request_validation` emits the `RequestCreated` event. Any off-chain process can subscribe:
56
+
57
+ ```ts
58
+ import { PublicKey } from "@solana/web3.js";
59
+
60
+ provider.connection.onLogs(
61
+ validationRegistry.programId,
62
+ (logs) => {
63
+ if (logs.err) return;
64
+ // Decode logs.logs entries — RequestCreated event has subject_asset,
65
+ // capability_hash, requester, claim_uri_hash, deadline.
66
+ },
67
+ "confirmed",
68
+ );
69
+ ```
70
+
71
+ For production attestors, [Helius webhooks](https://www.helius.dev/docs/webhooks) on the program's events stream is the standard pattern. The PDA itself is just an audit-trail record; attestors don't read it.
72
+
73
+ ## Step 3 — respond with an attestation
74
+
75
+ After the attestor verifies the off-chain claim (KYC document, audit report, model-card statement, etc.) the attestor signs `respond_to_validation` to write the on-chain attestation:
76
+
77
+ ```ts
78
+ import {
79
+ buildRespondToValidationIx,
80
+ computeCapabilityHash,
81
+ } from "@agenttrust-sdk/trustgate";
82
+
83
+ const subjectAsset = new PublicKey("…"); // payee asset
84
+ const capabilityHash = computeCapabilityHash("kyc.tier-1.v1"); // 32 bytes
85
+ const claimPayloadHash = sha256(claim_payload_bytes); // 32 bytes
86
+ const claimUriHash = sha256(Buffer.from(claim_uri_string)); // 32 bytes
87
+ const expiresAt = currentSlot + (30n * 24n * 60n * 60n * 2n); // ~30 days @ 2 slots/sec
88
+
89
+ const ix = await buildRespondToValidationIx({
90
+ program: validationRegistry,
91
+ payer: attestor.publicKey, // attestor pays rent
92
+ attestor: attestor.publicKey,
93
+ subjectAsset,
94
+ capabilityHash,
95
+ claimPayloadHash,
96
+ claimUriHash,
97
+ expiresAt: Number(expiresAt),
98
+ });
99
+
100
+ const sig = await sendAndConfirmTransaction(provider.connection, new Transaction().add(ix), [attestor]);
101
+ ```
102
+
103
+ Constraints: `expires_at == 0` (never expires) OR `expires_at > clock.slot` (`ExpiryInPast` otherwise). The `capability_namespace` PDA must already exist (`AccountNotInitialized` if you reference an unregistered capability). The `attestor_profile` PDA must exist (the attestor must have called `register_attestor` first).
104
+
105
+ PDA: `["attestation", subject_asset, capability_hash, attestor]`. Account size: 290 bytes.
106
+
107
+ The v1 trust model is "attestor signs the tx" — the Solana tx signature itself authenticates the attestor. The 64-byte `attestor_signature` field on `ValidationAttestation` is reserved for v1.1+ Ed25519 sysvar verification, which adds non-repudiation against future key compromise.
108
+
109
+ ## Step 4 — revoke when needed
110
+
111
+ ```ts
112
+ import { buildRevokeValidationIx } from "@agenttrust-sdk/trustgate";
113
+
114
+ const revocationReasonHash = sha256(Buffer.from("kyc-document-expired"));
115
+
116
+ const ix = await buildRevokeValidationIx({
117
+ program: validationRegistry,
118
+ attestor: attestor.publicKey,
119
+ subjectAsset,
120
+ capabilityHash,
121
+ revocationReasonHash,
122
+ });
123
+
124
+ await sendAndConfirmTransaction(provider.connection, new Transaction().add(ix), [attestor]);
125
+ ```
126
+
127
+ `revoke_validation` is audit-trail-preserving: it sets `revoked = true`, writes `revoked_at = clock.slot`, and stores the `revocation_reason_hash`. The PDA is not deleted. PolicyVault's `RequireValidation` policy treats `revoked == true` as `Deny(AttestationRevoked)` (DenyReason code 13).
128
+
129
+ Only the original attestor can revoke an attestation it issued (`UnauthorizedRevoker` otherwise). v1.1+ adds an external-revoke flow with attestor-profile externals counter.
130
+
131
+ ## Live four-signature trace
132
+
133
+ End-to-end devnet trace, 2026-05-06 — gate denies → request → respond → gate allows:
134
+
135
+ | Step | Tx |
136
+ |---|---|
137
+ | `gate_payment` (no attestation) → `RequireValidation` | [`3oKW7QugBLJ7…`](https://explorer.solana.com/tx/3oKW7QugBLJ7kH2QbLLWEuEn3MyNmLWCj3XovCSdDQNmq5HriwNKvPMUR9TQByZPBAPbvprDfdeYDZvh7ofntRRh?cluster=devnet) |
138
+ | `request_validation` | [`2KbXYCF67D2f…`](https://explorer.solana.com/tx/2KbXYCF67D2f2fKHk5yTzrkFBr1mV47Q3Yb1veH5e3PX4PuLa66suodAUc7uTBnr6Y44NGV1TfHHMtAZiFSnbbRF?cluster=devnet) |
139
+ | `respond_to_validation` (creates attestation) | [`67CzMS9GEt…`](https://explorer.solana.com/tx/67CzMS9GEtUBesNznKpT2UWqvjEBzhgZd7AVkhXKQ5SoqRoBotcaYf1sTF8sHxj55TNT9k847nj7FQdrwAqKussp?cluster=devnet) |
140
+ | `gate_payment` (with attestation) → `Allow` | [`dEXkCEeSn8…`](https://explorer.solana.com/tx/dEXkCEeSn8uiVAa14u7EusdFufSuUQttmcTdLHMSq5J3VSARM4KMRCfwpRSkVmYBc1yRQuyvPMCebifCf1dmrmC?cluster=devnet) |
141
+
142
+ Resulting `ValidationAttestation` PDA: [`8YKq…xt2q`](https://explorer.solana.com/address/8YKqxoBaKfQ4VcDNa6dcoMQGevxbRmcr4ENbWcyrJxt2q?cluster=devnet). Reproduce with `pnpm --filter ./examples/attestor-demo run chained` (~0.012 SOL total). Full trace: [Verification → Chained validation](/verification/chained-validation).
143
+
144
+ ## How the gate consumes your attestation
145
+
146
+ PolicyVault's `RequireValidation` policy reads the `ValidationAttestation` at fixed byte offsets:
147
+
148
+ | Offset | Width | Field |
149
+ |---:|---:|---|
150
+ | `8` | Pubkey | `subject_asset` |
151
+ | `40` | [u8; 32] | `capability_hash` |
152
+ | `72` | Pubkey | `attestor` |
153
+ | `208` | u64 LE | `expires_at` (0 = never expires) |
154
+ | `216` | bool | `revoked` |
155
+
156
+ A policy with `accepted_attestors = [your_pubkey, …]` accepts attestations only from your key. Permissionless mode (both `accepted_attestors` slots zero) accepts any attestor's signature for the capability. Full read semantics: [RequireValidation policy](/programs/policy-vault/require-validation-policy).
157
+
158
+ ## Validation against the Kani proof
159
+
160
+ The `validation_expiry_correct` proof (Kani #4, 85 sub-checks, 0.23 s) pins: an expired attestation cannot produce `Allow` from `require_validation::evaluate`. Even if all other fields match, expiry is the deciding gate.
161
+
162
+ So if you set `expires_at = currentSlot + 1000` and waited 1000 slots, your attestation goes from `Allow`-capable to `Deny(AttestationExpired)` automatically — no revocation tx required. Plan attestation lifetimes accordingly. Reference: [Verification → Kani proofs](/verification/kani-proofs).
163
+
164
+ ## Read next
165
+
166
+ <Cards>
167
+ <Card title="Capability namespaces" href="/integration-guides/capability-namespaces">
168
+ Register the capability your attestation references.
169
+ </Card>
170
+ <Card title="ValidationRegistry" href="/programs/validation-registry">
171
+ All five instructions, all four PDAs, byte-precise.
172
+ </Card>
173
+ <Card title="Chained validation" href="/verification/chained-validation">
174
+ The full live four-signature trace with reproduction commands.
175
+ </Card>
176
+ </Cards>
@@ -0,0 +1,76 @@
1
+ ---
2
+ title: Dexter adapter
3
+ description: In-flight worked example — the second AgentTrust facilitator adapter, used to prove portability of the FacilitatorAdapter contract.
4
+ ---
5
+
6
+ Dexter is the second facilitator AgentTrust supports. It exists to prove that the `FacilitatorAdapter` contract is portable: every adapter must implement the same five methods, expose the same Zod-validated wire schemas, and use the same SDK factories for on-chain validation and feedback emission. If wiring a second adapter forces route edits, policy edits, or registry-read changes, the adapter boundary failed and gets fixed before the integration ships.
7
+
8
+ Status: in flight. The current repo carries a stub at [`trustgate/server/src/facilitators/dexter/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/server/src/facilitators/dexter) that satisfies the type contract; full implementation lands once Dexter publishes its x402 wire format. Current adapter status: [`agenttrust_list_facilitators`](/mcp/tools#agenttrust_list_facilitators) → `{ name: "dexter", status: "in-flight" }`.
9
+
10
+ ## What the adapter has to land
11
+
12
+ The five methods every adapter implements, with Dexter-specific notes:
13
+
14
+ | Method | Dexter-specific note |
15
+ |---|---|
16
+ | `parseRequest(req)` | Translate Dexter's body shape + headers (TBD until spec lands) into `VerifyContext`. Strict Zod schema — reject unknown root fields. |
17
+ | `formatChallenge(decision, ctx)` | Render `Allow` / `Deny` / `RequireValidation` in Dexter's response shape. Headers: `X-Agent-Trust-Decision`, `X-Capability-Required` (when applicable). |
18
+ | `formatSettlement(ctx)` | Return Dexter's settlement metadata or an unsigned transaction skeleton. Payment ID and amount/mint/recipient are bound to the verify-time context. |
19
+ | `validatePaymentProof(proof, ctx)` | Verify the proof shape; cross-check against `VerifyContext`. Same defenses as Pay.sh: replay, self-pay, amount, mint, recipient, expiry. |
20
+ | `emitFeedback(ctx, settlement)` | Call the feedback CPI idempotently via `priorEmissionLookup` + `emitFeedbackCpi`. |
21
+
22
+ Implementation pattern: read [`facilitators/pay-sh/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/server/src/facilitators/pay-sh) end to end. Adapt the schemas + signature semantics to Dexter's wire format. Keep the proof-validator's defense list 1:1.
23
+
24
+ ## Why Dexter exists in the catalog
25
+
26
+ Pay.sh proves the adapter pattern handles the canonical x402 case. Dexter exists to prove the pattern handles a *different* x402 facilitator's quirks without forcing route layer changes. The same `FacilitatorRegistry` registers it; the same `/verify`, `/settle`, `/dispute` routes dispatch to it; the same SDK factories build its on-chain calls.
27
+
28
+ ```ts
29
+ import { FacilitatorRegistry, PaySh } from "./facilitators";
30
+ import { Dexter } from "./facilitators/dexter";
31
+
32
+ const registry = new FacilitatorRegistry();
33
+ registry.register(new PaySh(payShDeps));
34
+ registry.register(new Dexter(dexterDeps));
35
+ registry.setDefault("pay-sh");
36
+ ```
37
+
38
+ The route layer never branches on the facilitator name — selection happens via the `X-Facilitator` request header or the registry's default. Dexter requests use `X-Facilitator: dexter`; everything else routes to Pay.sh.
39
+
40
+ ## What lands when Dexter ships
41
+
42
+ | Deliverable | Where |
43
+ |---|---|
44
+ | `Dexter` class | `trustgate/server/src/facilitators/dexter/index.ts` |
45
+ | Zod schemas | `trustgate/server/src/facilitators/dexter/schemas.ts` |
46
+ | Proof validator | `trustgate/server/src/facilitators/dexter/proof-validator.ts` |
47
+ | Feedback helper | `trustgate/server/src/facilitators/dexter/feedback.ts` |
48
+ | Demo (optional) | `examples/dexter-demo/` |
49
+ | Adapter test suite | `trustgate/server/test/facilitators/dexter/` |
50
+
51
+ Each file mirrors the Pay.sh shape. The adapter test suite asserts the `FacilitatorAdapter` contract conformance — every method's input is type-safe and every method's output matches the documented schema.
52
+
53
+ ## Adapter contract conformance
54
+
55
+ CI runs an adapter-contract conformance workflow ([`.github/workflows/adapter-contract-conformance.yml`](https://github.com/agenttrust-labs/agenttrust/blob/main/.github/workflows/adapter-contract-conformance.yml)) on every PR. It walks every registered adapter and asserts:
56
+
57
+ - `parseRequest` accepts the verify-shape and rejects malformed inputs with `400`.
58
+ - `formatChallenge` emits the right headers per decision arm.
59
+ - `validatePaymentProof` rejects every member of the standard hostile-input matrix (replay, self-pay, mismatch, expired).
60
+ - `emitFeedback` is idempotent against the same `payment_id_hash`.
61
+
62
+ Dexter (when shipped) and any future adapter (atxp, MCPay) hook into the same conformance check. The Pay.sh adapter is the conformance reference — its 50+ test cases cover every branch of the contract.
63
+
64
+ ## Read next
65
+
66
+ <Cards>
67
+ <Card title="Pay.sh adapter" href="/integration-guides/pay-sh-adapter">
68
+ The canonical implementation. Read it end to end before writing your own.
69
+ </Card>
70
+ <Card title="Facilitator adapters" href="/integration-guides/facilitator-adapters">
71
+ The five-method contract in full.
72
+ </Card>
73
+ <Card title="Custom attestor" href="/integration-guides/custom-attestor">
74
+ If you're attesting capabilities rather than running a facilitator.
75
+ </Card>
76
+ </Cards>
@@ -1,85 +1,129 @@
1
1
  ---
2
2
  title: Facilitator adapters
3
- description: Add a new x402 facilitator without rewriting AgentTrust routes or policy.
3
+ description: Add a new x402 facilitator without rewriting AgentTrust routes or policy. Five-method contract, registry-based dispatch, conformance-tested.
4
4
  ---
5
5
 
6
- The adapter layer is the reason AgentTrust is not locked to Pay.sh. Pay.sh is live today; Dexter is the next worked path; atxp_ai and MCPay are marked roadmap through explicit stubs.
6
+ The adapter layer is what makes AgentTrust facilitator-agnostic. Pay.sh is the canonical reference; Dexter is the in-flight second worked example; atxp and MCPay are roadmap. Routes (`/verify`, `/settle`, `/dispute`), the PolicyVault gate, and the registry reads do not branch on facilitator name. Protocol quirks live in one adapter file.
7
+
8
+ The adapter implementations live in [`trustgate/server/src/facilitators/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/server/src/facilitators). The server package is workspace-internal (not published to npm); copy the pattern from [`facilitators/pay-sh/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/server/src/facilitators/pay-sh) into your own server, or use the published [SDK middleware](/sdk/mount-trustgate) which dispatches through the adapter layer for you.
7
9
 
8
10
  ## Mental model
9
11
 
10
- ```txt
12
+ ```
11
13
  HTTP request
12
- -> routes/{verify,settle}
13
- -> FacilitatorRegistry
14
- -> active FacilitatorAdapter
15
- -> VerifyContext
16
- -> policy decision
17
- -> settlement proof validation
18
- -> feedback emission
14
+ /verify · /settle · /dispute
15
+ → FacilitatorRegistry (selects adapter by X-Facilitator header or default)
16
+ active FacilitatorAdapter
17
+ VerifyContext
18
+ PolicyVault gate decision
19
+ settlement proof validation
20
+ feedback emission
19
21
  ```
20
22
 
21
- Routes, policy logic, and registry reads stay unchanged. Protocol quirks live in one adapter file.
23
+ The active adapter is the only code that knows about the chosen facilitator.
22
24
 
23
25
  ## The five-method contract
24
26
 
27
+ ```ts
28
+ export interface FacilitatorAdapter {
29
+ readonly protocol: FacilitatorProtocol; // identity tag for registry
30
+
31
+ parseRequest(req: Request): Promise<VerifyContext>;
32
+ formatChallenge(decision: GateDecision, ctx: VerifyContext): ChallengeResponse;
33
+ formatSettlement(ctx: VerifyContext): SettlementResponse;
34
+ validatePaymentProof(proof: unknown, ctx: VerifyContext): Promise<PaymentProofValidation>;
35
+ emitFeedback(ctx: VerifyContext, settlement: ConfirmedSettlement): Promise<FeedbackEmissionResult>;
36
+ }
37
+ ```
38
+
25
39
  | Method | Why it exists |
26
- | --- | --- |
27
- | `parseRequest(req)` | translate the facilitator's request body and headers into `VerifyContext` |
28
- | `formatChallenge(decision, ctx)` | render Allow / Deny / RequireValidation in that facilitator's wire format |
29
- | `formatSettlement(ctx)` | return protocol-specific settlement metadata or an unsigned transaction skeleton |
30
- | `validatePaymentProof(proof, ctx)` | verify proof shape and cross-check it against the verify-time context |
31
- | `emitFeedback(ctx, settlement)` | emit the AgentTrust feedback record after settlement |
40
+ |---|---|
41
+ | `parseRequest(req)` | Translate the facilitator's request body and headers into `VerifyContext` |
42
+ | `formatChallenge(decision, ctx)` | Render `Allow` / `Deny` / `RequireValidation` in that facilitator's wire format |
43
+ | `formatSettlement(ctx)` | Return protocol-specific settlement metadata or an unsigned transaction skeleton |
44
+ | `validatePaymentProof(proof, ctx)` | Verify proof shape and cross-check against the verify-time context |
45
+ | `emitFeedback(ctx, settlement)` | Emit the AgentTrust feedback record idempotently |
32
46
 
33
47
  If an adapter needs a sixth method, that is a contract change, not a one-off escape hatch.
34
48
 
35
49
  ## Status map
36
50
 
37
51
  | Facilitator | Repo status | Meaning |
38
- | --- | --- | --- |
39
- | Pay.sh | concrete adapter | ready path for the demo and production dependency factory |
40
- | Dexter | stub / in flight | worked example for proving adapter portability |
41
- | atxp_ai | stub | roadmap, not silently unsupported |
42
- | MCPay | stub | roadmap, not silently unsupported |
52
+ |---|---|---|
53
+ | Pay.sh | concrete adapter | Ready for the demo + production dependency factory; the canonical `FacilitatorAdapter` reference |
54
+ | Dexter | in-flight stub | Worked example for proving adapter portability |
55
+ | atxp | roadmap stub | Tracked, not silently unsupported |
56
+ | MCPay | roadmap stub | Tracked, not silently unsupported |
57
+
58
+ `agenttrust_list_facilitators` (an MCP tool) returns the live status set: [MCP → Tools](/mcp/tools#agenttrust_list_facilitators).
43
59
 
44
60
  ## Add a facilitator
45
61
 
46
- 1. Read the facilitator's wire format. Identify challenge headers, proof headers, body shape, recipient encoding, payment ID hints, and Allow / Deny responses.
47
- 2. Define strict Zod schemas for the request body and proof payload.
62
+ 1. Read the facilitator's wire format. Identify challenge headers, proof headers, body shape, recipient encoding, payment ID hints, Allow/Deny responses.
63
+ 2. Define strict Zod schemas for the request body and proof payload. Reject unknown root fields.
48
64
  3. Implement one `FacilitatorAdapter` class.
49
- 4. Re-export it from `trustgate/server/src/facilitators/index.ts`.
50
- 5. Register it with `FacilitatorRegistry`.
51
- 6. Add unit tests that mirror Pay.sh coverage.
65
+ 4. Re-export from [`trustgate/server/src/facilitators/index.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/index.ts).
66
+ 5. Register with `FacilitatorRegistry`.
67
+ 6. Add unit tests mirroring Pay.sh coverage (50+ cases — schema, signature, proof-validator, feedback idempotency).
52
68
  7. Add a demo under `examples/<name>-demo` if the integration needs a runnable walkthrough.
53
69
 
54
70
  ## Registry wiring
55
71
 
56
72
  ```ts
57
- import {
58
- FacilitatorRegistry,
59
- PaySh,
60
- } from "@agenttrust/trustgate-server";
73
+ import { FacilitatorRegistry, PaySh } from "./facilitators";
74
+ import { Dexter } from "./facilitators/dexter";
61
75
 
62
76
  const registry = new FacilitatorRegistry();
63
77
  registry.register(new PaySh(payShDeps));
78
+ registry.register(new Dexter(dexterDeps));
64
79
  registry.setDefault("pay-sh");
80
+
81
+ // Per-request selection via X-Facilitator header:
82
+ // curl -H "X-Facilitator: dexter" …
83
+ // Falls back to default if header is absent.
65
84
  ```
66
85
 
67
- Request-time selection can come from `X-Facilitator`, environment defaults, or a programmatic default. The active adapter is the only code that knows about the chosen facilitator.
86
+ The registry resolves the active adapter on every `/verify`, `/settle`, `/dispute` call. The dispatch site is one line; nothing in the route layer knows which adapter is active.
68
87
 
69
88
  ## Security checklist
70
89
 
71
90
  Every adapter must preserve the same proof-of-payment checks:
72
91
 
73
- - replay defense
74
- - self-pay defense
75
- - amount, mint, and recipient cross-checks
76
- - network match
77
- - expiry window
78
- - idempotent feedback emission
79
- - stable reason codes on Deny
92
+ - replay defense — reject proofs whose `paymentIdHash` matches a prior settlement
93
+ - self-pay defense — reject proofs where transfer authority equals facilitator fee payer
94
+ - amount, mint, recipient cross-checks — reject if the proof references different values than the verify-time context
95
+ - network match — reject mainnet proofs against a devnet-bound facilitator
96
+ - expiry window — reject proofs older than the SERVICE-signed challenge's `issuedAt + maxTimeoutSeconds`
97
+ - idempotent feedback emission — `priorEmissionLookup` short-circuits a second emission for the same `payment_id_hash`
98
+ - stable reason codes on `Deny` — clients consume the numeric `reasonCode`, decoupled from Borsh wire-format ordering
99
+
100
+ The Pay.sh adapter's [`proof-validator.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/proof-validator.ts) and [`feedback.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/feedback.ts) are the reference pattern.
101
+
102
+ ## Conformance test
103
+
104
+ CI runs [`.github/workflows/adapter-contract-conformance.yml`](https://github.com/agenttrust-labs/agenttrust/blob/main/.github/workflows/adapter-contract-conformance.yml) on every PR. The workflow walks every registered adapter and asserts:
105
+
106
+ - `parseRequest` accepts the verify-shape and rejects malformed inputs with HTTP `400`.
107
+ - `formatChallenge` emits the right headers per decision arm (Allow, Deny + reason, RequireValidation + capability hash).
108
+ - `validatePaymentProof` rejects every entry in the hostile-input matrix.
109
+ - `emitFeedback` is idempotent — a second call for the same `payment_id_hash` returns the prior emission's signature instead of double-emitting.
80
110
 
81
- Pay.sh's `proof-validator.ts` and `feedback.ts` are the reference pattern.
111
+ Adversarial-harness coverage: [Verification Adversarial harness](/verification/adversarial-harness).
82
112
 
83
113
  ## Target integration time
84
114
 
85
- The architecture target is under two hours for a facilitator that already has a clear x402 wire spec. If adding a facilitator forces route edits, policy edits, or registry read changes, the adapter boundary failed and should be fixed before the integration is considered done.
115
+ The architecture target is **under two hours** for a facilitator that already has a clear x402 wire spec. If adding a facilitator forces route edits, policy edits, or registry-read changes, the adapter boundary failed and gets fixed before the integration is considered done.
116
+
117
+ ## Read next
118
+
119
+ <Cards>
120
+ <Card title="Pay.sh adapter" href="/integration-guides/pay-sh-adapter">
121
+ Walk through the canonical implementation end to end.
122
+ </Card>
123
+ <Card title="Dexter adapter" href="/integration-guides/dexter-adapter">
124
+ See how the second adapter slots into the same contract.
125
+ </Card>
126
+ <Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
127
+ Why every adapter's settle path keeps gate + transfer + feedback in one tx.
128
+ </Card>
129
+ </Cards>