Repository navigation
Contract Overview and Architecture
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
This document describes the architecture of Sigvara’s smart contract system with a focus on:
- The foundational identity registry and its dependent contracts for staking, reputation, and oracle bonding.
- The UUPS proxy upgrade pattern using OpenZeppelin’s upgradeable primitives.
- Access control, role-based permissions, and upgradeability safeguards.
- Data flow between components, storage layout discipline, gas optimization strategies, and cross-contract communication patterns.
- Deployment, initialization, and upgrade procedures.
The design is modular: identity is the root registry, staking enforces collateral and slashing, reputation stores scored trust, and oracle bonding manages operator performance bonds. All core contracts are upgradeable through UUPS proxies controlled by an UPGRADER_ROLE.
At the protocol level, the relevant source files live under src/, deployment logic under script/, configuration under foundry.toml, and upgrade tests under test/.
graph TB
subgraph "Deployment Scripts"
Deploy["Deploy.s.sol"]
Upgrade["Upgrade.s.sol"]
end
subgraph "Core Contracts"
Identity["SigvaraIdentity.sol"]
Staking["SigvaraStaking.sol"]
Reputation["SigvaraReputation.sol"]
OracleBond["SigvaraOracleBond.sol"]
end
subgraph "Configuration"
Foundry["foundry.toml"]
end
subgraph "Tests"
UpgradeTest["Upgrade.t.sol"]
end
Deploy --> Identity
Deploy --> Reputation
Deploy --> Staking
Upgrade --> Identity
Upgrade --> Reputation
Upgrade --> Staking
Upgrade --> OracleBond
Foundry --> Deploy
UpgradeTest --> Identity
UpgradeTest --> Reputation
UpgradeTest --> Staking
Diagram sources
Section sources
- SigvaraIdentity: Foundational registry that anchors agent identities (DIDs), tracks operators, status transitions, public key rotation, and operator transfers. It also gates activation against stake via a view into the staking contract.
- SigvaraStaking: Manages agent stake tokens, minimum stake enforcement, unbonding withdrawals, and the slash lifecycle including initiation, dispute, execution, and proceeds distribution.
- SigvaraReputation: Stores six-factor reputation scores proposed by oracles, with optimistic challenge windows, maturity over time, evidence Merkle roots, and bond-gated oracle admission.
- SigvaraOracleBond: Separate performance-bond registry for oracle operators, with deposit/admit/unbond/slash flows and active-operator checks consumed by reputation.
All four contracts inherit from OpenZeppelin’s Initializable, AccessControlUpgradeable, and UUPSUpgradeable, and implement _authorizeUpgrade gated by UPGRADER_ROLE.
On main they also inherit two small shared bases, TimelockedGrants and InflowGuard. Each registry’s initialize names UPGRADER_ROLE as the admin of the roles that move bonded funds or change an agent’s standing, so once the timelock holds UPGRADER_ROLE, as planned for mainnet, granting one waits out its delay, and TimelockedGrants lets the admin revoke any of them at once, except UPGRADER_ROLE itself. InflowGuard lets a guardian pause new registrations, stake deposits, score proposals and bond deposits for three days at a time, and nothing else. SigvaraReader is separate: a stateless, non-upgradeable contract that answers isTrusted, isTrustedFresh and trust for on-chain consumers, and that the protocol itself never calls. The Arc testnet proxies have run these implementations since 26 October 2026, but they were initialised by the 21 September code, so their role administrators are the older ones and the timelocked grants do not apply to them. The reader is not yet deployed anywhere.
Section sources
The system follows a layered model:
- Identity is the root; it knows about agents and their operators.
- Staking depends on Identity to enforce slash-related state changes and uses Reputation only for zeroing scores after slash.
- Reputation depends on Identity to validate scorable agents and optionally on OracleBond to require bonded operators.
- OracleBond is independent but feeds Reputation’s operator admission check.
classDiagram
class SigvaraIdentity {
+initialize(admin, stakingCore)
+registerAgent(agentAddress, ed25519PubKey, signature)
+rotatePublicKey(didHash, newEd25519PubKey)
+updateStatus(didHash, newStatus)
+offerOperatorTransfer(didHash, newOperator)
+acceptOperatorTransfer(didHash)
+setStakeView(stakeView_)
+computeDidHash(agentAddress)
+isActive(didHash)
+getOperatorAgents(operator)
+getOperatorAgentsPaged(operator, offset, limit)
+_authorizeUpgrade(newImplementation)
}
class SigvaraStaking {
+initialize(admin, identityRegistry_, reputationRegistry_, svrToken_, minimumStake_, challengePeriod_, unbondingPeriod_)
+depositStake(didHash, amount)
+initiateWithdrawal(didHash, amount)
+claimWithdrawal(didHash)
+initiateSlash(didHash, victim, evidenceHash)
+disputeSlash(didHash)
+resolveDispute(didHash, uphold)
+expireDispute(didHash)
+expireSlash(didHash)
+cancelSlash(didHash)
+vetoSlash(didHash)
+executeSlash(didHash)
+claimSlashProceeds()
+hasMinimumStake(didHash)
+_authorizeUpgrade(newImplementation)
}
class SigvaraReputation {
+initialize(admin, oracle, stakingCore, slashingCommittee, challengeWindow_)
+proposeReputation(didHash, data, evidenceRoot)
+proposeIfEmpty(didHash, data, evidenceRoot)
+finalizeReputation(didHash)
+rejectReputation(didHash)
+zeroReputation(didHash)
+setOperatorBond(operatorBond_)
+getTotalScore(didHash)
+verifyEvidence(didHash, leaf, proof)
+_authorizeUpgrade(newImplementation)
}
class SigvaraOracleBond {
+initialize(admin, slasher, svrToken, bondAmount_, unbondingPeriod_, slashBeneficiary_)
+depositBond(amount)
+initiateUnbond()
+withdrawBond()
+admit(operator)
+removeOperator(operator)
+slash(operator, amount)
+isActiveOperator(operator)
+_authorizeUpgrade(newImplementation)
}
SigvaraStaking --> SigvaraIdentity : "reads/writes agent state"
SigvaraStaking --> SigvaraReputation : "zeros score on slash"
SigvaraReputation --> SigvaraIdentity : "validates scorable agent"
SigvaraReputation --> SigvaraOracleBond : "optional operator bond check"
Diagram sources
SigvaraIdentity is the foundational registry. It:
- Computes deterministic DIDs per chain and agent address.
- Requires agent-signed registration digests to bind an Ethereum address to an Ed25519 public key.
- Tracks operator ownership, status transitions, and operator transfer offers.
- Enforces collateral checks before allowing an agent to become Active again.
- Uses a one-way interface
IStakeViewto avoid circular imports while still checking stake.
Key behaviors:
- Registration requires a valid signature over a digest bound to chain ID, registry address, agent address, operator, and public key.
- Status updates allow operator toggling between Active and Suspended, but Slashed is terminal and can only be set by
STAKING_CORE_ROLE. - Operator transfers are two-step: offer then accept, with collateral checks at acceptance.
- Slash suspension lock prevents self-reinstatement during pending slashes.
Storage layout discipline:
- New mappings are appended after existing ones to preserve slot positions across upgrades.
- A secondary index maps operator to agent list plus position tracking for constant-time removal.
Access control:
-
DEFAULT_ADMIN_ROLEandUPGRADER_ROLEgranted to admin. -
STAKING_CORE_ROLEgranted to SigvaraStaking to mark agents as Slashed or manage slash suspension locks. - On main,
UPGRADER_ROLEadministers itself andSTAKING_CORE_ROLE, andGUARDIAN_ROLEmay pauseregisterAgent.
Upgradeability:
-
_authorizeUpgraderestricts upgrades toUPGRADER_ROLE. -
initializeV2wires the stake view after staking is deployed. - On main,
setStakeViewrepoints it and requiresUPGRADER_ROLE, so once the timelock holds that role a change takes its delay.
flowchart TD
Start(["Register Agent"]) --> ValidateInputs["Validate agentAddress and ed25519PubKey"]
ValidateInputs --> VerifySignature["Verify agent signature over registrationDigest"]
VerifySignature --> ComputeDid["Compute didHash from chainId and agentAddress"]
ComputeDid --> CheckRegistered{"Already registered?"}
CheckRegistered --> |Yes| RevertAlready["Revert AlreadyRegistered"]
CheckRegistered --> |No| CreateIdentity["Create AgentIdentity<br/>status = PendingBond"]
CreateIdentity --> IndexOperator["Push to operatorAgents and record position"]
IndexOperator --> EmitEvent["Emit AgentRegistered"]
EmitEvent --> End(["Done"])
Diagram sources
Section sources
SigvaraStaking manages agent stake and slashing:
- Deposits credit actual received amounts to handle fee-on-transfer tokens safely.
- Enforces minimum stake for activation and withdrawal constraints.
- Implements unbonding periods where queued withdrawals remain slashable.
- Slash lifecycle: initiate -> challenge period -> dispute or execute -> settlement.
- On main, an upheld ruling waits for the challenge deadline fixed when the slash was filed and is refused once the dispute can be expired, a dispute nobody rules on expires after 14 days (
expireDispute), and a pending slash nobody executes can be lapsed by anyone 14 days after its deadline (expireSlash). Every dropped proposal starts a cooldown of one challenge period before the committee can file against the same agent again. - Settlement burns half, distributes quarter to victim and quarter to reporter, marks agent Slashed, and zeros reputation.
Access control:
-
SLASHING_COMMITTEE_ROLEallows slash initiation and cancellation, and a slash can be executed only while its reporter still holds it. On main it no longer rules on disputes: the admin (DEFAULT_ADMIN_ROLE) callsresolveDisputeand can drop a pending or disputed slash withvetoSlash. -
UPGRADER_ROLEcontrols upgrades and, on main, grantsSLASHING_COMMITTEE_ROLE. - Admin roles update parameters like minimum stake, challenge period, and unbonding period. On main the minimum stake must be above zero, the challenge period between 3 and 30 days and the unbonding period between 1 and 90 days, in the initializers as well as the setters.
-
GUARDIAN_ROLEmay pausedepositStake.
Cross-contract interactions:
- Reads
AgentIdentityfrom Identity to validate operator and status. - Calls
identityRegistry.updateStatusto suspend or slash agents. - Calls
reputationRegistry.zeroReputationupon slash execution.
sequenceDiagram
participant Committee as "Slashing Committee"
participant Staking as "SigvaraStaking"
participant Identity as "SigvaraIdentity"
participant Reputation as "SigvaraReputation"
Committee->>Staking : initiateSlash(didHash, victim, evidenceHash)
Staking->>Identity : updateStatus(Suspended)
Note over Staking : Record proposal with snapshot challengeDeadline
alt Operator disputes
Operator->>Staking : disputeSlash(didHash)
Staking->>Staking : Set state=Disputed
Admin->>Staking : resolveDispute(didHash, uphold)
else No dispute
Any->>Staking : executeSlash(didHash)
Staking->>Staking : _settleSlash()
Staking->>Identity : updateStatus(Slashed)
Staking->>Reputation : zeroReputation(didHash)
Staking->>Staking : Transfer burn to 0xdead
Staking-->>Victim : Credit claimable
Staking-->>Reporter : Credit claimable
end
Diagram sources
Section sources
SigvaraReputation stores six-factor reputation scores:
- Oracle proposes scores within capped factors; a challenge window opens. On main each proposal records its proposer and a
finalizableAtfixed when it is made, so a later change to the window moves no pending proposal. - Anyone finalizes after the window if not rejected by the slashing committee, as long as the proposer still holds
ORACLE_ROLEand, with an operator set, is still active in it. A stranded proposal is replaced by a new one, or byproposeIfEmptyfrom a checking operator. - Scores mature over time at a configurable rate; handovers restart maturity, however mature the score was (on the Arc testnet proxy a score at or below its anchor is still returned whole).
- Evidence roots commit to payment leaves; Merkle proofs verify individual payments.
- Optional bonded operator registry integration ensures proposers are admitted and bonded, or are the signer of an admitted operator.
Access control:
-
ORACLE_ROLEallows proposing scores. -
SLASHING_COMMITTEE_ROLEallows rejecting proposals. -
STAKING_CORE_ROLEallows zeroing scores on slash. -
UPGRADER_ROLEcontrols upgrades. - On main,
UPGRADER_ROLEalso grantsSTAKING_CORE_ROLEandSLASHING_COMMITTEE_ROLE, andsetOperatorBondrequires it.ORACLE_ROLEstays with the admin.GUARDIAN_ROLEmay pauseproposeReputationandproposeIfEmpty. - The challenge window is held between 1 hour and 7 days and the maturity rate between 1 and 100 points a day.
Cross-contract interactions:
- Reads Identity to ensure the agent exists and is not slashed.
- Uses
stakeView.hasMinimumStakevia Identity to enforce bonding requirement. - Optionally reads
operatorBond.isActiveOperatorto gate oracle proposer eligibility.
flowchart TD
Propose["Oracle proposes score"] --> ValidateFactors["Validate factor caps"]
ValidateFactors --> RequireScorable["Require scorable agent via Identity"]
RequireScorable --> StorePending["Store PendingScore with evidenceRoot"]
StorePending --> ChallengeWindow["Challenge window starts"]
ChallengeWindow --> Decision{"Rejected by committee?"}
Decision --> |Yes| Discard["Delete pending score"]
Decision --> |No| Finalize["Finalize after window"]
Finalize --> UpdateReputation["Write finalized ReputationData"]
UpdateReputation --> Maturity["Set anchor and maturedAt"]
Diagram sources
Section sources
SigvaraOracleBond manages oracle operator performance bonds:
- Operators deposit bonds, get admitted by governance, and can exit via unbonding cooldown.
- Slashes reduce bond balances and may demote active operators below the required threshold.
- Provides
isActiveOperatorwhich checks both status and current bond against the required amount. - On main, an Active operator can name one signer (
setSigner,clearSigner).isActiveOperatoranswers for the signer as for the operator, so the key a service signs with need not be the key that owns the bond.
Access control:
-
SLASHER_ROLEallows slashing. On main it is granted byUPGRADER_ROLE, and the address that receives slashed bond cannot hold it. -
UPGRADER_ROLEcontrols upgrades. - Admin can admit/remove operators and adjust parameters. On main the unbonding period is held between 1 and 90 days and the bond amount above zero,
setSlashBeneficiaryrequiresUPGRADER_ROLE, andGUARDIAN_ROLEmay pausedepositBond.
Integration:
- Consumed by SigvaraReputation to require bonded operators when configured.
stateDiagram-v2
[*] --> None
None --> Bonded : "depositBond()"
Bonded --> Active : "admit()"
Active --> Exiting : "initiateUnbond()/removeOperator()"
Bonded --> Exiting : "initiateUnbond()/removeOperator()"
Exiting --> None : "withdrawBond()"
Active --> Bonded : "slash() drops below bondAmount"
Diagram sources
Section sources
The dependency graph reflects a clear hierarchy:
- Identity has no protocol dependencies beyond OpenZeppelin primitives and a minimal view of staking.
- Staking depends on Identity and Reputation.
- Reputation depends on Identity and optionally OracleBond.
- OracleBond is standalone.
- All four share the
TimelockedGrantsandInflowGuardbases on main. Both are abstract and exist only as part of each registry.SigvaraReaderreads Identity, Reputation and Staking, and nothing depends on it.
graph LR
Identity["SigvaraIdentity"] --> Staking["SigvaraStaking"]
Staking --> Reputation["SigvaraReputation"]
Reputation --> Identity
Reputation --> OracleBond["SigvaraOracleBond"]
Diagram sources
Section sources
- Storage layout discipline: all contracts append new state variables after existing ones to keep base slots stable across upgrades. This avoids costly reindexing and corruption risks.
- Gas optimizations:
- Constant-time removal from operator lists using recorded indices and swap-and-pop.
- Pull-based payouts for slash proceeds to avoid recipient revert failures.
- Reading total score in memory and emitting totals from memory to reduce storage reads.
- Using
SafeERC20and measuring balance deltas to safely support fee-on-transfer tokens without over-crediting.
- View functions minimize writes and use efficient paging for large operator lists.
Common issues and mitigations:
- Upgrade ordering: upgrading Reputation before Identity can cause read failures if newer code calls functions not present in older Identity versions. The upgrade script checks for required function signatures and reverts if dependencies are missing.
- Role separation: deploying with deployer holding multiple roles (oracle, committee, admin) is warned and blocked on mainnet to prevent single-key attacks. Mainnet also requires a guardian that is none of those keys.
- Stake view wiring: Identity must have
stakeViewset to SigvaraStaking before allowing agents to return to Active; otherwise activation attempts revert withStakeViewNotSet. - Unbonding and challenge periods: parameters have minimum bounds enforced to prevent trivially short windows that undermine dispute rights. On main they also have ceilings (a 30 day slash challenge period, a 7 day score window, a 90 day unbonding period, a maturity rate of 100 points a day), refused in the initializers as well as the setters.
- Paused inflows: on main,
registerAgent,depositStake,proposeReputation,proposeIfEmptyanddepositBondrevert withInflowsPausedUntilwhile a guardian pause runs. Exits, disputes, rulings, slash execution and finalization are never paused.
Operational steps:
- Use
script/Upgrade.s.solto simulate and broadcast upgrades with dependency checks. - Ensure
deployments/<chainId>.jsoncontains correct proxy addresses. - Run slot-pinning and upgrade tests (
test/Upgrade.t.sol) before broadcasting.
Section sources
Sigvara’s contract architecture separates concerns cleanly:
- Identity anchors agents and operators.
- Staking enforces economic security through bonds and slashing.
- Reputation captures off-chain verified trust with optimistic governance and maturity.
- OracleBond secures operator integrity independently.
The UUPS proxy pattern enables safe upgrades guarded by UPGRADER_ROLE, with careful storage layout and dependency checks ensuring continuity and security. The modular design supports future evolution while maintaining strong access control and slashing incentives.
- The deploy script constructs ERC1967 proxies for each implementation and initializes them in order:
- Token (existing or testnet).
- Identity proxy initialized with admin and placeholder staking core.
- Reputation proxy initialized with admin, oracle, staking core, committee, and challenge window (the script passes zero for oracle, staking core and committee and grants those roles in step 5); then wired to Identity.
- Staking proxy initialized with token, Identity, Reputation, minimum stake, challenge period, and unbonding period. The script then calls
initializeV2with the same unbonding period, which uses up the one call a fresh deployment leaves open. - Cross-role wiring: grant STAKING_CORE_ROLE to Staking on Identity and Reputation; grant ORACLE_ROLE and SLASHING_COMMITTEE_ROLE on Reputation; grant SLASHING_COMMITTEE_ROLE on Staking.
- If
GUARDIAN_ADDRESSis set, grantGUARDIAN_ROLEon Identity, Reputation and Staking. It is required on mainnet.
- On chain 5042 (Arc mainnet) the script first refuses a missing or non-18-decimal bond token, periods outside the contracts’ floors and ceilings, a maturity rate above 100, a minimum stake under 10,000 SVR, and a missing guardian or one that is the deployer, the oracle or the committee.
- Addresses are written to
deployments/<chainId>.json.
Section sources
- Each contract uses
initializerandreinitializerversioning to support multi-step setup:- Identity:
initializetheninitializeV2to wire stake view. - Reputation:
initialize,initializeV2for slashing committee and window,initializeV3to wire Identity,initializeV4to set maturity rate. - Staking:
initializethen optionalinitializeV2to set unbonding period for pre-upgrade deployments.initializeV2is held to the same floor and ceiling assetUnbondingPeriod. - OracleBond:
initializesets admin, slasher, token, bond amount, unbonding period, and slash beneficiary.
- Identity:
Section sources
- SigvaraIdentity.sol:177-216
- SigvaraReputation.sol:279-363
- SigvaraStaking.sol:237-302
- SigvaraOracleBond.sol:155-199
- Always run slot-pinning tests to ensure storage layout compatibility.
- Use
script/Upgrade.s.solto deploy a fresh implementation and callupgradeToAndCallatomically with any necessary initializer calldata. It broadcasts with the key it is given, so it fits a deployer that still holdsUPGRADER_ROLE. - After the mainnet handover the timelock holds
UPGRADER_ROLE, which the script cannot act as. The implementation is deployed from any funded key, the Safe schedulesupgradeToAndCall(impl, data)on the timelock, and anyone executes it once the delay has run.datacannot be a reinitializer there, because reinitializers needDEFAULT_ADMIN_ROLEand the timelock is not the admin, so the upgrade lands with empty data and the Safe calls the reinitializer in a second transaction. - The script verifies dependency readiness (e.g., Identity exposes required functions before upgrading Reputation), and that the proxy answers a getter only the target has (
ProxyIsNotTheTarget). - After upgrade, record live implementations using provided tooling to keep artifacts consistent.
Section sources
- 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