@agenttrust-sdk/mcp 0.2.5 → 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-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/read/get-quantu-reputation.d.ts +50 -11
- package/dist/tools/read/get-quantu-reputation.js +75 -26
- package/dist/tools/read/get-quantu-reputation.js.map +1 -1
- 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,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>
|
|
@@ -1,33 +1,52 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Pay.sh adapter
|
|
3
|
-
description:
|
|
3
|
+
description: Walk the live Pay.sh + AgentTrust integration end to end — challenge, retry, settle, feedback, with hosted-demo paths and the SERVICE-signed envelope contract.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Pay.sh is the first
|
|
7
|
-
|
|
8
|
-
Pay.sh launch reference: [Solana Foundation launch note](https://solana.com/news/solana-foundation-launches-pay-sh-in-collaboration-with-google-cloud).
|
|
6
|
+
Pay.sh is the Solana Foundation's first x402 facilitator, [launched 2026-05-05 with Google Cloud](https://solana.com/news/solana-foundation-launches-pay-sh-in-collaboration-with-google-cloud). AgentTrust ships day-one Pay.sh integration as the canonical `FacilitatorAdapter` implementation. Source: [`trustgate/server/src/facilitators/pay-sh/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/server/src/facilitators/pay-sh).
|
|
9
7
|
|
|
10
8
|
## What the adapter does
|
|
11
9
|
|
|
12
|
-
| Stage | Pay.sh
|
|
13
|
-
|
|
10
|
+
| Stage | x402 / Pay.sh wire shape | AgentTrust action |
|
|
11
|
+
|---|---|---|
|
|
14
12
|
| Initial request | no payment proof | emit `402 Payment Required` with a base64 x402 v2 envelope |
|
|
15
|
-
| Challenge | `paymentRequirements.extra.agentTrust` | include payer agent, payee agent, payee recipient, policy ID, `issuedAt`,
|
|
16
|
-
| Retry | `PAYMENT-SIGNATURE` or `X-PAYMENT` | parse proof
|
|
17
|
-
| Policy | `VerifyContext` | run
|
|
13
|
+
| Challenge | `paymentRequirements.extra.agentTrust` | include payer agent, payee agent, payee recipient, policy ID, `issuedAt`, `serviceSignature` |
|
|
14
|
+
| Retry | `PAYMENT-SIGNATURE` or `X-PAYMENT` header | parse proof, rebuild `VerifyContext` |
|
|
15
|
+
| Policy | `VerifyContext` | run `gate_payment` decision |
|
|
18
16
|
| Allow | signed payment proof | validate transfer fields, emit feedback, forward resource |
|
|
19
|
-
| Deny | gate decision | return `402` with reason code
|
|
17
|
+
| Deny | gate decision | return `402` with reason code + reason name |
|
|
20
18
|
|
|
21
|
-
##
|
|
19
|
+
## Hit the live demo
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
[`demo.agenttrust.tech`](https://demo.agenttrust.tech) runs the full Pay.sh + AgentTrust pipeline against deployed devnet programs. With no payment proof:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
curl -i https://demo.agenttrust.tech/protected
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Returns `HTTP/2 402` with a SERVICE-signed `payment-required` envelope. With Pay.sh installed, let the CLI pay and retry:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pay --sandbox curl https://demo.agenttrust.tech/protected
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The demo seeds three deterministic counterparties. Drive each branch by passing the matching `X-Demo-Payer-Agent` header:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Allow path (tier 3)
|
|
37
|
+
PAYER=$(curl -s https://demo.agenttrust.tech/health | jq -r '.counterparties[2].agent')
|
|
38
|
+
pay --sandbox curl -H "X-Demo-Payer-Agent: $PAYER" https://demo.agenttrust.tech/protected
|
|
39
|
+
|
|
40
|
+
# Deny path (tier 0)
|
|
41
|
+
PAYER=$(curl -s https://demo.agenttrust.tech/health | jq -r '.counterparties[0].agent')
|
|
42
|
+
pay --sandbox curl -H "X-Demo-Payer-Agent: $PAYER" https://demo.agenttrust.tech/protected
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
| Counterparty tier | Decision |
|
|
46
|
+
|---:|---|
|
|
47
|
+
| 0 | `402 Deny — CounterpartyTierBelowMin` |
|
|
48
|
+
| 1 | `402 Deny — CounterpartyTierBelowMin` |
|
|
49
|
+
| 3 | `200 Allow + X-Payment-Receipt` |
|
|
31
50
|
|
|
32
51
|
## SERVICE-signed challenge
|
|
33
52
|
|
|
@@ -44,58 +63,49 @@ The SERVICE emits a signed challenge when it returns the Pay.sh `402` response.
|
|
|
44
63
|
- policy ID
|
|
45
64
|
- payment ID hash
|
|
46
65
|
|
|
47
|
-
`PaySh.parseRequest()` verifies that signature against the facilitator public key before
|
|
48
|
-
|
|
49
|
-
## Run the demo
|
|
66
|
+
`PaySh.parseRequest()` verifies that signature against the facilitator public key before accepting the request. This closes the race window where a forged `paymentRequirements` envelope could race a legitimate one.
|
|
50
67
|
|
|
51
|
-
|
|
52
|
-
pnpm --filter ./examples/pay-sh-demo dev
|
|
53
|
-
```
|
|
68
|
+
## Files to read in repo
|
|
54
69
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
| 0 | `402 Deny` with `CounterpartyTierBelowMin` |
|
|
64
|
-
| 1 | `402 Deny` with `CounterpartyTierBelowMin` |
|
|
65
|
-
| 3 | `200 Allow` with `X-Payment-Receipt` |
|
|
66
|
-
|
|
67
|
-
Get the exact payer agent keys:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
curl -s https://demo.agenttrust.tech/health | jq '.counterparties'
|
|
71
|
-
```
|
|
70
|
+
| File | Purpose |
|
|
71
|
+
|---|---|
|
|
72
|
+
| [`facilitators/pay-sh/index.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/index.ts) | The five-method `PaySh` class |
|
|
73
|
+
| [`facilitators/pay-sh/schemas.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/schemas.ts) | Strict Zod wire schemas |
|
|
74
|
+
| [`facilitators/pay-sh/sig.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/sig.ts) | SERVICE challenge signature helpers |
|
|
75
|
+
| [`facilitators/pay-sh/proof-validator.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/proof-validator.ts) | Replay, self-pay, amount, mint, recipient checks |
|
|
76
|
+
| [`facilitators/pay-sh/feedback.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/facilitators/pay-sh/feedback.ts) | Idempotent feedback emission |
|
|
77
|
+
| [`examples/pay-sh-demo/src/middleware.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/examples/pay-sh-demo/src/middleware.ts) | Express bridge from Pay.sh retry to AgentTrust |
|
|
72
78
|
|
|
73
79
|
## Production wiring
|
|
74
80
|
|
|
75
|
-
The demo proves the pipeline without requiring devnet RPC in CI. The server package carries the real devnet
|
|
81
|
+
The demo proves the pipeline without requiring devnet RPC in CI. The server package carries the real devnet path: [`trustgate/server/src/chain.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/server/src/chain.ts) loads deployed program IDLs, `/verify` simulates `gate_payment`, and `/settle` delegates proof validation plus feedback emission through the active adapter.
|
|
76
82
|
|
|
77
|
-
Production swaps three dependency seams:
|
|
83
|
+
Production swaps three dependency seams the demo stubs:
|
|
78
84
|
|
|
79
85
|
```ts
|
|
86
|
+
import { PaySh } from "./facilitators/pay-sh";
|
|
87
|
+
|
|
80
88
|
const adapter = new PaySh({
|
|
81
89
|
signingNetwork: "solana-devnet",
|
|
82
|
-
feePayer:
|
|
83
|
-
validateOnChainTx,
|
|
84
|
-
emitFeedbackCpi,
|
|
85
|
-
priorEmissionLookup,
|
|
86
|
-
replayCache,
|
|
87
|
-
signDecision,
|
|
90
|
+
feePayer: facilitator.publicKey,
|
|
91
|
+
validateOnChainTx, // makeValidateOnChainTx({ connection, … })
|
|
92
|
+
emitFeedbackCpi, // makeEmitFeedbackCpi({ connection, programIds, … })
|
|
93
|
+
priorEmissionLookup, // makePriorEmissionLookup({ connection, programId })
|
|
94
|
+
replayCache, // ReplayCache (in-memory by default)
|
|
95
|
+
signDecision, // signs canonical decision bytes with facilitator key
|
|
88
96
|
});
|
|
89
97
|
```
|
|
90
98
|
|
|
91
99
|
| Dependency | Production implementation |
|
|
92
|
-
|
|
100
|
+
|---|---|
|
|
93
101
|
| `validateOnChainTx` | parse the confirmed SPL transfer transaction from RPC |
|
|
94
102
|
| `emitFeedbackCpi` | build and send `trustgate::emit_feedback` through Anchor |
|
|
95
103
|
| `priorEmissionLookup` | read the `FeedbackEmissionLog` PDA by payment hash |
|
|
96
104
|
| `signDecision` | sign canonical decision bytes with the facilitator key |
|
|
97
105
|
|
|
98
|
-
|
|
106
|
+
The factories (`makeValidateOnChainTx`, `makeEmitFeedbackCpi`, `makePriorEmissionLookup`) are SDK exports — see [SDK → Exports reference](/sdk/exports-reference).
|
|
107
|
+
|
|
108
|
+
## Failure cases the adapter must keep
|
|
99
109
|
|
|
100
110
|
Do not remove these checks when adapting Pay.sh into a production server:
|
|
101
111
|
|
|
@@ -105,6 +115,32 @@ Do not remove these checks when adapting Pay.sh into a production server:
|
|
|
105
115
|
- reject `payTo` / `payeeRecipient` mismatch
|
|
106
116
|
- reject expired challenges
|
|
107
117
|
- reject replayed proof bindings
|
|
108
|
-
- reject transfer authority equal to facilitator fee payer
|
|
118
|
+
- reject transfer authority equal to facilitator fee payer (self-pay defense)
|
|
119
|
+
|
|
120
|
+
These are part of the adapter, not the route layer. 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) is the reference implementation.
|
|
121
|
+
|
|
122
|
+
## Live evidence
|
|
123
|
+
|
|
124
|
+
Real atomic-settlement trace on devnet (2026-05-06):
|
|
125
|
+
|
|
126
|
+
| Step | Tx |
|
|
127
|
+
|---|---|
|
|
128
|
+
| Signed SPL `transferChecked` | [`5iV8EYmJh9XS…`](https://explorer.solana.com/tx/5iV8EYmJh9XSXkBQrrbQ5L9kmBQabD3G3RXVPsHn9PkWceTFoeRsUV4g5aLLzZyRjeBoFvK3Woxr2cZa5xeUwhVD?cluster=devnet) |
|
|
129
|
+
| `emit_feedback` PDA-signed CPI | [`jMobmWJUAXuL8…`](https://explorer.solana.com/tx/jMobmWJUAXuL8FmQujfxW9NmeMbzADUoABzqjiMeuc5m3YXyeuZeUw1ZJc29JGsqyWQGDY8q3vrtBdamhKXraag?cluster=devnet) |
|
|
130
|
+
| `FeedbackEmissionLog` PDA | [`HB4BBi9j…`](https://explorer.solana.com/address/HB4BBi9jaD3VPcZkQQaH3DxukSqBiXfW8RejtaLa8bF3?cluster=devnet) |
|
|
131
|
+
|
|
132
|
+
Full trace + reproduction commands: [Verification → Devnet smoke](/verification/devnet-smoke).
|
|
133
|
+
|
|
134
|
+
## Read next
|
|
109
135
|
|
|
110
|
-
|
|
136
|
+
<Cards>
|
|
137
|
+
<Card title="Facilitator adapters" href="/integration-guides/facilitator-adapters">
|
|
138
|
+
The five-method contract every adapter satisfies. Build your own.
|
|
139
|
+
</Card>
|
|
140
|
+
<Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
|
|
141
|
+
Why the gate, transfer, and feedback must execute as one Solana tx.
|
|
142
|
+
</Card>
|
|
143
|
+
<Card title="mountTrustGate" href="/sdk/mount-trustgate">
|
|
144
|
+
The SDK middleware that backs the adapter dispatch.
|
|
145
|
+
</Card>
|
|
146
|
+
</Cards>
|