diff --git a/stakr/SKILL.md b/stakr/SKILL.md new file mode 100644 index 00000000..c4100f29 --- /dev/null +++ b/stakr/SKILL.md @@ -0,0 +1,106 @@ +--- +name: stakr-protocol +description: Interact with the Stakr protocol — ERC-4626 vaults with multi-reward staking. Use when the user or an agent needs to work with Stakr vaults, add or modify rewards (addRewardToken, addRewards, modifyRewardToken, modifyReward), create or manage an "agent vault" or "own vault", fund incentive programs, configure reward schedules, or integrate Stakr in scripts or tooling. Prefer this skill whenever Stakr, StakrVault, rewards, staking, or vault ownership is mentioned. +--- + +# Stakr Protocol — Agent Overview + +This skill gives agents the context to interact with the **Stakr** protocol: ERC-4626 tokenized vaults with **multi-reward staking** for any ERC-20 token. Use it when building integrations, scripts, or tooling that create vaults, add rewards, modify reward schedules, or let an agent operate its "own" vault. + +--- + +## Protocol at a Glance + +- **StakrVault**: Single-asset ERC-4626 vault. Users deposit underlying, get shares; they can **lock** shares to earn multiple reward tokens over configurable windows. +- **StakrVaultFactory**: Deploys vaults and holds protocol fee configuration. One factory per chain. +- **Rewards**: Up to 25 reward tokens per vault. Each reward has `startTime`, `endTime`, and total `amount`. Distribution is linear over the window; logic is Masterchef-style (accumulated rewards per share). +- **Ownership**: A vault can have an `owner` (address that can add/modify rewards) or `address(0)` for permissionless reward addition. + +When an agent is said to have its "own vault", it means: the agent (or a controlled EOA/contract) is the vault **owner**, so it can call `addRewardToken` and `modifyRewardToken` to fund and adjust rewards without third-party permission. + +--- + +## Emphasis: Adding and Modifying Rewards (Agent-Owned Vaults) + +Agents that operate their own vault will use these two functions most: + +### 1. `addRewardToken(token, amount, settings)` + +**Purpose**: Start a new reward program for a given ERC-20 token. + +- **Caller**: Vault **owner** (or anyone if `owner() == address(0)`). +- **Parameters**: + - `token`: ERC-20 reward token address. Cannot be the vault’s share token `address(vault)`. + - `amount`: Total amount of `token` to distribute. Tokens are pulled from `msg.sender`; protocol may take a fee (see factory `feeOnAddReward`). + - `settings`: `Settings{ startTime, endTime }`. Distribution is linear from `startTime` to `endTime`; both must be in the future and `startTime < endTime`. +- **Effects**: Registers the reward, pulls tokens (minus fee) into the vault, and emits `AddReward`. Rewards cannot be withdrawn once added; they can only be modified (extended or topped up) via `modifyRewardToken`. +- **Limits**: No duplicate reward token; vault cannot have more than 25 active rewards. + +Use this when the agent wants to **create a new reward** (e.g. launch an incentive program on its vault). + +### 2. `modifyRewardToken(token, amount, settings)` + +**Purpose**: Add more amount and/or extend (or reschedule) an existing reward. + +- **Caller**: Same as `addRewardToken` (vault owner or permissionless if owner is zero). +- **Parameters**: + - `token`: Address of an **already active** reward token. + - `amount`: **Additional** amount of `token` to add. Pulled from `msg.sender`; fee may apply. Cannot reduce existing amount. + - `settings`: `Settings{ startTime, endTime }`. Rules: + - If the reward has **not** yet ended (`currentTime <= reward.settings.endTime`): you can only **extend** `endTime` (and add more amount). `startTime` cannot be changed. + - If the reward **has** ended (`currentTime > reward.settings.endTime`): you can set a **new** window (`startTime`, `endTime`) and add amount; `accRewardsPerShare` is reset. +- **Effects**: Increases `remainingAmount` (and total amount) by `amount`, updates `endTime` (and possibly `startTime` if reward had ended), pulls tokens from `msg.sender`, and emits `ModifyReward`. + +Use this when the agent wants to **top up** an existing reward or **extend** the distribution period (or reschedule after it has ended). + +**Summary for agents**: + +- New reward → `addRewardToken(token, amount, settings)`. +- More reward or longer duration (or new window after end) → `modifyRewardToken(token, amount, settings)`. +- Ensure the vault has been created via the factory and the agent (or its controlled address) is the vault owner to call these. + +### 3. Streaming rewards (continuous incentives) + +Agents can **stream** rewards over time instead of funding one large window up front: + +- **Pattern**: Call `addRewardToken` once to start a reward (short amount if desired). Then call **`modifyRewardToken`** repeatedly to **add more amount** and **extend `endTime`**. Each call tops up the reward and pushes the end of the distribution window forward. +- **Why**: This avoids locking a huge amount for a long period. The agent (or a script/cron) can fund the vault in chunks and extend the window as needed, effectively creating a continuous reward stream. +- **Rules**: While the reward is active (`currentTime <= reward.settings.endTime`), `modifyRewardToken` only allows extending `endTime` and adding `amount`; `startTime` cannot be changed. After the reward has ended, the agent can set a brand‑new window with `modifyRewardToken` (new `startTime` and `endTime`) and keep streaming. + +Use streaming when the agent wants to fund incentives on an ongoing basis (e.g. weekly top‑ups, or extending the program as budget allows) rather than committing to a single long window. + +--- + +## Creating an Agent-Owned Vault + +1. Get the **StakrVaultFactory** address for the chain. + - On **Base mainnet**, the factory is deployed at `0x7Ef55108fa37472296DA59D2287FdA92cd21A0d0` ([view on BaseScan](https://basescan.org/address/0x7Ef55108fa37472296DA59D2287FdA92cd21A0d0)). + - For an example Stakr vault implementation on Base, see `0x93125009209e23fBAFf2B78712029F7A7CdD23cD` ([example vault on BaseScan](https://basescan.org/address/0x93125009209e23fbaff2b78712029f7a7cdd23cd)). +2. Call **`createStakrVault(underlying, name, symbol, description, owner)`**: + - `underlying`: ERC-20 underlying asset. + - `name`, `symbol`: Vault share token name/symbol. + - `description`: Short metadata (e.g. "Agent incentive vault"). + - `owner`: Set to the agent’s address (or the EOA/contract the agent controls). Use `address(0)` for permissionless reward add/modify by anyone. +3. Use the returned vault address for all subsequent calls (`addRewardToken`, `modifyRewardToken`, etc.). + +--- + +## Core User Flows (for completeness) + +- **Deposit only**: `deposit(assets, receiver)` (ERC-4626). +- **Deposit and lock**: `depositAndLock(assets, user)`. +- **Lock existing shares**: `lock(shares, user)` (vault must be approved for the shares). +- **Harvest**: `harvest(user)` to send pending rewards to `user`. +- **Unlock**: `unlock(shares, user)`; then optionally `unlockAndRedeem(shares, receiver)` to redeem shares for underlying. + +--- + +## Settings and Types + +- **Settings**: `struct Settings { uint256 startTime; uint256 endTime; }` +- **Validation**: `block.timestamp <= startTime` and `startTime < endTime` for new or rescheduled rewards. +- **Token**: Any ERC-20 except the vault’s share token. The underlying can be used as a reward. + +--- + +For exact function signatures, revert reasons, and events, read the [vault API](references/vault-api.md) in this skill when implementing calls or debugging. diff --git a/stakr/references/vault-api.md b/stakr/references/vault-api.md new file mode 100644 index 00000000..9fe9cf15 --- /dev/null +++ b/stakr/references/vault-api.md @@ -0,0 +1,107 @@ +# Stakr Vault API Reference + +Use this file when implementing calls, writing tests, or debugging. It summarizes the main Stakr contract interfaces and behavior. + +--- + +## StakrVaultFactory + +| Function | Signature | Notes | +| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| `createStakrVault` | `createStakrVault(address underlying, string _name, string _symbol, string _description, address _owner) returns (address vault)` | Deploys a new vault. `_owner` can be `address(0)` for permissionless add/modify rewards. | +| `availableStakrVaults` | `availableStakrVaults(address _underlying) view returns (uint256)` | Number of vaults for that underlying. | +| `stakrVaultByIndex` | `stakrVaultByIndex(address _underlying, uint256 _index) view returns (address)` | Vault address at index. | +| `setProtocolFee` | `setProtocolFee(uint256 _feeOnAddReward, uint256 _feeOnLock, address _feeCollector)` | Requires `FEE_MANAGER_ROLE`. | + +--- + +## StakrVault — Reward API + +### Structs + +```solidity +struct Settings { + uint256 startTime; // Start of distribution + uint256 endTime; // End of distribution +} +``` + +### addRewardToken + +```solidity +function addRewardToken(address token, uint256 amount, Settings calldata _settings) external +``` + +- **Modifiers**: `nonReentrant`, `checkOwnable` (owner or anyone if owner is `address(0)`). +- **Reverts**: `StakrVaultInvalidReward()` if `token == address(this)`, reward already active for `token`, or `_rewardsCount >= MAX_REWARDS_COUNT` (25). `StakrVaultInvalidSettings()` if `block.timestamp > startTime` or `startTime >= endTime`. +- **Side effects**: Pulls `token` from `msg.sender` (after optional fee to factory `feeCollector`). Cannot withdraw rewards later; use `modifyRewardToken` to add amount or extend. + +### modifyRewardToken + +```solidity +function modifyRewardToken(address token, uint256 amount, Settings calldata _settings) external +``` + +- **Modifiers**: `nonReentrant`, `checkOwnable`. +- **Reverts**: `StakrVaultInvalidReward()` if reward for `token` is not active. `StakrVaultInvalidSettings()` if `block.timestamp > _endTime` or `_startTime >= _endTime`, or (when reward has ended) if `block.timestamp > _startTime`. +- **Behavior**: + - If reward **not** ended: only `endTime` can be updated (and extra `amount` added). `startTime` unchanged. + - If reward **ended**: new `startTime`/`endTime` allowed; `accRewardsPerShare` reset. +- **Side effects**: Pulls `amount` of `token` from `msg.sender` (fee may apply). Updates `remainingAmount`, `amount`, and `settings.endTime` (and optionally `startTime`). + +### Streaming rewards (for agents) + +Agents can **stream** rewards by using `modifyRewardToken` repeatedly instead of funding one long window with `addRewardToken`: + +- Start with **`addRewardToken(token, amount, settings)`** (e.g. a short initial window). +- Then call **`modifyRewardToken(token, amount, settings)`** on a schedule (e.g. weekly): pass **additional** `amount` and a **new `endTime`** that extends the distribution window. The reward stays active and linear; stakers see a continuous stream. +- **While the reward is active**: only `endTime` can be updated (and extra amount added). **After the reward has ended**: a new full window (`startTime`, `endTime`) can be set and more amount added, then the cycle can repeat. + +This pattern lets agents fund incentives in chunks and extend the program over time without locking a large lump sum upfront. + +### Other reward-related + +| Function | Signature | Notes | +| ------------------ | --------------------------------------------- | ---------------------------------------------------------------------- | +| `rewardsCount` | `rewardsCount() view returns (uint256)` | Number of active rewards (max 25). | +| `updateStakrVault` | `updateStakrVault() external` | Updates internal accRewardsPerShare state; anyone can call. | +| `rewards` | `rewards(address) view returns (RewardToken)` | Reward config per token. | +| `rewardDebt` | `rewardDebt(bytes32) view returns (uint256)` | Per-user reward debt; ID = `keccak256(abi.encodePacked(user, token))`. | + +--- + +## StakrVault — Lock / Harvest / ERC-4626 + +| Function | Signature | Notes | +| ------------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | +| `lock` | `lock(uint256 _shares, address _user)` | Locks shares on behalf of `_user`; caller must transfer shares to vault first (or use `depositAndLock`). | +| `unlock` | `unlock(uint256 _shares, address _user)` | Unlocks and sends shares back to `msg.sender`. | +| `depositAndLock` | `depositAndLock(uint256 assets, address _user)` | Deposit underlying and lock resulting shares for `_user`. | +| `unlockAndRedeem` | `unlockAndRedeem(uint256 shares, address receiver)` | Unlock and redeem shares for underlying to `receiver`. | +| `harvest` | `harvest(address _user)` | Sends all pending rewards to `_user`. | +| `lockedShares` | `lockedShares(address) view returns (uint256)` | Locked share balance per user. | +| `owner` | `owner() view returns (address)` | Address allowed to add/modify rewards; `address(0)` = permissionless. | +| `transferOwnership` | `transferOwnership(address _newOwner)` | Owner only. | + +Standard ERC-4626: `deposit`, `redeem`, `mint`, `withdraw`, `asset`, `totalAssets`, etc. + +--- + +## Events + +- `AddReward(address indexed token, uint256 amount, uint256 startDate, uint256 endDate)` +- `ModifyReward(address indexed token, uint256 amount, uint256 startDate, uint256 endDate)` +- `DeleteReward(address indexed token)` — when a reward is fully harvested. +- `Lock(address indexed user, uint256 amount, uint256 lastLockedTime)` +- `Unlock(address indexed user, uint256 amount, uint256 lastLockedTime)` +- `Harvest(address indexed user, address token, uint256 amount)` + +--- + +## Errors + +- `StakrVaultInvalidReward()` — Invalid or duplicate reward token, or max rewards reached. +- `StakrVaultInvalidSettings()` — Invalid start/end times. +- `StakrVaultInsufficientBalance()` — Unlock amount exceeds locked balance. +- `StakrVaultNotAllowed()` — Caller is not owner (when owner is set). +- `StakrVaultMathError()` — Internal math revert (e.g. trySub failure).