.claude/skills/reliquary-deployment/SKILL.md
This skill should be used when the user asks to "deploy Reliquary", "add a Reliquary pool", "set up rolling rewarder", "create relic", "deposit into Reliquary", "withdraw from Reliquary", or needs practical guidance on deploying and interacting with Reliquary smart contracts.
npx skillsauth add cyotee/crane reliquary-deploymentInstall 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 standard non-Diamond contract. Deploy it directly with new:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {Reliquary} from "@crane/contracts/protocols/staking/reliquary/v1/Reliquary.sol";
import {LinearCurve} from "@crane/contracts/protocols/staking/reliquary/v1/curves/LinearCurve.sol";
import {MockERC20} from "@crane/contracts/test/mocks/MockERC20.sol";
contract MyReliquaryDeploy {
function deploy() external {
// 1. Deploy reward token (any ERC20)
MockERC20 rewardToken = new MockERC20("Reward", "RWD", 18);
// 2. Deploy Reliquary — deployer gets DEFAULT_ADMIN_ROLE
Reliquary reliquary = new Reliquary({
_rewardToken: address(rewardToken),
_emissionRate: 1e18, // 1 reward token per second
_name: "Reliquary Deposit",
_symbol: "RELIC"
});
// 3. Fund Reliquary with reward tokens (must have balance to pay rewards)
rewardToken.mint(address(this), 1_000_000e18);
rewardToken.transfer(address(reliquary), 1_000_000e18);
// 4. Deploy a curve for the pool
LinearCurve curve = new LinearCurve({
_slope: 100, // multiplier increases by 100 per second
_minMultiplier: 365 days * 100 // f(0) = 365e5 (no lock = 100% annual boost)
});
// 5. Approve Reliquary to spend pool token (for addPool bootstrap)
MockERC20 poolToken = new MockERC20("Pool", "POOL", 18);
poolToken.mint(address(this), 1_000_000e18);
poolToken.approve(address(reliquary), type(uint256).max);
// 6. Add pool — triggers 1 wei bootstrap deposit (caller must hold + approve 1 wei)
reliquary.addPool({
_allocPoint: 100,
_poolToken: address(poolToken),
_rewarder: address(0), // or address of ParentRollingRewarder
_curve: curve,
_name: "My Pool",
_nftDescriptor: address(0),
_allowPartialWithdrawals: true,
_to: address(this) // receives the bootstrap 1 wei Relic
});
// 7. Create a Relic and deposit actual tokens
poolToken.approve(address(reliquary), type(uint256).max);
uint256 relicId = reliquary.createRelicAndDeposit({
_to: address(this),
_poolId: 0,
_amount: 1000e18
});
}
}
For production with deterministic addresses, use Crane's Create3Factory:
// Deploy Reliquary deterministically via Create3Factory
ICreate3FactoryProxy create3Factory = ...; // from InitDevService.initEnv()
bytes memory initArgs = abi.encode(
address(rewardToken), // _rewardToken
1e18, // _emissionRate
"Reliquary Deposit", // _name
"RELIC" // _symbol
);
// Salt derived from type name (Crane convention)
bytes32 salt = keccak256(abi.encode("Reliquary"));
address reliquaryAddr = create3Factory.deploy(
type(Reliquary).creationCode,
initArgs,
salt
);
vm.label(reliquaryAddr, "Reliquary");
Before calling addPool, the caller must:
DEFAULT_ADMIN_ROLE on Reliquary// Approve and add pool
IERC20(poolToken).approve(address(reliquary), type(uint256).max);
reliquary.addPool({
_allocPoint: 100,
_poolToken: address(poolToken),
_rewarder: address(0),
_curve: curve,
_name: "ETH/USDC LP",
_nftDescriptor: nftDescriptor,
_allowPartialWithdrawals: true,
_to: address(dao) // DAO receives the bootstrap Relic
});
// 1. Deploy ParentRollingRewarder first (its owner will manage children)
ParentRollingRewarder rewarder = new ParentRollingRewarder();
// 2. Add pool with rewarder — Reliquary calls rewarder.initialize(poolId)
reliquary.addPool({
_allocPoint: 100,
_poolToken: address(poolToken),
_rewarder: address(rewarder),
_curve: curve,
_name: "My Pool",
_nftDescriptor: address(0),
_allowPartialWithdrawals: true,
_to: address(this)
});
// 3. As rewarder owner: create a child for each reward token
address child = rewarder.createChild(address(rewardToken));
// 4. Fund the child — owner approves and calls fund()
IERC20(rewardToken).approve(child, type(uint256).max);
RollingRewarder(child).fund(100_000e18);
// 5. Optionally set distribution period before funding
RollingRewarder(child).updateDistributionPeriod(30 days);
// Approve Reliquary to spend your pool tokens
IERC20(poolToken).approve(address(reliquary), type(uint256).max);
// Deposit and harvest rewards in one transaction
reliquary.deposit({
_amount: 500e18,
_relicId: 1,
_harvestTo: address(this) // non-zero = harvest too
});
uint256 relicId = reliquary.createRelicAndDeposit({
_to: address(this),
_poolId: 0,
_amount: 1000e18
});
reliquary.withdraw({
_amount: 200e18,
_relicId: 1,
_harvestTo: address(this) // harvests rewards while withdrawing
});
reliquary.update({
_relicId: 1,
_harvestTo: address(this) // non-zero address triggers harvest
});
// Requires allowPartialWithdrawals == true for this pool
uint256 newId = reliquary.split({
_relicId: 1,
_amount: 100e18,
_to: address(otherPerson)
});
// Both relics must be in the same pool
reliquary.merge({
_fromId: 2,
_toId: 1 // burns fromId, moves all amount into toId
});
// Can only burn if amount == 0 AND no pending rewards
reliquary.burn(relicId);
For pools with ERC4626 vault or Reaper vault poolTokens, use the helpers instead of direct Reliquary calls.
// Deploy helper
DepositHelperERC4626 helper = new DepositHelperERC4626({
_reliquary: reliquary,
_weth: address(weth)
});
// Approve helper to pull underlying (e.g., USDC)
IERC20(underlying).approve(address(helper), type(uint256).max);
// Create relic + deposit underlying (helper converts to vault shares, deposits to Reliquary)
uint256 relicId = helper.createRelicAndDeposit{value: 0}(0, amount);
// Withdraw — optionally as ETH
helper.withdraw(amount, relicId, harvest, withdrawAsETH);
// Deposit ETH directly
uint256 relicId = helper.createRelicAndDeposit{value: 1 ether}(0, 1 ether);
DepositHelperReaperVault helper = new DepositHelperReaperVault({
_reliquary: reliquary,
_weth: address(weth)
});
// Approve underlying token
IERC20(underlying).approve(address(helper), type(uint256).max);
// Deposit
(uint256 relicId, uint256 shares) = helper.createRelicAndDeposit(0, amount);
// Withdraw underlying (optionally as ETH)
helper.withdraw(amount, relicId, harvest, asETH);
Reliquary pays rewards from its own balance. If rewardToken.balanceOf(reliquary) < pendingReward, users receive less than shown in pendingReward(). Keep Reliquary funded by topping up its balance periodically.
If allowPartialWithdrawals == false:
split() and shift() are disabledaddPool)totalSupply() must be ≤ 100e9 ethercurve.getFunction(0) must be > 0emissionRate * 10years * ACC_REWARD_PRECISION * curve.getFunction(10years) checked at addPool timePool IDs are uint8 — maximum 255 pools per Reliquary.
Use TestBase_Reliquary for tests:
contract MyReliquaryTest is TestBase_Reliquary {
function testDeposit() public {
// Inherits: reliquary, linearCurve, rewardToken, poolToken, etc.
// setUp() already: deploys Reliquary, adds pool 0 with linearCurve
uint256 relicId = _createAndDeposit(0, 1000e18);
assertEq(reliquary.getPositionForId(relicId).amount, 1000e18);
}
}
For ERC4626/Reaper vault pools, extend TestBase_Reliquary:
contract MyERC4626PoolTest is TestBase_Reliquary {
DepositHelperERC4626 helper;
IERC4626 vault;
MockERC20 oath;
WETH9 weth;
function setUp() public override {
TestBase_Reliquary.setUp();
oath = new MockERC20("Oath", "OATH", 18);
Reliquary r = new Reliquary(address(oath), 1e17, "Rel", "REL");
reliquary = IReliquary(address(r));
weth = new WETH9();
vault = new ERC4626Mock(address(weth), "Vault", "VLT", 18);
linearCurve = new LinearCurve(100, 365 days * 100);
// ... add pool with vault as poolToken
}
}
skill:reliquary-architecture — conceptual model, curves, rewarderscontracts/protocols/staking/reliquary/v1/test/bases/TestBase_Reliquary.sol — test deployment patterncontracts/protocols/staking/reliquary/v1/helpers/ — helper contracts for ERC4626/Reaper/BPT poolsdevelopment
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.