What if we could gamify—and reward—being a friend? That's the idea behind Friend.tech, the decentralized social network that took Web3 by storm. In this hack, you'll recreate Friend.tech on the Stacks blockchain.
There are a few key components to this hack: namely, subjects that have keys that anyone can buy. These keys start off low in price, but as people buy more keys, the price goes up faster and faster, rewarding friends who bought keys early. Key owners can sell their keys for a profit, or they can hold on to them to signal their friendship and build reputation. These keys can also be used to access exclusive chatrooms with their corresponding subject, receive exclusive airdrops, and more
This Hack walks you through the basics of building a Friend.tech clone. There are also challenges at the end, which are opportunities to stretch your skills and keep learning.
Start by setting up your development environment. We've prepared a repository that includes an initialized Clarinet project and a React frontend with some boilerplate code and all the required packages.
To clone the repository, open your terminal and run the following command:
Before you begin, we're assuming that you have clarinet installed and a basic understanding of how to use it. If you haven't installed clarinet yet, you can do so by referring to our installation guide.
To create your contract, navigate to your project's directory and run the following command:
This will create a new contract in the contracts directory called keys.clar.
If you don't want to clone the provided starter template, you can create your
Clarinet project manually by running clarinet new friendtech && cd friendtech.
Inside your keys.clar contract, you need to keep track of the balance of keys for each user (or holder) and the total supply of keys for each subject. You can do this using Clarity's define-map function:
The keysBalance stores each holder's key balance for a given subject and the keysSupply stores the total supply for each subject. These maps will be used in your contract's functions to manage the creation, buying, and selling of keys.
The next thing you need to do is define a function that calculates the price of keys for a given a supply and amount. You can do this by defining a get-price function:
The get-price function calculates the price of keys using a formula that calculates the average price per token over the range of supply affected. This can be done by integrating the price function over the range of supply and dividing by the amount.
Creating, buying, and selling keys
These functions form the core of the contract's operations, enabling users to manage keys through buying and selling.
Let's first take a look at the buy-keys function:
This function allows subjects to create their first keys, initiating their supply in the contract. The transaction only succeeds if the subject is creating the initial keys or if there are already keys in circulation, ie principal-a cannot buy the initial keys for principal-b.
Next up, the sell-keys function:
This is more or less the same logic as the buy-keys function, but instead you deduct the keysBalance and keysSupply and check if the seller owns enough keys and if they are authorized to sell.
Now that you have the ability to buy and sell keys, you need a way to verify if a user is a keyholder. You can do this by defining an is-keyholder read-only function:
This function checks if the keysBalance for a given subject and holder is greater than or equal to 1. If it is, then the user is-keyholder.
The following challenges are additional features you can implement to keep learning and building on Stacks.
Starter
Balance and supply query functions: Add two new read-only functions: get-keys-balance and get-keys-supply. These functions will provide valuable information to users about the distribution and availability of keys, and can be used in various parts of your application to display this information to users.
Starter
Price query functions: Add two new read-only functions: get-buy-price and get-sell-price. These helper functions will allow users to query the current price for buying or selling a specific amount of keys for a given subject.
Intermediate
Fee management: When a user buys or sells keys, you might want to introduce a fee, either at the protocol or subject level that can be distributed accordingly. Add a protocolFeePercent and/or subjectFeePercent variable, as well as a destination Stacks principal protocolFeeDestination for this new revenue. Now make sure to update the buy-keys and sell-keys functions to incorporate these fees and stx-transfer? into the buying and selling logic.
Intermediate
Access control: In a real-world application, you might need to adjust the protocolFeePercent, subjectFeePercent, or protocolFeeDestination values over time. However, you wouldn't want just anyone to be able to make these changes. This is where access control comes in. Specifically, you should add a set-fee function (or functions) that allows only a designated contractOwner to change the protocolFeePercent, subjectFeePercent, or protocolFeeDestination values.
Think about how you can verify whether the caller of the function is indeed the contractOwner. Also, consider what kind of feedback the function should give when someone else tries to call it. Test your implementation to ensure it works as expected.
Intermediate
UI integration: Using the provided starter template, integrate your contract using Stacks.js. For example, if you were calling the is-keyholder function, your code might look something like this:
By using a similar pattern to the code above, you will be able to show/hide chatrooms based on keyholdings, create an exchange for buying/selling keys, or add a search for displaying subject keyholdings.
Note
If you are planning to experiment with your contract on devnet, make sure to run clarinet devnet startto deploy and test your contract locally. You can then use the devnet network when calling your contract in the Leather wallet.
Advanced
Message signature: To further enhance the security and authenticity of your app, try to implemement a login feature using message signing.
The signed message will serve as a proof of identity - similar to a login, verifying that the user is indeed who they claim to be. This can be particularly useful in a chatroom setting, where only is-keyholder users are allowed to send messages.