Staking
The PinchStaking contract allows PINCH holders to stake tokens with tiered lock periods for multiplied reward shares. Longer locks earn proportionally more rewards.
Overview
| Property | Value |
|---|---|
| Contract | 0x4D43eD221a96db3C4A46E26ae19D05f07b65A3d7 |
| Token | PINCH (0xF8e86087dc452a52aA5d1bb66FaE56F869C33412) |
| Reward Token | PINCH (same token) |
| Lock Tiers | 3 tiers (7d, 30d, 90d) |
| Reward Model | Pro-rata by weighted stake |
Lock Tiers
| Tier | Lock Period | Multiplier | Weight per 100 PINCH |
|---|---|---|---|
| Tier 0 | 7 days | 1x (100) | 100 |
| Tier 1 | 30 days | 2x (200) | 200 |
| Tier 2 | 90 days | 4x (400) | 400 |
The weight of a stake is amount × multiplier / 100. Rewards are distributed proportionally to weight, so a 90-day stake earns 4x the rewards of an equal 7-day stake.
Example
If two stakers each deposit 1,000 PINCH:
| Staker | Tier | Weight | Reward Share |
|---|---|---|---|
| Alice | Tier 0 (7d, 1x) | 1,000 | 20% (1000/5000) |
| Bob | Tier 2 (90d, 4x) | 4,000 | 80% (4000/5000) |
| Total | 5,000 | 100% |
If 500 PINCH in rewards is distributed, Alice gets 100 PINCH and Bob gets 400 PINCH.
How Rewards Work
Accumulation
The contract uses a global reward accumulator (accRewardPerWeight) that increases each time rewards are deposited:
accRewardPerWeight += (rewardAmount × 1e18) / totalWeight
Each stake records its rewardDebt (the accumulator value at stake time). Pending rewards for a stake are:
pending = weight × (accRewardPerWeight - rewardDebt) / 1e18
Depositing Rewards
The contract owner deposits PINCH as rewards:
function addRewards(uint256 amount) external onlyOwner;
If rewards are deposited when nobody is staked (totalWeight == 0), the rewards are buffered — not lost. They're distributed to the first staker(s) when they enter, preventing permanent reward loss.
Staking
const STAKING = '0x4D43eD221a96db3C4A46E26ae19D05f07b65A3d7';
const PINCH = '0xF8e86087dc452a52aA5d1bb66FaE56F869C33412';
const staking = new ethers.Contract(STAKING, [
'function stake(uint256 amount, uint8 lockTier) external',
'function pendingReward(address user, uint256 stakeId) external view returns (uint256)',
'function getUserStakeCount(address user) external view returns (uint256)',
'function getUserStake(address user, uint256 stakeId) external view returns (tuple(uint256 amount, uint8 tier, uint256 stakedAt, uint256 unlockAt, uint256 rewardDebt, bool withdrawn))',
], wallet);
const pinch = new ethers.Contract(PINCH, [
'function approve(address spender, uint256 amount) external returns (bool)',
], wallet);
// Approve PINCH spending
const amount = ethers.parseEther('1000');
await pinch.approve(STAKING, amount);
// Stake with 90-day lock (Tier 2, 4x multiplier)
await staking.stake(amount, 2);
console.log('✅ Staked 1000 PINCH with 90-day lock (4x rewards)');
Claiming Rewards
Claim accumulated rewards without unstaking:
const staking = new ethers.Contract(STAKING, [
'function claimRewards(uint256 stakeId) external',
'function pendingReward(address user, uint256 stakeId) external view returns (uint256)',
], wallet);
// Check pending rewards
const pending = await staking.pendingReward(wallet.address, 0); // stakeId = 0
console.log('Pending rewards:', ethers.formatEther(pending), 'PINCH');
// Claim
if (pending > 0n) {
await staking.claimRewards(0);
console.log('✅ Rewards claimed!');
}
Unstaking
After the lock period expires, withdraw your stake plus accumulated rewards:
const staking = new ethers.Contract(STAKING, [
'function unstake(uint256 stakeId) external',
'function getUserStake(address user, uint256 stakeId) external view returns (tuple(uint256 amount, uint8 tier, uint256 stakedAt, uint256 unlockAt, uint256 rewardDebt, bool withdrawn))',
], wallet);
// Check if lock has expired
const stake = await staking.getUserStake(wallet.address, 0);
const now = Math.floor(Date.now() / 1000);
if (now >= Number(stake.unlockAt)) {
await staking.unstake(0);
console.log('✅ Unstaked! Principal + rewards transferred');
} else {
const remaining = Number(stake.unlockAt) - now;
console.log(`⏳ Lock expires in ${Math.ceil(remaining / 86400)} days`);
}
You cannot unstake before the lock period expires. The transaction will revert with "Still locked".
Multiple Stakes
Users can have multiple active stakes with different tiers:
// Stake #0: 500 PINCH, Tier 0 (7-day, 1x)
await staking.stake(ethers.parseEther('500'), 0);
// Stake #1: 1000 PINCH, Tier 1 (30-day, 2x)
await staking.stake(ethers.parseEther('1000'), 1);
// Stake #2: 2000 PINCH, Tier 2 (90-day, 4x)
await staking.stake(ethers.parseEther('2000'), 2);
// Check all stakes
const count = await staking.getUserStakeCount(wallet.address);
for (let i = 0; i < count; i++) {
const stake = await staking.getUserStake(wallet.address, i);
const pending = await staking.pendingReward(wallet.address, i);
console.log(`Stake ${i}: ${ethers.formatEther(stake.amount)} PINCH, ` +
`Tier ${stake.tier}, Pending: ${ethers.formatEther(pending)} PINCH`);
}
Contract Interface
// Staking
function stake(uint256 amount, uint8 lockTier) external;
function unstake(uint256 stakeId) external;
function claimRewards(uint256 stakeId) external;
// Reward management (owner only)
function addRewards(uint256 amount) external;
// Views
function getUserStakeCount(address user) external view returns (uint256);
function getUserStake(address user, uint256 stakeId) external view returns (Stake memory);
function pendingReward(address user, uint256 stakeId) external view returns (uint256);
// Global state
function totalWeight() external view returns (uint256);
function accRewardPerWeight() external view returns (uint256);
function totalRewardsDeposited() external view returns (uint256);
function bufferedRewards() external view returns (uint256);
function tiers(uint256 index) external view returns (uint256 lockDuration, uint256 multiplier);
function pinchToken() external view returns (address);
Events
| Event | Description |
|---|---|
Staked(user, stakeId, amount, tier) | New stake created |
Unstaked(user, stakeId, amount, reward) | Stake withdrawn with rewards |
RewardsClaimed(user, stakeId, reward) | Rewards claimed without unstaking |
RewardsAdded(amount) | Owner deposited rewards |