Repository navigation
SigvaraReputation Contract
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Security Analysis
- Conclusion
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
ReputationDatastruct 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.
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
Diagram sources
- SigvaraReputation.sol:1-76
- reputation-model.md:1-17
- architecture.md:1-42
- payment-backed-attestations.md:230-247
- scoring.js
- merkle.js:78-180
- index.js:1668-1687
- SigvaraReputation.t.sol:1-112
Section sources
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 closesproposeReputationandproposeIfEmpty, 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
- SigvaraReputation.sol:76-197
- SigvaraReputation.sol:279-363
- SigvaraReputation.sol:369-557
- SigvaraReputation.sol:600-631
- SigvaraReputation.sol:696-788
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)"
Diagram sources
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"
Diagram sources
Section sources
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"]
Diagram sources
Section sources
The workflow proceeds as follows:
- Oracle calls
proposeReputation(a backup operator callsproposeIfEmpty) with aReputationDataobject and an evidence root. Both revert while a guardian pause is running. - 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.
- Each factor is checked against its maximum cap.
- 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. - During the challenge window, the committee may reject the proposal. A later change to
challengeWindowdoes not move the end of a proposal that is already pending. - After
finalizableAt, anyone can finalize the proposal, as long as its proposer still holdsORACLE_ROLEand, when an operator set is configured, is still an active operator in it. Otherwise finalization reverts withProposerNotAuthorized, and a proposal from an authorized proposer replaces the stranded one. - 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"
Diagram sources
Section sources
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"]
Diagram sources
- payment-backed-attestations.md:230-247
- merkle.js:78-180
- index.js:1668-1687
- SigvaraReputation.sol:761-769
Section sources
- payment-backed-attestations.md:230-247
- merkle.js:78-180
- index.js:1668-1687
- SigvaraReputation.sol:761-769
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"]
Diagram sources
Section sources
Examples include:
- Querying earned and matured scores.
- Checking whether an agent meets a threshold.
meetsThresholdcompares 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.isTrustedadds 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
lastUpdatedis the only age signal.SigvaraReaderreports it asscoreUpdatedAt, andisTrustedFreshadds a limit on that age toisTrusted. The reader is not yet deployed anywhere.
These operations are exercised in tests and documented in the model and architecture files.
Section sources
- SigvaraReputation.sol:678-788
- SigvaraReputation.t.sol:127-138
- SigvaraReputation.t.sol:449-486
- SigvaraReputation.t.sol:1221-1244
- SigvaraReader.sol
The contract depends on:
- OpenZeppelin upgradeable primitives for access control and proxy upgrades.
- OpenZeppelin MerkleProof for on-chain verification.
-
SigvaraIdentityfor agent state and stake view. - Optional
IOperatorSetfor bonded oracle validation.isActiveOperatoralso answers for the signer an operator has named. -
TimelockedGrantsandInflowGuard, 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
Diagram sources
Section sources
- 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.
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. -
ProposerNotAuthorizedon finalize: The proposer no longer holdsORACLE_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. -
ScoreAlreadyPendingfromproposeIfEmpty: A proposal from an authorized proposer is pending. The error carries itsproposedAt. -
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
- SigvaraReputation.sol:427-448
- SigvaraReputation.sol:600-631
- SigvaraReputation.sol:696-752
- payment-backed-attestations.md:230-247
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
challengeWindowcannot hurry a pending proposal past the committee or cut the time it has to reject one.
Section sources
- reputation-model.md:17-227
- reputation-model.md:237-338
- SigvaraReputation.sol:130-168
- SigvaraReputation.sol:427-557
- SigvaraReputation.sol:696-752
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.
- Agent Registration and Lifecycle
- Contract Overview and Architecture
- Economic Security Model and Incentives
- Evidence Verification and Merkle Proofs
- Key Management and Rotation
- Maturity Mechanism and Score Release
- Proposal Workflow and Challenge System
- Score Storage and Data Model
- SigvaraIdentity Contract
- SigvaraOracleBond Contract
- SigvaraReputation Contract
- SigvaraStaking Contract
- Slashing Lifecycle and Dispute Resolution
- Stake Management Functions
- Status Management and Control
- Token and Fee Contracts
- Unbonding Period and Withdrawal Security
- Anti-Manipulation and Security Measures
- Community Flag Processing
- Evidence Sources and Verification
- External Trust Integration
- Merkle Proof System
- Oracle Score Computation
- Payment-Backed Attestations
- Scoring Model and Formulas
- Agent Creation and Key Management
- Agent Operations
- Agent Registration Workflow
- Challenge-Response Authentication
- Gate Access Control
- Integration Patterns and Examples
- SDK Overview and Setup
- Verifier Functions