How to send tip to the author via web3 js solidity?

I'm new at Solidity and trying to follow some tutorials. I've got a single page application backed with .sol files doing insert and read functions.

Scenario:

  • Person-1 enters a content and it successfully can be seen on the frontend.
  • People can tip some ETH to the content but it goes directly to the contract address.

How can I modify it so that people can tip ETH to the content author?

My .sol file:

//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.6;

contract SocialNetwork {
    string public name;
    uint public postCount = 0;
    mapping(uint => Post) public posts;

    struct Post {
        uint id;
        string content;
        uint tipAmount;
        address payable author;
    }

    event PostCreated(
        uint id,
        string content,
        uint tipAmount,
        address payable author
    );

    event PostTipped(
        uint id,
        string content,
        uint tipAmount,
        address payable author
    );

    constructor() {
        name = "Social Network";
    }

    function createPost(string memory _content) public {
        // Require valid content
        require(bytes(_content).length > 0);
        // Increment the post count
        postCount ++;
        // Create the post
        posts[postCount] = Post(postCount, _content, 0, payable(msg.sender));
        // Trigger event
        emit PostCreated(postCount, _content, 0, payable(msg.sender));
    }

    function tipPost(uint _id) public payable {
        // Make sure the id is valid
        require(_id > 0 && _id <= postCount);
        // Fetch the post
        Post memory _post = posts[_id];
        // Fetch the author
        address payable _author = _post.author;
        // Pay the author by sending them Ether
        payable(address(_author)).transfer(msg.value);
        // Incremet the tip amount
        _post.tipAmount = _post.tipAmount + msg.value;
        // Update the post
        posts[_id] = _post;
        // Trigger an event
        emit PostTipped(postCount, _post.content, _post.tipAmount, _author);
    }
}

My App.js file:

import React, { Component } from 'react';
import Web3 from 'web3';
import './App.css';
import SocialNetwork from '../abis/SocialNetwork.json'
import Navbar from './Navbar'
import Main from './Main'

class App extends Component {

  async componentWillMount() {
    await this.loadWeb3()
    await this.loadBlockchainData()
  }

  async loadWeb3() {
    if (window.ethereum) {
      window.web3 = new Web3(window.ethereum)
      await window.ethereum.enable()
    }
    else if (window.web3) {
      window.web3 = new Web3(window.web3.ethereum)
    }
    else {
      window.alert('Non-Ethereum browser detected. You should consider trying MetaMask!')
    }
  }

  async loadBlockchainData() {
    const web3 = window.web3
    // Load account
    const accounts = await window.ethereum.request({method: 'eth_requestAccounts'})
    this.setState({ account: accounts[0] })
    // Network ID
    const networkId = await web3.eth.net.getId()
    const networkData = SocialNetwork.networks[networkId]
    if(networkData) {
      const socialNetwork = web3.eth.Contract(SocialNetwork.abi, networkData.address)
      this.setState({ socialNetwork })
      const postCount = await socialNetwork.methods.postCount().call()
      this.setState({ postCount })
      // Load Posts
      for (var i = 1; i <= postCount; i++) {
        const post = await socialNetwork.methods.posts(i).call()
        this.setState({
          posts: [...this.state.posts, post]
        })
      }
      // Sort posts. Show highest tipped posts first
      this.setState({
        posts: this.state.posts.sort((a,b) => b.tipAmount - a.tipAmount )
      })
      this.setState({ loading: false})
    } else {
      window.alert('SocialNetwork contract not deployed to detected network.')
    }
  }

  createPost(content) {
    this.setState({ loading: true })
    this.state.socialNetwork.methods.createPost(content).send({ from: this.state.account })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
    })
    .on('confirmation', function(confirmationNumber, receipt){
      window.location.reload();
    })
  }

  tipPost(id, tipAmount) {
    this.setState({ loading: true })
    //web3.eth.sendTransaction({from: this.state.account, to: _authorPay, value: tipAmount})
    this.state.socialNetwork.methods.tipPost(id).send({ from: this.state.account, value: tipAmount })
    .once('receipt', (receipt) => {
      this.setState({ loading: false })
    })
    .on('confirmation', function(confirmationNumber, receipt){
      window.location.reload();
    })
  }

  constructor(props) {
    super(props)
    this.state = {
      account: '',
      socialNetwork: null,
      postCount: 0,
      posts: [],
      loading: true
    }

    this.createPost = this.createPost.bind(this)
    this.tipPost = this.tipPost.bind(this)
  }

  render() {
    return (
      <div>
        <Navbar account={this.state.account} />
        { this.state.loading
          ? <div id="loader" className="text-center mt-5"><p>Loading...</p></div>
          : <Main
              posts={this.state.posts}
              createPost={this.createPost}
              tipPost={this.tipPost}
            />
        }
      </div>
    );
  }
}

export default App;

My Main.js file:

import React, { Component } from 'react';
import Identicon from 'identicon.js';

class Main extends Component {

  render() {
    return (
      <div className="container-fluid mt-5">
        <div className="row">
          <main role="main" className="col-lg-12 ml-auto mr-auto" style={{ maxWidth: '500px' }}>
            <div className="content mr-auto ml-auto">
              <p>&nbsp;</p>
                <form onSubmit={(event) => {
                  event.preventDefault()
                  const content = this.postContent.value
                  this.props.createPost(content)
                }}>
                <div className="form-group mr-sm-2">
                  <input
                    id="postContent"
                    type="text"
                    ref={(input) => { this.postContent = input }}
                    className="form-control"
                    placeholder="What's on your mind?"
                    required />
                </div>
                <button type="submit" className="btn btn-primary btn-block">Share</button>
              </form>
              <p>&nbsp;</p>
              { this.props.posts.map((post, key) => {
                return(
                  <div className="card mb-4" key={key} >
                    <div className="card-header">
                      <img
                        alt="Avatar"
                        className='mr-2'
                        width='30'
                        height='30'
                        src={`data:image/png;base64,${new Identicon(post.author, 30).toString()}`}
                      />
                      <small className="text-muted">{post.author}</small>
                    </div>
                    <ul id="postList" className="list-group list-group-flush">
                      <li className="list-group-item">
                        <p>{post.content}</p>
                      </li>
                      <li key={key} className="list-group-item py-2">
                        <small className="float-left mt-1 text-muted">
                          TIPS: {window.web3.utils.fromWei(post.tipAmount.toString(), 'Ether')} ETH
                        </small>
                        <button
                          className="btn btn-link btn-sm float-right pt-0"
                          name={post.id}
                          onClick={(event) => {
                            let tipAmount = window.web3.utils.toWei('0.1', 'Ether')
                            this.props.tipPost(event.target.name, tipAmount)
                          }}
                        >
                          TIP 0.1 ETH
                        </button>
                      </li>
                    </ul>
                  </div>
                )
              })}
            </div>
          </main>
        </div>
      </div>
    );
  }
}

export default Main;

I can send the tip to the author via the code below but I'd like to perform this action via using "this.state.socialNetwork.methods.tipPost()" function.

//web3.eth.sendTransaction({from: this.state.account, to: _authorPay, value: tipAmount})

Anyone would like to help?

Hi, welcome! :wave:

So current, this code does not work, right?

this.state.socialNetwork.methods.tipPost(id).send(
    { from: this.state.account, value: tipAmount }
)

This one send the tip directly to the contract address itself. I want it to send to the author, who wrote the particular content.

Emmm, but I think your contract is right.
When Alice calls createPost(), she will generate a post, let us assume its id is 1. So later, when Bob calls tipPost() with the parameter id = 1, the author of this post, Alice, will get the tip.

So could you please show the transaction hash, let me have a check, why user sends the tip directly to the contract address itself.

I am using Ganache, I guess no point to provide the txID :confused:

So could you please deploy the contract on a testnet, such as Rinkey or Kovan to have a test?

I am using below GitHub repo to test it:

https:// github. com/dappuniversity/starter_kit

Contract Address:

https:// ropsten.etherscan. io/address/0x4569b57e159116bcfdb5ceeb8532830ec521a3aa

The wallet ID created a content on the application and should get the tip amount:

Sending the tip TXID, (sent to the contract instead of the content creator):

https:// ropsten.etherscan. io/tx/0x212d76bd0586bea14105bd4d5c9ffa679a17a7319694fc05db75373ffd403490

Yeah, it seems like all worked well, you can look at the first transaction you shared above: Ropsten Transaction Hash (Txhash) Details | Etherscan

Caller is: 0x0dd7d91798094b34cd3ab787f358f87eb4a70e64, called function with 0.1 eth, and the 0.1 eth was sent to 0xDDbAf754f85c88AcAe5e965BCa2E00ed236Bf77e, this is the author of the post, and there is no ETH in the contract:

So what is your question now?

Ah, that's OK now.

However, at the TX below I can see that caller sent the 0.1ETH tip to the contract first, then it goes to the content creator, which is "0xddbaf754f85c88acae5e965bca2e00ed236bf77e". Why doesn't it send the tip directly to the content creator? And why it is not seen on the Transactions and can be seen on Internal Txns?

I think this is function is payable, so when you call this function, your eth has been transferred to the contract, and then in your function, according to your logic, it will transfer eth to the author.

1 Like