.claude/skills/resupply-insurance/SKILL.md
This skill should be used when the user asks about "insurance pool", "reIP", "InsurancePool", "protocol insurance", "bad debt coverage", "exit delay", "insurance vault", or needs to understand Resupply's insurance mechanism.
npx skillsauth add cyotee/crane resupply-insuranceInstall 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.
The Insurance Pool (reIP) is an ERC4626 vault that provides coverage against protocol insolvency and bad debt. Located in /src/protocol/InsurancePool.sol.
The Insurance Pool:
contract InsurancePool is ERC4626, RewardDistributorMultiEpoch {
// Exit delay configuration
uint256 public constant EXIT_DELAY = 7 days;
// Pending withdrawal tracking
struct PendingWithdrawal {
uint256 shares;
uint256 unlockTime;
}
mapping(address => PendingWithdrawal) public pendingWithdrawals;
// Bad debt tracking
uint256 public totalBadDebt;
uint256 public coveredBadDebt;
}
Deposit reUSD to receive reIP shares:
// Standard ERC4626 deposit
function deposit(uint256 assets, address receiver) public returns (uint256 shares) {
shares = previewDeposit(assets);
// Transfer reUSD from depositor
IERC20(asset()).safeTransferFrom(msg.sender, address(this), assets);
// Mint reIP shares
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
}
// Share calculation
function previewDeposit(uint256 assets) public view returns (uint256) {
uint256 supply = totalSupply();
uint256 totalAssets = totalAssets();
if (supply == 0) return assets;
return (assets * supply) / totalAssets;
}
Withdrawals require a two-step process with a 7-day delay:
function requestWithdrawal(uint256 shares) external {
require(balanceOf(msg.sender) >= shares, "Insufficient shares");
// Lock shares
_transfer(msg.sender, address(this), shares);
// Set unlock time
pendingWithdrawals[msg.sender] = PendingWithdrawal({
shares: shares,
unlockTime: block.timestamp + EXIT_DELAY
});
emit WithdrawalRequested(msg.sender, shares, block.timestamp + EXIT_DELAY);
}
function completeWithdrawal() external returns (uint256 assets) {
PendingWithdrawal memory pending = pendingWithdrawals[msg.sender];
require(pending.shares > 0, "No pending withdrawal");
require(block.timestamp >= pending.unlockTime, "Still locked");
// Calculate assets
assets = previewRedeem(pending.shares);
// Clear pending
delete pendingWithdrawals[msg.sender];
// Burn shares and transfer assets
_burn(address(this), pending.shares);
IERC20(asset()).safeTransfer(msg.sender, assets);
emit Withdraw(msg.sender, msg.sender, msg.sender, assets, pending.shares);
}
function cancelWithdrawal() external {
PendingWithdrawal memory pending = pendingWithdrawals[msg.sender];
require(pending.shares > 0, "No pending withdrawal");
// Return shares
_transfer(address(this), msg.sender, pending.shares);
delete pendingWithdrawals[msg.sender];
emit WithdrawalCancelled(msg.sender, pending.shares);
}
The insurance pool covers bad debt from undercollateralized liquidations:
function coverBadDebt(uint256 amount) external onlyLiquidationHandler {
uint256 available = totalAssets();
if (available >= amount) {
// Full coverage - burn reUSD
IStablecoin(asset()).burn(address(this), amount);
coveredBadDebt += amount;
} else {
// Partial coverage
if (available > 0) {
IStablecoin(asset()).burn(address(this), available);
coveredBadDebt += available;
}
// Remaining is protocol loss
totalBadDebt += (amount - available);
}
emit BadDebtCovered(amount, available);
}
Bad debt coverage reduces total assets, affecting share value:
// Before bad debt: 1000 reUSD, 1000 shares → 1 reUSD/share
// After 100 reUSD bad debt: 900 reUSD, 1000 shares → 0.9 reUSD/share
function totalAssets() public view returns (uint256) {
return IERC20(asset()).balanceOf(address(this));
}
Insurance pool participants earn rewards:
function getReward(address _account) external {
_checkpoint(_account);
for (uint256 i = 0; i < rewardTokens.length; i++) {
address token = rewardTokens[i];
uint256 reward = earned(_account, token);
if (reward > 0) {
rewards[_account][token] = 0;
IERC20(token).safeTransfer(_account, reward);
}
}
}
// Per-epoch reward accumulation
mapping(address token => mapping(uint256 epoch => uint256 amount)) public epochRewards;
// User reward integral
mapping(address user => mapping(address token => uint256 integral)) public userRewardIntegral;
function earned(address _account, address _token) public view returns (uint256) {
uint256 balance = balanceOf(_account);
uint256 rewardPerToken = rewardIntegral[_token] - userRewardIntegral[_account][_token];
return (balance * rewardPerToken / 1e18) + rewards[_account][_token];
}
The reIP share value fluctuates based on:
Value increases from:
Value decreases from:
// Share price calculation
function sharePrice() public view returns (uint256) {
uint256 supply = totalSupply();
if (supply == 0) return 1e18;
return (totalAssets() * 1e18) / supply;
}
The 7-day exit delay:
event WithdrawalRequested(
address indexed user,
uint256 shares,
uint256 unlockTime
);
event WithdrawalCancelled(
address indexed user,
uint256 shares
);
event BadDebtCovered(
uint256 totalBadDebt,
uint256 amountCovered
);
event RewardDistributed(
address indexed token,
uint256 amount,
uint256 epoch
);
development
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.