Skip to content

SigvaraReputation Contract

RunTimeAdmin edited this page Nov 2, 2026 · 14 revisions

SigvaraReputation Contract

**Referenced Files in This Document** - [SigvaraReputation.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReputation.sol) - [reputation-model.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/reputation-model.md) - [architecture.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/architecture.md) - [payment-backed-attestations.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/payment-backed-attestations.md) - [scoring.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/scoring.js) - [merkle.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/merkle.js) - [index.js](https://github.com/RunTimeAdmin/Sigvara/blob/main/oracle/index.js) - [SigvaraReputation.t.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/test/SigvaraReputation.t.sol) - [SigvaraReader.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/SigvaraReader.sol) - [InflowGuard.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/InflowGuard.sol) - [TimelockedGrants.sol](https://github.com/RunTimeAdmin/Sigvara/blob/main/src/TimelockedGrants.sol) - [0004-operator-custody.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/adr/0004-operator-custody.md) - [0005-governance-and-emergency-powers.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/adr/0005-governance-and-emergency-powers.md)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Security Analysis
  10. Conclusion

Introduction

This document explains the SigvaraReputation contract, which stores and serves a deterministic six-factor reputation score for registered agents. The oracle computes scores off-chain; the contract validates proposals, enforces governance and slashing integration, exposes evidence verification through Merkle proofs, and applies a maturity mechanism that gradually reveals spendable reputation over time. Consumers read matured scores to gate trust decisions.

The documentation covers:

  • The ReputationData struct and its six factor scores.
  • The mathematical relationships between factors and their caps.
  • The proposal, challenge window, and finalization workflow.
  • Evidence verification using Merkle proofs.
  • Maturity mechanics and operator transfer effects.
  • Anti-sybil design choices.
  • Integration with external trust sources and community flags.
  • Performance considerations and storage layout.
  • Security analysis and attack vectors.

Project Structure

The relevant codebase is organized into:

  • Solidity contracts under src/.
  • Documentation under docs/.
  • Oracle implementation under oracle/.
  • Tests under test/.
graph TB
SRC["src/"]
DOCS["docs/"]
ORACLE["oracle/"]
TESTS["test/"]
SRC --> REP["SigvaraReputation.sol"]
SRC --> ID["SigvaraIdentity.sol"]
DOCS --> RM["reputation-model.md"]
DOCS --> ARCH["architecture.md"]
DOCS --> PAY["payment-backed-attestations.md"]
ORACLE --> SC["scoring.js"]
ORACLE --> MK["merkle.js"]
ORACLE --> IDX["index.js"]
TESTS --> TST["SigvaraReputation.t.sol"]
REP --> ID
RM --> REP
ARCH --> REP
PAY --> REP
SC --> REP
MK --> REP
IDX --> REP
TST --> REP
Loading

Diagram sources

Section sources

Core Components

The core on-chain component is SigvaraReputation, an upgradeable contract implementing:

  • Role-based access control for oracles, staking core, committee, and upgrader.
  • Score storage and pending proposal management.
  • Challenge window enforcement and committee rejection.
  • Maturity calculation and threshold checks.
  • Evidence root commitment and Merkle proof verification.
  • Integration with identity registry and optional bonded operator set.
  • Proposer memory: each pending proposal records who made it, and its review window is fixed when it is proposed (finalizableAt).
  • A guardian pause (InflowGuard) that closes proposeReputation and proposeIfEmpty, and nothing else, for three days at a time.

These proposer, window and pause behaviours and the bounds described under Troubleshooting are live on the Arc testnet proxy since 26 October 2026. Role administration by UPGRADER_ROLE (it grants STAKING_CORE_ROLE and SLASHING_COMMITTEE_ROLE, and setOperatorBond needs it; ORACLE_ROLE stays with the admin) is how a fresh deployment is set up. The testnet proxy was initialised by the September code, so its admin still grants those roles.

Key responsibilities:

  • Accept oracle proposals only from authorized callers.
  • Validate each factor against its maximum cap.
  • Prevent scoring unregistered, slashed, or unbonded agents.
  • Provide permissionless finalization after the challenge window, refusing a proposal whose proposer has since lost its authority.
  • Serve matured scores for consumers.
  • Verify evidence leaves against stored roots.

Section sources

Architecture Overview

The reputation system separates computation and storage:

  • The oracle network computes scores and submits them to the contract.
  • The contract stores finalized scores and pending proposals.
  • Consumers query matured scores to enforce thresholds.
  • Slashing via staking zeroes scores.
  • Evidence roots enable third-party verification of payments and outcomes.
sequenceDiagram
participant CA as "CounterAudit"
participant WD as "Watchdog"
participant EREP as "ERC-8004 Registry"
participant ORA as "Oracle"
participant REP as "SigvaraReputation"
participant STK as "SigvaraStaking"
participant CONS as "Consumers"
CA-->>ORA : "Payer-signed outcomes (delivered)"
WD-->>ORA : "Flags"
EREP-->>ORA : "Feedback"
ORA->>ORA : "Compute 6-factor score"
ORA->>REP : "proposeReputation(didHash, data, evidenceRoot)"
Note over REP : "Challenge window opens"
STK->>REP : "zeroReputation(didHash) if slashed"
CONS->>REP : "getTotalScore(didHash)"
CONS->>REP : "meetsThreshold(didHash, threshold)"
CONS->>REP : "verifyEvidence(didHash, leaf, proof)"
Loading

Diagram sources

Detailed Component Analysis

Reputation Data and Factor Caps

The ReputationData struct contains six capped factor scores plus a timestamp:

  • feeScore: max 20
  • successScore: max 15
  • ageScore: max 30
  • externalScore: max 25
  • communityScore: max 5
  • propagationScore: max 5
  • lastUpdated: block timestamp of last finalized write (a slash also stamps it when it zeroes the score)

These caps sum to 100 and are enforced at proposal time. The total score is the sum of all factors.

classDiagram
class ReputationData {
+uint8 feeScore
+uint8 successScore
+uint8 ageScore
+uint8 externalScore
+uint8 communityScore
+uint8 propagationScore
+uint256 lastUpdated
}
class PendingScore {
+ReputationData data
+uint256 proposedAt
+bool exists
+bytes32 evidenceRoot
+address proposer
+uint256 finalizableAt
}
class SigvaraReputation {
+MAX_FEE_SCORE() uint8
+MAX_SUCCESS_SCORE() uint8
+MAX_AGE_SCORE() uint8
+MAX_EXTERNAL_SCORE() uint8
+MAX_COMMUNITY_SCORE() uint8
+MAX_PROPAGATION_SCORE() uint8
+proposeReputation(didHash, data, evidenceRoot) void
+finalizeReputation(didHash) void
+rejectReputation(didHash) void
+zeroReputation(didHash) void
+getEarnedScore(didHash) uint8
+getTotalScore(didHash) uint8
+verifyEvidence(didHash, leaf, proof) bool
}
SigvaraReputation --> ReputationData : "stores"
SigvaraReputation --> PendingScore : "manages"
Loading

Diagram sources

Section sources

Mathematical Relationships Between Factors

Factors combine additively to produce a total score out of 100. Each factor has a distinct source and formula:

Factor Max Points Source Formula Summary
Fee Activity 20 Settled payments verified on chain Volume decayed by half-life, capped per payer
Success Rate 15 Attestation outcomes Floor(successful / (total + 5) × 15)
Tenure 30 Span of verified paid activity min(30, floor(log₂(spanDays+1) × 3)) × recency
External Trust 25 ERC-8004 feedback Normalized mean of recognized tags scaled to 25
Community 5 Unresolved flags max(0, 5 − flags × 2)
Propagation 5 Counterparty standing 1 point per trusted counterparty, pro-rated by external score

The oracle implements these formulas in JavaScript, while the contract enforces caps and sums.

flowchart TD
Start(["Compute Score"]) --> Fee["Fee Activity<br/>decayed volume"]
Start --> Success["Success Rate<br/>successful/(total+5)*15"]
Start --> Tenure["Tenure<br/>log2(span+1)*3*recency"]
Start --> External["External Trust<br/>ERC-8004 normalized"]
Start --> Community["Community<br/>max(0,5-flags*2)"]
Start --> Propagation["Propagation<br/>trusted counterparties"]
Fee --> Sum["Sum Factors"]
Success --> Sum
Tenure --> Sum
External --> Sum
Community --> Sum
Propagation --> Sum
Sum --> Cap{"Total ≤ 100?"}
Cap --> |Yes| Return["Return Total"]
Cap --> |No| Revert["Revert OutOfRange"]
Loading

Diagram sources

Section sources

Score Proposal Workflow

The workflow proceeds as follows:

  1. Oracle calls proposeReputation (a backup operator calls proposeIfEmpty) with a ReputationData object and an evidence root. Both revert while a guardian pause is running.
  2. The contract validates the agent’s registration, status, and bond requirement. When an operator set is configured, the caller must also be an active operator in it, or the signer of one.
  3. Each factor is checked against its maximum cap.
  4. A pending proposal is created with the current timestamp, the evidence root, the proposer’s address, and finalizableAt: the proposal time plus the challenge window in force at that moment.
  5. During the challenge window, the committee may reject the proposal. A later change to challengeWindow does not move the end of a proposal that is already pending.
  6. After finalizableAt, anyone can finalize the proposal, as long as its proposer still holds ORACLE_ROLE and, when an operator set is configured, is still an active operator in it. Otherwise finalization reverts with ProposerNotAuthorized, and a proposal from an authorized proposer replaces the stranded one.
  7. Finalization writes the score, clears the pending entry, and sets maturity anchors.

proposeIfEmpty is the compare-and-swap for a checking operator. It reverts with ScoreAlreadyPending while a proposal from an authorized proposer is pending, and replaces one whose proposer has lost its authority, since that proposal cannot be finalized while its proposer lacks that authority.

sequenceDiagram
participant ORA as "Oracle"
participant REP as "SigvaraReputation"
participant ID as "SigvaraIdentity"
participant STV as "StakeView"
participant CMT as "Committee"
participant PUB as "Public Caller"
ORA->>REP : "proposeReputation(didHash, data, evidenceRoot)"
REP->>ID : "getIdentity(didHash)"
ID-->>REP : "AgentIdentity"
REP->>STV : "hasMinimumStake(didHash)"
STV-->>REP : "bonded"
REP->>REP : "validate factor caps"
REP->>REP : "create pendingScores[didHash]"
Note over REP : "Challenge window starts"
CMT->>REP : "rejectReputation(didHash)"
REP->>REP : "delete pendingScores[didHash]"
PUB->>REP : "finalizeReputation(didHash)"
REP->>REP : "write reputations[didHash]"
REP->>REP : "set maturity anchors"
Loading

Diagram sources

Section sources

Evidence Verification System

The oracle commits a Merkle root over evidence leaves to the contract. Each leaf represents a payment event encoded deterministically. Verifiers can rebuild leaves from chain data and verify membership using the stored root.

Leaf encoding includes:

  • Transaction hash
  • Payer address
  • Amount
  • Settlement timestamp in seconds
  • Outcome code: failed, succeeded, or not reported
flowchart TD
Events["Payment Events"] --> Encode["Encode Leaf Fields"]
Encode --> Hash["Double keccak256"]
Hash --> Leaves["Leaves Array"]
Leaves --> Tree["Build Merkle Tree"]
Tree --> Root["Merkle Root"]
Root --> Store["Store Root on Chain"]
Leaves --> Proof["Generate Merkle Proof"]
Proof --> Verify["Verify Proof Against Root"]
Loading

Diagram sources

Section sources

Maturity Mechanism

Maturity controls how quickly earned reputation becomes spendable:

  • Earned score is available immediately upon finalization.
  • Spendable score climbs toward earned score at a configured rate per day.
  • Falls apply immediately to protect consumers.
  • Operator transfers restart maturity for the new operator, however mature the score was. A transfer accepted in the same second as a finalize counts too. The Arc testnet proxy has done both since 26 October 2026.
  • Slash events reset both score and maturity anchor.

The diagram shows the order, with the handover check before the comparison with the anchor. The Arc testnet proxy has used it since 26 October 2026; the September code compared earned with the anchor first.

flowchart TD
Start(["Finalize Score"]) --> Earned["Set Earned Score"]
Earned --> Anchor["Set Matured Anchor"]
Anchor --> Read["Read getTotalScore"]
Read --> Handover{"Operator changed at or after the anchor?"}
Handover --> |Yes| Restart["Anchor 0, clock restarts at the change"]
Handover --> |No| Compare{"Earned ≤ Anchor?"}
Restart --> Compare
Compare --> |Yes| ReturnEarned["Return Earned"]
Compare --> |No| Accrue["Accrue Points Per Day"]
Accrue --> Release["Release From Anchor"]
Release --> Cap{"Released ≥ Earned?"}
Cap --> |Yes| ReturnEarned
Cap --> |No| ReturnReleased["Return Released"]
Loading

Diagram sources

Section sources

Examples of Queries and Operations

Examples include:

  • Querying earned and matured scores.
  • Checking whether an agent meets a threshold. meetsThreshold compares the spendable score and nothing else, so a suspended agent passes it and so, at a threshold of zero, does an address that was never registered. SigvaraReader.isTrusted adds the Active status and the bond held now.
  • Verifying evidence leaves against stored roots.
  • Proposing and finalizing scores.
  • Rejecting proposals during the challenge window.
  • Reading how old a score is. Nothing on chain ages a score, so lastUpdated is the only age signal. SigvaraReader reports it as scoreUpdatedAt, and isTrustedFresh adds a limit on that age to isTrusted. The reader is not yet deployed anywhere.

These operations are exercised in tests and documented in the model and architecture files.

Section sources

Dependency Analysis

The contract depends on:

  • OpenZeppelin upgradeable primitives for access control and proxy upgrades.
  • OpenZeppelin MerkleProof for on-chain verification.
  • SigvaraIdentity for agent state and stake view.
  • Optional IOperatorSet for bonded oracle validation. isActiveOperator also answers for the signer an operator has named.
  • TimelockedGrants and InflowGuard, two small shared bases (main): the first lets the admin revoke at once any role the upgrader administers, the second adds the guardian’s pause on proposals.
graph TB
OZAC["OpenZeppelin AccessControlUpgradeable"]
OZUUPS["OpenZeppelin UUPSUpgradeable"]
OZINIT["OpenZeppelin Initializable"]
OZMK["OpenZeppelin MerkleProof"]
ID["SigvaraIdentity"]
OPS["IOperatorSet"]
REP["SigvaraReputation"]
OZAC --> REP
OZUUPS --> REP
OZINIT --> REP
OZMK --> REP
ID --> REP
OPS --> REP
Loading

Diagram sources

Section sources

Performance Considerations

  • Score computation occurs off-chain in the oracle; the contract performs lightweight validation and storage writes.
  • Factor caps prevent overflow and keep totals bounded.
  • Maturity accrual is computed on read, avoiding frequent state updates.
  • Storage layout is pinned to ensure upgrade safety and efficient access.
  • Merkle verification uses OpenZeppelin’s optimized proof checker.

Recommendations:

  • Keep oracle computation deterministic to minimize divergence.
  • Use fixed-point arithmetic where possible to avoid float drift.
  • Monitor evidence tree size to manage proof generation costs.
  • Tune maturity rate to balance farm-and-cash-out resistance with usability.

Troubleshooting Guide

Common issues and resolutions:

  • Proposal reverts due to factor caps: Ensure each factor does not exceed its maximum.
  • Unregistered or slashed agent: Confirm the agent exists and is active in the identity registry.
  • Unbonded agent: Ensure the agent meets minimum stake requirements.
  • Challenge window active: Wait until finalizableAt, the end of the window the proposal was made under (the error carries it), before finalizing.
  • ProposerNotAuthorized on finalize: The proposer no longer holds ORACLE_ROLE, or its operator was removed or unbonded, or its signer was cleared. The proposal cannot be finalized while its proposer lacks that authority, and a proposal from an authorized proposer replaces it.
  • ScoreAlreadyPending from proposeIfEmpty: A proposal from an authorized proposer is pending. The error carries its proposedAt.
  • InflowsPausedUntil: A guardian pause is running on proposals. It ends by itself within three days.
  • ChallengeWindowTooShort, ChallengeWindowTooLong, MaturityRateTooHigh: A value is outside its bounds, in an initializer or a setter. The challenge window must be between 1 hour and 7 days, and the maturity rate above 0 and at most 100 points a day.
  • Evidence verification fails: Check leaf encoding matches the documented format and proof corresponds to the stored root.
  • Maturity not increasing: Verify maturity rate is set and wall-clock time advances.

Section sources

Security Analysis

Anti-sybil measures:

  • Bond requirement prevents bulk registration without collateral.
  • Only bonded agents can be scored.
  • Tenure measures verified paid activity, not idle time.
  • External trust requires standing in an independent registry.
  • Propagation relies on counterparties’ external score, preventing bootstrapping.
  • Maturity prevents immediate spending of newly earned reputation.
  • Slash resets score and maturity anchor.

Potential attack vectors:

  • Wash trading to inflate fee activity and success rate.
  • Collusion among counterparties to inflate propagation.
  • Farm-and-cash-out before maturity completes.
  • Operator transfer to reset maturity unfairly.
  • Float drift causing oracle divergence.

Mitigations:

  • Per-payer caps and decaying volume.
  • Prior in success rate denominator.
  • External-only propagation.
  • Maturity anchoring on matured value.
  • Deterministic epoch timestamps.
  • Committee rejection and evidence verification.
  • The proposer is recorded and asked about again at finalization, so removing an operator, clearing its signer or revoking its role also ends what that key has pending, without the committee rejecting each proposal in time.
  • The review window is fixed when a score is proposed, so changing challengeWindow cannot hurry a pending proposal past the committee or cut the time it has to reject one.

Section sources

Conclusion

The SigvaraReputation contract provides a robust, verifiable reputation layer that combines multiple signals into a capped, additive score. It enforces governance, slashing, and anti-sybil protections while exposing evidence for third-party verification. Maturity ensures trust cannot be instantly spent, reducing farm-and-cash-out risks. The separation of computation and storage allows flexible evolution of scoring logic without costly migrations. Consumers should rely on matured scores and threshold checks when integrating with the system.

Overview

Clone this wiki locally