.claude/skills/reliquary-architecture/SKILL.md
This skill should be used when the user asks about "Reliquary", "maturity-based incentives", "Relic NFT", "level", "curve multiplier", "rolling rewarder", or needs to understand how Reliquary's ERC-721 position management and time-weighted reward system works.
npx skillsauth add cyotee/crane reliquary-architectureInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reliquary is a maturity-based incentive system for ERC-20 LP tokens. Instead of a simple staking pool, positions earn increasing reward multipliers as they age. Each position is an ERC-721 "Relic" NFT.
Core idea: LP tokens deposited into Reliquary earn rewards that grow over time based on a configurable curve. The longer you leave a position, the higher your multiplier — creating time-locked incentive alignment.
Every position is an NFT. This allows:
A position's entry timestamp records when it was created. The level is derived from block.timestamp - entry — so it increases automatically over time.
The curve maps level (seconds of age) to a reward multiplier. A simple linear curve: multiplier = 1e18 + level * slope.
ICurves)getFunction(uint256 _level) → uint256 multiplier
| Curve | File | Description |
|-------|------|-------------|
| LinearCurve | curves/LinearCurve.sol | slope * level + minMultiplier — strictly increasing |
| LinearPlateauCurve | curves/LinearPlateauCurve.sol | Linear until plateau, then flat |
| PolynomialPlateauCurve | curves/PolynomialPlateauCurve.sol | Polynomial that plateaus at a max multiplier |
Constraint: curve.getFunction(0) must be > 0. Decreasing curves are not supported (they would break split/shift when combined with allowPartialWithdrawals=false).
PoolInfo)PoolInfo {
string name
uint256 accRewardPerShare // accumulated reward per share (scaled by 1e41)
uint256 totalLpSupplied // total effective LP units (amount * multiplier)
address nftDescriptor // returns tokenURI for the NFT
address rewarder // external rewarder contract (or address(0))
address poolToken // ERC-20 token deposited into this pool
uint40 lastRewardTime
bool allowPartialWithdrawals // if false, split/shift disabled
uint96 allocPoint // share of emission pool receives
ICurves curve
}
Three roles (keccak256 constants):
| Role | Hash | Who Has It | Functions |
|------|------|------------|-----------|
| DEFAULT_ADMIN_ROLE | 0x00 | Deployer | addPool, role admin |
| OPERATOR | keccak256("OPERATOR") | Granted by admin | modifyPool |
| EMISSION_RATE | keccak256("EMISSION_RATE") | Granted by admin | setEmissionRate |
Deployer gets DEFAULT_ADMIN_ROLE in constructor.
Constants:
ACC_REWARD_PRECISION = 1e41MAX_SUPPLY_ALLOWED = 100e9 etherPer-pool reward per second:
rewardPerSecond = emissionRate * pool.allocPoint / totalAllocPoint
Position effective balance: effectiveAmount = position.amount * curve.getFunction(position.level)
Pending reward:
pending = effectiveAmount * accRewardPerShare / ACC_REWARD_PRECISION + rewardCredit - rewardDebt
Important: Actual payout is min(pending, rewardToken.balanceOf(Reliquary)) — if the contract is underfunded, users receive less than pendingReward() shows.
There is no separate harvest() function. Harvesting is embedded in deposit(), withdraw(), and update() by passing a non-zero _harvestTo address.
reliquary.withdraw(amount, relicId, harvestTo); // withdraw + harvest in one tx
reliquary.update(relicId, harvestTo); // harvest-only (no deposit/withdraw)
Reliquary supports external rewarders via hooks. When a pool has a rewarder != address(0), Reliquary calls hook methods after every state change.
IRewarder HooksonDeposit(ICurves curve, uint256 relicId, uint256 depositAmount, uint256 oldAmount, uint256 oldLevel, uint256 newLevel)
onWithdraw(ICurves curve, uint256 relicId, uint256 withdrawalAmount, uint256 oldAmount, uint256 oldLevel, uint256 newLevel)
onUpdate(ICurves curve, uint256 relicId, uint256 amount, uint256 oldLevel, uint256 newLevel)
onSplit(ICurves curve, uint256 fromId, uint256 newId, uint256 amount, uint256 fromAmount, uint256 level)
onShift(ICurves curve, uint256 fromId, uint256 toId, uint256 amount, uint256 oldFromAmount, uint256 oldToAmount, uint256 fromLevel, uint256 oldToLevel, uint256 newToLevel)
onMerge(ICurves curve, uint256 fromId, uint256 toId, uint256 fromAmount, uint256 toAmount, uint256 fromLevel, uint256 oldToLevel, uint256 newLevel)
onReward(uint256 relicId, address to)
RollingRewarder (child)Time-based reward distribution. Key features:
fund(amount)distributionPeriod (default: 7 days)rewardPerSecond scaled by REWARD_PER_SECOND_PRECISION = 10_000 (allows fractional rates)rewardDebt and rewardCreditConstructor: constructor(address _rewardToken, address _reliquary, uint8 _poolId)
parent = msg.sender (the ParentRollingRewarder that deployed it)poolId is immutable — set at constructionAdmin: fund(amount), updateDistributionPeriod(seconds) — both onlyOwner (owner of parent)
ParentRollingRewarder (manager)Manages multiple RollingRewarder children (one per reward token).
Constructor: constructor() (Ownable — owner set to msg.sender)
Deploy: ParentRollingRewarder pr = new ParentRollingRewarder()
Add child: pr.createChild(rewardToken) → deploys a new RollingRewarder as a child
Fund child: child.fund(amount) — owner must approve child to pull rewardToken
Reliquary integration: When addPool(rewarder=address(pr)) is called, Reliquary calls pr.initialize(poolId), which stores reliquary = msg.sender (the Reliquary address). Only the Reliquary can call the rewarder hooks.
| Operation | Function | Constraints |
|----------|----------|-------------|
| Create + deposit | createRelicAndDeposit(to, poolId, amount) | Creates NFT, deposits in one tx |
| Deposit more | deposit(amount, relicId, harvestTo) | Must be approved/owner of relic |
| Withdraw | withdraw(amount, relicId, harvestTo) | Partial withdrawals allowed if pool allows |
| Harvest only | update(relicId, harvestTo) | harvestTo non-zero → pay rewards |
| Split | split(fromId, amount, to) | allowPartialWithdrawals == true, amount > 0 |
| Shift | shift(fromId, toId, amount) | Same pool, partial withdrawals allowed |
| Merge | merge(fromId, toId) | Same pool, burns fromId |
| Burn | burn(relicId) | amount == 0 && pending == 0 && rewarder.pendingTokens == 0 |
| Emergency | emergencyWithdraw(relicId) | Owner only, no rewards paid, burns NFT |
When addPool is called, it automatically creates a 1 wei deposit into the new pool via createRelicAndDeposit(_to, newPoolId_, 1). This means:
addPool must hold and approve at least 1 unit of the pool token_to addresscontracts/protocols/staking/reliquary/v1/
├── Reliquary.sol # Core contract
├── interfaces/IReliquary.sol # Interface + structs + errors
├── interfaces/ICurves.sol # Curve interface (getFunction)
├── interfaces/IRewarder.sol # Rewarder hook interface
├── interfaces/IRollingRewarder.sol # RollingRewarder interface
├── interfaces/IParentRollingRewarder.sol # ParentRollingRewarder interface
├── curves/
│ ├── LinearCurve.sol
│ ├── LinearPlateauCurve.sol
│ └── PolynomialPlateauCurve.sol
├── rewarders/
│ ├── RollingRewarder.sol
│ └── ParentRollingRewarder.sol
├── helpers/
│ ├── DepositHelperERC4626.sol # ERC4626 vault helper
│ ├── DepositHelperReaperVault.sol # Reaper-style vault helper
│ └── DepositHelperReaperBPT.sol # BPT zap helper
└── services/
└── ReliquaryService.sol # Internal reward math library
skill:reliquary-deployment — practical deployment and usage guidecontracts/protocols/staking/reliquary/v1/test/bases/TestBase_Reliquary.sol — test deployment patterndevelopment
Review UI code for Web Interface Guidelines compliance. Use when asked to "review my UI", "check accessibility", "audit design", "review UX", or "check my site against best practices".
documentation
Write to contracts and send transactions. Use when executing state-changing contract functions.
development
HTTP and WebSocket transports for blockchain connectivity. Use when configuring network connections.
data-ai
Read contract data with type-safe ABI. Use when querying smart contract view/pure functions.