AgentTrust
AgentTrust
Integration guides

Pay.sh adapter

Walk the live Pay.sh + AgentTrust integration end to end — challenge, retry, settle, feedback, with hosted-demo paths and the SERVICE-signed envelope contract.

Pay.sh is the Solana Foundation's first x402 facilitator, launched 2026-05-05 with Google Cloud. AgentTrust ships day-one Pay.sh integration as the canonical FacilitatorAdapter implementation. Source: trustgate/server/src/facilitators/pay-sh/.

What the adapter does

Stagex402 / Pay.sh wire shapeAgentTrust action
Initial requestno payment proofemit 402 Payment Required with a base64 x402 v2 envelope
ChallengepaymentRequirements.extra.agentTrustinclude payer agent, payee agent, payee recipient, policy ID, issuedAt, serviceSignature
RetryPAYMENT-SIGNATURE or X-PAYMENT headerparse proof, rebuild VerifyContext
PolicyVerifyContextrun gate_payment decision
Allowsigned payment proofvalidate transfer fields, emit feedback, forward resource
Denygate decisionreturn 402 with reason code + reason name

Hit the live demo

demo.agenttrust.tech runs the full Pay.sh + AgentTrust pipeline against deployed devnet programs. With no payment proof:

curl -i https://demo.agenttrust.tech/protected

Returns HTTP/2 402 with a SERVICE-signed payment-required envelope. With Pay.sh installed, let the CLI pay and retry:

pay --sandbox curl https://demo.agenttrust.tech/protected

The demo seeds three deterministic counterparties. Drive each branch by passing the matching X-Demo-Payer-Agent header:

# Allow path (tier 3)
PAYER=$(curl -s https://demo.agenttrust.tech/health | jq -r '.counterparties[2].agent')
pay --sandbox curl -H "X-Demo-Payer-Agent: $PAYER" https://demo.agenttrust.tech/protected

# Deny path (tier 0)
PAYER=$(curl -s https://demo.agenttrust.tech/health | jq -r '.counterparties[0].agent')
pay --sandbox curl -H "X-Demo-Payer-Agent: $PAYER" https://demo.agenttrust.tech/protected
Counterparty tierDecision
0402 Deny — CounterpartyTierBelowMin
1402 Deny — CounterpartyTierBelowMin
3200 Allow + X-Payment-Receipt

SERVICE-signed challenge

The SERVICE emits a signed challenge when it returns the Pay.sh 402 response. The signature binds:

  • issuedAt
  • network
  • amount
  • asset
  • payTo
  • payer agent
  • payee agent
  • payee recipient
  • policy ID
  • payment ID hash

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.

Files to read in repo

FilePurpose
facilitators/pay-sh/index.tsThe five-method PaySh class
facilitators/pay-sh/schemas.tsStrict Zod wire schemas
facilitators/pay-sh/sig.tsSERVICE challenge signature helpers
facilitators/pay-sh/proof-validator.tsReplay, self-pay, amount, mint, recipient checks
facilitators/pay-sh/feedback.tsIdempotent feedback emission
examples/pay-sh-demo/src/middleware.tsExpress bridge from Pay.sh retry to AgentTrust

Production wiring

The demo proves the pipeline without requiring devnet RPC in CI. The server package carries the real devnet path: 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.

Production swaps three dependency seams the demo stubs:

import { PaySh } from "./facilitators/pay-sh";

const adapter = new PaySh({
  signingNetwork: "solana-devnet",
  feePayer:       facilitator.publicKey,
  validateOnChainTx,        // makeValidateOnChainTx({ connection, … })
  emitFeedbackCpi,          // makeEmitFeedbackCpi({ connection, programIds, … })
  priorEmissionLookup,      // makePriorEmissionLookup({ connection, programId })
  replayCache,              // ReplayCache (in-memory by default)
  signDecision,             // signs canonical decision bytes with facilitator key
});
DependencyProduction implementation
validateOnChainTxparse the confirmed SPL transfer transaction from RPC
emitFeedbackCpibuild and send trustgate::emit_feedback through Anchor
priorEmissionLookupread the FeedbackEmissionLog PDA by payment hash
signDecisionsign canonical decision bytes with the facilitator key

The factories (makeValidateOnChainTx, makeEmitFeedbackCpi, makePriorEmissionLookup) are SDK exports — see SDK → Exports reference.

Failure cases the adapter must keep

Do not remove these checks when adapting Pay.sh into a production server:

  • reject unknown schema fields at the root
  • reject zero or u64-overflow amounts
  • reject network mismatch
  • reject payTo / payeeRecipient mismatch
  • reject expired challenges
  • reject replayed proof bindings
  • reject transfer authority equal to facilitator fee payer (self-pay defense)

These are part of the adapter, not the route layer. The Pay.sh adapter's proof-validator.ts is the reference implementation.

Live evidence

Real atomic-settlement trace on devnet (2026-05-06):

StepTx
Signed SPL transferChecked5iV8EYmJh9XS…
emit_feedback PDA-signed CPIjMobmWJUAXuL8…
FeedbackEmissionLog PDAHB4BBi9j…

Full trace + reproduction commands: Verification → Devnet smoke.

On this page

⌘I