Read-only trust scores for ERC-8004 agents on Base mainnet. This page is the canonical reference for the scoring math; the values below are pulled live from scoring.py so they can never drift from the implementation.
0 to 100. Higher = more positive feedback from more independent clients, recently. Or null when data is too thin (see refusal threshold below).
Each component is computed independently on a 0–100 scale and then combined with the fixed weights below. Sum of weights = 1.00.
| Component | Weight | What it measures |
|---|---|---|
value_avg | 0.50 | Simple arithmetic mean of feedback values, after each value is normalised onto 0–100. |
client_breadth | 0.20 | Log-scaled count of distinct client addresses that have left feedback. |
volume | 0.15 | Log-scaled total count of (non-revoked) feedback entries. |
recency | 0.15 | Weighted mean of feedback values, with older feedback decaying exponentially. |
If the agent has fewer than 3 distinct clients leaving feedback, we return score = null with status = "insufficient_data — fewer than 3 distinct clients have left feedback". This prevents one client farming a high score by self-rating and is the single largest sybil-mitigation lever in v1.
ERC-8004 feedback values are signed decimals with arbitrary scale (e.g. 99.77, 5.0, -3.0). We clamp to the inclusive range [-100, +100] then linearly remap to [0, 100]:
normalised_value = (clamp(value, -100, +100) + 100) / 2
Distinct-client breadth and total feedback volume are mapped via log1p to the 0–100 range. Saturation references:
CLIENT_BREADTH_REF = 25.0 distinct clients → component saturates at 100.VOLUME_REF = 50.0 feedback entries → component saturates at 100.def log_axis(count, ref):
if count <= 0: return 0.0
return min(100, 100 * log1p(count) / log1p(ref))
Each feedback entry is weighted by an exponential decay on chain-block age:
recency_weight(block) = 0.5 ** ((latest_block - block) / 50000)
Half-life is 50,000 blocks (~27.8 hours at Base’s 2-second block time). Half of a year-old feedback is roughly worth 0.5 ** (315.4) ≈ vanishingly small.
score = sum(component[k] * WEIGHTS[k] for k in WEIGHTS)
= value_avg * 0.50
+ client_breadth * 0.20
+ volume * 0.15
+ recency * 0.15
indexedTag1 & tag1/tag2 strings are passed through in raw feedback but do not bias the score).agent_uri on Registered events is surfaced verbatim, never trusted.v2 work-items would tighten the sybil model and add tag-conditional scoring. For now we prefer to refuse cleanly when data is thin (see threshold above).
IdentityRegistry 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432ReputationRegistry 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63/agent-trust response and the free /agent-trust/preview response carry signed_by + signature. Verify with Ethereum personal_sign/ecrecover over json.dumps(payload, sort_keys=True, separators=(",",":")) with the two keys stripped — recover == signed_by ⇒ authentic & untampered. Signer: 0x5e63d01d6A266BC17f577B80199a2a07B15053C7.