DEV Community

USER
USER

Posted on • Edited on

Soroban Social Network - Tutorial

Social Network built on Soroban Stellar Futurenet


Built With


Prerequisites

  1. Node v18 - Install here: https://nodejs.org/en/download

  2. Rust - How to install Rust:
    https://soroban.stellar.org/docs/getting-started/setup#install-rust

  3. Soroban CLI - How to install Soroban CLI:
    https://soroban.stellar.org/docs/getting-started/setup#install-the-soroban-cli

  4. Stellar 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-identity

  5. Freighter Wallet - Wallet extension for interact with the app. Link: https://www.freighter.app


Explanation of the smart contract code

The lib.rs file from contracts/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;
Enter fullscreen mode Exit fullscreen mode

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, 
}
Enter fullscreen mode Exit fullscreen mode
  • 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,
}
Enter fullscreen mode Exit fullscreen mode
  • 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,
}
Enter fullscreen mode Exit fullscreen mode

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 like Users, 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),
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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 a Post 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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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, and get_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)
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

How to run this dapp on your PC:

  • Clone this repository:
git clone https://github.com/snowstorm134/SorobanSocialNetwork.git
Enter fullscreen mode Exit fullscreen mode

Build & deploy the smart contract

  • Run
   npm run setup
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

and try again to run

npm run setup
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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)"
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
)"
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

with this one:

parseResultXdr: (xdr) => {
    return scValStrToJs(xdr);
}
Enter fullscreen mode Exit fullscreen mode

Run the app frontend:

  • Rename the env.example file in the project directory to .env. Open the .env file and set the value of VITE_THIRDWEB_CLIENT_ID with client ID obtained for FREE from ThirdWeb [https://thirdweb.com/dashboard/storage].
  • Run
   npm run dev
Enter fullscreen mode Exit fullscreen mode
  • 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)