Regen Staker
Purpose: Explain how to choose, deploy, and operate Octant's community staking module. Audience: Developers integrating staking, rewards, and contribution flows into an Octant deployment. Level: Intermediate Source of truth:
[email protected]and theRegenStaker,RegenStakerBase, andRegenEarningPowerCalculatorreference pages. Use this page when: you need to choose between staking variants and understand the main configuration and operational flows. Do not use this page for: final token-compatibility assumptions or exact method details without checking the underlying source and reference pages.
- Required: Core Concepts (you understand the donation and routing model)
RegenStaker is Octant v2's community staking module. It lets users stake an ecosystem token, earn streamed rewards, optionally compound those rewards, and contribute unclaimed rewards to approved allocation mechanisms.
Use the delegation-enabled RegenStaker variant when the stake token supports onchain delegation and users need to keep voting power while staked. Use RegenStakerWithoutDelegateSurrogateVotes when the stake token does not support that model or when you want a simpler setup without delegation surrogates.
Task card
Ask a coding assistant to follow this page in order and run the verification commands before moving on. It should not invent contract addresses, use placeholder implementation addresses, or skip fork/RPC prerequisites.
| Field | Value |
|---|---|
| Goal | Choose a RegenStaker variant, deploy it via factory or constructor, configure access and rewards, and verify the deployment is operational. |
| Core target pin | octant-v2-core@06ae2b9f |
| Files to edit | Your deployment script (e.g. a Foundry script importing DeployRegenStaker.s.sol), .env with constructor parameters. |
| Files NOT to edit | RegenStaker.sol, RegenStakerBase.sol, RegenStakerFactory.sol, RegenEarningPowerCalculator.sol (these are upstream contracts). |
| Required setup | Foundry installed, octant-v2-core cloned and dependencies installed, RPC endpoint for target chain (or Anvil fork), funded deployer account. |
| Verification | After deployment: call STAKE_TOKEN(), REWARD_TOKEN(), rewardDuration(), minimumStakeAmount() on the deployed staker and confirm they match your parameters. Confirm earningPowerCalculator() returns your calculator address. If using the factory, confirm the factory emitted a creation event with the expected address. |
| Do not | Invent contract addresses. Use placeholder implementation addresses. Skip token-compatibility checks. Deploy without reviewing every address in allocationMechanismAllowset. Assume compounding works when reward token differs from stake token. |
(See also: Agent Anti-Patterns)
This guide explains deployment and operation at a workflow level. When you need exact methods, events, or role-gated behavior, keep the matching reference pages open: RegenStaker, RegenStakerBase, and RegenEarningPowerCalculator. Use in production only after confirming token compatibility, access-control policy, approved allocation mechanisms, deployment method, and your operational response plan for paused or misconfigured states.
RegenStaker extends ScopeLift's Staker with Octant-specific features: configurable reward duration, configurable minimum stake amount, access control for stakers, an allowset for approved allocation mechanisms, optional reward compounding, pause controls, and contribution flows into allocation mechanisms.
What this contract is for
Token staking lets community members lock tokens to demonstrate commitment and, in return, earn rewards or influence over how funds are allocated. In Octant, staking is optional — it is a feature Capital Providers can enable to involve their community in allocation decisions.
Use Regen Staker when you want to create a community staking pool around an ecosystem token and fund stakers with a separate reward stream. In an Octant deployment, that reward stream will often come from yield routed into the staking contract.
If the same yield stream also needs to fund other recipients, put a Payment Splitter upstream and treat RegenStaker as one recipient among several.
At a high level, the lifecycle looks like this:
- Users stake a token.
- Rewards are streamed over a configurable distribution period.
- Users can claim rewards, compound them when the reward token and stake token are the same, or contribute unclaimed rewards to an approved allocation mechanism.
- In the delegation-enabled variant, users keep governance delegation while tokens are staked by using surrogate contracts.
Which variant should you choose?
Use RegenStaker when
- your stake token implements
IERC20StakingandIERC20Permit, - users need governance delegation while their tokens are staked,
- you accept the extra gas and complexity of surrogate-based delegation.
In this variant, the contract deploys or reuses a delegation surrogate for each delegatee. Tokens are held in the surrogate, and the surrogate delegates voting power to the chosen delegatee. This is useful when your governance token has voting power — stakers do not have to give up influence just because tokens are locked.
Use RegenStakerWithoutDelegateSurrogateVotes when
- your stake token is a standard ERC-20 without delegation support,
- users do not need voting power while staked,
- you want a simpler and cheaper staking setup.
The shared configuration model is largely the same across both variants, but the no-delegation contract is a distinct deployment target with different custody behavior and no delegatee-management flow.
Choose RegenStaker (with delegation) when your community token has governance voting that participants want to retain while staking. The surrogate pattern lets you custody their tokens while keeping their voting power live.
Choose RegenStakerWithoutDelegateSurrogateVotes when your token has no external governance, participants do not need to delegate votes, or you want to minimize gas costs and operational complexity. This variant is also appropriate when voting power is managed separately from staking.
Off-chain signatures are variant-specific. The delegation-enabled contract uses the EIP-712 domain name "RegenStaker", while RegenStakerWithoutDelegateSurrogateVotes uses "RegenStakerWithoutDelegateSurrogateVotes". Both use version "1" through RegenStakerBase. Signers, relayers, and typed-data builders must choose the domain name that matches the deployed variant.
Core concepts
Staker access and reward eligibility are separate concerns
RegenStaker controls who is allowed to stake through:
stakerAllowsetstakerBlocksetstakerAccessMode
RegenEarningPowerCalculator separately controls whether a staker has earning power through its own:
allowsetblocksetaccessMode
That separation matters. A user may be allowed to hold a deposit in the staking contract but still have zero earning power if the calculator's access rules exclude them.
Access control is explicit and mode-based
The current API uses explicit AccessMode values rather than treating address(0) as the main control surface.
NONE: permissionlessALLOWSET: only addresses in the allowset have accessBLOCKSET: all addresses except those in the blockset have access
Inactive sets may still be passed as address(0), but disabling access control is done by setting AccessMode.NONE.
Contribution flows are gated by approved mechanisms
Users can contribute unclaimed rewards to an allocation mechanism, but only if that mechanism is in allocationMechanismAllowset.
That allowset is security-critical. Funds contributed to a malicious or buggy mechanism may be unrecoverable. Only vetted mechanisms should be approved.
canSignup()RegenStaker hard-casts the contribution destination to the OctantQFMechanism interface and calls canSignup(...) on it. A custom TAM that does not implement canSignup() will cause contributions to revert even when it is in allocationMechanismAllowset. If you are building a custom mechanism intended to receive RegenStaker contributions, implement canSignup() — see Writing a new funding mechanism.
Authorization is split across layers:
RegenStakerchecks whether the destination mechanism is approved.RegenStakeralso checks that the mechanism uses the same asset as the reward token.- The allocation mechanism checks whether the relevant participant is allowed to take part under its own rules.
One subtle but important detail: contribution eligibility is a dual check. The mechanism's canSignup(deposit.owner) must accept the deposit owner, and the mechanism's signup hook independently checks the caller (signupOnBehalfWithSignature(msg.sender, …)). contribute also requires a valid EIP-712 contributor signature. Voting power from a successful contribution is credited to the caller.
Claimers are trusted, but not all-powerful
A deposit owner can designate a claimer. Claimers can:
- claim rewards,
- compound rewards,
- contribute unclaimed rewards to approved allocation mechanisms.
Claimers cannot:
- withdraw principal,
- change deposit parameters,
- alter the delegatee,
- alter the claimer assignment itself.
One subtle but important detail: when rewards are contributed to an allocation mechanism, the caller receives the voting power there. If a claimer contributes on behalf of the owner, the claimer gets the allocation-mechanism voting power.
Pausing does not lock user principal
Pausing disables user actions such as staking, claiming, compounding, contributing, and deposit-management actions like changing delegatees or claimers, but withdrawals remain available. That is an intentional protection for depositors.
Compounding only works when reward token equals stake token
If REWARD_TOKEN != STAKE_TOKEN, users can still claim or contribute rewards, but compounding is not available.
Deployment checklist
Before deployment, decide the following.
Token model
- Stake token: the token users will lock in the staking pool.
- Reward token: the token streamed as rewards.
Both must be standard ERC-20 tokens. Non-standard transfer semantics, fee-on-transfer behavior, rebasing, or deflationary mechanics are not supported.
For the delegation-enabled variant, the stake token must also implement:
IERC20StakingIERC20Permit
Access model
Decide separately:
- who may open or top up stakes,
- who is eligible to earn rewards,
- which allocation mechanisms are approved as destinations for contributed rewards.
A common configuration is:
- staking access:
NONEfor open participation, - earning power access:
ALLOWSETorBLOCKSETif reward eligibility should be curated, - allocation mechanisms: a strict allowset containing only reviewed contracts.
Reward configuration
Choose:
rewardDuration,minimumStakeAmount,maxBumpTip.
rewardDuration must stay between 7 days and 3000 days. minimumStakeAmount affects new deposits immediately, while older deposits below a later-raised threshold are grandfathered but become restricted in certain operations.
Recommended deployment flow
Step 1: Decide whether to deploy directly or via factory
Octant v2 includes a RegenStakerFactory (deployed at 0x6a8250C95d2e866e95fe4749eD540357B8e44a9a — see Deployed Addresses) with dedicated creation paths for the delegation-enabled and no-delegation variants. Use the factory when you want deterministic CREATE2 deployment and a standard Octant deployment path.
Both factory methods accept a CreateStakerParams struct that bundles the constructor arguments:
struct CreateStakerParams {
IERC20 rewardsToken;
IERC20 stakeToken;
address admin;
IAddressSet stakerAllowset;
IAddressSet stakerBlockset;
AccessMode stakerAccessMode;
IAddressSet allocationMechanismAllowset;
IEarningPowerCalculator earningPowerCalculator;
uint256 maxBumpTip;
uint256 minimumStakeAmount;
uint256 rewardDuration;
}
Although CreateStakerParams exposes minimumStakeAmount and rewardDuration as uint256, the factory ABI-encodes those values into constructors whose corresponding parameters are uint128. Keep both values within uint128 bounds; rewardDuration must also satisfy the contract's 7-day to 3000-day duration check. maxBumpTip remains a uint256.
The two factory methods are:
// Delegation-enabled variant
function createStakerWithDelegation(
CreateStakerParams calldata params,
bytes32 salt, // caller-chosen salt for deterministic address
bytes calldata code // creation bytecode for the RegenStaker variant
) external returns (address);
// No-delegation variant
function createStakerWithoutDelegation(
CreateStakerParams calldata params,
bytes32 salt,
bytes calldata code // creation bytecode for RegenStakerWithoutDelegateSurrogateVotes
) external returns (address);
The salt parameter gives you a deterministic deployment address via CREATE2, which is useful when governance proposals must reference the staker address before it exists on-chain. The code parameter is the creation bytecode for the specific variant — the factory validates it against a pinned bytecode hash to prevent deploying arbitrary contracts through the factory.
Sourcing the code argument
The factory rejects any bytecode whose keccak256 hash does not match the hash pinned at factory-deploy time. You cannot pass arbitrary bytecode — it must be the exact creation code for RegenStaker or RegenStakerWithoutDelegateSurrogateVotes as compiled from octant-v2-core.
The recommended way to obtain the correct bytecode is to use the existing deploy scripts in the core repo rather than assembling it by hand:
script/deploy/DeployRegenStaker.s.sol— the production deploy script. It imports pre-computed bytecode constants fromscript/prod/RegenStakerBytecodes.sol(REGEN_STAKER_V1_CREATION_CODEandREGEN_STAKER_WITHOUT_DELEGATION_V1_CREATION_CODE) and passes the correct one to the factory based on the variant you choose. It reads allCreateStakerParamsfields from environment variables (REWARDS_TOKEN,STAKE_TOKEN,ADMIN,STAKER_ALLOWSET,STAKER_BLOCKSET,ACCESS_MODE,ALLOCATION_ALLOWSET,EARNING_POWER_CALCULATOR,MAX_BUMP_TIP,MIN_STAKE,REWARD_DURATION) plusREGEN_STAKER_FACTORYfor the factory address. Salts default to"OCTANT_REGEN_STAKER_WITH_DELEGATION_V1"and"OCTANT_REGEN_STAKER_WITHOUT_DELEGATION_V1"but can be overridden viaREGEN_STAKER_WITH_DELEGATION_SALT/REGEN_STAKER_WITHOUT_DELEGATION_SALT.script/deploy/DeployRegenStakerFactory.s.sol— deploys the factory itself. It computes the two bytecode hashes inline viakeccak256(type(RegenStaker).creationCode)andkeccak256(type(RegenStakerWithoutDelegateSurrogateVotes).creationCode), then passes them as constructor arguments to the factory. You only need this script if you are deploying your own factory instance.
If you need to compute the bytecode yourself (for example, in a custom script or from a multisig proposal), use Solidity's type(RegenStaker).creationCode expression. The result must hash to the same value the factory was initialised with.
The factory will revert if keccak256(code) does not match the hash it was initialised with. Common causes: you compiled with a different Solidity version or optimizer setting than the factory expects, you passed the bytecode for the wrong variant (delegation vs. no-delegation), or you assembled the creation code by hand and introduced a mismatch. To resolve this, use the pre-computed bytecode constants from script/prod/RegenStakerBytecodes.sol (REGEN_STAKER_V1_CREATION_CODE or REGEN_STAKER_WITHOUT_DELEGATION_V1_CREATION_CODE) or run the official DeployRegenStaker.s.sol script, which handles variant selection automatically.
If you deploy directly with constructors instead of using the factory, make sure you are using the correct variant and that your constructor arguments have been reviewed carefully. The factory validates the deployment bytecode path, but it does not remove the need to validate constructor arguments, admin addresses, allowsets, and calculator wiring.
Step 2: Deploy the address-set contracts you need
Depending on your setup, you may need:
- a staker allowset,
- a staker blockset,
- an earning-power allowset,
- an earning-power blockset,
- an allocation-mechanism allowset.
You do not need every set. What matters is that your deployed sets match your chosen AccessMode values.
One hard requirement stands out: allocationMechanismAllowset cannot be address(0).
Step 3: Deploy the earning power calculator
The current constructor is:
RegenEarningPowerCalculator calculator = new RegenEarningPowerCalculator(
owner,
earningPowerAllowset,
earningPowerBlockset,
earningPowerAccessMode
);
Use this contract when you want earning power to be linear in staked amount, capped at uint96, with access control layered on top.
Operationally:
- users with access get earning power equal to their staked amount,
- users without access get earning power
0.
Be careful when changing calculator access rules after launch. Those changes are non-retroactive for existing deposits until earning power is bumped.
Step 4: Deploy the staking contract
For the delegation-enabled variant, the constructor is:
RegenStaker regenStaker = new RegenStaker(
rewardsToken,
stakeToken,
calculator,
maxBumpTip,
admin,
rewardDuration,
minimumStakeAmount,
stakerAllowset,
stakerBlockset,
stakerAccessMode,
allocationMechanismAllowset
);
Parameter meanings:
rewardsToken: token streamed as rewards,stakeToken: token users stake,calculator: earning power calculator,maxBumpTip: maximum tip for earning power bumps,admin: trusted admin address, preferably a multisig,rewardDuration: reward streaming duration,minimumStakeAmount: minimum valid stake,stakerAllowset: allowset used whenstakerAccessMode == ALLOWSET,stakerBlockset: blockset used whenstakerAccessMode == BLOCKSET,stakerAccessMode:NONE,ALLOWSET, orBLOCKSET,allocationMechanismAllowset: approved contribution destinations.
If you are deploying the no-delegation variant, keep the same mental model and shared configuration, but use RegenStakerWithoutDelegateSurrogateVotes as the deployment target.
Step 5: Seed and operate rewards
After deployment, you still need to make the pool operational:
- authorize the appropriate reward notifier flow from the underlying staking system,
- transfer or approve reward tokens as required,
- notify reward amounts according to your chosen distribution schedule.
For exact notifier and reward-notification signatures, treat the reference pages as the source of truth.
One variant-specific detail matters here. In the no-delegation variant, when the reward token and stake token are the same asset, reward-balance checks must coexist with principal already held in the staking contract. Account for that in your funding and operational testing.
Post-deployment admin controls
The most important admin methods are:
Staker access
setStakerAllowset(...)setStakerBlockset(...)setAccessMode(...)
Use these to change who may stake. These changes affect new staking activity immediately.
Approved allocation mechanisms
setAllocationMechanismAllowset(...)
Treat this like a security-sensitive governance action. Only add mechanisms you trust.
Reward policy
setRewardDuration(...)setMinimumStakeAmount(...)setMaxBumpTip(...)setEarningPowerCalculator(...)
A few restrictions matter:
setRewardDurationcannot be used during an active reward period.- Raising
minimumStakeAmountduring an active reward period is blocked. - Increasing
maxBumpTipduring an active reward period is also blocked.
Emergency controls
pause()unpause()
Remember that pause does not disable withdrawals.
What the user journey looks like
At a conceptual level, users interact with Regen Staker in six ways:
- open a deposit,
- add stake to an existing deposit,
- claim rewards,
- compound rewards when supported,
- withdraw stake,
- contribute unclaimed rewards to an approved allocation mechanism.
For the delegation-enabled variant, each new deposit also specifies a delegatee. The first time a particular delegatee is used, the contract may have to deploy a surrogate, which makes that transaction materially more expensive than later deposits using the same delegatee.
For exact external signatures, use the reference docs for the deployed variant and RegenStakerBase. This page explains the operational model, it is not a substitute for the ABI reference.
Practical configuration patterns
Pattern A: Open community staking
Use this when you want a straightforward staking pool with no participant gating.
Suggested setup:
stakerAccessMode = NONE- calculator
accessMode = NONE - conservative
minimumStakeAmount - strict
allocationMechanismAllowset
This is the simplest setup to understand and operate.
Pattern B: Open staking, curated rewards
Use this when anyone may stake, but only approved users should earn rewards.
Suggested setup:
stakerAccessMode = NONE- calculator
accessMode = ALLOWSETorBLOCKSET - reward administrators prepared to bump earning power after access-list changes
This pattern is useful when deposits should remain open but reward eligibility needs governance oversight.
Pattern C: Governance-token staking with delegation
Use this when stakers should keep governance influence while tokens remain locked.
Suggested setup:
- delegation-enabled
RegenStaker, IERC20Staking-compatible stake token,- communication around higher first-deposit gas for new delegatees,
- strict review of approved contribution destinations.
Common gotchas
"I changed access rules, but old deposits still behave the old way."
That is expected for the earning power calculator. Access-mode changes there are non-retroactive until earning power is bumped.
"A stake transaction for one delegatee costs much more than another."
That is expected in the delegation-enabled variant. The first use of a delegatee may deploy a surrogate. Later deposits reusing the same delegatee are cheaper.
"The contract is paused, but users can still withdraw."
That is intentional. Pause is designed to stop new reward-sensitive operations without trapping principal.
"Compounding reverts or is unavailable."
Check whether reward token and stake token are the same asset. Compounding depends on that.
"Contributions reverted even though the mechanism looked valid."
There are several checks to review:
- whether the mechanism is in
allocationMechanismAllowset, - whether the mechanism uses the same asset as the reward token,
- whether the deposit owner is accepted by the mechanism's signup logic (
canSignup(deposit.owner)), - whether the caller is the owner or assigned claimer for that deposit,
- whether the caller itself is accepted by the mechanism's signup hook (the hook checks
msg.sender, not just the owner), - whether the EIP-712 contributor signature is valid — an expired signature or one signed for the wrong domain/chain will revert.
"Can users contribute without moving any rewards yet?"
Yes. A zero-amount contribution can still be useful when the goal is to enter the allocation mechanism or establish voting power without routing any current rewards.
Security notes
A few security points deserve extra emphasis:
- Allocation mechanism approval is a trust decision. Do not treat it as a UI convenience list.
- Token compatibility matters. Non-standard ERC-20 behavior can break accounting.
- Admin changes have operational consequences. Access and minimum-stake changes can affect user behavior immediately.
- Claimers should be assigned deliberately. They can redirect reward usage into contribution flows and receive allocation-mechanism voting power when they contribute.
- Use a multisig for admin in production. This contract has meaningful governance and security controls.
- Test both contribution and reward-notification paths with real token assumptions. Same-token and cross-token setups behave differently.
Before production
Before sending real rewards through a Regen Staker deployment, confirm that you have:
- selected the correct variant for your token, especially around delegation support,
- chosen whether to deploy directly or via
RegenStakerFactory, - verified stake token, reward token, and compounding assumptions,
- chosen and tested your
stakerAccessModeand earning-power access model, - reviewed every address in
allocationMechanismAllowsetas a trust decision, not just a convenience list, - tested staking, claiming, compounding, contributing, pausing, and withdrawing with realistic roles and user states,
- tested contribution reverts for mismatched asset or mechanism signup restrictions,
- documented who can change access rules, minimum stake, reward duration, and approved mechanisms,
- prepared user communication for pause events, access changes, and delegation-specific gas behaviour,
- arranged an independent security review before handling real funds.
RegenStakerFactory method signatures
Signature as of
06ae2b9f. Full reference: RegenStakerFactory.
| Method | Signature |
|---|---|
createStakerWithDelegation | createStakerWithDelegation(CreateStakerParams calldata params, bytes32 salt, bytes calldata code) external returns (address) |
createStakerWithoutDelegation | createStakerWithoutDelegation(CreateStakerParams calldata params, bytes32 salt, bytes calldata code) external returns (address) |
Source of truth
Use this page to understand the operational model. For exact signatures and edge-case behavior, treat these reference pages as authoritative:
That split is intentional: this guide explains how to reason about deployment and operation, while the reference docs remain the normative ABI-level source.