Repository navigation
Stake Management Functions
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
This document explains the stake management functions in the SigvaraStaking contract: depositing stake, initiating withdrawals, and claiming queued withdrawals. It focuses on agent validation, token transfer handling, automatic status transitions from PendingBond to Active, unbonding period mechanics, minimum stake enforcement, fee-on-transfer token accounting, slash-related restrictions, and gas/storage efficiency patterns used by the implementation.
The stake management logic lives in the core Solidity contract, while tests and SDK bindings provide usage examples and interface definitions.
graph TB
Client["Client / Operator"] --> SDK["SDK stake helpers<br/>packages/sdk/src/stake.ts"]
SDK --> ABI["STAKING_ABI<br/>packages/sdk/src/abis.ts"]
ABI --> Contract["SigvaraStaking<br/>src/SigvaraStaking.sol"]
Contract --> Identity["SigvaraIdentity"]
Contract --> Token["IERC20 SVR Token"]
Contract --> Reputation["IReputationZero"]
Tests["SigvaraStaking tests<br/>test/SigvaraStaking.t.sol"] --> Contract
Diagram sources
- SigvaraStaking.sol:1-13
- SigvaraStaking.sol:120-135
- abis.ts:23-34
- stake.ts:15-39
- SigvaraStaking.t.sol:1-12
Section sources
- SigvaraStaking.sol:1-13
- SigvaraStaking.sol:120-135
- abis.ts:23-34
- stake.ts:15-39
- SigvaraStaking.t.sol:1-12
- depositStake: Validates operator and agent state, transfers tokens using balance delta accounting, credits stake, and auto-activates agents transitioning from PendingBond when the minimum stake is met.
- initiateWithdrawal: Queues a withdrawal into an unbonding queue, enforces active-agent minimum stake rules, blocks during pending or disputed slashes, and prevents draining an active agent below the floor.
- claimWithdrawal: Enforces unbonding period and slash-state guards, clears the queued amount, and transfers tokens to the operator.
Key data structures:
- Stake: Tracks active stake, last deposit timestamp, queued withdrawal amount, and initiation time.
- SlashProposal: Tracks slash lifecycle, challenge deadlines, dispute state, and evidence hash.
Section sources
- SigvaraStaking.sol:76-99
- SigvaraStaking.sol:321-364
- SigvaraStaking.sol:378-424
- SigvaraStaking.sol:432-462
The staking flow integrates three main parties:
- Operator: Agent controller who deposits stake, queues withdrawals, and claims them after unbonding.
- Committee: Authorized role that initiates slashes. It does not rule on disputes; the admin does.
- Token: ERC-20 bond token (SVR), treated as a plain ERC-20 with explicit fee-on-transfer safety notes.
sequenceDiagram
participant Operator as "Operator"
participant Staking as "SigvaraStaking"
participant Identity as "SigvaraIdentity"
participant Token as "IERC20 SVR"
Operator->>Staking : "depositStake(didHash, amount)"
Staking->>Identity : "getIdentity(didHash)"
Identity-->>Staking : "AgentIdentity"
Staking->>Token : "safeTransferFrom(operator, this, amount)"
Token-->>Staking : "tokens received"
Staking->>Staking : "credit stake via balance delta"
alt "Agent is PendingBond and meets minimum"
Staking->>Identity : "updateStatus(Active)"
end
Staking-->>Operator : "emit StakeDeposited"
Diagram sources
Purpose:
- Validate caller is the registered operator.
- Reject unregistered or slashed agents; allow suspended agents so they can top up.
- Transfer tokens and credit the actual received amount using pre/post balance checks.
- Auto-transition from PendingBond to Active when total stake reaches the minimum.
- Refuse deposits while a guardian pause is running (
InflowsPausedUntil). The pause covers deposits and nothing else, soinitiateWithdrawalandclaimWithdrawalare never held up by it.
Validation and state handling:
- Agent must be registered and not Slashed.
- Caller must match the identity’s operator.
- Suspended agents may deposit but are not automatically reactivated.
Token transfer and fee-on-transfer handling:
- Uses SafeERC20.safeTransferFrom.
- Measures balanceBefore and balanceAfter to compute received amount, preventing over-crediting on fee-on-transfer tokens.
- Emits StakeDeposited with the actual credited amount.
Automatic status transition:
- If the agent is in PendingBond and the accumulated stake meets or exceeds minimumStake, the contract updates the agent to Active.
Edge cases:
- Slashed agents cannot deposit.
- Below-minimum deposits do not activate the agent.
- Deposits while Suspended are allowed but do not change status.
flowchart TD
Start(["depositStake Entry"]) --> ReadId["Read AgentIdentity"]
ReadId --> CheckRegistered{"Registered and not Slashed?"}
CheckRegistered --> |No| RevertNotActive["Revert AgentNotActive"]
CheckRegistered --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> |Yes| BalanceBefore["Record token balance before"]
BalanceBefore --> Transfer["safeTransferFrom(amount)"]
Transfer --> BalanceAfter["Record token balance after"]
BalanceAfter --> Credit["Credit received = after - before"]
Credit --> UpdateStake["Update stake.amount and lockedAt"]
UpdateStake --> CheckPendingBond{"Status == PendingBond<br/>and stake >= minimumStake?"}
CheckPendingBond --> |Yes| Activate["Update status to Active"]
CheckPendingBond --> |No| EmitDeposit["Emit StakeDeposited(received)"]
Activate --> EmitDeposit
EmitDeposit --> End(["Exit"])
Diagram sources
Examples:
- New agent activation: Register an agent, then deposit at least minimumStake to move it from PendingBond to Active.
- Partial top-up: Deposit additional stake while Active or Suspended to increase backing without changing status.
Test references:
- Successful deposit and accumulation.
- Operator-only restriction.
- Unregistered agent rejection.
- Activation from PendingBond.
- No reactivation for Suspended agents.
- Allowed deposits while Suspended.
Section sources
Purpose:
- Queue a withdrawal amount into an unbonding queue.
- Keep the queued amount slashable until claimed.
- Enforce minimum stake constraints for Active agents.
- Prevent multiple concurrent withdrawals per agent.
Preconditions:
- Agent must have non-zero active stake.
- No existing queued withdrawal.
- Caller must be the operator.
- No pending or disputed slash proposal.
- Requested amount must not exceed active stake.
Minimum stake enforcement:
- While Active, remaining stake after withdrawal must remain at or above minimumStake.
- Full exit requires the agent to be Suspended first.
Unbonding mechanics:
- Sets unbondingAmount and unbondingAt, and records the claim time in unbondingClaimableAt.
- Emits WithdrawalInitiated with claimableAt = current timestamp + unbondingPeriod. That time is fixed here: a later change to unbondingPeriod moves only withdrawals queued after it.
Edge cases:
- Cannot withdraw more than held; returns InsufficientStake with context.
- Cannot initiate while slash is Pending or Disputed.
- Cannot drain an Active agent below minimumStake; full exit requires Suspension.
flowchart TD
Start(["initiateWithdrawal Entry"]) --> HasStake{"s.amount > 0?"}
HasStake --> |No| RevertNoStake["Revert NoStake"]
HasStake --> |Yes| AlreadyQueued{"unbondingAmount == 0?"}
AlreadyQueued --> |No| RevertAlready["Revert WithdrawalAlreadyPending"]
AlreadyQueued --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> SlashState{"Slash Pending or Disputed?"}
SlashState --> |Yes| RevertSlash["Revert SlashAlreadyPending"]
SlashState --> |No| AmountOk{"amount <= s.amount?"}
AmountOk --> |No| RevertInsuf["Revert InsufficientStake(amount, requested)"]
AmountOk --> Remaining["remaining = s.amount - amount"]
Remaining --> ActiveCheck{"Agent Active?"}
ActiveCheck --> |Yes & remaining < minimumStake| RevertMin["Revert InsufficientStake(remaining, minimumStake)"]
ActiveCheck --> |No| SetQueue["Set unbondingAmount/unbondingAt"]
ActiveCheck --> |Yes & remaining >= minimumStake| SetQueue
SetQueue --> Emit["Emit WithdrawalInitiated(claimableAt)"]
Emit --> End(["Exit"])
Diagram sources
Examples:
- Partial withdrawal during Active operation: Queue part of the stake while keeping remaining stake at or above minimumStake.
- Full stake exit: First Suspend the agent, then queue the entire stake; claim after unbonding.
Test references:
- Queuing behavior and claimableAt calculation.
- Reverts for already queued, insufficient stake, below-minimum while Active, and pending slash.
- Full exit path through Suspension.
Section sources
Purpose:
- Allow the operator to claim the queued withdrawal after the unbonding period has elapsed.
- Ensure slash protections remain effective during the window.
Preconditions:
- There must be a queued withdrawal.
- Caller must be the operator.
- Slash must not be Pending or Disputed.
- Current block timestamp must be at or after the claimableAt fixed when the withdrawal was queued. A withdrawal queued before the unbondingClaimableAt field existed has none and uses its queue time plus the period in force at claim.
Transfer logic:
- Clears unbondingAmount, unbondingAt and the fixed claim time.
- Transfers the recorded amount to the operator.
- Emits WithdrawalClaimed.
Edge cases:
- Early claim attempts revert with UnbondingPeriodActive.
- Claiming with no queued withdrawal reverts with NoWithdrawalPending.
- Slash execution sweeps both active and queued amounts, leaving nothing to claim.
flowchart TD
Start(["claimWithdrawal Entry"]) --> HasQueue{"unbondingAmount > 0?"}
HasQueue --> |No| RevertNone["Revert NoWithdrawalPending"]
HasQueue --> |Yes| CheckOperator{"Caller is operator?"}
CheckOperator --> |No| RevertNotOp["Revert NotOperator"]
CheckOperator --> SlashGuard{"Slash Pending or Disputed?"}
SlashGuard --> |Yes| RevertSlash["Revert SlashAlreadyPending"]
SlashGuard --> |No| TimeCheck{"block.timestamp >= claimableAt?"}
TimeCheck --> |No| RevertTime["Revert UnbondingPeriodActive"]
TimeCheck --> |Yes| Clear["Clear unbondingAmount/unbondingAt"]
Clear --> Transfer["safeTransfer(operator, amount)"]
Transfer --> Emit["Emit WithdrawalClaimed"]
Emit --> End(["Exit"])
Diagram sources
Examples:
- Claim after unbonding: After waiting past claimableAt, claim the queued amount.
- Slash protection: Even if unbonding elapsed, a pending/disputed slash blocks the claim.
Test references:
- Successful claim after unbonding.
- Revert before unbonding.
- Revert when none queued.
- Slash protection even after unbonding elapsed.
Section sources
- The contract measures the token balance before and after the transfer to determine the exact amount received.
- This prevents over-crediting stake when a token deducts fees on transfer.
- Outbound transfers send the recorded amount; any shortfall due to fees lands on the recipient rather than being compensated by the contract.
- Rebasing tokens are explicitly unsupported because stake is stored as a fixed amount.
Practical implications:
- Deposits are safe against fee-on-transfer tokens.
- Withdrawals and slash payouts may deliver less than emitted values if the token charges fees on transfer.
- Operators should prefer standard ERC-20 tokens like SVR where possible.
Section sources
- Suspended agents can deposit stake to climb back above minimumStake, but deposits do not reactivate them.
- Slashed agents cannot deposit; their status is terminal.
- During a pending or disputed slash, operators cannot initiate new withdrawals or claim queued withdrawals.
- Slash execution sweeps both active stake and queued withdrawal, ensuring accountability even if an operator tries to queue funds to escape slashing.
Section sources
- SigvaraStaking.sol:309-364
- SigvaraStaking.sol:378-424
- SigvaraStaking.sol:432-462
- SigvaraStaking.sol:474-531
- SigvaraStaking.sol:715-778
- SigvaraStaking.t.sol:194-218
- SigvaraStaking.t.sol:342-400
- Register a new agent; its status becomes PendingBond.
- Approve the staking contract and call depositStake with at least minimumStake.
- The contract credits the received amount and updates the agent to Active.
SDK usage reference:
- The SDK helper approves allowance if needed and calls depositStake.
Section sources
- Deposit more than minimumStake to maintain a buffer.
- Call initiateWithdrawal with a partial amount such that remaining stake stays at or above minimumStake.
- Wait until claimableAt passes, then call claimWithdrawal.
Section sources
- Suspend the agent via the identity registry.
- Call initiateWithdrawal for the full stake amount.
- Wait until claimableAt passes, then call claimWithdrawal to receive the full amount.
Section sources
The stake management functions depend on:
- SigvaraIdentity: For reading AgentIdentity and updating status.
- IERC20: For token transfers and balance reads.
-
AccessControlUpgradeable: For role-based admin controls, through
TimelockedGrants. - InflowGuard: For the guardian's pause on deposits.
- ReentrancyGuard: To prevent reentrant calls.
classDiagram
class SigvaraStaking {
+svrToken : IERC20
+identityRegistry : SigvaraIdentity
+reputationRegistry : IReputationZero
+minimumStake : uint256
+challengePeriod : uint256
+unbondingPeriod : uint256
+depositStake(didHash, amount)
+initiateWithdrawal(didHash, amount)
+claimWithdrawal(didHash)
}
class SigvaraIdentity {
+getIdentity(didHash)
+updateStatus(didHash, status)
}
class IERC20 {
+balanceOf(address)
+safeTransferFrom(address,address,uint256)
+safeTransfer(address,uint256)
}
class IReputationZero {
+zeroReputation(bytes32)
}
SigvaraStaking --> SigvaraIdentity : "reads and updates"
SigvaraStaking --> IERC20 : "transfers and balances"
SigvaraStaking --> IReputationZero : "zeros reputation on slash"
Diagram sources
- SigvaraStaking.sol:46-52
- SigvaraStaking.sol:120-135
- SigvaraStaking.sol:321-364
- SigvaraStaking.sol:378-424
- SigvaraStaking.sol:432-462
Section sources
- Balance delta accounting: Two extra balanceOf calls around the transfer protect against fee-on-transfer tokens; the cost is justified by solvency correctness.
- Single queued withdrawal per agent: Simplifies state and avoids complex multi-queue accounting.
- Pull-based slash proceeds: The victim's share is credited and pulled later, avoiding failed transfers blocking settlement.
- Storage layout stability: New storage variables are appended to preserve mapping slots across upgrades; this avoids costly migration and corruption risks.
- Gas-efficient checks: Early reverts on invalid operator, slash state, and insufficient stake reduce unnecessary computation.
Storage efficiency patterns:
- Stake struct fields are ordered to avoid shifting live mappings on upgrade.
- Mappings stakes and slashProposals are pinned to specific slots; new variables are added after existing ones.
- ReentrancyGuard does not consume sequential storage slots in this inheritance chain, preserving slot layout.
Section sources
- SigvaraStaking.sol:105-120
- SigvaraStaking.sol:127-142
- SigvaraStaking.sol:780-789
- SigvaraStaking.t.sol:682-812
Common errors and causes:
- AgentNotActive: Attempting to deposit for an unregistered or slashed agent.
- NotOperator: Caller is not the registered operator for the agent.
- InsufficientStake: Withdrawing more than held or dropping below minimumStake while Active.
- WithdrawalAlreadyPending: Trying to queue another withdrawal while one is already queued.
- SlashAlreadyPending: Initiating withdrawal or claiming while slash is Pending or Disputed.
- NoWithdrawalPending: Claiming when there is no queued withdrawal.
- UnbondingPeriodActive: Claiming before claimableAt.
- InflowsPausedUntil: Depositing while the guardian's pause is running; deposits reopen when it ends, three days after it began at the latest.
Operational tips:
- Always approve the staking contract for the desired amount before depositing.
- Use getPendingWithdrawal to check claimableAt before attempting to claim. It reports the time fixed when the withdrawal was queued.
- If you need to fully exit, suspend the agent first, then queue the full stake.
- Be aware that slash proposals freeze stake activity; resolve or wait for expiration before withdrawing or claiming.
Section sources
- SigvaraStaking.sol:189-219
- SigvaraStaking.sol:321-364
- SigvaraStaking.sol:378-424
- SigvaraStaking.sol:432-462
- SigvaraStaking.t.sol:133-218
- SigvaraStaking.t.sol:295-340
The SigvaraStaking contract provides robust stake management with careful attention to security and operational clarity:
- Deposit logic validates operators, handles fee-on-transfer tokens safely, and auto-activates PendingBond agents once the minimum stake is reached.
- Withdrawal queuing preserves slashability during the unbonding period and enforces minimum stake constraints for Active agents.
- Claim logic enforces unbonding periods and slash-state guards, transferring only after all conditions are satisfied.
- Storage layout and pull-based payout patterns optimize upgrade safety and gas efficiency.
- Comprehensive tests cover normal flows, edge cases, and regression scenarios, providing confidence in the contract’s behavior under realistic conditions.
- 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