OnlyPaws: Bearing it all for Proof of Liquidity
OnlyFans just got more... Liquid
Instead of subscribing to, erm, content creators, we introduce to you to OnlyPaws, a marketplace for bear paws 🐾
Besides being able to purchase your creators' favorite paws, each purchase on OnlyPaws automatically earn users rewards through Berachain's Proof of Liquidity (PoL). However, it wouldn't be fair for someone who bought way earlier than someone else to be earning that sweet BGT in perpetuity right?
✨ OnlyPaws fixes this by introducing the concept of time-limited PoL rewards:
Users' purchases only earn BGT rewards for 7 days
OnlyPaws App Walkthrough
Check out the app here: https://only-paws.vercel.app/
OnlyPaws allows users to:
- List their paw images for sale, allowing others to purchase them
- Automatically earn BGT rewards for 7 days after each purchase
- Track earnings and claim through the Leaderboard
Marketplace
Creators can upload their paw pics, and set a price for them (in BERA). Other thirsty bears can then pay to access these precious paws.
Proof of Liquidity Integration
Upon purchasing a paw, the smart contract will automatically stake in a PoL Reward Vault for you to earn BGT rewards. The BGT Leaderboard page shows the biggest active stakes.
Remember, stakes don't last forever, because we have to give future purchasers an equal chance to earn. Once 7 days is up from your paw's purchase, you are automatically unstaked so that others get a chance to earn BGT.
But don't fret, because your paws are still there in the Gallery!
OnlyPaws Smart Contracts
Now, let's dive deep into how OnlyPaws implements this unique reward mechanism. After all, how the heck do you automatically unstake someone??
If you are unfamiliar with how the delegateStake
functionality works, we invite you to read the Smart Contracts section of the POLTech Article, which goes into detail on this mechanic.
In short:
Contracts can use the delegateStake
function on Reward Vaults to manage their users' PoL staking positions
Users can see the live OnlyPaws contract here: https://bartio.beratrail.io/address/0x3Df96af91D7a802132A996108C06B69f102bf7db/contract/80084/code
The following diagram illustrates how the different elements interact:
Key Data Structures
There are two key Solidity structs that power OnlyPaws:
struct PawImage {
address owner;
uint256 price;
}
struct StakeInfo {
address user;
uint256 pawId;
uint256 amount;
uint256 expiryTime;
}
- PawImage contains the owner and price of a listed Paw, indexed by
pawID
inmapping(uint256 => PawImage) public pawImages
- StakeInfo contains the PoL staking information for purchased paws, particularly when it is set to expire, ordered by ascending expiry:
StakeInfo[] private orderedStakes
Buying a Paw Makes Magic 🪄
The meat of OnlyPaws' PoL integration happens when someone buys a Paw. A trimmed down version of the code is shown below:
function purchasePaw(uint256 pawId) external payable {
purgeExpiredStakes();
uint256 expiryTime = block.timestamp + 7 days;
// Add new stake to ordered array
orderedStakes.push(
StakeInfo({
user: msg.sender,
pawId: pawId,
amount: 1e18, // flat rate for every purchase
expiryTime: expiryTime
})
);
userHasPaw[msg.sender][pawId] = true;
userTotalStake[msg.sender] += PAW_STAKE_AMOUNT;
// Delegate stake to user
stakingToken.mint(address(this), PAW_STAKE_AMOUNT); // 1e18
vault.delegateStake(msg.sender, PAW_STAKE_AMOUNT); // 1e18
}
Skipping the first line, we see that a new entry is added to orderedStakes
and then a delegateStake
call is made to stake in the Reward Vault on behalf of the user (now the user begins to earn BGT!). The stakingToken
is a dummy token whose sole purpose is to facilitate incentivizing Paw purchases in this system.
Cleaning up Old Stakes 🧹
Now let's return to the first line, which calls the purgeExpiredStakes()
function and have a look inside:
function purgeExpiredStakes() public {
uint256 i = 0;
// Continue until we find a non-expired stake or process all stakes
while (
i < orderedStakes.length &&
orderedStakes[i].expiryTime < block.timestamp
) {
StakeInfo memory stake = orderedStakes[i];
// Withdraw stake from vault
vault.delegateWithdraw(stake.user, stake.amount);
stakingToken.burn(address(this), stake.amount);
// Update user state
userHasPaw[stake.user][stake.pawId] = false;
userTotalStake[stake.user] -= stake.amount;
emit PawUnstaked(stake.user, stake.pawId, stake.amount);
i++;
}
}
Because we have the orderedStakes
array, which lists all of the Paws currently staked in the PoL Reward Vault, in order of which stakes are expiring first, each buy transaction performs a cleanup, and unstakes any Paws which are no longer rewards eligible (older than 7 days).
Note how the contract code performs a delegateWithdraw
on different Stakes' users (no more BGT for them). Below is a diagram illustrating how the code traverses old stakes to perform the cleanup:
In this example, because Stakes #1 and #2 are seven and eight days old, respectively, these stakes are automatically purged following the new purchase.
In short, OnlyPaws relies on regular user activity to help upkeep the system and keep everything in order. This is a dependency that any developers hoping to implement a similar algorithm should be aware of.
Conclusion
Congratulations on making it this far! Hopefully, you've learned about how OnlyPaws enables time-limited PoL rewards, showcasing Proof of Liquidity's flexibility to launch creative incentive programs. Let's recap the key features of the OnlyPaws system.
- Automatic Staking: create a new stake on behalf of Paw purchasers using
delegateStake
- This grants the purchaser entitlement to temporary BGT rewards until...
- Automatic Cleanup: others' purchases check for stakes which are older than 7 days, and automatically unstakes them
More examples on how PoL can be adapted to other interesting applications coming soon!
🐻 Full Code Repository
If you want to see the final code and see other guides, check out OnlyPaws Code.
🛠️ Want To Build More?
If you’re looking to dive deeper into the details, take a look at our Berachain Docs.
Looking For Dev Support?
Make sure to join our Berachain Discord server and check out our developer channels to ask questions.
❤️ Don’t forget to show some love for this article 👏🏼