@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.
- package/dist/embedded-data/devnet-smoke.json +4 -4
- package/dist/embedded-docs/architecture.mdx +174 -0
- package/dist/embedded-docs/getting-started/quickstart.mdx +79 -56
- package/dist/embedded-docs/index.mdx +54 -37
- package/dist/embedded-docs/integration-guides/capability-namespaces.mdx +135 -8
- package/dist/embedded-docs/integration-guides/custom-attestor.mdx +169 -8
- package/dist/embedded-docs/integration-guides/dexter-adapter.mdx +76 -0
- package/dist/embedded-docs/integration-guides/facilitator-adapters.mdx +85 -41
- package/dist/embedded-docs/integration-guides/pay-sh-adapter.mdx +90 -54
- package/dist/embedded-docs/integration-guides/x402-facilitator.mdx +55 -24
- package/dist/embedded-docs/mcp/hosted-endpoint.mdx +197 -0
- package/dist/embedded-docs/mcp/index.mdx +108 -0
- package/dist/embedded-docs/mcp/install.mdx +183 -0
- package/dist/embedded-docs/mcp/prompts.mdx +90 -0
- package/dist/embedded-docs/mcp/resources.mdx +115 -0
- package/dist/embedded-docs/mcp/tools.mdx +156 -0
- package/dist/embedded-docs/programs/policy-vault/composer.mdx +117 -0
- package/dist/embedded-docs/programs/policy-vault/counterparty-tier-policy.mdx +81 -9
- package/dist/embedded-docs/programs/policy-vault/index.mdx +77 -47
- package/dist/embedded-docs/programs/policy-vault/kill-switch-policy.mdx +65 -8
- package/dist/embedded-docs/programs/policy-vault/require-validation-policy.mdx +76 -8
- package/dist/embedded-docs/programs/policy-vault/spending-policy.mdx +83 -8
- package/dist/embedded-docs/programs/policy-vault/velocity-policy.mdx +85 -8
- package/dist/embedded-docs/programs/trustgate.mdx +112 -30
- package/dist/embedded-docs/programs/validation-registry.mdx +139 -32
- package/dist/embedded-docs/reference/byte-offset-reference.mdx +102 -13
- package/dist/embedded-docs/reference/capability-namespaces.mdx +56 -0
- package/dist/embedded-docs/reference/changelog.mdx +230 -13
- package/dist/embedded-docs/reference/deny-reason-codes.mdx +86 -0
- package/dist/embedded-docs/reference/devnet-program-ids.mdx +50 -8
- package/dist/embedded-docs/reference/discriminator-constants.mdx +104 -10
- package/dist/embedded-docs/reference/mainnet-program-ids.mdx +89 -5
- package/dist/embedded-docs/reference/quantu-agent-registry.mdx +104 -9
- package/dist/embedded-docs/sdk/exports-reference.mdx +239 -0
- package/dist/embedded-docs/sdk/gate-payment.mdx +99 -14
- package/dist/embedded-docs/sdk/index.mdx +141 -40
- package/dist/embedded-docs/sdk/mount-trustgate.mdx +178 -8
- package/dist/embedded-docs/verification/adversarial-harness.mdx +88 -0
- package/dist/embedded-docs/verification/atomic-tx-invariant.mdx +141 -0
- package/dist/embedded-docs/verification/chained-validation.mdx +87 -0
- package/dist/embedded-docs/verification/devnet-smoke.mdx +85 -0
- package/dist/embedded-docs/verification/index.mdx +31 -0
- package/dist/embedded-docs/verification/kani-proofs.mdx +144 -0
- package/dist/embedded-docs/verification/live-evidence.mdx +180 -0
- package/dist/tools/write/emit-feedback.d.ts +6 -0
- package/dist/tools/write/emit-feedback.js +12 -1
- package/dist/tools/write/emit-feedback.js.map +1 -1
- package/package.json +16 -15
- package/scripts/install-claude-desktop.sh +0 -0
|
@@ -1,15 +1,142 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Capability namespaces
|
|
3
|
-
description:
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
```
|
|
12
|
+
```
|
|
11
13
|
HTTP request
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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)` |
|
|
28
|
-
| `formatChallenge(decision, ctx)` |
|
|
29
|
-
| `formatSettlement(ctx)` |
|
|
30
|
-
| `validatePaymentProof(proof, ctx)` |
|
|
31
|
-
| `emitFeedback(ctx, 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 |
|
|
40
|
-
| Dexter |
|
|
41
|
-
|
|
|
42
|
-
| MCPay | stub |
|
|
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,
|
|
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
|
|
50
|
-
5. Register
|
|
51
|
-
6. Add unit tests
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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>
|