Playboi.eth
13 min readSep 27, 2023

--

Building a Staking & Yield farming Smart contract with QuickNode RPC.

Prerequisites;

  • What is Staking & Yield Farming?
  • Key features and components of a Staking & Yield Farming Smart Contract.
  • Use cases of a Staking & Yield Farming Smart Contract.
  • Writing a Staking & Yield Farming Smart Contract.
  • Deployment with QuickNode RPC.

WHAT IS STAKING & YIELD FARMING ?

Staking and Yield Farming are both popular activities in the cryptocurrency and blockchain space. They are methods for users like me and you to earn rewards by participating in blockchain networks. Here’s an explanation of each:

Staking:

Staking is the process of actively participating in transaction validation on a proof-of-stake (PoS) blockchain. It involves holding a certain amount of a cryptocurrency in a wallet to support the operations of the network, and in return, participants receive rewards in the form of additional cryptocurrency tokens.

How it Works:

  1. Acquiring Cryptocurrency: The user acquires a certain amount of a cryptocurrency that operates on a PoS blockchain. This cryptocurrency is often referred to as a “staking token.”
  2. Wallet Setup: The user needs to hold these tokens in a specific type of wallet (usually referred to as a staking wallet) that supports staking.
  3. Locking Tokens: The user locks a certain amount of tokens in the wallet as collateral. These tokens remain in the wallet but are temporarily unusable for other purposes.
  4. Participating in Network Consensus: By staking their tokens, the user is actively participating in the network’s consensus mechanism, helping to validate transactions and secure the blockchain.
  5. Earning Rewards: In return for their participation, stakers receive rewards in the form of additional tokens. The rewards are typically proportional to the amount of tokens staked.

Benefits:

  • Passive Income: Staking allows users to earn rewards passively, simply by holding and staking their tokens.
  • Network Security: Stakers play a crucial role in maintaining the security and integrity of the blockchain network.

Risks:

  • Slashing: In some PoS systems, if a staker behaves maliciously or fails to follow the network’s rules, they may face penalties, including losing a portion of their staked tokens.

Yield Farming:

Definition: Yield Farming is a DeFi (Decentralized Finance) practice that allows users to generate returns on their cryptocurrency holdings by actively participating in lending or liquidity provision on blockchain platforms.

How it Works:

  1. Providing Liquidity: Users provide liquidity to a decentralized exchange (DEX) or a lending platform by depositing their cryptocurrency into a liquidity pool.
  2. Receiving Tokens: In return for providing liquidity, users receive a new token representing their share of the pool.
  3. Yield Maximization: Users can take further steps to maximize their yield. This might involve strategies like “yield swapping” or “yield compounding,” where users move their assets between different protocols to capture additional rewards.

Benefits:

  • High Yield Potential: Yield farming can offer significantly higher returns compared to traditional savings accounts or staking.

Risks:

  • Impermanent Loss: When providing liquidity to a pool, users may experience losses compared to holding the tokens individually due to price fluctuations.
  • Smart Contract Risks: Yield farming often involves interacting with smart contracts, and vulnerabilities in these contracts can lead to financial losses.

KEY FEATURES AND COMPONENTS OF STAKE & YIELD FARMING SMART CONTRACT.

These are general features;

Staking Smart Contract:

  1. Stake Management:
  • Allows users to lock and unlock their tokens for staking.
  • Tracks the amount of tokens staked by each user.

2. Reward Distribution:

  • Calculates and distributes rewards to stakers based on the amount of tokens they have staked and the duration of their staking.

3. Token Locking:

  • Implements a mechanism to lock staked tokens for a specified period, ensuring users cannot withdraw them immediately.

4. Slashing Mechanism (if applicable):

  • Enforces penalties for malicious behavior or rule violations by stakers.

5. Event Logging;

  • Emits events to notify users and external systems about key staking events (staking, unstaking, rewards distribution).

6. User Interface (optional):

  • Provides a user-friendly interface for users to interact with the staking contract, including staking, unstaking, and viewing rewards.

Yield Farming Smart Contract:

  1. Liquidity Pool Management:
  • Manages the creation, operation, and closure of liquidity pools.
  • Allows users to deposit and withdraw liquidity.

2. Reward Allocation:

  • Allocates rewards (in the form of tokens) to liquidity providers based on their share of the pool.

3. Yield Maximization Strategies:

  • Implements strategies to maximize yield, such as yield swapping, compounding, or yield farming across multiple protocols.

4. Automated Yield Reinvestment (optional):

  • Automatically reinvests earned rewards back into the liquidity pool to compound returns.

5. Impermanent Loss Mitigation (optional):

  • Implements mechanisms to mitigate or compensate for impermanent losses experienced by liquidity providers.

6. Event Logging:

  • Emits events to notify users and external systems about key yield farming events (deposit, withdrawal, rewards distribution).

7. User Interface (optional):

  • Provides a user-friendly interface for users to interact with the yield farming contract, including depositing, withdrawing, and viewing rewards.

8. Smart Contract Integration:

  • May integrate with other smart contracts, such as decentralized exchanges or lending protocols, to facilitate yield farming strategies.

Common Components for Both:

  1. Ownership and Access Control:
  • Includes mechanisms for contract ownership and access control to ensure only authorized parties can make critical changes.

2. Security Features:

  • Implements security best practices to protect against common vulnerabilities, such as reentrancy attacks or overflow/underflow issues.

3. Timelock Mechanism (optional):

  • Provides a timelock mechanism to delay certain operations or upgrades to the contract, allowing for governance or security measures.

4. Integration with External Systems (optional):

  • May integrate with oracles, price feeds, or other external systems to access real-world data for decision-making within the contract.

5. Governance Mechanism (optional):

  • Allows for on-chain governance, enabling token holders or stakeholders to vote on key parameters or changes in the contract.

6. Documentation:

  • Includes clear and comprehensive documentation to guide users, developers, and auditors in understanding and interacting with the contract.

USECASES OF STAKING & YIELD FARMING SMART CONTRACT.

  1. Proof of Stake (PoS) Blockchain:
  • Validators stake their tokens to secure the network and validate transactions.

2. Governance and Voting:

Users stake tokens to participate in governance decisions, such as protocol upgrades, parameter changes, or fund allocation.

3. Liquidity Provision in DeFi:

  • Users stake their tokens to provide liquidity to decentralized exchanges or lending platforms and earn rewards.

4. Token Vesting and Locking:

  • Projects can implement staking as a way to lock tokens for a specified period, ensuring long-term commitment from stakeholders.

5. Incentivized Testnets:

  • Projects use staking as an incentive mechanism for users to participate in testnets and provide valuable feedback.

6. Security Deposit for Services:

  • Users may stake tokens as a security deposit when utilizing certain services, creating trust and reducing fraudulent behavior.

Use Cases of Yield Farming Smart Contracts:

  1. Liquidity Mining:
  • Projects incentivize users to provide liquidity to their platform by rewarding them with tokens.

2. Bootstrapping New Projects:

  • New projects can use yield farming to distribute their tokens and attract liquidity to their platform.

3. Governance Token Distribution:

  • Yield farming can be used as a means to distribute governance tokens to early participants or liquidity providers.

4. Balancing Liquidity Pools:

  • Yield farming strategies can be employed to balance liquidity across different assets within a decentralized exchange.

5. Stimulating Platform Adoption:

  • Platforms can use yield farming to attract users and liquidity, kickstarting activity on their platform.

6. Providing Liquidity for NFT Marketplaces:

  • Users can provide liquidity for NFT marketplaces, allowing for easier trading and exchange of NFTs.

7. Decentralized Insurance:

  • Yield farming can be used to provide liquidity for decentralized insurance platforms, ensuring coverage for users.

8. Lending and Borrowing Protocols:

  • Yield farming can be used to incentivize users to lend or borrow assets within a decentralized lending platform.

WRITING A STAKING & YIELD FARMING SMART CONTRACT.

Hey, enough of the fundamentals, its time to go technical!

Below is a Staking & yield farming smart contract code;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StakingYieldFarm {
address public owner;
uint256 public totalStaked;
mapping(address => uint256) public stakedBalances;
mapping(address => uint256) public rewards;

constructor() {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}

function stake(uint256 amount) external {
require(amount > 0, "Stake amount must be greater than zero");
require(msg.sender != address(0), "Invalid address");

// Transfer tokens to contract
// (Assuming there's a token contract and it has an `approve` function)
// tokenContract.approve(address(this), amount);
// tokenContract.transferFrom(msg.sender, address(this), amount);

stakedBalances[msg.sender] += amount;
totalStaked += amount;
}

function unstake(uint256 amount) external {
require(amount > 0, "Unstake amount must be greater than zero");
require(stakedBalances[msg.sender] >= amount, "Insufficient staked balance");

stakedBalances[msg.sender] -= amount;
totalStaked -= amount;

// Transfer staked tokens back to user
// tokenContract.transfer(msg.sender, amount);
}

function claimRewards() external {
uint256 reward = rewards[msg.sender];
require(reward > 0, "No rewards to claim");

rewards[msg.sender] = 0;

// Transfer rewards to user
// rewardTokenContract.transfer(msg.sender, reward);
}

function distributeRewards(address[] memory recipients, uint256[] memory amounts) external onlyOwner {
require(recipients.length == amounts.length, "Invalid input lengths");

for (uint256 i = 0; i < recipients.length; i++) {
rewards[recipients[i]] += amounts[i];
}
}

function getStakedBalance(address account) external view returns (uint256) {
return stakedBalances[account];
}

function getTotalStaked() external view returns (uint256) {
return totalStaked;
}

function getRewardBalance(address account) external view returns (uint256) {
return rewards[account];
}
}

A detailed explanation of the code below:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
  • // SPDX-License-Identifier: MIT: This is a special comment that indicates the license under which the code is released. In this case, it specifies the MIT license, which is a permissive open-source license. It allows others to use, modify, and distribute the code with proper attribution.
  • pragma solidity ^0.8.0;: This pragma statement specifies the version of the Solidity compiler to be used. It tells the compiler to use a version equal to or greater than 0.8.0. Solidity is the programming language used to write Ethereum smart contracts.
contract StakingYieldFarm {
address public owner;
uint256 public totalStaked;
mapping(address => uint256) public stakedBalances;
mapping(address => uint256) public rewards;
  • contract StakingYieldFarm { ... }: This line defines a new smart contract named StakingYieldFarm.
  • address public owner;: This declares a public state variable owner of type address. It will store the address of the owner or creator of the contract.
  • uint256 public totalStaked;: This declares a public state variable totalStaked of type uint256. It will keep track of the total amount of tokens staked in the contract.
  • mapping(address => uint256) public stakedBalances;: This declares a public state variable stakedBalances as a mapping. It associates addresses with their respective staked token balances.
  • mapping(address => uint256) public rewards;: This declares a public state variable rewards as a mapping. It associates addresses with their earned rewards.
constructor() {
owner = msg.sender;
}
  • constructor() { ... }: This is the constructor function of the contract. It is executed once when the contract is deployed.
  • owner = msg.sender;: Inside the constructor, it sets the owner variable to the address of the sender (the one who deploys the contract). This establishes the initial owner of the contract.
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
  • modifier onlyOwner() { ... }: This defines a modifier named onlyOwner. Modifiers are used to change the behavior of functions. This modifier restricts access to functions to only the owner.
  • require(msg.sender == owner, "Only owner can call this function");: This checks if the address calling the function (msg.sender) is equal to the owner address. If not, it raises an exception with the message "Only owner can call this function".
  • _;: This is a placeholder for the actual function code. It indicates where the modified function's code will be executed.
function stake(uint256 amount) external {
require(amount > 0, "Stake amount must be greater than zero");
require(msg.sender != address(0), "Invalid address");

// Transfer tokens to contract
// (Assuming there's a token contract and it has an `approve` function)
// tokenContract.approve(address(this), amount);
// tokenContract.transferFrom(msg.sender, address(this), amount);

stakedBalances[msg.sender] += amount;
totalStaked += amount;
}
  • function stake(uint256 amount) external { ... }: This defines a function named stake. It can be called externally by anyone who wants to stake tokens.
  • require(amount > 0, "Stake amount must be greater than zero");: This checks if the amount parameter is greater than zero. If not, it raises an exception with the message "Stake amount must be greater than zero".
  • require(msg.sender != address(0), "Invalid address");: This checks if the sender's address (msg.sender) is not the null address (address(0)). If it is, it raises an exception with the message "Invalid address".
  • The commented lines represent the expected functionality of transferring tokens to the contract. It assumes that a token contract exists with approve and transferFrom functions. These lines should be replaced with the actual implementation for token staking.
  • stakedBalances[msg.sender] += amount;: This increases the staked balance of the sender (msg.sender) by the specified amount.
  • totalStaked += amount;: This increases the total staked amount in the contract.
function unstake(uint256 amount) external {
require(amount > 0, "Unstake amount must be greater than zero");
require(stakedBalances[msg.sender] >= amount, "Insufficient staked balance");

stakedBalances[msg.sender] -= amount;
totalStaked -= amount;

// Transfer staked tokens back to user
// tokenContract.transfer(msg.sender, amount);
}
  • function unstake(uint256 amount) external { ... }: This defines a function named unstake. It allows a user to unstake a specified amount of tokens.
  • require(amount > 0, "Unstake amount must be greater than zero");: This checks if the amount parameter is greater than zero. If not, it raises an exception with the message "Unstake amount must be greater than zero".
  • require(stakedBalances[msg.sender] >= amount, "Insufficient staked balance");: This checks if the sender (msg.sender) has a staked balance greater than or equal to the amount. If not, it raises an exception with the message "Insufficient staked balance".
  • stakedBalances[msg.sender] -= amount;: This decreases the staked balance of the sender by the specified amount.
  • totalStaked -= amount;: This decreases the total staked amount in the contract.
  • The commented line represents the expected functionality of transferring staked tokens back to the user. It assumes that a token contract exists with a transfer function. This line should be replaced with the actual implementation for transferring unstaked tokens.
function claimRewards() external {
uint256 reward = rewards[msg.sender];
require(reward > 0, "No rewards to claim");

rewards[msg.sender] = 0;

// Transfer rewards to user
// rewardTokenContract.transfer(msg.sender, reward);
}
  • function claimRewards() external { ... }: This defines a function named claimRewards. It allows a user to claim their earned rewards.
  • uint256 reward = rewards[msg.sender];: This retrieves the reward balance associated with the sender's address.
  • require(reward > 0, "No rewards to claim");: This checks if the reward balance is greater than zero. If not, it raises an exception with the message "No rewards to claim".
  • rewards[msg.sender] = 0;: This sets the sender's reward balance to zero, indicating that the rewards have been claimed.
  • The commented line represents the expected functionality of transferring rewards to the user. It assumes that a reward token contract exists with a transfer function. This line should be replaced with the actual implementation for transferring rewards.
function distributeRewards(address[] memory recipients, uint256[] memory amounts) external onlyOwner {
require(recipients.length == amounts.length, "Invalid input lengths");

for (uint256 i = 0; i < recipients.length; i++) {
rewards[recipients[i]] += amounts[i];
}
}
  • function distributeRewards(address[] memory recipients, uint256[] memory amounts) external onlyOwner { ... }: This defines a function named distributeRewards. It allows the owner to distribute rewards to multiple recipients.
  • require(recipients.length == amounts.length, "Invalid input lengths");: This checks if the lengths of the recipients and amounts arrays are equal. If not, it raises an exception with the message "Invalid input lengths".
  • It then iterates over the recipients array and adds the specified rewards to each recipient's balance.
function getStakedBalance(address account) external view returns (uint256) {
return stakedBalances[account];
}

function getTotalStaked() external view returns (uint256) {
return totalStaked;
}

function getRewardBalance(address account) external view returns (uint256) {
return rewards[account];
}
}
  • function getStakedBalance(address account) external view returns (uint256) { ... }: This defines a function named getStakedBalance that allows anyone to query the staked balance of a specified account.
  • function getTotalStaked() external view returns (uint256) { ... }: This defines a function named getTotalStaked that allows anyone to query the total staked amount in the contract.
  • function getRewardBalance(address account) external view returns (uint256) { ... }: This defines a function named getRewardBalance that allows anyone to query the reward balance of a specified account.

This smart contract provides functionalities for staking tokens, unstaking tokens, claiming rewards, and distributing rewards. It also allows the owner to manage the rewards distribution.

Lets write this code in our REMIX IDE.

DEPLOYMENT WITH QUICKNODE RPC.

STEP 1.

Create a new sepolia node on QuickNode. You would have to navigate to the QuickNode Dashboard and click on “Create”.

Then after, make sure you click on the Ethereum chain. Check a screenshoot below;

Click on Sepolia:

Then click on ‘continue’ to proceed. Finally click on ‘create endpoint’ to get your Sepolia url.

STEP 2.

Click on “Add network”. Follow the instructions on how to add the RPC url to your wallet browser.

Click on “Add network manually”.

STEP 3.

Enter a name (any) since your using QuickNode , you can use ‘QKN’ copy/paste your Web3 endpoint (be sure to include the “/” at the end!), enter ChainID and click “Save”.

We are using Sepolia Teastnet, so its advisable to use sepolia chain ID, Which is ‘11155111’.

Finally, you would get the below result.

Perfect! We are almost finished, Now we would be requesting some Eth from Sepolia Testnet so as to be able to deploy our smart contract.

STEP 4.

Now to get gas fees, we are going to be using https://faucet.quicknode.com/drip. Below is the procedure.

Then connect wallet and get some Eth.

After doing this, you would get the result below:

With this, we are ready to deploy our staking and yield farming smart contract.

Deploying smart contract code to sepolia testnet.

Click on ‘confirm’ to finalize transactions.

Conclusion

Congratulations on successfully creating your very own Staking & Yield farming Smart contract on the Ethereum network! .

Subscribe to QuickNode newsletter for more articles and guides on Ethereum. If you have any feedback, feel free to reach out to us via Twitter. You can always chat with us on our Discord community server, featuring some of the coolest developers you’ll ever meet :)

--

--

Playboi.eth

22y/o smart contract engineer. Advocate @graphprotocol, Ambassador @quicknode, Contributor @girscriptsoc . prev @web3_Nados, @blockchain