Skip to main content

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

PropertyValue
Contract0x4D43eD221a96db3C4A46E26ae19D05f07b65A3d7
TokenPINCH (0xF8e86087dc452a52aA5d1bb66FaE56F869C33412)
Reward TokenPINCH (same token)
Lock Tiers3 tiers (7d, 30d, 90d)
Reward ModelPro-rata by weighted stake

Lock Tiers

TierLock PeriodMultiplierWeight per 100 PINCH
Tier 07 days1x (100)100
Tier 130 days2x (200)200
Tier 290 days4x (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:

StakerTierWeightReward Share
AliceTier 0 (7d, 1x)1,00020% (1000/5000)
BobTier 2 (90d, 4x)4,00080% (4000/5000)
Total5,000100%

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;
Buffered Rewards (H-01 Fix)

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`);
}
Lock Period

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

EventDescription
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