paint-brush
Karma: an ERC20-compatible Alternative Money on the Ethereum Blockchainby@thebojda
210 reads

Karma: an ERC20-compatible Alternative Money on the Ethereum Blockchain

by Laszlo FazekasSeptember 11th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

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.
featured image - Karma: an ERC20-compatible Alternative Money on the Ethereum Blockchain
Laszlo Fazekas HackerNoon profile picture

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 just 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 10000$, the bank will just rewrite the figure to 10100$. Thus, the bank loans out money it does not have. Of course, the loan must be repaid. When the loan is repaid, the money that was 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 in relation to the point of money which 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 and then 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 when it is burned, it disappears like bank loans. Nevertheless, from this temporary money, the bar got a new roof, 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 take a 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. It is thus evident that 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 0 and increases with every spending (money creation). The greater our balance, the more indebted we become, so here 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 10 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 here 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);
        }
    }
}


As can be seen, this is a completely standard ERC20 token. In the constructor, we can specify the token name and symbol and also 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 1 hour in exchange for 1 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. It is very important that each person is 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 are able to 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 and, 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 both 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, for example, there were 3 people long the chain, however, it is conceivable for a chain to contain 10 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, and 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, and 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. 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), which in return produces karma.


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 in the form of karma and similarly 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 the money is backed by our very own identity.