Beginner

Reading Contracts on Block Explorers — Kudos board build (step 5/6)

Navigate real contracts.

Developer Beginner
7/7 — Reading Contracts on Block Explorers — Kudos board build (step 5/6)

GoalAdd optional tips: accept ETH, forward it, and track totals.

Bitcoin seems to be a very promising idea. I like the idea of basing security on the assumption that the CPU power of honest participants outweighs that of the attacker.
— Hal Finney

What to write

  1. Tip tracking
    new mapping for received tips.
    mapping(address => uint256) public tipsReceived;

    Public getter lets recipients see how much they’ve earned.

  2. Payable send
    mark sendKudos payable and forward value.
    function sendKudos(address to, string calldata message) external payable {
        // ... validations ...
        _lastKudos[to] = message;
        kudosCount[to] += 1;
        totalKudos += 1;
        _lastSent[msg.sender] = block.timestamp;
        if (msg.value > 0) {
            tipsReceived[to] += msg.value;
            (bool ok, ) = to.call{value: msg.value}('');
            require(ok, 'tip failed');
        }
        emit KudosSent(msg.sender, to, message, msg.value);
    }

    Checks-effects-interactions: update state first, then transfer ETH with call and guard success.

  3. Event update
    include the tip amount in the event.
    event KudosSent(address indexed from, address indexed to, string message, uint256 tip);

    Logging the tip makes payouts auditable in explorers.

Why Shows payable patterns, safe forwarding, and transparent accounting.

Try it Send kudos with a small tip; confirm tipsReceived and that the recipient receives ETH.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract KudosBoard {
    error EmptyMessage();
    error TooLong();
    error TooSoon(uint256 readyAt);

    uint256 private constant MAX_LEN = 140;
    uint256 private constant COOLDOWN = 60;

    mapping(address => string) private _lastKudos;
    mapping(address => uint256) public kudosCount;
    mapping(address => uint256) private _lastSent;
    mapping(address => uint256) public tipsReceived;
    uint256 public totalKudos;

    event KudosSent(address indexed from, address indexed to, string message, uint256 tip);

    function sendKudos(address to, string calldata message) external payable {
        if (bytes(message).length == 0) revert EmptyMessage();
        if (bytes(message).length > MAX_LEN) revert TooLong();
        uint256 ready = _lastSent[msg.sender] + COOLDOWN;
        if (block.timestamp < ready) revert TooSoon(ready);

        _lastKudos[to] = message;
        kudosCount[to] += 1;
        totalKudos += 1;
        _lastSent[msg.sender] = block.timestamp;
        if (msg.value > 0) {
            tipsReceived[to] += msg.value;
            (bool ok, ) = to.call{value: msg.value}('');
            require(ok, 'tip failed');
        }
        emit KudosSent(msg.sender, to, message, msg.value);
    }

    function lastKudos(address user) external view returns (string memory) {
        return _lastKudos[user];
    }
}

NextWe’ll keep extending this in the following lesson; this version already deploys and runs.

← Previous section
Next lesson →