James Bachini

Creating an UpOnly ERC20 Token

upOnly Token

In this tutorial we are going to create a ponzi game in the form of an ERC20 token that has an internal marketplace function. The idea is to increase the price over time so that early buyers get to dump on late buyers at a higher price. The last buyer will have no liquidity to sell their tokens.

The simple version of this contract is below but it has a MEV issue, can you spot it?

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

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

contract UpOnly is ERC20 {
    uint256 public initialPrice = 1;
    uint256 public deployedTimestamp = block.timestamp;

    constructor() ERC20("UpOnly Token", "UPONLY") {}

    function price() public view returns (uint256) {
        return (initialPrice + block.timestamp - deployedTimestamp);
    }

    function buyTokens() public payable {
        require(msg.value >= 0, "Insufficient ETH sent");
        uint256 amount = msg.value / price();
        require(amount >= 0, "Nothing to send");
        _mint(msg.sender, amount);
    }

    function sellTokens(uint256 amount) public {
        uint256 revenue = amount * price();
        require(address(this).balance >= revenue, "Insufficient contract balance");
        _burn(msg.sender, amount);
        payable(msg.sender).transfer(revenue);
    }

    receive() external payable {}
}

This contract can be bought and sold every block to drain the funds so we need a way to prevent someone from selling immediately after buying.

Now we could override the _update internal function so every time there was a mint or transfer it logs the timestamp and we restrict the sellTokens function to one hour since last mint or transfer. The issue with this would be that a malicious user could prevent someone else from selling just by sending them 1 token every hour.

So the solution I came up with was to add a orderTokens function and a claim function with a one hour delay in between. So degens have to deposit eth and then wait an hour before they can claim their tokens (by which time it will have probably been rug pulled).

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

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

contract UpOnly is ERC20 {
    uint256 public initialPrice = 1;
    uint256 public deployedTimestamp = block.timestamp;
    uint256 public constant sellDelay = 1 hours;
    mapping(address => uint256) public lastDelivery;
    mapping(address => uint256) public deliveryAmount;

    constructor() ERC20("UpOnly Token", "UPONLY") {}

    function price() public view returns (uint256) {
        return (initialPrice + block.timestamp - deployedTimestamp);
    }

    function orderTokens() public payable {
        require(msg.value > 0, "Insufficient ETH sent");
        uint256 amount = msg.value / price();
        require(amount > 0, "Nothing to send");
        deliver(msg.sender, amount);
    }

    function sellTokens(uint256 amount) public {
        require(block.timestamp > lastDelivery[msg.sender] + sellDelay, "Cannot sell tokens within one hour of last delivery");
        uint256 revenue = amount * price();
        require(address(this).balance >= revenue, "Insufficient contract balance");
        _burn(msg.sender, amount);
        payable(msg.sender).transfer(revenue);
    }

    function deliver(address recipient, uint256 amount) private {
        lastDelivery[recipient] = block.timestamp;
        deliveryAmount[recipient] += amount;
    }

    function claim() public {
        require(block.timestamp > lastDelivery[msg.sender] + sellDelay, "Cannot claim tokens within one hour");
        require(deliveryAmount[msg.sender] > 0, "Nothing to claim");
        _mint(msg.sender, deliveryAmount[msg.sender]);
        deliveryAmount[msg.sender] = 0;
    }

    receive() external payable {}
}

This is deployed to Sepolia testnet and you can play with it here: 0x8B320E83c7CA097E98DA2eA2035bE78dA94389Ce

uponly etherscan

Beware of scams and this isn’t suitable for mainnet deployment.


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.