AgentTrust
AgentTrust
Programs

TrustGate

The facilitator-side Anchor program — PDA-signed feedback CPIs into Quantu and per-payment idempotency receipts.

TrustGate is the program a facilitator owns. One PDA per facilitator (TrustGateAuthority) signs CPIs into Quantu's agent-registry-8004::give_feedback. One PDA per payment (FeedbackEmissionLog) is the on-chain idempotency receipt. Three instructions.

Devnet: HF8zHfoyA7b5mhLViopTnRMprc6ZT5KActHTdkFrih2N

Instructions

InstructionEffect
init_authority(facilitator)Create TrustGateAuthority PDA for facilitator. One-time per facilitator.
emit_feedback(payment_id_hash, facilitator, payee_asset, score, tag1, tag2, endpoint, feedback_uri)PDA-signed CPI into agent-registry-8004::give_feedback. Initialises FeedbackEmissionLog.
dispute_payment(payment_id_hash, facilitator, payee_asset, dispute_reason_hash, feedback_uri)PDA-signed CPI emitting NEGATIVE-score feedback (score = DISPUTE_SCORE, tag1 = dispute). Initialises FeedbackEmissionLog.

Both emit_feedback and dispute_payment enforce signer == facilitator (FacilitatorSignerMismatch). Without that guard, anyone could write fake feedback under any facilitator's authority — a real attack against on-chain reputation.

State accounts

TrustGateAuthority

#[account]
pub struct TrustGateAuthority {
    pub facilitator: Pubkey,  // off  8..40 — external facilitator wallet
    pub bump: u8,             // off 40
    pub _pad0: [u8; 7],       // off 41..48
    pub feedback_count: u64,  // off 48..56 — total successful give_feedback CPIs
    pub dispute_count: u64,   // off 56..64 — total dispute_payment CPIs
    pub created_at_slot: u64, // off 64..72
    pub _reserved: [u8; 32],  // off 72..104
}

PDA seeds: ["trustgate_auth", facilitator]. Different facilitators (Pay.sh, Dexter, atxp, MCPay) each get their own PDA, so feedback emission is namespaced per-facilitator on-chain. The feedback_count and dispute_count accumulate as that facilitator emits.

FeedbackEmissionLog

#[account]
pub struct FeedbackEmissionLog {
    pub payment_id_hash: [u8; 32], // off  8..40 — caller-supplied payment ID hash
    pub bump: u8,                  // off 40
    pub score: u8,                 // off 41 — score that was emitted
    pub is_dispute: u8,            // off 42 — 1 = dispute path, 0 = emit_feedback path
    pub _pad0: [u8; 5],            // off 43..48
    pub emitted_at_slot: u64,      // off 48..56
    pub _reserved: [u8; 16],       // off 56..72
}

PDA seeds: ["feedback_log", payment_id_hash]. The init-only constraint is the idempotency mechanism: a second emit_feedback (or dispute_payment) for the same payment_id_hash fails with account-already-in-use and the entire transaction reverts. There is no recovery path — that is exactly the desired property: a successful CPI mints exactly one feedback per payment.

The caller computes payment_id_hash = SHA-256(payment_id_string) off-chain. The SDK does this in composeAtomicSettleTx; facilitators that build their own settlement path replicate the same hash construction.

PDA-signed CPI to Quantu

emit_feedback PDA-signs the CPI into Quantu's agent-registry-8004::give_feedback:

let signer_seeds: &[&[u8]] = &[
    TrustGateAuthority::SEED_PREFIX,    // b"trustgate_auth"
    facilitator.as_ref(),
    &[bump],
];
invoke_give_feedback(&cpi_accounts, &args, signer_seeds)?;

Quantu's give_feedback instruction expects the caller to be the agent's owner — TrustGate's PDA is registered as the feedback authority via Quantu's identity flow on first use. The client account in the CPI is set to authority.to_account_info(), so Quantu sees a PDA-signed call rather than the facilitator's wallet directly.

remaining_accounts ordering for the CPI:

IndexAccountNotes
0agent_accountpayee's Quantu AgentAccount PDA
1assetpayee's Metaplex Core asset
2collectionQuantu's collection asset
3system_programfor rent on AtomStats init
4atom_config (optional)Quantu's AtomConfig PDA
5atom_stats (optional)payee's AtomStats PDA — created on first feedback
6atom_engine_program (optional)Quantu's atom-engine program
7registry_authority (optional)Quantu's registry authority PDA

Indexes 0..=3 are required (AgentRegistryProgramMismatch on count mismatch). Indexes 4..=7 come as a group of 4 — pass all four or none. The SDK's composeAtomicSettleTx builds this account list correctly; integrators going lower-level should consult programs/trustgate/src/ext/agent_registry.rs.

give_feedback args

pub struct GiveFeedbackArgs {
    pub value: u64,
    pub value_decimals: u8,
    pub score: Option<u8>,                      // 0..=100
    pub feedback_file_hash: Option<[u8; 32]>,   // dispute_reason_hash for disputes
    pub tag1: String,                           // ≤ 32 bytes
    pub tag2: String,                           // ≤ 32 bytes
    pub endpoint: String,                       // ≤ 64 bytes
    pub feedback_uri: String,                   // ≤ 256 bytes
}

emit_feedback validates score ≤ 100, tag1.len() ≤ 32, tag2.len() ≤ 32, endpoint.len() ≤ 64, feedback_uri.len() ≤ 256. Discriminator: [145, 136, 123, 3, 215, 165, 98, 41] (Reference → Discriminator constants).

Disputes

dispute_payment is the negative-feedback path. Per docs/plan/research/04-policyvault-build-playbook.md §C.4, revocations are events-only; new negative feedback is the canonical "downstream consumers see and react to" signal. The handler:

  • Requires dispute_reason_hash != [0; 32] (DisputeReasonRequired).
  • Hard-codes score = DISPUTE_SCORE, tag1 = "dispute".
  • Sets tag2 to the first 16 hex chars of the dispute reason hash for off-chain consumers.
  • Sets is_dispute = 1 on the FeedbackEmissionLog.

Same idempotency pattern: the init constraint blocks double-disputes per payment_id_hash.

Events

EventFields
AuthorityInitializedfacilitator, authority, slot
FeedbackEmittedfacilitator, payee_asset, payment_id_hash, score, slot
PaymentDisputedfacilitator, payee_asset, payment_id_hash, dispute_reason_hash, slot

Live trace

Real emit_feedback CPI on devnet, captured 2026-05-06: jMobmWJUAXuL8FmQujfxW9NmeMbzADUoABzqjiMeuc5m3YXyeuZeUw1ZJc29JGsqyWQGDY8q3vrtBdamhKXraag — the resulting FeedbackEmissionLog PDA at HB4BBi9jaD3VPcZkQQaH3DxukSqBiXfW8RejtaLa8bF3 has score=100, is_dispute=0. Full trace: Verification → Devnet smoke.

Source

On this page

⌘I