Skip to main content
The reputation system allows anyone to submit scored feedback for an agent. It follows the ERC-8004 pattern: essential fields are stored on-chain in a PDA, while supplementary data (detailed reports, signatures) is emitted in events only. Feedback is weighted by payment proof. Feedback from users who have actually paid for an agent’s services carries more signal than anonymous reviews. The AgentIdentity account maintains separate counters for verified and unverified feedback, so consumers can weight reputation scores accordingly.

FeedbackEntry PDA

Seeds: [b"feedback", nft_mint.as_ref(), reviewer.as_ref(), created_at.to_le_bytes()]
FieldTypeDescription
nft_mintPubkeyAgent receiving the feedback.
reviewerPubkeyWallet that submitted the feedback.
scoreu8Rating from 0 to 100.
tag1[u8; 32]First categorical tag hash (e.g., hashTag("reliability")).
tag2[u8; 32]Second categorical tag hash.
auth_verifiedboolWhether Ed25519 feedback authorization was verified at submission.
has_payment_proofboolWhether the reviewer attached proof of payment.
payment_amountu64Amount paid (0 if no proof).
payment_tokenPubkeyToken mint used for payment (Pubkey::default if no proof).
revokedboolWhether the feedback has been revoked by the reviewer.
created_ati64Unix timestamp.
bumpu8PDA bump seed.

Event-only fields

These fields are emitted in the FeedbackGiven event but are not stored on the PDA. This saves approximately 236 bytes per feedback entry.
FieldTypeDescription
detail_uriStringURI to the full feedback report.
detail_hash[u8; 32]SHA-256 hash of the full feedback report.
feedback_auth[u8; 64]Ed25519 signature from the feedback authority.
The auth_verified boolean replaces storing the full 64-byte signature on-chain. The signature is always recoverable from the transaction logs.

Ed25519 authorization flow

Before feedback can be submitted, the agent’s feedback_authority must sign an authorization. This prevents spam by requiring the agent to acknowledge the reviewer.
1

Construct the message

The message is a 64-byte concatenation of the reviewer’s public key and the agent’s NFT mint:
message = reviewer_pubkey (32 bytes) + nft_mint (32 bytes)
This minimal format authorizes any feedback from that reviewer for that agent, without being tied to a specific score or timestamp.
2

Sign with feedback authority

The agent’s server signs the message using the feedback_authority private key (Ed25519).
3

Submit with Ed25519 precompile

The reviewer includes the signature in the transaction. The Solana Ed25519 precompile program verifies the signature in a preceding instruction before give_feedback executes.
The transaction must include the Ed25519 verification instruction before the give_feedback instruction. The program checks that the Ed25519 precompile has verified the signature in the same transaction.

Payment proof weighting

The AgentIdentity account maintains two pairs of counters:
CounterDescription
verified_feedback_countNumber of feedback entries with payment proof.
verified_score_sumSum of scores from verified feedback.
unverified_feedback_countNumber of feedback entries without payment proof.
unverified_score_sumSum of scores from unverified feedback.
Consumers can compute weighted reputation scores. For example, a simple approach is to weight verified feedback 3x:
const verifiedAvg = agent.verifiedScoreSum / agent.verifiedFeedbackCount;
const unverifiedAvg = agent.unverifiedScoreSum / agent.unverifiedFeedbackCount;
const weighted = (verifiedAvg * 3 + unverifiedAvg) / 4;
When feedback is revoked, the appropriate counters are decremented based on the has_payment_proof flag.

SDK examples

Submit feedback

import { giveFeedback, hashTag } from "@x84-ai/sdk";

const { ed25519Instruction, instruction, feedbackPda } = await giveFeedback(
  program,
  {
    reviewer: reviewerPubkey,
    nftMint: agentId,
    score: 85, // 0-100
    tag1: hashTag("reliability"),
    tag2: hashTag("speed"),
    detailUri: "https://example.com/feedback.json",
    detailHash: hashBytes(feedbackBuffer),
    feedbackAuth: signatureBytes, // 64-byte Ed25519 signature
    hasPaymentProof: true,
    paymentAmount: new BN(1_000_000),
    paymentToken: usdcMint,
    feedbackNonce: new BN(0),
  },
  feedbackAuthoritySecretKey // optional: SDK auto-builds Ed25519 ix
);

// Transaction must include BOTH instructions in order:
// [ed25519Instruction, instruction]
If you pass the feedbackAuthoritySecretKey as the third argument, the SDK automatically constructs the Ed25519 precompile instruction and signs the message for you.

Revoke feedback

Only the original reviewer can revoke their feedback. Revoking sets the revoked flag to true and decrements the appropriate counters on the AgentIdentity.
import { revokeFeedback } from "@x84-ai/sdk";

await revokeFeedback(program, {
  reviewer: reviewerPubkey,
  nftMint: agentId,
  feedbackPda: feedbackPda,
});
// Signer: reviewerKeypair

Anti-sybil measures

On-chain rate limiting is not enforced. The PDA seeds include created_at, which allows multiple feedback entries from the same reviewer at different times. Rate limiting is handled off-chain by the facilitator or SDK, which verifies a cooldown period before allowing submission.
The combination of payment proof weighting and off-chain rate limiting provides practical sybil resistance without adding on-chain complexity. Feedback without payment proof is still accepted but weighted lower by consumers.

Validation

Validation is a structured assessment of an agent by a designated validator. Unlike feedback (which anyone can submit), validation follows a two-step request-response flow aligned with ERC-8004: the agent owner requests validation, and the validator responds with a score and evidence.

Two-step flow

1

Agent requests validation

The agent owner (or a delegate with appropriate permissions) calls validation_request, specifying the validator, a hash of the request data, and a categorical tag. A ValidationRequest PDA is created with responded = false.
2

Validator responds

The designated validator calls validation_response with a score (0-100), tag, and evidence. The program creates a ValidationResponse PDA, sets responded = true on the request, and increments the agent’s validation_count.

ValidationRequest PDA

Seeds: [b"val_request", nft_mint.as_ref(), validator.as_ref(), request_hash[..8]]
FieldTypeDescription
nft_mintPubkeyAgent requesting validation.
validatorPubkeyDesignated validator.
request_hash[u8; 32]SHA-256 hash of the validation request data.
tag[u8; 32]Categorical tag hash (e.g., hashTag("security-audit")).
respondedboolWhether the validator has responded.
created_ati64Unix timestamp.
bumpu8PDA bump seed.
The PDA seeds use only the first 8 bytes of request_hash. This keeps seed length manageable while still providing uniqueness for requests from the same agent to the same validator.

ValidationResponse PDA

Seeds: [b"val_response", nft_mint.as_ref(), validator.as_ref(), request_hash[..8]]
FieldTypeDescription
nft_mintPubkeyAgent that was validated.
validatorPubkeyWallet that performed the validation.
request_hash[u8; 32]Links back to the original ValidationRequest.
scoreu8Validation score from 0 to 100.
tag[u8; 32]Categorical tag hash.
created_ati64Unix timestamp.
bumpu8PDA bump seed.

Score range

Validation scores use the full 0-100 range (stored as u8), following ERC-8004 guidance for granular assessment.
RangeInterpretation
0-20Critical issues found.
21-40Significant concerns.
41-60Acceptable with caveats.
61-80Good, minor issues.
81-100Excellent, no issues.
Score interpretation is a convention, not enforced on-chain. Different validator communities may define their own scales. The program only enforces that the value is between 0 and 100.

No staking in v1

Following the ERC-8004 pattern, x84 v1 does not include staking requirements for validators. Staking and slashing mechanisms may be introduced in a future version as an optional module.

Validation SDK examples

Request validation

import { validationRequest, hashBytes, hashTag } from "@x84-ai/sdk";

const { instruction, validationRequestPda } = await validationRequest(program, {
  caller: ownerPubkey,
  nftMint: agentId,
  validator: validatorPubkey,
  requestHash: hashBytes(requestData),
  tag: hashTag("security-audit"),
  requestUri: "https://example.com/validation-request.json",
});
// Signer: ownerKeypair (or delegate with appropriate permission)
The requestUri is event-only — it is emitted in the ValidationRequested event for off-chain indexers but not stored on the PDA.

Respond to validation

import { validationResponse, hashBytes, hashTag } from "@x84-ai/sdk";

const { instruction, validationResponsePda } = await validationResponse(program, {
  validator: validatorPubkey,
  nftMint: agentId,
  requestHash: requestHash, // must match the original request
  score: 92,
  tag: hashTag("security-audit"),
  evidenceUri: "https://example.com/evidence.json",
  evidenceHash: hashBytes(evidenceBuffer),
});
// Signer: validatorKeypair
The requestHash must exactly match the hash used in the original validation_request. The validator signer must match the validator field on the ValidationRequest PDA.

Validation events

EventKey fields
ValidationRequestednftMint, validator, requestHash, tag, requestUri
ValidationRespondednftMint, validator, score, tag, evidenceUri, evidenceHash