AgentTrust
AgentTrust
SDK

gatePayment

Read-only policy decision call. Simulates PolicyVault's gate_payment instruction and returns the typed GateDecision union.

gatePayment() is the read-only entry point. It builds an Anchor simulation of gate_payment, parses the return-data channel into the TypeScript union, and never sends a transaction.

Use it when your facilitator already owns its x402 routing and only needs the AgentTrust decision. For atomic settlement (gate + transfer + feedback in one tx), use mountTrustGate or composeAtomicSettleTx.

Source: trustgate/sdk/src/client.ts.

Signature

import { gatePayment } from "@agenttrust-sdk/trustgate/client";

export function gatePayment(req: GatePaymentRequest): Promise<GateDecision>;

GatePaymentRequest

interface GatePaymentRequest {
  rpcUrl:          string;
  caller:          Keypair;     // facilitator keypair (signs simulate-tx)
  payerAgentAsset: PublicKey;   // payer's Metaplex Core asset (Quantu agent identity)
  payeeAgentAsset: PublicKey;   // payee's Metaplex Core asset
  amount:          bigint | number;  // base units (1_000_000n = 1 USDC at 6 decimals)
  mint:            PublicKey;   // USDC devnet: EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
  policyId:        number;      // PolicyAccount.policy_id (u32)
  programIds?:     ProgramIds;  // defaults to DEFAULT_DEVNET_PROGRAM_IDS
}

The caller keypair signs the simulation transaction; the result is never committed, so the keypair is only used to satisfy the simulate-tx fee-payer. Pass any funded devnet keypair.

GateDecision

type GateDecision =
  | { kind: "Allow" }
  | { kind: "Deny";              reasonCode: number; reasonName: string }
  | { kind: "RequireValidation"; capabilityHash: Uint8Array /* 32 bytes */ };
DecisionNext step
AllowProceed to settlement (build / sign / submit the atomic tx).
DenyReturn 402 to the user with reasonName. Surface reasonCode for machine consumers.
RequireValidationRoute the user to the attestation flow for capabilityHash. After the attestor responds, re-call gatePayment — the gate now sees the new ValidationAttestation and flips to Allow.

reasonCode is decoupled from Borsh wire-format ordering. Full table: Reference → DenyReason codes.

Example

import { Keypair, PublicKey } from "@solana/web3.js";
import { gatePayment } from "@agenttrust-sdk/trustgate/client";

const facilitatorKey = Keypair.fromSecretKey(new Uint8Array(JSON.parse(process.env.FACILITATOR_KEY!)));

const decision = await gatePayment({
  rpcUrl:          "https://api.devnet.solana.com",
  caller:          facilitatorKey,
  payerAgentAsset: new PublicKey("5PfaofvEUf3adtJwMho7zzbfvgxwxbvp2V5moqhtLK8y"),
  payeeAgentAsset: new PublicKey("5PfaofvEUf3adtJwMho7zzbfvgxwxbvp2V5moqhtLK8y"),
  amount:          1_000_000n,                                                 // 1 USDC
  mint:            new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"),
  policyId:        1,
});

switch (decision.kind) {
  case "Allow":
    console.log("gate=Allow — proceed to settle");
    break;
  case "Deny":
    console.log(`gate=Deny ${decision.reasonCode} (${decision.reasonName})`);
    break;
  case "RequireValidation":
    const hex = Buffer.from(decision.capabilityHash).toString("hex");
    console.log(`gate=RequireValidation capability=${hex}`);
    break;
}

Implementation note — return-data channel

PolicyVault's gate_payment (lazy variant) returns the decision via Anchor's set_return_data so simulation can decode it. The strict variant — used by composeAtomicSettleTx — instead returns Err on non-Allow, which is what makes the atomic tx revert as a unit.

gatePayment() simulates the lazy variant and parses the return-data via parseGateDecision. The wire format is documented in programs/policy-vault/src/state/decision.rs.

Errors

ErrorCause
AccountNotFoundPolicyAccount PDA missing for (payerAgentAsset, policyId) — call init_policy first.
RPC error: insufficient fundscaller keypair has zero SOL. Fund via solana airdrop 1 <pubkey> --url devnet.
IdlAccountUnsupportedProgram IDL not yet published on the cluster. Run anchor idl init <programId> --provider.cluster <cluster>.

For the atomic-settle path (which can also return AtomicityNotEnforcedError), see composeAtomicSettleTx.

Source

On this page

⌘I