AgentTrust
AgentTrust
SDK

@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/trustgate

Peer 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:

RoutePurpose
POST /verifyRead-only gate_payment simulation. Returns 200/Allow, 402/Deny + reason headers, or 402/RequireValidation + capability hash.
GET /receipt/:paymentIdHashHexFeedbackEmissionLog lookup. Returns { exists: false } until settlement.
POST /settleAtomic settle (gate_payment_strict + transferChecked + emit_feedback).
POST /disputeDispute 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(); // new

One-line migration: search-and-replace .trustgate.trustGate for every programIds.* field access. Full changelog: Reference → Changelog.

On-chain programs (devnet)

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.

anchor idl fetch <programId> --provider.cluster devnet

Test coverage

SuiteCountWhere
Atomicity unit tests6 (literal-type guard + runtime + composer structure)trustgate/sdk/test/atomicity.test.ts
PDA derivation tests24trustgate/sdk/test/chain.test.ts
Quantu helpers12trustgate/sdk/test/quantu.test.ts
ValidationRegistry instruction builders14trustgate/sdk/test/validation-registry.test.ts
All SDK unit tests56 (+ 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-trip

On this page

⌘I