@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,73 +1,174 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "@agenttrust-sdk/trustgate"
|
|
3
|
-
description: TypeScript client helpers,
|
|
3
|
+
description: TypeScript surface for AgentTrust on Solana — Express middleware, client helpers, atomicity guard, PDA derivers, and the full ValidationRegistry instruction builder set.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
[`@agenttrust-sdk/trustgate@0.2.0`](https://www.npmjs.com/package/@agenttrust-sdk/trustgate) is the TypeScript package every facilitator pulls in. Express middleware, client helpers for direct calls, the atomicity guard, PDA derivers + Anchor loaders for all three programs, and instruction builders for ValidationRegistry's full lifecycle.
|
|
7
|
+
|
|
8
|
+
Source: [`trustgate/sdk/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/sdk). License: MIT.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
7
11
|
|
|
8
12
|
```bash
|
|
9
13
|
pnpm add @agenttrust-sdk/trustgate
|
|
14
|
+
# or: npm install @agenttrust-sdk/trustgate
|
|
15
|
+
# or: yarn add @agenttrust-sdk/trustgate
|
|
10
16
|
```
|
|
11
17
|
|
|
12
|
-
|
|
18
|
+
Peer dep: `express ^4.21` (only required if you mount the middleware). Node `>=20`.
|
|
19
|
+
|
|
20
|
+
## Three import surfaces
|
|
13
21
|
|
|
14
22
|
```ts
|
|
15
|
-
import { mountTrustGate }
|
|
16
|
-
import { gatePayment, settle, dispute }
|
|
17
|
-
import {
|
|
18
|
-
AtomicityNotEnforcedError,
|
|
19
|
-
DEFAULT_DEVNET_PROGRAM_IDS,
|
|
20
|
-
derivePolicyPda,
|
|
21
|
-
} from "@agenttrust-sdk/trustgate";
|
|
23
|
+
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
|
|
24
|
+
import { gatePayment, settle, dispute } from "@agenttrust-sdk/trustgate/client";
|
|
25
|
+
import { composeAtomicSettleTx } from "@agenttrust-sdk/trustgate";
|
|
22
26
|
```
|
|
23
27
|
|
|
24
|
-
|
|
28
|
+
The root namespace re-exports every helper grouped by concern — atomicity guard, PDA derivers, Anchor loaders, Quantu helpers, ValidationRegistry instruction builders, SPL helpers, x402 header constants. Full list: [Exports reference](/sdk/exports-reference).
|
|
29
|
+
|
|
30
|
+
The `/express` and `/client` subpaths are tree-shake-friendly entry points; the root namespace doesn't re-export them so a client-only consumer doesn't pull Express into the bundle.
|
|
25
31
|
|
|
26
|
-
|
|
32
|
+
## Quick start — Express
|
|
27
33
|
|
|
28
34
|
```ts
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
import express from "express";
|
|
36
|
+
import { Keypair } from "@solana/web3.js";
|
|
37
|
+
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
|
|
38
|
+
|
|
39
|
+
const app = express();
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
|
|
42
|
+
await mountTrustGate(app, {
|
|
43
|
+
rpcUrl: "https://api.devnet.solana.com",
|
|
44
|
+
facilitatorKeypair: Keypair.fromSecretKey(/* facilitator key */),
|
|
45
|
+
network: "solana-devnet",
|
|
46
|
+
atomicityEnforced: true, // literal `true`
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app.listen(3000);
|
|
33
50
|
```
|
|
34
51
|
|
|
35
|
-
|
|
52
|
+
Mounts four routes:
|
|
53
|
+
|
|
54
|
+
| Route | Purpose |
|
|
55
|
+
|---|---|
|
|
56
|
+
| `POST /verify` | Read-only `gate_payment` simulation. Returns 200/Allow, 402/Deny + reason headers, or 402/RequireValidation + capability hash. |
|
|
57
|
+
| `GET /receipt/:paymentIdHashHex` | `FeedbackEmissionLog` lookup. Returns `{ exists: false }` until settlement. |
|
|
58
|
+
| `POST /settle` | Atomic settle (`gate_payment_strict + transferChecked + emit_feedback`). |
|
|
59
|
+
| `POST /dispute` | Dispute path (`dispute_payment` ix). |
|
|
36
60
|
|
|
37
|
-
|
|
61
|
+
Full route reference: [mountTrustGate](/sdk/mount-trustgate).
|
|
38
62
|
|
|
39
|
-
|
|
63
|
+
## Quick start — client
|
|
40
64
|
|
|
41
65
|
```ts
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
66
|
+
import { Keypair, PublicKey } from "@solana/web3.js";
|
|
67
|
+
import { gatePayment } from "@agenttrust-sdk/trustgate/client";
|
|
68
|
+
|
|
69
|
+
const decision = await gatePayment({
|
|
70
|
+
rpcUrl: "https://api.devnet.solana.com",
|
|
71
|
+
caller: facilitatorKeypair,
|
|
72
|
+
payerAgentAsset: new PublicKey("…"),
|
|
73
|
+
payeeAgentAsset: new PublicKey("…"),
|
|
74
|
+
amount: 1_000_000n,
|
|
75
|
+
mint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC devnet
|
|
76
|
+
policyId: 1,
|
|
48
77
|
});
|
|
78
|
+
|
|
79
|
+
switch (decision.kind) {
|
|
80
|
+
case "Allow": /* proceed */ break;
|
|
81
|
+
case "Deny": console.log(decision.reasonName); break;
|
|
82
|
+
case "RequireValidation": /* route to attestation flow with decision.capabilityHash */ break;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Full reference: [gatePayment](/sdk/gate-payment).
|
|
87
|
+
|
|
88
|
+
## Atomic-tx invariant
|
|
89
|
+
|
|
90
|
+
`gate_payment + SPL transfer + emit_feedback` MUST execute as one Solana transaction. Splitting them silently corrupts `VelocityLedger` when a Token-2022 mint's `TransferHook` extension reverts the transfer.
|
|
91
|
+
|
|
92
|
+
The SDK enforces atomicity at three layers:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import {
|
|
96
|
+
AtomicityEnforced,
|
|
97
|
+
AtomicityNotEnforcedError,
|
|
98
|
+
composeAtomicSettleTx,
|
|
99
|
+
} from "@agenttrust-sdk/trustgate";
|
|
100
|
+
|
|
101
|
+
// 1. Compile-time literal-type guard:
|
|
102
|
+
// `AtomicityEnforced` is `{ atomicityEnforced: true }` (literal `true`).
|
|
103
|
+
// TS rejects `false`, missing, or `boolean` widening.
|
|
104
|
+
//
|
|
105
|
+
// 2. Runtime guard:
|
|
106
|
+
// `assertAtomicityEnforced` throws `AtomicityNotEnforcedError` for any
|
|
107
|
+
// value that isn't strictly `=== true`. Catches `as any` cast bypasses.
|
|
108
|
+
//
|
|
109
|
+
// 3. Composer structure:
|
|
110
|
+
// `composeAtomicSettleTx` returns one `Transaction` with exactly three
|
|
111
|
+
// instructions in canonical order (gate, transfer, feedback).
|
|
49
112
|
```
|
|
50
113
|
|
|
51
|
-
|
|
114
|
+
Plus the on-chain Kani proof `gate_payment_strict_correctness` pins the strict handler's contract — `Ok(())` if and only if the composer returned `Allow`. Full proof: [Verification → Atomic-tx invariant](/verification/atomic-tx-invariant).
|
|
52
115
|
|
|
53
|
-
|
|
116
|
+
## Breaking changes (0.2.0)
|
|
117
|
+
|
|
118
|
+
`ProgramIds.trustgate` (lowercase) renamed to `ProgramIds.trustGate` (camelCase, matches `policyVault`). New `validationRegistry` field populated by default with the deployed devnet ID `Cx4RFa6ysw3qXYhugPkF8pFSWBkmKq59h2dWgF2tKhtv`.
|
|
54
119
|
|
|
55
120
|
```ts
|
|
56
|
-
|
|
121
|
+
// 0.1.x
|
|
122
|
+
DEFAULT_DEVNET_PROGRAM_IDS.trustgate.toBase58();
|
|
123
|
+
|
|
124
|
+
// 0.2.0
|
|
125
|
+
DEFAULT_DEVNET_PROGRAM_IDS.trustGate.toBase58();
|
|
126
|
+
DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry.toBase58(); // new
|
|
57
127
|
```
|
|
58
128
|
|
|
59
|
-
|
|
129
|
+
One-line migration: search-and-replace `.trustgate` → `.trustGate` for every `programIds.*` field access. Full changelog: [Reference → Changelog](/reference/changelog).
|
|
60
130
|
|
|
61
|
-
##
|
|
131
|
+
## On-chain programs (devnet)
|
|
62
132
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
133
|
+
<ProgramIdsTable />
|
|
134
|
+
|
|
135
|
+
`loadPolicyVault` / `loadTrustGate` / `loadValidationRegistry` fetch each IDL from chain by default. Pass an explicit `idl` argument to use a bundled snapshot — useful when avoiding an extra RPC hop in latency-sensitive paths or when running against a freshly redeployed program before `anchor idl upgrade` runs.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
anchor idl fetch <programId> --provider.cluster devnet
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Test coverage
|
|
142
|
+
|
|
143
|
+
| Suite | Count | Where |
|
|
144
|
+
|---|---:|---|
|
|
145
|
+
| Atomicity unit tests | 6 (literal-type guard + runtime + composer structure) | `trustgate/sdk/test/atomicity.test.ts` |
|
|
146
|
+
| PDA derivation tests | 24 | `trustgate/sdk/test/chain.test.ts` |
|
|
147
|
+
| Quantu helpers | 12 | `trustgate/sdk/test/quantu.test.ts` |
|
|
148
|
+
| ValidationRegistry instruction builders | 14 | `trustgate/sdk/test/validation-registry.test.ts` |
|
|
149
|
+
| All SDK unit tests | 56 (+ 16 INTEGRATION-gated devnet round-trips) | `pnpm --filter ./trustgate/sdk run test` |
|
|
150
|
+
|
|
151
|
+
Run via:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
cd trustgate/sdk
|
|
155
|
+
pnpm test
|
|
156
|
+
INTEGRATION=1 pnpm test:integration # devnet round-trip
|
|
157
|
+
```
|
|
72
158
|
|
|
73
|
-
|
|
159
|
+
## Read next
|
|
160
|
+
|
|
161
|
+
<Cards>
|
|
162
|
+
<Card title="gatePayment" href="/sdk/gate-payment">
|
|
163
|
+
Read-only policy decision call — type signature, config fields, decision union, errors.
|
|
164
|
+
</Card>
|
|
165
|
+
<Card title="mountTrustGate" href="/sdk/mount-trustgate">
|
|
166
|
+
Express middleware reference — every route, every header, the atomicity guard contract.
|
|
167
|
+
</Card>
|
|
168
|
+
<Card title="Exports reference" href="/sdk/exports-reference">
|
|
169
|
+
One-screen list of every public export grouped by concern.
|
|
170
|
+
</Card>
|
|
171
|
+
<Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
|
|
172
|
+
Three layers of proof + the on-chain Kani strict-correctness binding.
|
|
173
|
+
</Card>
|
|
174
|
+
</Cards>
|
|
@@ -1,15 +1,185 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: mountTrustGate
|
|
3
|
-
description: Express middleware
|
|
3
|
+
description: Drop-in Express middleware. Adds the four x402 routes to any app — verify, receipt, settle, dispute — with the atomicity guard enforced at compile-time and runtime.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`
|
|
6
|
+
`mountTrustGate(app, config)` adds four endpoints to any Express app. The atomicity guard runs at the top of the call so a missing or `false` `atomicityEnforced` flag fails synchronously before any route binds.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Source: [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts).
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
| --- | --- |
|
|
12
|
-
| Express middleware | [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts) |
|
|
13
|
-
| x402 helpers | [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts) |
|
|
14
|
-
| atomicity tests | [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) |
|
|
10
|
+
## Signature
|
|
15
11
|
|
|
12
|
+
```ts
|
|
13
|
+
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
|
|
14
|
+
|
|
15
|
+
export function mountTrustGate(
|
|
16
|
+
app: Application,
|
|
17
|
+
config: MountTrustGateConfig,
|
|
18
|
+
): Promise<void>;
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## `MountTrustGateConfig`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface MountTrustGateConfig extends AtomicityEnforced {
|
|
25
|
+
rpcUrl: string; // Solana RPC URL
|
|
26
|
+
facilitatorKeypair: Keypair; // signs emit_feedback CPIs + tx fees
|
|
27
|
+
defaultPolicyId?: number; // fallback if /verify body omits policyId
|
|
28
|
+
programIds?: ProgramIds; // defaults to DEFAULT_DEVNET_PROGRAM_IDS
|
|
29
|
+
network?: string; // x402 header label, e.g. "solana-devnet"
|
|
30
|
+
atomicityEnforced: true; // literal `true` — TS compile error on `false`
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The promise resolves once IDLs are loaded from the cluster and routes are bound. If the cluster doesn't have published IDLs, `mountTrustGate` rejects — call `anchor idl init` once per program before mounting.
|
|
35
|
+
|
|
36
|
+
## Routes
|
|
37
|
+
|
|
38
|
+
### `POST /verify`
|
|
39
|
+
|
|
40
|
+
Read-only `gate_payment` simulation. Same semantics as [`gatePayment()`](/sdk/gate-payment).
|
|
41
|
+
|
|
42
|
+
```http
|
|
43
|
+
POST /verify
|
|
44
|
+
Content-Type: application/json
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
"payerAgentAsset": "<base58 pubkey>",
|
|
48
|
+
"payeeAgentAsset": "<base58 pubkey>",
|
|
49
|
+
"amount": "1000000",
|
|
50
|
+
"mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
|
|
51
|
+
"policyId": 1
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Responses:
|
|
56
|
+
|
|
57
|
+
```http
|
|
58
|
+
HTTP/1.1 200 OK
|
|
59
|
+
X-Agent-Trust-Decision: Allow
|
|
60
|
+
X-Payment-Network: solana-devnet
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```http
|
|
64
|
+
HTTP/1.1 402 Payment Required
|
|
65
|
+
X-Agent-Trust-Decision: Deny
|
|
66
|
+
X-Payment-Required: denied
|
|
67
|
+
X-Payment-Reason-Code: 6
|
|
68
|
+
X-Payment-Reason-Name: CounterpartyTierBelowMin
|
|
69
|
+
X-Payment-Network: solana-devnet
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```http
|
|
73
|
+
HTTP/1.1 402 Payment Required
|
|
74
|
+
X-Agent-Trust-Decision: RequireValidation
|
|
75
|
+
X-Payment-Required: validation
|
|
76
|
+
X-Capability-Required: 366c075140aa69746625d4b733b55e267fc5c28387fd6d1c24901976ee3ddc42
|
|
77
|
+
X-Payment-Network: solana-devnet
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Headers built via `buildHeadersForDecision` in [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts).
|
|
81
|
+
|
|
82
|
+
### `GET /receipt/:paymentIdHashHex`
|
|
83
|
+
|
|
84
|
+
`FeedbackEmissionLog` PDA lookup. Returns `{ exists: false }` until the payment settles.
|
|
85
|
+
|
|
86
|
+
```http
|
|
87
|
+
GET /receipt/6984738594e493bfd4314866840427a11e8e53677bc0ff4b98ae8aa39ce0c859
|
|
88
|
+
|
|
89
|
+
HTTP/1.1 200 OK
|
|
90
|
+
Content-Type: application/json
|
|
91
|
+
|
|
92
|
+
{
|
|
93
|
+
"exists": true,
|
|
94
|
+
"score": 100,
|
|
95
|
+
"isDispute": false,
|
|
96
|
+
"emittedAtSlot": 460466788,
|
|
97
|
+
"feedbackEmissionLog": "HB4BBi9jaD3VPcZkQQaH3DxukSqBiXfW8RejtaLa8bF3"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `paymentIdHashHex` parameter is the lowercase hex encoding of `SHA-256(payment_id_string)`. The SDK exports `deriveFeedbackLogPda(programId, paymentIdHashBytes)` for callers that derive the PDA themselves.
|
|
102
|
+
|
|
103
|
+
### `POST /settle`
|
|
104
|
+
|
|
105
|
+
Atomic settle path. The handler accepts the verify-time payment context plus the `tag1`/`tag2`/`endpoint`/`feedback_uri` metadata, builds the three-instruction transaction via `composeAtomicSettleTx`, and submits.
|
|
106
|
+
|
|
107
|
+
```http
|
|
108
|
+
POST /settle
|
|
109
|
+
Content-Type: application/json
|
|
110
|
+
|
|
111
|
+
{
|
|
112
|
+
"payerAgentAsset": "<base58>",
|
|
113
|
+
"payeeAgentAsset": "<base58>",
|
|
114
|
+
"amount": "1000000",
|
|
115
|
+
"mint": "<base58>",
|
|
116
|
+
"policyId": 1,
|
|
117
|
+
"paymentIdHash": "6984738594e493bfd4314866840427a11e8e53677bc0ff4b98ae8aa39ce0c859",
|
|
118
|
+
"feedbackUri": "ipfs://…",
|
|
119
|
+
"score": 100,
|
|
120
|
+
"tag1": "successful",
|
|
121
|
+
"tag2": "x402",
|
|
122
|
+
"endpoint": "/protected"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The route requires `payer` keypair access (the SPL transfer authority). The default Express handler reads it from `req.body.payerKey` for testing; production deploys typically wrap this route with their own auth + signature scheme. See [Pay.sh adapter](/integration-guides/pay-sh-adapter) for the canonical wrapping pattern.
|
|
127
|
+
|
|
128
|
+
### `POST /dispute`
|
|
129
|
+
|
|
130
|
+
Same shape as `/settle`, but invokes `dispute_payment` (negative-score feedback) instead. Requires `dispute_reason_hash` in the body. The same `FeedbackEmissionLog` idempotency rules apply: a second dispute for the same `payment_id_hash` fails account-already-in-use.
|
|
131
|
+
|
|
132
|
+
## Atomicity guard
|
|
133
|
+
|
|
134
|
+
`mountTrustGate` is itself a `MountTrustGateConfig extends AtomicityEnforced` consumer:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
export interface MountTrustGateConfig extends AtomicityEnforced {
|
|
138
|
+
// … the literal `atomicityEnforced: true` is required
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
export async function mountTrustGate(app, config) {
|
|
144
|
+
assertAtomicityEnforced(config, "mountTrustGate");
|
|
145
|
+
// …
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Compile-time: `await mountTrustGate(app, { …, atomicityEnforced: false });` is a TS error. Runtime: `await mountTrustGate(app, { …, atomicityEnforced: true as any });` works because TS would have already let it through, BUT `await mountTrustGate(app, JSON.parse(unsafeUserConfig));` still fails the runtime guard.
|
|
150
|
+
|
|
151
|
+
Six SDK unit tests cover both layers in [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) — including the `as any` cast bypass and the `composeAtomicSettleTx` three-instruction structure check.
|
|
152
|
+
|
|
153
|
+
## Example
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import express from "express";
|
|
157
|
+
import { Keypair } from "@solana/web3.js";
|
|
158
|
+
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
|
|
159
|
+
|
|
160
|
+
const app = express();
|
|
161
|
+
app.use(express.json());
|
|
162
|
+
|
|
163
|
+
const facilitatorKeypair = Keypair.fromSecretKey(
|
|
164
|
+
Uint8Array.from(JSON.parse(process.env.FACILITATOR_KEYPAIR!)),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
await mountTrustGate(app, {
|
|
168
|
+
rpcUrl: process.env.SOLANA_RPC_URL ?? "https://api.devnet.solana.com",
|
|
169
|
+
facilitatorKeypair,
|
|
170
|
+
defaultPolicyId: 1,
|
|
171
|
+
network: "solana-devnet",
|
|
172
|
+
atomicityEnforced: true,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
app.get("/", (_req, res) => res.send("AgentTrust facilitator up"));
|
|
176
|
+
app.listen(3000, () => console.log("listening on :3000"));
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The hosted facilitator at [`api.agenttrust.tech`](https://api.agenttrust.tech) runs exactly this code path. Health: `GET /healthz`.
|
|
180
|
+
|
|
181
|
+
## Source
|
|
182
|
+
|
|
183
|
+
- Express middleware: [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts)
|
|
184
|
+
- x402 helpers: [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts)
|
|
185
|
+
- Atomicity tests: [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Adversarial harness
|
|
3
|
+
description: Fourteen hostile-scenario assertions that exercise the gate against malformed PDAs, replayed proofs, and corrupted state.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The adversarial harness sits alongside the Anchor end-to-end suite. While the e2e tests cover the happy + edge paths a normal integration would hit, the adversarial harness exercises the failure modes a hostile caller would try.
|
|
7
|
+
|
|
8
|
+
Source: [`tests/adversarial.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/adversarial.spec.ts). Run via `anchor test --skip-deploy --provider.cluster localnet`.
|
|
9
|
+
|
|
10
|
+
## The fourteen scenarios
|
|
11
|
+
|
|
12
|
+
Each scenario constructs a hostile input and asserts the program rejects it with a typed error rather than producing an `Allow` or silently mutating state.
|
|
13
|
+
|
|
14
|
+
| # | Scenario | Expected behaviour |
|
|
15
|
+
|---:|---|---|
|
|
16
|
+
| 1 | Forged `AtomStats` — wrong owner program | `Deny(AtomStatsWrongOwner)` (code 9) |
|
|
17
|
+
| 2 | Forged `AtomStats` — schema-version canary mismatch | `Deny(AtomStatsSchemaMismatch)` (code 10) |
|
|
18
|
+
| 3 | Forged `AtomStats` — tier byte > `ATOM_TIER_MAX = 4` | `Deny(AtomStatsSchemaMismatch)` (code 10) |
|
|
19
|
+
| 4 | Forged `AtomStats` — undersized buffer | `Deny(AtomStatsSchemaMismatch)` (code 10) |
|
|
20
|
+
| 5 | Replayed `payment_id_hash` | `account-already-in-use` on `FeedbackEmissionLog` init; whole tx reverts |
|
|
21
|
+
| 6 | Self-pay attempt — facilitator signing as both payer and payee | `Deny` from facilitator-signer-mismatch check |
|
|
22
|
+
| 7 | Wrong attestor — attestation issued by a key not in `accepted_attestors[]` | `Deny(AttestationAttestorRejected)` (code 14) |
|
|
23
|
+
| 8 | Stale attestation — `expires_at <= now_slot` (and != 0) | `Deny(AttestationExpired)` (code 12) |
|
|
24
|
+
| 9 | Revoked attestation — `revoked == true` | `Deny(AttestationRevoked)` (code 13) |
|
|
25
|
+
| 10 | Wrong subject — attestation's `subject_asset` doesn't match the payee | `Deny(AttestationMissing)` (code 11) |
|
|
26
|
+
| 11 | Wrong capability — attestation's `capability_hash` doesn't match `required_capability_hash` | `Deny(AttestationMissing)` (code 11) |
|
|
27
|
+
| 12 | Multisig threshold bypass attempt — fewer distinct signers than `threshold` | `ThresholdNotMet` (Anchor error) |
|
|
28
|
+
| 13 | Multisig duplicate-signer attempt — same key signing twice | counted as one (pubkey-dedup); `ThresholdNotMet` if remaining distinct count is below threshold |
|
|
29
|
+
| 14 | Velocity counter manipulation — `now_slot < window_start_slot` (clock-skew or replay) | `saturating_sub` clamps elapsed to 0; window NOT expired; cumulative correctly preserved |
|
|
30
|
+
|
|
31
|
+
## What the harness covers that Kani doesn't
|
|
32
|
+
|
|
33
|
+
Kani's bounded model checker proves the pure-Rust composer's safety invariants over symbolic state. The adversarial harness covers the on-chain Anchor wrapper that Kani doesn't:
|
|
34
|
+
|
|
35
|
+
- account-validation checks (owner mismatch, size mismatch, schema-version canary)
|
|
36
|
+
- the `init`-only `FeedbackEmissionLog` + replay mechanism (Anchor's `account-already-in-use` semantics)
|
|
37
|
+
- the Anchor-handler's signer constraints (facilitator self-pay, attestor key validation)
|
|
38
|
+
- end-to-end on-chain tx behaviour the bounded composer can't see (idempotency, atomic revert)
|
|
39
|
+
|
|
40
|
+
The two together cover both the in-program decision logic (Kani) and the surrounding Anchor wrapper (adversarial harness). Either alone leaves gaps.
|
|
41
|
+
|
|
42
|
+
## Why localnet, not devnet
|
|
43
|
+
|
|
44
|
+
Adversarial scenarios sometimes require constructing accounts the real Quantu programs would never produce (forged `AtomStats` with bad schema versions, forged `ValidationAttestation` PDAs with wrong owner). On localnet, the test fixture writes those raw bytes directly. On devnet, the only way to construct such an account is to compromise the real program — which we obviously can't do in CI.
|
|
45
|
+
|
|
46
|
+
Localnet also produces deterministic timing for slot-based assertions (clock-skew test #14). Devnet cluster-time variance would make that test flaky.
|
|
47
|
+
|
|
48
|
+
## Reproduce
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
git clone https://github.com/agenttrust-labs/agenttrust && cd agenttrust
|
|
52
|
+
pnpm install
|
|
53
|
+
anchor build
|
|
54
|
+
anchor test --provider.cluster localnet --validator legacy --skip-build \
|
|
55
|
+
--grep "adversarial"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Expected: 14 / 14 passing in ~30 s.
|
|
59
|
+
|
|
60
|
+
## Where it complements other layers
|
|
61
|
+
|
|
62
|
+
- **PolicyVault unit tests (113 cases).** Cover normal paths through every policy module.
|
|
63
|
+
- **Anchor TS e2e suite (50 cases).** Cover the Anchor wrapper happy paths against real (or cloned) Quantu state.
|
|
64
|
+
- **Kani proofs (6 / 635 sub-checks).** Cover the pure-Rust composer's safety invariants over symbolic state.
|
|
65
|
+
- **Adversarial harness (14 cases).** Cover the failure modes a hostile caller would try.
|
|
66
|
+
- **MCP protocol conformance (21 cases).** Cover the MCP wire protocol shape.
|
|
67
|
+
- **Adapter contract conformance (per-adapter test suites).** Cover the `FacilitatorAdapter` contract.
|
|
68
|
+
|
|
69
|
+
The combination — 113 + 50 + 635 sub-checks + 14 + 21 + per-adapter — is what underwrites the v1 safety claim.
|
|
70
|
+
|
|
71
|
+
## Source
|
|
72
|
+
|
|
73
|
+
- Test file: [`tests/adversarial.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/adversarial.spec.ts)
|
|
74
|
+
- Helper fixtures: [`tests/helpers/`](https://github.com/agenttrust-labs/agenttrust/tree/main/tests/helpers)
|
|
75
|
+
|
|
76
|
+
## Read next
|
|
77
|
+
|
|
78
|
+
<Cards>
|
|
79
|
+
<Card title="Kani proofs" href="/verification/kani-proofs">
|
|
80
|
+
The other side of the invariant proof — pure-Rust over symbolic state.
|
|
81
|
+
</Card>
|
|
82
|
+
<Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
|
|
83
|
+
The biggest scenario the harness covers — split-tx state corruption.
|
|
84
|
+
</Card>
|
|
85
|
+
<Card title="DenyReason codes" href="/reference/deny-reason-codes">
|
|
86
|
+
The 15 reason codes the harness asserts against.
|
|
87
|
+
</Card>
|
|
88
|
+
</Cards>
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Atomic-tx invariant
|
|
3
|
+
description: gate_payment + transfer + emit_feedback execute as one Solana transaction or none. Three SDK layers + the on-chain Kani anchor + localnet runtime proof.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The load-bearing safety property of the SDK:
|
|
7
|
+
|
|
8
|
+
> `gate_payment` + SPL `transferChecked` + `emit_feedback` MUST execute as one Solana transaction. Splitting them silently corrupts state when a Token-2022 mint's `TransferHook` extension reverts the transfer.
|
|
9
|
+
|
|
10
|
+
Background writeup: [`docs/proofs/transfer-hook-atomicity.md`](https://github.com/agenttrust-labs/agenttrust/blob/main/docs/proofs/transfer-hook-atomicity.md). The proof is structured as five layers; all five must hold.
|
|
11
|
+
|
|
12
|
+
## The corruption vector
|
|
13
|
+
|
|
14
|
+
`gate_payment_strict` mutates state on `Allow`:
|
|
15
|
+
|
|
16
|
+
- `PolicyAccount.spending_today_used`, `spending_today_anchor`, `spending_week_used`, `spending_week_anchor`
|
|
17
|
+
- `VelocityLedger.cumulative_amount`, `last_commit_slot`, `window_start_slot`
|
|
18
|
+
|
|
19
|
+
If a downstream SPL transfer reverts after that mutation — and the two instructions are in *different* Solana transactions — the gate's state is durable while no money has moved. Subsequent `gate_payment` calls read the inflated counters and may `Deny` a legitimate payment. In the inverse, counters can drift such that velocity caps appear hit when nothing real has happened.
|
|
20
|
+
|
|
21
|
+
A Token-2022 `TransferHook` extension is the canonical instance of this fault model. The hook program runs synchronously inside the SPL `transferChecked` execution; if it returns `Err`, the transfer aborts. The specifics — compliance check, allowlist denial, freeze on suspicious counterparty — are domain-specific. From the runtime's perspective, a `TransferHook` revert is indistinguishable from any other inner-ix revert (NonTransferable mint, DefaultAccountState=Frozen, MissingRequiredSignature, out-of-funds). The atomicity guarantee is mint-extension-agnostic.
|
|
22
|
+
|
|
23
|
+
## Five layers of proof
|
|
24
|
+
|
|
25
|
+
| Layer | What it proves | Evidence |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| A.1 — compile-time guard | `AtomicityEnforced` is a literal `true` marker. TS rejects `false`, missing, and `boolean` widening at compile time. | [`trustgate/sdk/src/atomicity.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/atomicity.ts) lines 31-33 + [`test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) `describe("AtomicityEnforced literal-type guard")` |
|
|
28
|
+
| A.2 — runtime guard | `assertAtomicityEnforced` throws `AtomicityNotEnforcedError` for any value that isn't strictly `=== true`. Catches `as any` casts. | [`trustgate/sdk/src/atomicity.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/atomicity.ts) lines 51-58 + 6-case test suite |
|
|
29
|
+
| A.3 — composer structure | `composeAtomicSettleTx` returns ONE `Transaction` with EXACTLY 3 instructions in canonical order — gate (PolicyVault) + `transferChecked` (Token / Token-2022) + `emit_feedback` (TrustGate). | [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) `describe("composeAtomicSettleTx")` (5 cases including Token-2022 program-id propagation) |
|
|
30
|
+
| B — single-tx atomic revert | When the bundled tx contains a failing inner ix, Solana reverts the entire bundle; `gate_payment_strict`'s state mutation rolls back. PolicyAccount + VelocityLedger stay clean. | [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) `describe("B. single-tx atomic revert")` |
|
|
31
|
+
| C — split-tx corruption (anti-pattern) | If a caller splits the bundle into two txs, gate_payment_strict commits in tx1, the failing transfer reverts in tx2, and VelocityLedger is left dirty (counted a payment that never moved). This is the corruption vector A + B prevent. | [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) `describe("C. split-tx anti-pattern corrupts state")` |
|
|
32
|
+
| On-chain anchor | `gate_payment_strict_correctness` (Kani #6, 258 sub-checks) — strict handler returns `Ok(())` if and only if the lazy composer returns `Allow`. | [`programs/policy-vault/src/proofs/inv_gate_payment_strict_correctness.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/policy-vault/src/proofs/inv_gate_payment_strict_correctness.rs) |
|
|
33
|
+
|
|
34
|
+
## A — SDK type + runtime guards
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
export interface AtomicityEnforced {
|
|
38
|
+
readonly atomicityEnforced: true; // literal true, not boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class AtomicityNotEnforcedError extends Error {
|
|
42
|
+
constructor(siteName: string) {
|
|
43
|
+
super(`[${siteName}] atomicityEnforced=true is required …`);
|
|
44
|
+
this.name = "AtomicityNotEnforcedError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function assertAtomicityEnforced(
|
|
49
|
+
cfg: { atomicityEnforced?: unknown },
|
|
50
|
+
siteName: string,
|
|
51
|
+
): void {
|
|
52
|
+
if ((cfg as any).atomicityEnforced !== true) {
|
|
53
|
+
throw new AtomicityNotEnforcedError(siteName);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Compile-time: `composeAtomicSettleTx({ …, atomicityEnforced: false })` is a TS error. Runtime: `composeAtomicSettleTx({ …, atomicityEnforced: 1 } as any)` throws `AtomicityNotEnforcedError("composeAtomicSettleTx")`. Both layers required: skipping either re-opens the corruption vector.
|
|
59
|
+
|
|
60
|
+
## A.3 — Composer structure
|
|
61
|
+
|
|
62
|
+
`composeAtomicSettleTx` returns:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
interface ComposedAtomicSettle {
|
|
66
|
+
readonly tx: Transaction;
|
|
67
|
+
readonly instructions: ReadonlyArray<TransactionInstruction>; // exactly 3
|
|
68
|
+
readonly accounts: { … };
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The three instructions, in order:
|
|
73
|
+
|
|
74
|
+
1. `gate_payment_strict` — `Err` on `Deny` / `RequireValidation`. Reverts the whole bundle if the gate denies.
|
|
75
|
+
2. SPL `transferChecked` (legacy Token or Token-2022 — `tokenProgram` arg switches).
|
|
76
|
+
3. `emit_feedback` — PDA-signed CPI into Quantu via TrustGate.
|
|
77
|
+
|
|
78
|
+
Five test cases pin this contract — including a Token-2022 program-id propagation case that ensures the SPL ix uses `TOKEN_2022_PROGRAM_ID` when the caller passes it.
|
|
79
|
+
|
|
80
|
+
## B+C — Localnet runtime proofs
|
|
81
|
+
|
|
82
|
+
The Anchor TS test [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) splits into two `describe` blocks:
|
|
83
|
+
|
|
84
|
+
- **`B. single-tx atomic revert`.** Builds the bundled tx with an intentionally-failing System.transfer (source keypair never signs → guaranteed `MissingRequiredSignature`) and asserts the entire bundle reverts; PolicyAccount + VelocityLedger remain in their pre-tx state.
|
|
85
|
+
- **`C. split-tx anti-pattern corrupts state`.** Splits the same flow into two transactions. Tx 1 commits the gate's velocity update. Tx 2 reverts on the failing transfer. The ledger now reflects a payment that never moved — the corruption vector that B prevents.
|
|
86
|
+
|
|
87
|
+
### Why System.transfer instead of TransferHook
|
|
88
|
+
|
|
89
|
+
The localnet tests deliberately use a System.transfer with a never-signed source as the failing inner instruction. This:
|
|
90
|
+
|
|
91
|
+
1. Exercises the same Solana atomicity semantics a `TransferHook` revert would — the runtime aborts the bundle, all child mutations roll back.
|
|
92
|
+
2. Requires no Token-2022 mint setup or custom hook program on the localnet validator, keeping the test focused on the property under proof rather than mint-init plumbing.
|
|
93
|
+
3. Documents the corruption vector at the layer where it actually matters: any failing inner ix corrupts split-tx state. `TransferHook` is one specific failure mode, not the property itself.
|
|
94
|
+
|
|
95
|
+
The Token-2022 angle is fully covered at the SDK layer in `composeAtomicSettleTx` test cases.
|
|
96
|
+
|
|
97
|
+
## On-chain Kani anchor
|
|
98
|
+
|
|
99
|
+
The strict handler's contract is pinned by Kani `gate_payment_strict_correctness`:
|
|
100
|
+
|
|
101
|
+
- `strict_returns_ok_iff_allow` — `Ok(())` ⇔ `Allow`. 131 sub-checks.
|
|
102
|
+
- `gate_decision_is_one_of_three_disjoint_variants` — three arms pairwise disjoint; a fourth variant cannot silently slip past. 127 sub-checks.
|
|
103
|
+
|
|
104
|
+
Combined: 258 sub-checks, ~0.9 s. Sub-check 0 failed. If a future change re-routes the `Deny` arm to an `Ok` return, both proofs fail loud.
|
|
105
|
+
|
|
106
|
+
The on-chain anchor + the SDK guards together close the corruption vector at three layers: compile, runtime, and on-chain.
|
|
107
|
+
|
|
108
|
+
## Reproduce
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
git clone https://github.com/agenttrust-labs/agenttrust && cd agenttrust
|
|
112
|
+
pnpm install
|
|
113
|
+
|
|
114
|
+
# A — SDK layers
|
|
115
|
+
cd trustgate/sdk
|
|
116
|
+
pnpm test # 6 atomicity-specific cases + 50 others
|
|
117
|
+
|
|
118
|
+
# B + C — localnet
|
|
119
|
+
cd ../..
|
|
120
|
+
anchor build
|
|
121
|
+
anchor test --provider.cluster localnet --validator legacy --skip-build
|
|
122
|
+
|
|
123
|
+
# On-chain anchor (Kani)
|
|
124
|
+
cd programs/policy-vault
|
|
125
|
+
cargo kani --harness strict_returns_ok_iff_allow
|
|
126
|
+
cargo kani --harness gate_decision_is_one_of_three_disjoint_variants
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Read next
|
|
130
|
+
|
|
131
|
+
<Cards>
|
|
132
|
+
<Card title="Kani proofs" href="/verification/kani-proofs">
|
|
133
|
+
Per-harness deep-dive on all 6 invariants.
|
|
134
|
+
</Card>
|
|
135
|
+
<Card title="Composer" href="/programs/policy-vault/composer">
|
|
136
|
+
The pure-Rust orchestration the strict handler wraps.
|
|
137
|
+
</Card>
|
|
138
|
+
<Card title="mountTrustGate" href="/sdk/mount-trustgate">
|
|
139
|
+
Where the SDK consumer plugs into the atomicity guard.
|
|
140
|
+
</Card>
|
|
141
|
+
</Cards>
|