reputation
A 0–10,000 integer score per writ, aggregated from on-chain behavior reports. Every report is program-signed. Disputes use staked resolution with a 72-hour challenge window. Age weighting caps at 1.5× after 180 days on-chain.
F8yFcvoXpupNahzJ2wSKDBErqKgmE7ws1gVVtdAq33FCScoring model
The score is not a weighted average of reports. It is a bounded running signal that rewards consistent good behavior and penalizes recent bad behavior asymmetrically. The update function is pure and deterministic:
score_new = clamp(
score_prev + delta,
0, 10_000,
)
where delta =
+ weight(report.kind) × age_bonus(writ) if report.kind == POSITIVE
- weight(report.kind) × severity(report.severity) if report.kind == NEGATIVE
and age_bonus(writ) = min(1.0 + (age_days / 180) × 0.5, 1.5)Report kinds
| Kind | Weight | Typical source |
|---|---|---|
POS_COMPLETED | +3 | successful swap, repaid loan |
POS_LIQUIDITY | +5 | LP sustained > 7 days |
POS_LONGEVITY | +1 | passive on-chain age tick (auto) |
NEG_FAILED | -10 | failed instruction, slipped past budget |
NEG_DISPUTED | -25 | lost a dispute |
NEG_EXPLOIT | -500 | confirmed MEV exploit, wash trading |
programs/reputation/src/constants.rs and are not upgradable in v0.4. They will become parameters governed by a reputation council in v0.5.State
#[account]
#[derive(InitSpace)]
pub struct ReputationAccount {
pub writ: Pubkey, // 32
pub score: u16, // 2
pub total_reports: u32, // 4
pub ring_head: u8, // 1
pub recent: [Report; 64],
pub bump: u8, // 1
}
pub struct Report {
pub reporter: Pubkey, // 32
pub kind: u8, // 1
pub severity: u8, // 1 — 0..=10 multiplier for NEG_*
pub context_hash: [u8; 32], // 32 — tx sig or caller-supplied key
pub timestamp: i64, // 8
pub disputed: bool, // 1
}Disputes
A negative report can be challenged by any wallet (not just the writ owner) within 72 hours of submission. The challenger stakes at least 0.1 SOL. Resolution is by a quorum of reputation council validators in v0.4 (bootstrapped set); in v0.5 it moves to a reputation-weighted DAO vote.
72-hour dispute window. Uncontested reports become canonical after expiry.
Instructions
submit_report
Called by any program. The caller signs with its program authority via PDA. Reports about a writ you are not currently interacting with will still succeed, but can be contested more easily — the context_hash must be derivable from an on-chain event.
pub fn submit_report(
ctx: Context<SubmitReport>,
kind: u8,
severity: u8,
context_hash: [u8; 32],
) -> Result<()>open_dispute
pub fn open_dispute(
ctx: Context<OpenDispute>,
report_index: u8,
stake_lamports: u64, // ≥ 100_000_000
) -> Result<()>resolve_dispute
Callable only by the reputation council PDA. Commits the dispute outcome, reverts the report delta if upheld, slashes or returns stake, and emits a resolution event.
Reading the score
Callers almost never read ReputationAccount directly — writ_gate::verify returns score as part of AgentStatus. If you do need raw access (e.g. to build a leaderboard), the account is public and can be deserialized with any Anchor client.
score >= threshold. Common thresholds: 500 (basic liveness), 2,500 (trusted LP), 5,000 (high-stakes perp), 8,000 (governance voting weight).