Social Network built on Soroban Stellar Futurenet
Built With
- Soroban smart contracts - https://soroban.stellar.org
- React
- IPFS Storage - https://thirdweb.com/dashboard/infrastructure/storage
- Chakra UI - https://chakra-ui.com/
Prerequisites
Node v18 - Install here: https://nodejs.org/en/download
Rust - How to install Rust:
https://soroban.stellar.org/docs/getting-started/setup#install-rustSoroban CLI - How to install Soroban CLI:
https://soroban.stellar.org/docs/getting-started/setup#install-the-soroban-cliStellar Account with test tokens on Futurenet - How to create new wallet using soroban-cli & receive test tokens:
https://soroban.stellar.org/docs/getting-started/deploy-to-futurenet#configure-an-identityFreighter Wallet - Wallet extension for interact with the app. Link: https://www.freighter.app
Explanation of the smart contract code
The
lib.rs
file fromcontracts/social-network-contract/src
directory contains the code of our smart contract.
- The code's architecture is meticulously structured:
- It begins by importing essential libraries and defining a constant value that likely influences the contract's initialization process.
- Precisely tailored structs encapsulate vital user information, posts, and comments, collectively shaping the foundation of the network's data model.
- The use of an enum called
DataKey
orchestrates the management of diverse data types within the contract's storage. - A set of strategically designed helper functions is purpose-built for data retrieval from the storage layer, enhancing the contract's efficiency and readability.
- The contract itself, represented by the
SocialNetworkContract
struct, is formulated to encapsulate the essence of the social network's functionalities. - The implementation of the contract is subdivided into methods, each catering to a specific aspect of user interaction, including initialization, user information updates, following users, post creation, like management, and comment addition. This modular approach is augmented by supplementary methods that facilitate the retrieval of information such as user profiles, post details, like counts, and follower statistics.
Let's dive in and analyze the main parts.
pub(crate) const BUMP_AMOUNT: u32 = 518400;
BUMP_AMOUNT
is defined as a constant with a value of 518400used to bump contract instance with 30 days at initialization.
Struct Definitions:
These structs (Custom Types) define the basic components of user profiles, posts, and comments within the social network. They will be used to store and manipulate data in the contract's storage.
- UserInfo - represents information about a user, including their name, bio, and content URI of uploaded image to IPFS.
pub struct UserInfo {
pub name: String,
pub bio: String,
pub avatar_uri: String,
}
- Post represents a post, containing the ID, author's address, creation time, text content, and content URI of uploaded image to IPFS.
pub struct Post {
pub id: u32,
pub author: Address,
pub create_time: u64,
pub text: String,
pub content_uri: String,
}
- Comment - represents a comment on a post, with an ID, author's address, creation time, and text content.
pub struct Comment {
pub id: u32,
pub author: Address,
pub create_time: u64,
pub text: String,
}
Enum Definition and Data Keys
- This section defines an enum named
DataKey
, which represents keys used to access data in the contract's storage. - For example,
Initialized
represents whether the contract has been initialized, and other variants likeUsers
,UserFollowersCount
,Posts
, and more, represent specific pieces of data associated with users, posts, likes, and comments. - These enum variants will be used to retrieve and store data in the contract's storage based on the specific context and type of information needed.
#[derive(Clone)]
#[contracttype]
pub enum DataKey {
Initialized,
Users(Address),
UserFollowersCount(Address),
UserFollowerByNr(Address, u32),
UserIsFollowedBy(Address, Address),
PostsCount,
Posts(u32),
PostsOfUserCount(Address),
PostOfUserByNr(Address, u32),
Likes(u32),
LikeStatus(u32, Address),
PostCommentsCount(u32),
PostCommentByNr(u32, u32),
}
Helper Functions for Data Retrieval
- This part introduces a series of helper functions that are used to retrieve data from the contract's storage.
-
get_user_info
takes a user's address as parameters and returns the user's information. -
get_user_followers_count
retrieves the count of followers for a given user. - These functions retrieve specific data based on the provided keys and will be used throughout the contract's methods to access necessary information.
fn get_user_info(e: &Env, address: &Address) -> UserInfo {
// ...
// (Implementation details)
// ...
}
fn get_user_followers_count(e: &Env, address: &Address) -> u32 {
// ...
// (Implementation details)
// ...
}
// ... (Other helper functions for data retrieval)
Contract Initialization and Update User Info
- This part introduces the main SocialNetworkContract struct, which represents the smart contract.
- The
initialize
method is a contract initialization function that sets up the contract's initial state. It is called once during the contract's deployment and ensures that the contract is only initialized once. - The
update_user_info
method allows users to update their information such as name, bio, and avatar URI.
#[contract]
pub struct SocialNetworkContract;
#[contractimpl]
impl SocialNetworkContract {
pub fn initialize(e: Env) {
// ...
// (Implementation details)
// ...
}
pub fn update_user_info(
e: Env,
address: Address,
name: String,
bio: String,
avatar_uri: String,
) -> UserInfo {
// ...
// (Implementation details)
// ...
}
// ... (Other contract methods)
}
Following Users and Adding Posts
- The
follow_user
method enables a user to follow another user; - The
add_post
method allows users to create and add new posts. It increments the total post count, creates aPost
instance, stores it in the contract's storage, and updates the user's post count. - These methods extend the functionality of the contract by enabling users to interact with each other through following and creating posts.
#[contractimpl]
impl SocialNetworkContract {
// ...
pub fn follow_user(e: Env, follower: Address, address: Address) -> u32 {
// ...
// (Implementation details)
// ...
}
pub fn add_post(e: Env, address: Address, text: String, content_uri: String) -> Post {
// ...
// (Implementation details)
// ...
}
// ... (Other contract methods)
}
Managing Likes on Posts
- The
set_or_remove_like
method allows users to like or remove their like from a post. It manages the post's like count and the status of whether the user has liked the post. - This method adds the ability for users to express their appreciation for posts by liking them. It adjusts the like count and status accordingly in the contract's storage.
#[contractimpl]
impl SocialNetworkContract {
// ...
pub fn set_or_remove_like(e: Env, address: Address, post_nr: u32) -> u32 {
// ...
// (Implementation details)
// ...
}
// ... (Other contract methods)
}
Adding Comments to Posts
- The
add_comment
method enables users to add comments to posts. It increments the comment count for a specific post, creates a Comment instance, stores it in the contract's storage, and returns the comment. - This method allows users to engage in discussions around posts by adding comments, enhancing the interactivity of the social network contract.
#[contractimpl]
impl SocialNetworkContract {
// ...
pub fn add_comment(e: Env, address: Address, post_nr: u32, text: String) -> Comment {
// ...
// (Implementation details)
// ...
}
// ... (Other contract methods)
}
Getter Methods for Data Retrieval
- The contract includes various getter methods that allow users to retrieve specific pieces of data from the contract's storage.
- For instance,
get_user_info
lets users retrieve their own user information, andget_user_followers_count
retrieves the follower count for a specific user. - These getter methods provide a way for users to access information about themselves, other users, and the content of the social network.
#[contractimpl]
impl SocialNetworkContract {
// ...
pub fn get_user_info(e: Env, address: Address) -> UserInfo {
get_user_info(&e, &address)
}
pub fn get_user_followers_count(e: Env, address: Address) -> u32 {
get_user_followers_count(&e, &address)
}
// ... (Other getter methods for data retrieval)
}
Getting Post Information and Interaction Status
- The
get_post
allows users to retrieve the details of a specific post by providing its post number (post_nr
). -
get_post_likes
allows users to retrieve the like count for a specific post by providing its post number (post_nr
).
#[contractimpl]
impl SocialNetworkContract {
// ...
pub fn get_post(e: Env, post_nr: u32) -> Post {
get_post(&e, &post_nr)
}
pub fn get_post_likes(e: Env, post_nr: u32) -> u32 {
get_post_likes(&e, &post_nr)
}
// ... (Other getter methods for post data and interaction status)
}
How to run this dapp on your PC:
- Clone this repository:
git clone https://github.com/snowstorm134/SorobanSocialNetwork.git
Build & deploy the smart contract
- Run
npm run setup
It will execute the initialize.sh
bash script. *
- - If you are using Linux or Ubuntu OS, you may get the following error:
./initialize.sh: Permission denied
This error occurs when the shell script you’re trying to run doesn’t have the permissions to execute. To fix that, use this command:
chmod +x initialize.sh
and try again to run
npm run setup
The initialize.sh
script is designed to streamline the deployment and setup of a Soroban smart contract. It covers network configuration, contract deployment, and initialization, as well as setting up necessary configurations for example wallet identities.
Let's break down the script step by step:
Variables and Network Setup:
This section sets up the script to accept command line arguments for the network type (standalone or futurenet) and the Soroban RPC host.
NETWORK="$1"
SOROBAN_RPC_HOST="$2"
PATH=./target/bin:$PATH
Soroban RPC Host Configuration:
This section determines the Soroban RPC URL based on the provided host or network type.
if [[ "$SOROBAN_RPC_HOST" == "" ]]; then
# ...
else
SOROBAN_RPC_URL="$SOROBAN_RPC_HOST"
fi
Depending on the network type specified as the first argument, this block sets the Soroban network passphrase, Friendbot URL, and other network-specific configurations.
case "$1" in
standalone)
# ...
futurenet)
# ...
*)
# ...
esac
Network Setup Logging:
This section logs the chosen network, its associated RPC URL, and Friendbot URL for informational purposes.
echo "Using $NETWORK network"
echo " RPC URL: $SOROBAN_RPC_URL"
echo " Friendbot URL: $FRIENDBOT_URL"
Configure Soroban Client:
The script configures the Soroban client to connect to the chosen network.
echo Add the $NETWORK network to cli client
soroban config network add \
--rpc-url "$SOROBAN_RPC_URL" \
--network-passphrase "$SOROBAN_NETWORK_PASSPHRASE" "$NETWORK"
Creation of a new wallet (identity):
This section creates an identity and an associated address if it doesn't already exist, and retrieves the admin's address.
if !(soroban config identity ls | grep token-admin 2>&1 >/dev/null); then
# ...
fi
ADMIN_ADDRESS="$(soroban config identity address token-admin)"
Fund Admin Account:
It uses the Friendbot service to fund the admin's account.
echo Fund token-admin account from friendbot
curl --silent -X POST "$FRIENDBOT_URL?addr=$ADMIN_ADDRESS" >/dev/null
Build Contracts:
This block sets up arguments and deploy the social network contract and save its contract ID.
ARGS="--network $NETWORK --source token-admin"
echo Build contracts
make build
echo Deploy the Social Network contract
SOCIAL_NETWORK_CONTRACT_ID="$(
soroban contract deploy $ARGS \
--wasm target/wasm32-unknown-unknown/release/soroban_social_network_contract.wasm
)"
Initialize Social Network Contract:
This initializes the deployed contract using the initialize method.
echo Initialize the Social Network contract
deadline="$(($(date +"%s") + 604800))"
soroban contract invoke \
$ARGS \
--id "$SOCIAL_NETWORK_CONTRACT_ID" \
-- \
initialize
echo "Done"
Correction of errors in typescript binding files
The npm run setup
command from the previous steps also executed a script that creates typescript binding files for the smart contract.
Soroban-tooling is still in development, and the team is working to improve generated bindings that may not fully integrate with some frontends at this time.
In this project we will fix this by following these steps:
- Go to:
.soroban/social-network-contract/dist/esm/
; - Open
index.js
file; - Find all
export async function
and in each of them replace this part:
parseResultXdr: (xdr) => {
THIS_ROW_NEEDS_TO_BE_REPLACED
}
with this one:
parseResultXdr: (xdr) => {
return scValStrToJs(xdr);
}
Run the app frontend:
- Rename the
env.example
file in the project directory to.env
. Open the.env
file and set the value ofVITE_THIRDWEB_CLIENT_ID
withclient ID
obtained for FREE from ThirdWeb [https://thirdweb.com/dashboard/storage]. - Run
npm run dev
- This command will start the app frontend and make it accessible on port 3000 by default
- Open your web browser and navigate to the address where the app is running (e.g., http://localhost:3000).
- Start using the dApp to create profiles, make posts, interact with content, and explore the decentralized social networking features.
Top comments (0)