James Bachini

ERC20 Carbon Credits | Creating A Depleting Token

Carbon Credits

Carbon credits are used to tokenise finances put towards carbon offsetting. On-chain products often have a purchase and burn mechanism where a user must first purchase the token on a DEX and then manually execute a transaction to burn the token.

I had the idea to create a automatically depleting token which you just hold in your wallet and the balance decreases over the course of a year. A bit like LidoFinance’s stETH product but the balance goes down instead of up.

This could provide a user with an easy way to purchase carbon credits and let them burn at the correct rate to offset their personal or corporate carbon emissions.

The full source code for this proof of concept is at:
https://github.com/jamesbachini/CarbonCredits

Carbon Credits Ethereum

Carbon Credits Code Walkthrough

The first step was to import the OpenZeppelin ERC20 library which we will use as a base.

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

The general idea will be to overwrite the balance function to create a dynamic value.

Initially I thought that you could do this with a liquidity pool and give each token holder a share of that pool but I decided to create a couple of uint variables for each user whenever a transfer/mint is made

mapping(address => uint256) public _annualOffset;
mapping(address => uint256) public _lastUpdate;

Whenever funds are moved we update the _annualOffset for that user to the latest amount they have in their wallet and set _lastUpdate to the block.timestamp value

From there we can create a dynamic balanceOf() function overwriting the default

function balanceOf(address account) public view virtual override returns (uint256) {
    if (_annualOffset[account] <= 0) return 0;
    if (block.timestamp - _lastUpdate[account] > 365 days) return 0;
    uint remaining = _annualOffset[account] - ((_annualOffset[account] / 365 days) * (block.timestamp - _lastUpdate[account]));
    return remaining;
}

We do some sanity checks to return zero funds if the date is greater than a year or there is no initial balance for a new account. We then calculate the remaining value in their wallet by taking the initial balance minus the amount of depreciation calculated by timeSinceLastUpdate * _annualOffset / one year in seconds.

There are benefits and drawbacks to this method. It has an elegant simplicity but might not be robust enough to use in production. A user who has 100 tokens in his account would have 50 left which would normally last another 180 days. If he sends some funds or purchases more then his _lastUpdate timestamp updates and his 50 tokens now get burnt over 365 days instead.

The vision I had for a UI would be a web frontend which can be used to calculate a users carbon credits. A user then goes on and plans for the following year. They can see how much they have, if anything in their current balance, calculate their annual footprint and top up to offset their carbon footprint for the next 365 days.

The mint function is pegged to ETH in this example and the contract owner can withdraw that to spend on carbon offseting programs. An alternative mechanism would be to integrate 3rd party carbon tokens and create a wrapper contract which acts as a user facing proxy simplifying and diversifying the underlying carbon credit portfolio.

We also need to overwrite the totalSupply() function to replicate the dynamic balances. Finally the internal _transfer() function needs modifying to update the balances accordingly and make everything work.

function _transfer(address from, address to, uint256 amount) internal virtual override {
    require(from != address(0), "ERC20: transfer from the zero address");
    require(to != address(0), "ERC20: transfer to the zero address");
    _beforeTokenTransfer(from, to, amount);
    uint256 fromBalance = balanceOf(from);
    require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    _annualOffset[from] = fromBalance - amount;
    _lastUpdate[from] = block.timestamp;
    uint256 toBalance = balanceOf(to);
    _annualOffset[to] = toBalance + amount;
    _lastUpdate[to] = block.timestamp;
    emit Transfer(from, to, amount);
    _afterTokenTransfer(from, to, amount);
}

There’s a basic unit test script in the repository but it would need thorough testing before being put into production as I haven’t looked at it from a security perspective at this stage.

Carbon Credits Conclusion

Carbon credits are a market-based mechanism that aims to reduce greenhouse gas emissions by tokenising carbon offsetting funding. The concept of carbon credits is based on the principle that greenhouse gas emissions from various sources have the same impact on the environment regardless of where they are emitted. By putting a price on these emissions, companies and organizations can be incentivized to reduce their emissions and contribute to investing in cleaner technologies.

The method used in this proof-of-concept creates a dynamic balance which can be useful to create a seamless user experience where there is no staking or burning. The user just holds the carbon credit token in Metamask and the quantity depreciates over time automatically.

I hope this has been of interest and has inspired someone out there to look more into on-chain carbon offsetting which seems to be an ideal product for a digital asset.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.