DEV Community

Cover image for Karma: An ERC20-compatible Reputation-based Money on the Ethereum Blockchain
Laszlo Fazekas
Laszlo Fazekas

Posted on

Karma: An ERC20-compatible Reputation-based Money on the Ethereum Blockchain

Have you ever thought about how money is created? Well, typically, it is made out of nothing by banks. When someone takes out a loan, the bank adds the borrowed money to the amount already recorded in its database for the bank account. So if $100 was already present in the database entry for the bank account and someone takes out a loan of $10,000, the bank will rewrite the entry's content to $10,100. Thus, the bank loans out money it does not have. Of course, the loan must be repaid.

When the loan is repaid, the money created by the loan disappears. So this money comes out of nothing and disappears into nothing (let’s not get into the subject of interest, which complicates matters a bit). This money thus only exists temporarily. There is a parable about the point of money that exists only temporarily.

A cowboy walks into the bar. After a few drinks, the barkeep asks him if he would lend him $10 to fix a hole in the roof. The cowboy agrees to lend the $10, with the condition that it would be paid back in a week. The barkeep calls a roofer, who fixes the roof, and the barkeep pays the $10. Meanwhile, the roofer’s shoe falls apart, and he goes to the cobbler to buy a new one for another $10. The cobbler is delighted by his unexpected windfall and quickly has the money spent in the same bar. The $10 thus returns to the barkeep.

When a week passes, the cowboy returns and the barkeep returns the $10. The cowboy thanks him, pulls out a match, and burns the money. The barkeep, eyes wide, asks, ‘Why did you do that?’, to which the cowboy replies, ‘It’s fake money; it’s worth nothing.’

In the parable, the cowboy is like a bank. He lends money he doesn’t have. He creates this money out of thin air, and it disappears like bank loans when it is burned. Nevertheless, the bar got a new roof from this temporary money, the roofer received a new pair of shoes, and the cobbler had enough money to down a few drinks. These are tangible results of money that exists only temporarily. So what we use as money is nothing more than somebody’s debt. Money is debt!

The value of money is ultimately based on a form of trust. We trust that others value it as well, so when we do something for someone in exchange, we accept money because we trust that it will be accepted when we require something in return. This trust makes money a universal medium of exchange.

A good example of this is Bitcoin. Our Bitcoin balance is nothing more than an entry in a distributed ledger. There is nothing to back it up, yet people around the world are willing to pay real money for Bitcoin. The value of Bitcoin is thus purely a matter of trust. Money is trust!

These were the basic ideas that inspired the concept of karma money. What if everyone operated like a bank? Instead of taking money on credit, they would create it themselves. Of course, such a system would only work if there was a limit to the amount of money that could be created. So everyone’s debt would be public.

If I had to choose who to sell my products or services to, I would give it to the one with the least debt, as this gives the best chance of it being paid back quickly. Therefore, everyone is interested in keeping their debt low by repaying it.

Let’s look at how a cowboy parable would look in this system. There is no need for a cowboy (bank), as the barkeeper can create the $10 for the roofer himself. This creates a $10 debt for the barkeeper visible to everyone, but in return, his roof is fixed. The roofer passes this $10 on to the cobbler, and when the cobbler passes it back to the barkeeper, the $10 is destroyed along with the barkeeper’s debt. Thus, the barkeeper’s debt is again zero.

Thus, the process of creating transient money works well even without banks. All that is needed is for the roofer to trust the money created by the barkeeper and to believe that it is equally good money as the $10 created by the bank.

I have realized this karma money on the Ethereum Blockchain with a simple Solidity smart contract. My karma implementation is an ERC20 contract, which is great because it can easily be used with any Ethereum wallet. The peculiarity of karma is that, while in the case of other currencies, we spend from our balance, here, our balance starts from zero and increases with every spending (money creation). The greater our balance, the more indebted we become, so the goal is to reduce our balance.

Let’s look at a brief example. Alice wants to buy apples from John, so she pays John ten karma dollars for the apples. Alice’s balance thus increases to $10. Later, John buys oranges from Alice, so when he pays $10 to Alice, the debt is settled, and Alice’s balance is reduced back to $0.

After the theory, let us take a look at the code (available on GitHub):

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

contract Karma is IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _debts;
    mapping(address => mapping(address => uint256)) private _allowances;

    string private _name;
    string private _symbol;
    uint private _cycleReward;

    constructor(string memory name_, string memory symbol_, uint cycleReward) {
        _name = name_;
        _symbol = symbol_;
        _cycleReward = cycleReward;
    }

    function name() public view virtual override returns (string memory) {
        return _name;
    }

    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    // totalSupply is meaningless
    function totalSupply() public view virtual override returns (uint256) {
        return 0;
    }

    function balanceOf(
        address account
    ) public view virtual override returns (uint256) {
        return _balances[account];
    }

    function debtOf(
        address debtor,
        address creditor
    ) public view virtual returns (uint256) {
        return _debts[debtor][creditor];
    }

    function transfer(
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function allowance(
        address owner,
        address spender
    ) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(
        address spender,
        uint256 amount
    ) public virtual override returns (bool) {
        require(spender != address(0), "ERC20: approve to the zero address");

        address owner = msg.sender;
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual override returns (bool) {
        _spendAllowance(from, msg.sender, amount);
        _transfer(from, to, amount);
        return true;
    }

    function mineCycle(
        address[] memory nodes,
        uint256 amount
    ) public virtual returns (bool) {
        // checking debts in cycle from 0..n
        for (uint i = 0; i < nodes.length - 1; i++) {
            require(
                _debts[nodes[i]][nodes[i + 1]] >= amount,
                "Karma: Not enough debt for the cycle"
            );
        }

        // checking the last debt (end of cycle)
        require(
            _debts[nodes[nodes.length - 1]][nodes[0]] >= amount,
            "Karma: Not enough debt for the cycle"
        );

        // decreasing the debts and balances and pay cyleReward
        for (uint i = 0; i < nodes.length - 1; i++) {
            _debts[nodes[i]][nodes[i + 1]] -= amount;
            _balances[nodes[i]] -= amount;
            _transfer(nodes[i], msg.sender, _cycleReward);
        }

        _debts[nodes[nodes.length - 1]][nodes[0]] -= amount;
        _balances[nodes[nodes.length - 1]] -= amount;
        _transfer(nodes[nodes.length - 1], msg.sender, _cycleReward);

        return true;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(to != address(0), "ERC20: transfer to the zero address");

        _balances[from] += amount;
        _debts[from][to] += amount;

        emit Transfer(from, to, amount);
    }

    function _spendAllowance(
        address owner,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(
                currentAllowance >= amount,
                "ERC20: insufficient allowance"
            );

            uint256 newAmount = currentAllowance - amount;
            _allowances[owner][spender] = newAmount;
            emit Approval(owner, spender, newAmount);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

As can be seen, this is a completely standard ERC20 token. In the constructor, we can specify the token name and symbol and a parameter called cycleReward, which we will discuss later. Karma can represent dollars, euros, any money, but also time (e.g., I will work for you for one hour in exchange for one hour of work) or anything else. So karma is a general lending system.

The ERC20 standard’s balanceOf method is for querying any individual’s balance. Each person must be only linked to one Ethereum address. If that were not the case, then if someone got very indebted, they would simply open a new account, thus the whole system would be pointless. Fortunately, there are solutions such as Proof of Humanity or WorldCoin, which can ensure that each individual has a unique address.

I also introduced a debtOf function, which can precisely query who owes how many points to whom.

The standard transfer function is used for moving the balance. On the one hand, this increases the user’s balance; on the other, it records the debt to the receiving party. Therefore, if Alice pays John $10, Alice’s balance will be increased by $10, and it will be noted that Alice owes John $10.

It may appear that the code isn’t exactly doing what I described earlier. For instance, if John now pays Alice $10, instead of Alice and John having a balance of $0, Alice’s balance will stay at $10, and John’s balance will also become $10. In addition, in the debt matrix, Alice will now owe John $10, and John will owe Alice $10. Why doesn’t the smart contract resolve these debts?

Alice and John’s case is very straightforward, but typically, much more complicated debt chains form. In the cowboy example, there were three people along the chain. However, it is conceivable for a chain to contain ten or more people. Finding such chains is by no means trivial. This is why karma miners are needed.

The karma miners constantly watch the debt graph; if they discover a cycle, they can submit it to the smart contract. This is what the mineCycle method is for. The method checks the submitted chain; if it is valid, it will execute the modifications to balances. For this, the miner will receive a small karma from each member of the chain for reducing their balances (the amount of reward is defined by the cycleReward).

Thus, karma mining is almost exactly the same as mining Bitcoin. The member runs a program on their machine, consuming electricity and computation power (finding cycles in a graph), producing karma in return.

There are still a few ERC20 standard-related methods, but these are not of interest from a system standpoint.

Karma can be an ideal solution for communities seeking an alternative to the traditional money system. But members of such a system can also be companies or projects. For example, an open source project can accept donations and pay developers in karma, who can then exchange this for, say, locally grown vegetables and fruits in a community using karma.

Of course, there is no legal framework backing up karma like there is for actual money, but Bitcoin has shown that a currency can work purely on trust — without any backing. Compared to Bitcoin, karma does not require huge computing power, nor does it have a country’s energy consumption. The system relies on trust, and our identity backs the money.

UPDATE: I wrote a short article about how karma could be implemented on any blockchain paying the gas fee in karma instead of the chain’s native currency.

Top comments (0)