@agenttrust-sdk/trustgate
TypeScript surface for AgentTrust on Solana — Express middleware, client helpers, atomicity guard, PDA derivers, and the full ValidationRegistry instruction builder set.
@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.
Source: trustgate/sdk/. License: MIT.
Install
pnpm add @agenttrust-sdk/trustgate
# or: npm install @agenttrust-sdk/trustgate
# or: yarn add @agenttrust-sdk/trustgatePeer dep: express ^4.21 (only required if you mount the middleware). Node >=20.
Three import surfaces
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
import { gatePayment, settle, dispute } from "@agenttrust-sdk/trustgate/client";
import { composeAtomicSettleTx } from "@agenttrust-sdk/trustgate";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.
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.
Quick start — Express
import express from "express";
import { Keypair } from "@solana/web3.js";
import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
const app = express();
app.use(express.json());
await mountTrustGate(app, {
rpcUrl: "https://api.devnet.solana.com",
facilitatorKeypair: Keypair.fromSecretKey(/* facilitator key */),
network: "solana-devnet",
atomicityEnforced: true, // literal `true`
});
app.listen(3000);Mounts four routes:
| Route | Purpose |
|---|---|
POST /verify | Read-only gate_payment simulation. Returns 200/Allow, 402/Deny + reason headers, or 402/RequireValidation + capability hash. |
GET /receipt/:paymentIdHashHex | FeedbackEmissionLog lookup. Returns { exists: false } until settlement. |
POST /settle | Atomic settle (gate_payment_strict + transferChecked + emit_feedback). |
POST /dispute | Dispute path (dispute_payment ix). |
Full route reference: mountTrustGate.
Quick start — client
import { Keypair, PublicKey } from "@solana/web3.js";
import { gatePayment } from "@agenttrust-sdk/trustgate/client";
const decision = await gatePayment({
rpcUrl: "https://api.devnet.solana.com",
caller: facilitatorKeypair,
payerAgentAsset: new PublicKey("…"),
payeeAgentAsset: new PublicKey("…"),
amount: 1_000_000n,
mint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC devnet
policyId: 1,
});
switch (decision.kind) {
case "Allow": /* proceed */ break;
case "Deny": console.log(decision.reasonName); break;
case "RequireValidation": /* route to attestation flow with decision.capabilityHash */ break;
}Full reference: gatePayment.
Atomic-tx invariant
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.
The SDK enforces atomicity at three layers:
import {
AtomicityEnforced,
AtomicityNotEnforcedError,
composeAtomicSettleTx,
} from "@agenttrust-sdk/trustgate";
// 1. Compile-time literal-type guard:
// `AtomicityEnforced` is `{ atomicityEnforced: true }` (literal `true`).
// TS rejects `false`, missing, or `boolean` widening.
//
// 2. Runtime guard:
// `assertAtomicityEnforced` throws `AtomicityNotEnforcedError` for any
// value that isn't strictly `=== true`. Catches `as any` cast bypasses.
//
// 3. Composer structure:
// `composeAtomicSettleTx` returns one `Transaction` with exactly three
// instructions in canonical order (gate, transfer, feedback).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.
Breaking changes (0.2.0)
ProgramIds.trustgate (lowercase) renamed to ProgramIds.trustGate (camelCase, matches policyVault). New validationRegistry field populated by default with the deployed devnet ID Cx4RFa6ysw3qXYhugPkF8pFSWBkmKq59h2dWgF2tKhtv.
// 0.1.x
DEFAULT_DEVNET_PROGRAM_IDS.trustgate.toBase58();
// 0.2.0
DEFAULT_DEVNET_PROGRAM_IDS.trustGate.toBase58();
DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry.toBase58(); // newOne-line migration: search-and-replace .trustgate → .trustGate for every programIds.* field access. Full changelog: Reference → Changelog.
On-chain programs (devnet)
8Y6fGeNEHgmWmbt8JsRcF72jxbeBfJhomMjG6SuoJQTRCx4RFa6ysw3qXYhugPkF8pFSWBkmKq59h2dWgF2tKhtvloadPolicyVault / 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.
anchor idl fetch <programId> --provider.cluster devnetTest coverage
| Suite | Count | Where |
|---|---|---|
| Atomicity unit tests | 6 (literal-type guard + runtime + composer structure) | trustgate/sdk/test/atomicity.test.ts |
| PDA derivation tests | 24 | trustgate/sdk/test/chain.test.ts |
| Quantu helpers | 12 | trustgate/sdk/test/quantu.test.ts |
| ValidationRegistry instruction builders | 14 | trustgate/sdk/test/validation-registry.test.ts |
| All SDK unit tests | 56 (+ 16 INTEGRATION-gated devnet round-trips) | pnpm --filter ./trustgate/sdk run test |
Run via:
cd trustgate/sdk
pnpm test
INTEGRATION=1 pnpm test:integration # devnet round-tripRead next
gatePayment
Read-only policy decision call — type signature, config fields, decision union, errors.
mountTrustGate
Express middleware reference — every route, every header, the atomicity guard contract.
Exports reference
One-screen list of every public export grouped by concern.
Atomic-tx invariant
Three layers of proof + the on-chain Kani strict-correctness binding.