Repository navigation
Score Storage and Data Model
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains the on-chain score storage and data model used by the reputation system. It focuses on how six factor scores are stored, validated, proposed, matured, and verified through evidence roots. The contract is a verified store: scoring computation happens off-chain in the oracle network, while the contract enforces caps, lifecycle state, maturity, and verifiable evidence.
The key concepts are:
-
ReputationData: the six-factor score plus a finalization timestamp. -
PendingScore: the proposal lifecycle wrapper with a timestamp, evidence root, proposer and a finalizable time fixed at proposal. - Storage mappings for finalized reputations, pending proposals, matured anchors, and evidence roots.
- Scoring caps that ensure the total never exceeds 100 points.
- Maturity mechanics that delay spendable score growth while allowing immediate drops.
This page describes the contract on main. The Arc testnet proxy has run it since 26 October 2026. The September implementation it replaced had no proposer or finalizableAt in PendingScore, read the live challengeWindow at finalization, put no bounds on the window, checked the maturity rate only against zero, and returned a score at or below its anchor whole after an operator handover. A proposal made before the upgrade reads zero in the two new fields and is judged against the live window.
- Gas optimization strategies embedded in the data layout and read/write paths.
The score storage and data model live primarily in the reputation contract, with supporting oracle logic computing factors and committing evidence roots. Tests and documentation provide examples and constraints.
graph TB
subgraph "On-chain"
SR["SigvaraReputation.sol"]
SI["SigvaraIdentity.sol"]
end
subgraph "Off-chain Oracle"
SC["scoring.js"]
IDX["index.js"]
CH["chain.js"]
MKT["merkle.test.js"]
EP["epoch-policy.js"]
end
subgraph "Docs & Examples"
RP["reputation.html"]
WP["whitepaper.md"]
DM["demo.js"]
TST["SigvaraReputation.t.sol"]
end
IDX --> CH
CH --> SR
SC --> IDX
MKT --> IDX
EP --> IDX
RP --> SR
WP --> SR
DM --> SC
TST --> SR
SR --> SI
Diagram sources
- SigvaraReputation.sol:12-75
- scoring.js:136-154
- index.js:1077-1088
- chain.js:702-714
- merkle.test.js:17-50
- epoch-policy.js:82-110
- reputation.html:140-150
- whitepaper.md:177-194
- demo.js:110-125
- SigvaraReputation.t.sol:97-111
Section sources
This section documents the two primary data structures and their mathematical relationships.
ReputationData stores the six factor scores and a finalization timestamp:
-
feeScore: up to 20 points, measuring settled fee volume or an attestation-count proxy when payment verification is off. -
successScore: up to 15 points, measuring successful outcomes smoothed by a Bayesian prior. -
ageScore: up to 30 points, measuring tenure based on span of paid activity and recency. -
externalScore: up to 25 points, normalized ERC-8004 cross-protocol feedback. -
communityScore: up to 5 points, derived from flags with decay. -
propagationScore: up to 5 points, inherited trust from counterparties that are themselves scored agents. -
lastUpdated: block timestamp of the last finalized write (a slash also stamps it when it zeroes the score).
Mathematical relationship:
- Total earned score = sum of all six factors.
- Each factor is capped independently; the sum is constrained to at most 100.
Storage pattern:
- Finalized scores are stored per agent identifier (
didHash) in a mapping keyed bybytes32. - The struct includes a finalization timestamp. Maturity is anchored separately, in
maturedScoreandmaturedAt. - Nothing on chain ages a score, so
lastUpdatedis the only age signal.SigvaraReaderreports it asscoreUpdatedAt, and itsisTrustedFreshadds a limit on that age toisTrusted.
Section sources
- SigvaraReputation.sol:130-149
- SigvaraReputation.sol:771-784
- scoring.js:136-154
- reputation.html:140-150
- whitepaper.md:177-194
- demo.js:110-125
PendingScore wraps a proposed ReputationData with lifecycle metadata:
-
data: nestedReputationDatacontaining the six factors. -
proposedAt: timestamp when the proposal was created. -
exists: boolean flag indicating whether a proposal slot is occupied. -
evidenceRoot: Merkle root committing to the set of payments used to compute the score. -
proposer: the address that made the proposal. A proposal that was already pending when the field was added readsaddress(0)and is treated as live. -
finalizableAt: when the proposal can first be finalized and the last moment it can still be rejected, fixed at proposal time asproposedAtplus the challenge window in force then. A proposal that was already pending when the field was added reads zero and is judged against the live window.
Lifecycle behavior:
- A proposal opens a challenge window starting at
proposedAtand ending atfinalizableAt. ChangingchallengeWindowlater moves no proposal already pending. - If unchallenged, anyone can finalize after the window elapses, provided its proposer still holds
ORACLE_ROLEand, when an operator set is configured, is still an active operator in it. Otherwise finalization reverts withProposerNotAuthorized. - A fresh
proposeReputationreplaces any still-pending proposal and restarts the window.proposeIfEmptyreplaces one only when its proposer has lost its authority. - Evidence root is held with the proposal and only becomes live upon finalization.
Storage pattern:
- Pending proposals are stored per
didHashin a mapping keyed bybytes32. - The struct is appended rather than inserted into existing slots to preserve upgrade compatibility.
Section sources
- SigvaraReputation.sol:151-168
- SigvaraReputation.sol:427-448
- SigvaraReputation.sol:454-504
- chain.js:702-714
The reputation system uses an optimistic scoring model:
- Off-chain oracle computes six factors and proposes them on-chain.
- On-chain contract validates caps, identity, stake, and operator bonding.
- A challenge window allows committee rejection before finalization.
- Finalization writes the earned score, evidence root, and maturity anchor.
- Consumers read the matured score, which climbs toward the earned score over time.
sequenceDiagram
participant Oracle as "Oracle (index.js)"
participant Chain as "Chain (chain.js)"
participant Contract as "SigvaraReputation.sol"
participant Identity as "SigvaraIdentity.sol"
participant Consumer as "Consumer"
Oracle->>Oracle : Compute six factors (scoring.js)
Oracle->>Chain : Build evidence root (merkle)
Chain->>Contract : proposeReputation(didHash, data, evidenceRoot)
Contract->>Identity : Validate agent registered, not slashed, bonded
Contract->>Contract : Validate each factor cap
Contract->>Contract : Store PendingScore(proposedAt, exists, evidenceRoot, proposer, finalizableAt)
Note over Contract : Challenge window starts
Consumer->>Contract : finalizeReputation(didHash) after window
Contract->>Contract : Write ReputationData + evidenceRoot
Contract->>Contract : Set maturedScore anchor + maturedAt
Contract-->>Consumer : ReputationUpdated event
Consumer->>Contract : getTotalScore(didHash)
Contract-->>Consumer : Matured score (climbs toward earned)
Diagram sources
- scoring.js:136-154
- index.js:1077-1088
- chain.js:702-714
- SigvaraReputation.sol:378-448
- SigvaraReputation.sol:454-504
- SigvaraReputation.sol:696-752
The contract maintains four primary storage areas:
-
reputations: mapping fromdidHashto finalizedReputationData. -
pendingScores: mapping fromdidHashtoPendingScore. -
maturedScoreandmaturedAt: mappings fromdidHashto matured anchor values and timestamps. -
evidenceRoots: mapping fromdidHashto Merkle root of committed evidence.
Additional configuration:
-
challengeWindow: seconds a proposal remains open for challenges. The contract holds it between 1 hour and 7 days, in the initializers as well as the setter. -
identityRegistry: pointer to the identity registry for validation. -
maturityRatePerDay: points per day that matured score climbs toward earned score. Above 0 and at most 100. -
operatorBond: optional bonded operator registry for oracle checks. On main,setOperatorBondrequiresUPGRADER_ROLE.
The guardian’s pause state (InflowGuard) is not in the contract’s own layout. It sits in an ERC-7201 namespaced slot, so adding it to a live proxy shifts no existing variable.
classDiagram
class SigvaraReputation {
+mapping bytes32 => ReputationData reputations
+mapping bytes32 => PendingScore pendingScores
+uint256 challengeWindow
+SigvaraIdentity identityRegistry
+uint256 maturityRatePerDay
+mapping bytes32 => uint8 maturedScore
+mapping bytes32 => uint256 maturedAt
+mapping bytes32 => bytes32 evidenceRoots
+IOperatorSet operatorBond
}
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
}
SigvaraReputation --> ReputationData : "stores finalized"
SigvaraReputation --> PendingScore : "stores proposals"
SigvaraReputation --> SigvaraIdentity : "validates agent"
Diagram sources
Section sources
The six factors have independent maximums that sum to 100:
MAX_FEE_SCORE = 20MAX_SUCCESS_SCORE = 15MAX_AGE_SCORE = 30MAX_EXTERNAL_SCORE = 25MAX_COMMUNITY_SCORE = 5MAX_PROPAGATION_SCORE = 5
Constraints enforced:
- Each factor is validated against its cap during proposal.
- Total earned score is asserted to be at most 100.
- Fuzz tests verify that total never exceeds 100 across valid inputs.
- The oracle keeps the same six caps in one place,
oracle/score-caps.js, and a test reads them fromSigvaraReputation.sol, so a reweight in the contract that is not copied across fails the oracle suite.
flowchart TD
Start([Proposal Input]) --> CheckFee["Check feeScore <= 20"]
CheckFee --> CheckSuccess["Check successScore <= 15"]
CheckSuccess --> CheckAge["Check ageScore <= 30"]
CheckAge --> CheckExternal["Check externalScore <= 25"]
CheckExternal --> CheckCommunity["Check communityScore <= 5"]
CheckCommunity --> CheckPropagation["Check propagationScore <= 5"]
CheckPropagation --> SumTotal["Sum all six factors"]
SumTotal --> AssertTotal{"Total <= 100?"}
AssertTotal --> |Yes| Accept["Accept Proposal"]
AssertTotal --> |No| Reject["Reject with ScoreOutOfRange"]
Diagram sources
- SigvaraReputation.sol:130-135
- SigvaraReputation.sol:431-436
- SigvaraReputation.sol:771-784
- SigvaraReputation.t.sol:449-483
Section sources
- SigvaraReputation.sol:130-135
- SigvaraReputation.sol:431-436
- SigvaraReputation.sol:771-784
- SigvaraReputation.t.sol:449-483
Evidence roots commit to the set of payments used to compute the score:
- The oracle builds a Merkle tree from payment events.
- The root is included with the proposal and stored in
evidenceRootsupon finalization. - Verifiers can prove individual payments belong to the committed set using Merkle proofs.
Key behaviors:
- Empty evidence sets produce a zero hash root.
- Any change in payment fields changes the root.
- Omission detection is possible because dropping a payment changes the root.
- Block numbers are intentionally excluded from leaf hashing to maintain reproducibility.
sequenceDiagram
participant Oracle as "Oracle"
participant Merkle as "Merkle Tree"
participant Contract as "SigvaraReputation.sol"
participant Verifier as "Verifier"
Oracle->>Merkle : Build tree from payment events
Merkle-->>Oracle : Root hash
Oracle->>Contract : proposeReputation(..., evidenceRoot)
Contract->>Contract : Store evidenceRoot in pending proposal
Contract->>Contract : On finalize, move evidenceRoot to evidenceRoots[didHash]
Verifier->>Contract : verifyEvidence(didHash, leaf, proof)
Contract-->>Verifier : True if leaf in committed set
Diagram sources
- index.js:1077-1088
- merkle.test.js:17-50
- merkle.test.js:188-270
- SigvaraReputation.sol:199-220
- SigvaraReputation.sol:761-769
Section sources
- index.js:1077-1088
- merkle.test.js:17-50
- merkle.test.js:188-270
- SigvaraReputation.sol:199-220
- SigvaraReputation.sol:761-769
Matured score represents the spendable portion of the earned score:
- Starts at an anchor value set at finalization.
- Climbs toward the earned score at
maturityRatePerDay. - Falls are immediate; rises are delayed to prevent farm-and-cash-out patterns.
- Operator handover resets maturity clock to prevent buying scored identities, however mature the score was.
Read path:
-
getTotalScorereturns matured score. - If the operator changed at or after the anchor (
operatorChangedAtis non-zero and not earlier thanmaturedAt), the anchor restarts at 0 from the change time. This comes first, so a score that had fully matured, or had fallen below its anchor, is not returned whole to the buyer. - If earned score ≤ anchor, return earned score.
- Otherwise, calculate released points based on elapsed time and rate.
- Cap matured score at earned score.
flowchart TD
Start([Get Total Score]) --> GetEarned["Compute earned score"]
GetEarned --> CheckSince["Read anchor and its timestamp"]
CheckSince --> Handover{"Operator changed at or after the anchor?"}
Handover --> |Yes| ResetAnchor["Reset anchor to 0, use changedAt"]
Handover --> |No| UseAnchor["Use existing anchor and maturedAt"]
ResetAnchor --> CompareAnchor{"earned <= anchor?"}
UseAnchor --> CompareAnchor
CompareAnchor --> |Yes| ReturnEarned["Return earned score"]
CompareAnchor --> |No| CalcReleased["Calculate released = anchor + ((now - since) * rate / 1 day)"]
CalcReleased --> CapReleased{"released >= earned?"}
CapReleased --> |Yes| ReturnEarned
CapReleased --> |No| ReturnReleased["Return released"]
Diagram sources
Section sources
Example scenarios demonstrate how factors combine:
-
Maximum score: All six factors at their maximums sum to exactly 100.
- Fee: 20, Success: 15, Age: 30, External: 25, Community: 5, Propagation: 5.
- Total: 100.
-
Realistic high score: With external feedback enabled, a realistic ceiling is around 89 due to success score approaching but not reaching 15.
- Fee: 20, Success: 14, Age: 25, External: 25, Community: 5, Propagation: 0.
- Total: 89.
-
New agent with no activity: Only community score applies initially.
- Community: 5, others: 0.
- Total: 5.
-
Slashed-like scenario: High flags reduce community score to 0.
- Community: 0, other factors depend on activity.
These examples are validated by tests and oracle scoring logic.
Section sources
The reputation system has clear dependency boundaries:
-
Contract dependencies:
-
SigvaraIdentity: validates agent registration, status, and stake requirements. -
IOperatorSet: optional bonded operator registry for oracle authorization. - OpenZeppelin libraries for access control, upgradeability, and Merkle proofs.
-
TimelockedGrantsandInflowGuard, shared bases on main: admin revocation of upgrader-administered roles, and the guardian’s pause on proposals.
-
-
Oracle dependencies:
-
scoring.js: computes six factors with clamping to contract caps. -
index.js: orchestrates proposal flow and evidence commitment. -
chain.js: ABI bindings for contract interactions. -
merkle.test.js: validates evidence root construction and verification. -
epoch-policy.js: divergence detection between oracle computations.
-
graph LR
SR["SigvaraReputation.sol"] --> SI["SigvaraIdentity.sol"]
SR --> IOps["IOperatorSet"]
SR --> OZ["OpenZeppelin Libraries"]
IDX["index.js"] --> SC["scoring.js"]
IDX --> CH["chain.js"]
IDX --> MKT["merkle.test.js"]
IDX --> EP["epoch-policy.js"]
CH --> SR
SC --> SR
Diagram sources
- SigvaraReputation.sol:4-11
- SigvaraReputation.sol:12
- scoring.js:136-154
- index.js:1077-1088
- chain.js:702-714
Section sources
- SigvaraReputation.sol:4-11
- SigvaraReputation.sol:12
- scoring.js:136-154
- index.js:1077-1088
- chain.js:702-714
The contract implements several gas optimization strategies:
-
Compact data types:
- Six factor scores use
uint8instead of larger types. - Timestamps use
uint256where needed for arithmetic. - Evidence roots use
bytes32for efficient hashing.
- Six factor scores use
-
Efficient storage layout:
- Structs are designed to minimize storage slots.
-
PendingScoreappends fields to avoid corrupting existing proposals during upgrades. - Mappings are declared in order to maintain upgrade compatibility.
-
Optimized reads:
-
getTotalScoreperforms memory operations to avoid repeated storage reads. - Matured score calculation uses integer division for daily rate application.
- View functions minimize external calls and handle fallback gracefully.
-
-
Batch operations:
- Finalization writes multiple related values in a single transaction.
- Events emit comprehensive data to reduce off-chain reprocessing.
-
Upgrade safety:
- Storage layout preserves existing proxies without requiring migration.
- New fields are appended to avoid shifting existing data.
Section sources
- SigvaraReputation.sol:141-168
- SigvaraReputation.sol:174-220
- SigvaraReputation.sol:476-504
- SigvaraReputation.sol:696-752
Common issues and their resolutions:
-
Symptom: Transaction reverts with
ScoreOutOfRange. - Cause: One or more factor scores exceed their maximum caps.
- Resolution: Ensure oracle computes factors within contract-defined limits.
-
Symptom: Reverts with
AgentNotRegistered,AgentSlashed, orAgentNotBonded. - Cause: Agent not properly registered, already slashed, or insufficient stake.
- Resolution: Verify agent registration, check stake requirements, and ensure agent is active.
-
Symptom:
NoScorePending,ChallengeWindowActive,ChallengeWindowExpired,ProposerNotAuthorized,ScoreAlreadyPending, orInflowsPausedUntil. -
Cause: Incorrect timing or state assumptions in proposal/finalization flow.
ProposerNotAuthorizedmeans the proposer lostORACLE_ROLE, or its operator was removed or unbonded or its signer cleared.ScoreAlreadyPendingcomes fromproposeIfEmptywhen a proposal from an authorized proposer is pending.InflowsPausedUntilmeans a guardian pause is running on proposals. -
Resolution: Check challenge window timing (
finalizableAt) and proposal existence before calling functions. A proposal from an authorized proposer replaces a stranded one, and a pause ends by itself within three days.
-
Symptom:
verifyEvidencereturns false. - Cause: Invalid Merkle proof, wrong evidence root, or payment not in committed set.
- Resolution: Verify leaf construction matches oracle implementation and proof corresponds to correct root.
- Symptom: Score appears lower than expected.
- Cause: Maturity delay preventing immediate spendability.
- Resolution: Understand that matured score climbs gradually; falls are immediate.
Section sources
- SigvaraReputation.sol:239-260
- SigvaraReputation.sol:427-448
- SigvaraReputation.sol:454-504
- SigvaraReputation.sol:761-769
The reputation system's score storage and data model provide a robust foundation for agent trust assessment. The six-factor scoring approach balances different aspects of agent behavior while maintaining strict caps to prevent manipulation. The optimistic proposal model with challenge windows ensures security, while evidence roots provide verifiable audit trails. Maturity mechanics prevent gaming of the system by delaying score spendability. The design prioritizes gas efficiency and upgrade safety while maintaining clear separation between off-chain computation and on-chain validation.
The system successfully addresses key challenges in decentralized reputation: preventing wash trading attacks through structural factors like tenure and external trust, ensuring accountability through slashing mechanisms, and providing transparency through merkle-provable evidence. The modular architecture allows for evolution while preserving existing deployments and data integrity.
- 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