Soroban Social Network - Tutorial
Published: 2023-08-28 23:07:06

> 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

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](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](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](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;
```

`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.

```rs
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.

```rs
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.

```rs
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 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.

```rs
#[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.

```rs
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. 

```rs
#[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 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.

```rs
#[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.

```rs
#[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.

```rs
#[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, 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.

```rs
#[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`).

```rs
#[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
  ```sh
   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:
```sh
chmod +x initialize.sh
```

and try again to run 
```sh
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.

**<u>Let's break down the script step by step:</u>**

#### 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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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.

```bash
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:

```js
parseResultXdr: (xdr) => {
    THIS_ROW_NEEDS_TO_BE_REPLACED
}
```
    
with this one:
```js
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 of `VITE_THIRDWEB_CLIENT_ID` with `client ID` obtained for FREE from ThirdWeb [https://thirdweb.com/dashboard/storage].
- Run
  ```sh
   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.