Repository navigation
Anti Manipulation and Security Measures
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains Sigvara’s anti-manipulation and security design as implemented across the on-chain contracts and the oracle scoring pipeline. It focuses on:
- Farm-and-cash-out prevention through maturity, decay, and activity requirements.
- Self-payment detection, wash trading prevention, and counterparty diversity enforcement.
- Sybil resistance via bonding, slashing, and economic disincentives.
- Score manipulation defenses including the challenge window, dispute resolution, slash proposals, and evidence commitments.
- The oracle’s adversarial testing and monitoring role.
The goal is to make these mechanisms understandable for both technical and non-technical readers while remaining grounded in the actual codebase.
Security-relevant logic is split between on-chain contracts and off-chain oracle modules:
- On-chain identity, stake, reputation, and operator-bond registries enforce state transitions, collateralization, and governance-gated actions.
- Off-chain oracle modules compute scores from verified payments, apply decay and diversity caps, and propose score updates that are subject to a challenge window.
graph TB
subgraph "On-chain"
Identity["SigvaraIdentity"]
Staking["SigvaraStaking"]
Reputation["SigvaraReputation"]
OracleBond["SigvaraOracleBond"]
end
subgraph "Off-chain Oracle"
Index["oracle/index.js"]
Payments["oracle/payments.js"]
Scoring["oracle/scoring.js"]
Adversarial["oracle/adversarial.test.js"]
end
Index --> Payments
Index --> Scoring
Adversarial --> Scoring
Adversarial --> Payments
Identity --> Staking
Staking --> Reputation
Reputation --> OracleBond
Diagram sources
- SigvaraIdentity.sol:22-107
- SigvaraStaking.sol:31-143
- SigvaraReputation.sol:14-75
- SigvaraOracleBond.sol:14-45
- index.js:285-305
- payments.js:1-27
- scoring.js:1-8
- adversarial.test.js:1-29
Section sources
- whitepaper.md:59-67
- SigvaraIdentity.sol:22-107
- SigvaraStaking.sol:31-143
- SigvaraReputation.sol:14-75
- SigvaraOracleBond.sol:14-45
- index.js:285-305
- payments.js:1-27
- scoring.js:1-8
- adversarial.test.js:1-29
- Identity registry: Anchors agent identities, enforces “bond before trust,” tracks status, and gates operator transfers.
- Staking contract: Manages collateral, unbonding, slash initiation/dispute/execution, and prevents self-payment in slash reporting.
- Reputation contract: Stores six-factor scores, applies maturity, and exposes a challenge window with committee rejection.
- Operator bond registry: Requires bonded operators to propose scores; supports slashing and demotion if bonds fall below thresholds. A bonded operator can name a signer key to propose for it, so the host’s key need not hold the bond. The Arc testnet registry has supported this since 26 October 2026, but neither operator there has named a signer.
- Oracle scoring pipeline: Verifies payments, applies decay, diversity caps, and computes factors used by the on-chain reputation system.
Key anti-manipulation properties:
- Scores are earned immediately but spendable over time (maturity).
- Payment-backed attestations prevent fake volume.
- Per-payer caps and distinct counterparty counts resist rings.
- Tenure measures paid activity span and recency, not registration age.
- Slashing and unbonding periods penalize misbehavior and exit attempts.
- Challenge windows and evidence roots allow third-party verification and committee intervention.
Section sources
- SigvaraIdentity.sol:47-107
- SigvaraStaking.sol:31-143
- SigvaraReputation.sol:14-75
- SigvaraOracleBond.sol:14-45
- payments.js:1-27
- scoring.js
The security model spans multiple layers. In the flow below, the admin Safe rules on a dispute and the committee only files and withdraws a slash; that split is live on the Arc testnet proxies since 26 October 2026, where the admin is a single EOA rather than a Safe.
sequenceDiagram
participant Agent as "Agent Operator"
participant Identity as "SigvaraIdentity"
participant Staking as "SigvaraStaking"
participant Oracle as "Oracle Pipeline"
participant Reputation as "SigvaraReputation"
participant Committee as "Slashing Committee"
participant Admin as "Admin Safe"
Agent->>Identity : registerAgent()
Agent->>Staking : depositStake()
Note over Identity,Staking : Agent becomes Active when stake >= minimumStake
Agent->>Oracle : submit payment-backed attestation
Oracle->>Oracle : verifyPayment(), decay, diversity caps
Oracle->>Reputation : proposeReputation()
Reputation-->>Reputation : open challengeWindow
Committee->>Reputation : rejectReputation() during window
Reputation-->>Committee : proposal discarded
Committee->>Staking : initiateSlash()
Staking-->>Identity : updateStatus(Suspended)
Agent->>Staking : disputeSlash()
Admin->>Staking : resolveDispute(uphold or cancel), an upheld ruling only after the deadline and before the dispute expires
Note over Staking : An undisputed slash is settled by anyone calling executeSlash() after the window
Staking->>Identity : updateStatus(Slashed)
Staking->>Reputation : zeroReputation()
Diagram sources
- SigvaraIdentity.sol:295-321
- SigvaraStaking.sol:321-364
- payments.js:167-272
- SigvaraReputation.sol:378-448
- SigvaraStaking.sol:474-531
- SigvaraStaking.sol:543-559
- SigvaraStaking.sol:583-618
- SigvaraStaking.sol:715-778
Farm-and-cash-out is prevented by combining:
- Maturity: A finalized score climbs toward being spendable at a fixed rate per day. Falls apply immediately.
- Decay: Payment volume and attestations decay over time so old activity does not permanently inflate scores.
- Activity requirements: Tenure measures the span between first and last verified payment, weighted by recency. Idle agents earn little even if registered long ago.
Implementation highlights:
- Maturity rate is configurable and applied on read; it restarts on operator handover to avoid buying/selling matured identities.
- Decay uses exponential half-life weighting for volume and attestations.
- AgeScore uses logarithmic growth over activity span multiplied by recency.
flowchart TD
Start(["Finalized Score"]) --> Earned["Earned Score Stored"]
Earned --> Anchor["Anchor = Matured Score Before Finalize"]
Anchor --> RateCheck{"Maturity Rate > 0?"}
RateCheck --> |No| ReturnEarned["Return Earned Score"]
RateCheck --> |Yes| TimeSince["Compute Time Since Last Finalize"]
TimeSince --> Release["Released = Anchor + (time * ratePerDay)"]
Release --> CapCheck{"Released >= Earned?"}
CapCheck --> |Yes| ReturnEarned
CapCheck --> |No| ReturnReleased["Return Released"]
Diagram sources
Section sources
- reputation-model.md:237-267
- SigvaraReputation.sol:560-577
- SigvaraReputation.sol:696-752
- scoring.js:57-98
- payments.js:282-303
- Deploy.s.sol:51-77
Self-payment detection and wash trading prevention rely on:
- Payment verification: Attestations must reference a settled ERC-20 transfer to the agent’s address. The payer is taken from the transfer log, not the submitter.
- Self-payment refusal: Payments from the operator or agent address are rejected.
-
Diversity caps: Each counterparty contributes at most
maxPerPayerpoints to feeScore and attestations. - Distinct counterparties: Volume and success ratios are computed per payer and capped, preventing one wallet from simulating many customers. At the default of 4 points per payer, filling the 20-point fee factor takes five payers with no standing of their own, and three if each is fully trusted (a payer's cap rises with its own hard standing, up to double).
-
Real value only: A zero-value transfer never counts as a payment, because on a standard ERC-20 anyone can emit one naming any sender. Each sender must also meet
PAYMENT_MIN_AMOUNTon its own, and in required mode the oracle refuses to start with a minimum of 0. - Outcomes are not open: A settlement hash is public, so a stranger who submits one has not shown that they paid or that they saw the work. Their attestation credits the payment and records no outcome, and only the payer's own signature or a caller holding an oracle token records one, which stops an operator filing "success" against its customers’ payments.
flowchart TD
Submit["Submit Attestation with txHash"] --> Verify["verifyPayment(txHash, agentAddress)"]
Verify --> Logs["Extract Transfer Logs"]
Logs --> Payer["Identify Payer from Log"]
Payer --> SelfCheck{"isSelfPayment(payer, identity)?"}
SelfCheck --> |Yes| Reject["Reject Attestation"]
SelfCheck --> |No| Group["Group by Payer"]
Group --> Cap["Apply maxPerPayer Cap"]
Cap --> Diversity["Compute diversifiedVolume/diversifiedAttestations"]
Diversity --> Score["Feed into computeScore()"]
Diagram sources
Section sources
Counterparty diversity is enforced by:
-
Per-payer caps: Both volume and attestations are capped per counterparty using
maxPerPayer. - Trust-weighted multiplier: A counterparty’s contribution can be raised based on its external standing, but only up to a bounded multiplier.
- Propagation score: Inherited trust is limited to fully trusted counterparties, pro-rated by their external standing, with each counterparty counted once regardless of payment size.
flowchart TD
Events["Payment Events"] --> ByPayer["byPayer(events)"]
ByPayer --> TrustMult["trustMultiplier(payer, payerScores, trustWeight)"]
TrustMult --> Capped["cappedAt(base, payer, payerScores, trustWeight)"]
Capped --> DiversifiedVol["diversifiedVolume()"]
Capped --> DiversifiedAtt["diversifiedAttestations()"]
DiversifiedVol --> FeeScore["feeScoreFromVolume()"]
DiversifiedAtt --> SuccessRatio["successScore()"]
Diagram sources
Section sources
Sybil resistance combines:
- Bonding requirements: Agents must hold minimum stake to be Active and scoreable. Operators proposing scores must be admitted and bonded in the operator set.
- Slashing mechanisms: Misbehavior leads to stake slashing, identity marking as Slashed, and reputation zeroing.
- Economic disincentives: Wash rings cost gas and working capital; tenure requires sustained paid operation; external trust cannot be minted within the protocol.
classDiagram
class SigvaraIdentity {
+registerAgent()
+updateStatus()
+acceptOperatorTransfer()
+slashSuspended
+operatorChangedAt
}
class SigvaraStaking {
+depositStake()
+initiateWithdrawal()
+initiateSlash()
+disputeSlash()
+executeSlash()
+claimSlashProceeds()
}
class SigvaraReputation {
+proposeReputation()
+finalizeReputation()
+rejectReputation()
+zeroReputation()
+getTotalScore()
}
class SigvaraOracleBond {
+depositBond()
+admit()
+slash()
+isActiveOperator()
}
SigvaraIdentity --> SigvaraStaking : "status checks"
SigvaraStaking --> SigvaraReputation : "zeroReputation()"
SigvaraReputation --> SigvaraOracleBond : "require bonded operator"
Diagram sources
- SigvaraIdentity.sol:295-321
- SigvaraIdentity.sol:344-397
- SigvaraStaking.sol:321-364
- SigvaraStaking.sol:474-531
- SigvaraStaking.sol:715-778
- SigvaraReputation.sol:378-448
- SigvaraReputation.sol:541-557
- SigvaraOracleBond.sol:219-243
- SigvaraOracleBond.sol:364-392
Section sources
- SigvaraIdentity.sol:47-107
- SigvaraStaking.sol:321-364
- SigvaraStaking.sol:474-531
- SigvaraStaking.sol:715-778
- SigvaraReputation.sol:378-448
- SigvaraReputation.sol:541-557
- SigvaraOracleBond.sol:219-243
- SigvaraOracleBond.sol:364-392
Score manipulation is addressed through:
- Challenge window: Proposed scores sit open for a configurable period before finalization. On main the end of a proposal’s window is fixed when it is proposed, so an admin who changes the window moves no proposal already pending, and the window is held between 1 hour and 7 days.
- Committee rejection: A slashing committee can reject bad proposals during the window.
-
Proposer check: On main, finalization is still permissionless but refuses a proposal whose proposer has since been removed, had its signer cleared or lost
ORACLE_ROLE, so a stolen key’s pending proposals can be ended at once. A successor replaces them. - Evidence roots: Each finalized score commits to a Merkle root of the underlying payments, enabling third-party verification.
- Slash proposals: Misbehavior triggers slash initiation, dispute, and execution, with proceeds distributed and identity marked Slashed. On main the admin can veto a pending or disputed slash, a slash nobody executes can be lapsed by anyone 14 days after its deadline, and the committee cannot file again against an agent for one challenge period after a proposal against it was dropped.
sequenceDiagram
participant Oracle as "Oracle"
participant Reputation as "SigvaraReputation"
participant Committee as "Slashing Committee"
participant Consumer as "Consumer"
Oracle->>Reputation : proposeReputation(didHash, data, evidenceRoot)
Reputation-->>Reputation : open challengeWindow
Committee->>Reputation : rejectReputation(didHash)
Reputation-->>Committee : proposal discarded
Consumer->>Reputation : verifyEvidence(didHash, leaf, proof)
Reputation-->>Consumer : bool
Oracle->>Reputation : finalizeReputation(didHash) after window (anyone may call)
Reputation-->>Consumer : getTotalScore(didHash)
Diagram sources
- SigvaraReputation.sol:378-448
- SigvaraReputation.sol:454-504
- SigvaraReputation.sol:513-523
- SigvaraReputation.sol:761-769
Section sources
- SigvaraReputation.sol:378-448
- SigvaraReputation.sol:454-504
- SigvaraReputation.sol:513-523
- SigvaraReputation.sol:761-769
The oracle pipeline:
- Verifies payments against chain logs, extracting payer, amount, and timestamp.
- Applies decay and diversity caps to prevent wash rings and self-payment inflation.
- Computes six factors and proposes scores subject to challenge windows.
- Is tested adversarially to bound what an attacker can buy with synthetic activity.
- Does not trust the node for the clock or the chain. Whoever supplies the time chooses how old every payment looks, so an epoch does not run when the chain’s latest block time and the host clock differ by more than
MAX_CLOCK_SKEW_SECONDS(600 by default). The chain id is checked againstEXPECTED_CHAIN_ID(when set) and against the chain the state file was saved on, and the oracle will not sign a transaction whose fee is aboveMAX_FEE_GWEIor whose worst-case cost is aboveMAX_TX_COST. - Skips an agent for the epoch when a counterparty’s standing cannot be read, instead of scoring it as no standing.
- Hardens its HTTP surface. Reads and writes are limited to 60 requests per caller per minute, in a separate bucket for each kind of route.
X-Forwarded-Foris believed only from loopback and the proxies inTRUSTED_PROXIES. A request target that is not a canonical path gets 400, a handler that throws costs one request rather than the process, and public routes do not echo upstream error text. Write credentials are named bearer tokens that can be limited to the scopesattest,flag,linkandepoch. - Refuses to start on a numeric setting that is set but unusable. A misspelt
PAYMENT_MAX_PER_PAYERwould otherwise switch the per-payer cap off.
flowchart TD
Scan["Scan Chain for Transfers"] --> Verify["verifyPayment()"]
Verify --> Group["byPayer()"]
Group --> Caps["diversifiedVolume()/diversifiedAttestations()"]
Caps --> Compute["computeScore()"]
Compute --> Propose["proposeReputation()"]
Propose --> Challenge["challengeWindow"]
Diagram sources
- index.js:285-305
- payments.js:167-272
- payments.js:472-504
- scoring.js:130-155
- SigvaraReputation.sol:378-448
Section sources
- index.js:285-305
- payments.js:167-272
- payments.js:472-504
- scoring.js:130-155
- SigvaraReputation.sol:378-448
The contracts and oracle modules form a layered dependency graph:
- Identity depends on staking view for collateral checks.
- Staking depends on identity for status and reputation for zeroing scores.
- Reputation depends on identity for agent validation and optionally on operator bond registry for proposer requirements.
- Oracle pipeline depends on payments and scoring modules to produce evidence-backed score proposals.
graph LR
Identity["SigvaraIdentity"] --> Staking["SigvaraStaking"]
Staking --> Reputation["SigvaraReputation"]
Reputation --> OracleBond["SigvaraOracleBond"]
Index["oracle/index.js"] --> Payments["oracle/payments.js"]
Index --> Scoring["oracle/scoring.js"]
Adversarial["oracle/adversarial.test.js"] --> Scoring
Adversarial --> Payments
Diagram sources
- SigvaraIdentity.sol:18-20
- SigvaraStaking.sol:27-29
- SigvaraReputation.sol:12
- index.js:285-305
- payments.js:1-27
- scoring.js:1-8
- adversarial.test.js:1-29
Section sources
- SigvaraIdentity.sol:18-20
- SigvaraStaking.sol:27-29
- SigvaraReputation.sol:12
- index.js:285-305
- payments.js:1-27
- scoring.js:1-8
- adversarial.test.js:1-29
- Read-time maturity: Maturity accrues on read, avoiding frequent writes and allowing honest agents to mature even if oracles go offline.
- Efficient grouping: The oracle groups events once and reuses the grouped map for volume, attestations, and distinct payer calculations.
- Gas-aware indexing: Identity maintains an index for operator-agent lists to avoid linear scans on transfers.
- Configurable parameters: Maturity rate, challenge window, unbonding period, and decay half-life are tunable to balance security and responsiveness. On main the contract parameters sit between a floor and a ceiling enforced where they are set, including the initializers: the score challenge window from 1 hour to 7 days, the maturity rate up to 100 points a day, the slash challenge period from 3 to 30 days and the unbonding period from 1 to 90 days.
Common issues and mitigations:
-
Proposal replacement race: Use
proposeIfEmptyfor backup oracles to avoid overwriting live proposals. -
Proposals revert during a pause (main): A guardian can close new inflows for three days at a time:
registerAgent,depositStake,proposeReputation,proposeIfEmptyanddepositBond. Proposals revert withInflowsPausedUntiluntil the pause ends or the guardian or admin lifts it. Withdrawals, disputes, slash execution, and finalizing or rejecting a score already proposed are never paused. - Unbonding vs slash timing: Queued withdrawals remain slashable; disputes freeze stakes to prevent escape.
- Self-payment in slash reporting: The committee takes no share of a slash, and neither the reporter nor any other committee member can be named as the victim, so no member can be paid out of slashed stake.
-
Evidence verification failures: Consumers should call
verifyEvidencewith leaves built from chain-verified payments.
Section sources
- SigvaraReputation.sol:408-422
- SigvaraStaking.sol:378-424
- SigvaraStaking.sol:474-531
- SigvaraReputation.sol:761-769
Sigvara’s anti-manipulation and security measures combine on-chain collateral and governance with off-chain verified scoring. Farm-and-cash-out is constrained by maturity, decay, and activity requirements. Self-payment and wash trading are blocked by payment verification, diversity caps, and tenure metrics. Sybil attacks are resisted through bonding, slashing, and economic disincentives. Score manipulation is countered by challenge windows, committee rejection, and evidence roots. The oracle’s adversarial tests ensure that synthetic activity cannot easily buy high scores, preserving the integrity of the reputation 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