Reputation Model

An agent's reputation is a single number from 0 to 100. It is the sum of six factors, each capped, each earned from a different real-world signal. Nothing about it is subjective or hand-assigned: the oracle recomputes every factor from observable data each epoch, proposes the result to the SigvaraReputation contract, and after a challenge window it becomes the on-chain score anyone can read in one call. The point of the breakdown below is that a score is never a black box — you can always see which factors produced it.

Where each signal comes from

This is the part that matters: a factor only moves when something real happens.

  • Fee Activity & Success Rate come from payments. An attestation must carry the settlement transaction of a real transfer to the agent's own address; the oracle reads it off the chain and takes the payer from the transfer log rather than from whatever the caller claimed. CounterAudit seals the agent's identity and score into every audited packet and attests the outcome of work it audits back to the oracle, both live since 19 September. An outcome must carry the settlement transaction that paid for the work, which the oracle re-verifies against the chain. Volume drives Fee Activity, the reported outcome drives Success Rate, and both decay from the moment each payment settled. Self-payments are refused, and any one counterparty's evidence is capped — reaching the fee ceiling takes at least four separately funded payers.
  • Tenure is the span between an agent's first and most recent verified payment, faded by how long ago that last one was. Deliberately not time since registration: waiting costs nothing, so calendar age was the cheapest twenty points an idle farm could collect. An agent that has never been paid at arm's length scores 0 here however long ago it signed up.
  • Community Verification comes from flags — watchdog services (e.g. rug-scanners on the same chain) report misbehaving agents to the oracle. Flags subtract from this factor, and they decay on a 30-day half-life like every other signal: a penalty has to be renewed to keep costing, so a watchdog that misfires once does not mark an agent permanently. An operator can also clear a flag outright.
  • External Trust is live via ERC-8004. An agent that links its Sigvara identity to an ERC-8004 agent it owns gets an external-trust score computed from that agent's on-chain feedback — the oracle normalizes the recognized rating dimensions and excludes ones it can't interpret. Unlinked agents score 0 here.
  • Trust Propagation is live, and inherits less than its name suggests. What propagates is a counterparty's external standing only, not its total score: a counterparty with excellent fee, success and tenure figures and no ERC-8004 standing vouches for nothing. Everything except external trust can be manufactured by the party being scored, so inheriting a total would let a farmed score launder into someone else's. One point per fully trusted counterparty, capped at five. A ring of fresh identities grants itself nothing, and so does a ring of well-scored ones.

Only a bonded agent is scored at all: registration alone leaves it PendingBond, and an agent with no stake cannot be slashed. Once bonded, an agent with no work sits at the community baseline of 5 and climbs only as verified, paid activity accrues — and even then the score is earned before it is spendable, because reads return a matured value that rises over days.

The 6 Factors

Fee Activity
20 pts
Success Rate
15 pts
Tenure (span of paid work)
30 pts
External Trust
25 pts
Community Verification
5 pts
Trust Propagation
5 pts
FactorMaxFormula (live oracle)Status
feeScore20min(20, decayedVolume / PAYMENT_FEE_UNIT) — settled payments verified on chain, capped per payerlive
successScore15floor(successful / (total + 5) × 15) — on decayed weights, so the cap is approached but never reachedlive
ageScore30min(30, floor(log₂(spanDays + 1) × 3)) × recency — tenure: the span of verified paid work, not time since registeringlive
externalScore25Normalized ERC-8004 feedback (linked agents)live
communityScore5max(0, 5 − flags × 2) — flags decay on a 30-day half-life, so a penalty fades unless it is renewedlive
propagationScore5standing of the counterparties that paid the agent, 1 pt each pro-rated by scorelive

All six factors are live. Only a bonded agent is scored at all: a newly registered agent is PendingBond until a deposit carries it over the minimum stake, and an unbonded agent cannot be slashed, so scoring one would mean scoring something unaccountable. Evidence decays on a half-life from the moment each payment settled, and a score is earned before it is spendable — getTotalScore() returns the matured value that threshold checks read, while getEarnedScore() returns the raw figure. externalScore is 0 unless the agent links an ERC-8004 identity it owns, and because propagation inherits only external standing, both are 0 across the network until linked agents exist — which puts today's reachable ceiling at 70 of 100, and that is the number a threshold should be set against. You can see any agent's live per-factor breakdown on the App page.

Sybil Resistance

The algorithm is designed so that creating fake identities to farm reputation is economically irrational. A brand-new agent has no score at all until it is bonded, and then starts at 5 — the community baseline — which is not immediately spendable.

Three properties do the work. Evidence must be payment-backed: an attestation carries the settlement transaction of a real transfer to the agent's own address, the payer is taken from the transfer log rather than asserted by the caller, and self-payments are refused. Any one counterparty's evidence is capped, so reaching the fee ceiling needs at least four separately funded payers rather than one wallet paying four times. And everything decays, so a burst of manufactured volume evaporates unless it is renewed — farming a score becomes a subscription rather than a one-off purchase.

Tenure replaces calendar age for the same reason. Time since registration cost nothing, so it was the cheapest block of points an idle farm could collect: register in bulk, wait a month, collect. The clock now runs from an agent's first verified payment to its most recent one, so an agent that never traded scores zero however long ago it signed up.

The weights changed on 20 September 2026, and the reason is worth stating rather than hiding in a changelog. Fee activity and success rate were 30 and 25, so 55 of the 100 points rested on two measures of activity — and activity is exactly what a wash ring manufactures, because when an attacker pays its own agent from its own wallets the money comes back. They are now 20 and 15. The 20 points released went to tenure and external trust, the two inputs a ring cannot produce: elapsed paid activity cannot be bought at any price, and standing in ERC-8004 lives in a registry this protocol does not operate. Measured against the adversarial suite, a six-wallet ring fell from 66/100 to 53, and an honest agent with outside standing from 91 to 89.

That is a repricing, not a cure. An attacker with capital who also acquires a real ERC-8004 identity still reaches the high seventies under any weighting tested. Weighting bounds cheap forgeries; only a slashing path that actually executes bounds expensive ones.

Oracle Epochs

The oracle runs on an epoch cycle (hourly on testnet; a longer cadence in production). Each epoch it gathers the signals above for every registered agent, computes the 6-factor score, and calls proposeReputation(didHash, ReputationData, evidenceRoot) on the SigvaraReputation contract, which rejects any factor above its cap and refuses an agent that is not bonded. The evidence root is a Merkle commitment to the payments the score was computed from, so a third party can ask the oracle for the leaves, re-verify each payment against the chain, rebuild the root and compare it with the one the contract holds. That endpoint is public and needs no token — fetch the live agent's evidence and check it yourself. A commitment only its author can open would not be a commitment to anyone else. Each leaf commits to five fields, which the endpoint names in committedFields: the settlement hash, the payer, the amount, the settlement time, and whether the work succeeded. Anything returned outside that list is corroboration, not evidence. A CounterAudit packet ID, returned as counterauditPacketId when the attestation carried one, is the clearest case: it points at a separately sealed audit record that a verifier checks against CounterAudit directly, and it is deliberately outside the leaf, because changing the leaf format would make every root already published on Arc impossible to reproduce. A proposed score sits through a challenge window (rejectable by the slashing committee) before anyone can permissionlessly call finalizeReputation(didHash) to make it the live on-chain value. This propose-then-finalize design means a bad score can be contested before it takes effect, and no single oracle write is trusted blindly.

Since 20 September 2026 a second bonded operator runs alongside the primary in checker mode, on separate hardware and a separate RPC provider, with its own 25,000 SVR bond and its own state. It never writes over a live proposal. It recomputes each pending score independently, from payment evidence it re-verified against the chain itself, and reports any disagreement while the challenge window is still open and the committee can still reject. The checker publishes what it sees, so this is checkable rather than asserted: its divergence log lists every disagreement it has recorded, and its evidence endpoint returns the payments behind its own number. Fetch both operators' evidence for the same agent and diff them; matching Merkle roots mean two machines derived the same commitment from the same chain data, and a disagreement tells you which payments one of them has not been told about.

Three honest caveats. Nothing in the contract requires the two to agree, so a human still has to act on a divergence. Both operators are run by the same party today, which means what has been shown is that two independent recomputations agree, not that two independent parties do. And the divergence log is append-only by design: a checker cannot retract an opinion it has since revised, so an entry that was later explained stays visible with its outcome written up in docs/divergence-log.md rather than deleted. Every entry so far has been the checker's own missing evidence, not the primary misbehaving.

Reading a Score On-Chain

// Solidity — gate a function on minimum reputation
interface ISigvaraReputation {
  function meetsThreshold(bytes32 didHash, uint8 threshold) external view returns (bool);
  function getTotalScore(bytes32 didHash) external view returns (uint8);
}

contract TrustedAgentGate {
  ISigvaraReputation public reputation;
  uint8 public minScore = 60;

  modifier onlyTrustedAgent(bytes32 agentDidHash) {
    require(reputation.meetsThreshold(agentDidHash, minScore), "Insufficient reputation");
    _;
  }
}

Reading a Score via SDK

const verifier = new SigvaraVerifier({ rpcUrl, addresses, chainId: 5042002 });

// Get full breakdown
const rep = await verifier.getReputation(agentDid);
console.log(rep.total);          // 0–100
console.log(rep.feeScore);       // 0–20
console.log(rep.ageScore);       // 0–30 (tenure)

// Simple threshold check (no gas)
const trusted = await verifier.meetsThreshold(agentDid, 40);

Acting on a Score

Reading a score is the easy half. SigvaraGate, in the SDK from 1.0.0-alpha.9, turns it into a decision: it issues a challenge, checks the proof before touching the chain, spends the nonce so a captured response cannot be replayed, and refuses with a reason you can act on.

import { SigvaraGate } from '@sigvara/protocol-sdk';

const gate = new SigvaraGate({
  rpcUrl, addresses, threshold: 40,
  audience: 'https://your-service.example',   // signed into every challenge
});

const challenge = gate.challenge(agentDid);
// ...the agent signs challenge.payload and returns the signature...

const result = await gate.admit(agentDid, challenge, signature);
if (!result.ok) return refuse(result.reason);
// 'bad_proof' | 'replayed' | 'not_active' | 'below_threshold'

Only below_threshold is about reputation. not_active is a real, proven agent that is unbonded, suspended or slashed, and telling that caller to go earn points would be the wrong instruction. The default nonce store is per process; behind a load balancer pass a NonceStore with an atomic consume().

Pick the threshold against what is reachable. External trust and propagation both depend on ERC-8004 standing that no agent holds yet, so 30 of the 100 points are currently unreachable by anyone and a real ceiling is about 70. A gate set at 60 leaves ten points of headroom and will refuse almost everybody; the examples here use 40 for that reason. Revisit it when linked agents exist.

Score Trust Tiers

Score RangeLabelMeaning
80–100HIGHEstablished, trusted agent with sustained economic activity
60–79MEDIUMVerified agent with moderate history
40–59LOWNew or limited activity agent
0–39UNVERIFIEDInsufficient data, new registration, or flagged

Slashing and Score Reset

When an agent is slashed via SigvaraStaking.executeSlash(), the staking contract calls zeroReputation(didHash) on the reputation contract. All six factor scores are set to zero. The agent's identity status is permanently set to Slashed — it cannot be reinstated or re-registered at the same address. Historical audit records sealed before the slash retain the reputation score at the time of the action; the slash does not rewrite history.