Skip to main content

Overview

The Computable Token Machine is a revolutionary token standard that combines ERC-20 functionality with the Diamond Standard (EIP-2535). Each CTM is both a standard token AND a modular execution environment capable of running its own applications, managing state, and evolving over time.
Beyond value, beyond utility—tokens that think.

EIP-2535 Diamond

Modular architecture

Modular Programs

Infinite extensibility

On-Chain Autonomy

Self-executing logic

Live Contract

Deployed CTM

Address: 0x477A9f214c947e6D81b9d32b6b1883F4a4ffFb24View on PaxeerScan →

What is CTM?

The Computable Token Machine is a revolutionary token standard that combines ERC-20 functionality with the Diamond Standard (EIP-2535). Each CTM is both a standard token AND a modular execution environment capable of running its own applications, managing state, and evolving over time.

Key Features

Add new features and applications to your token after deployment. Your token evolves with your needs.
All Programs share the same storage context. Programs can read and interact with each other seamlessly.
Modular Programs optimize gas usage and bypass EVM contract size limits.
Create complex on-chain agents that manage assets and execute tasks based on rich internal state.

Core Concepts

Two Personalities

CTM acts as both a token and a machine:
On the outside, a CTM behaves like any standard ERC-20 token. It can be:
  • Held in wallets
  • Traded on exchanges
  • Used in DeFi protocols
  • Transferred between addresses
No special handling required - it’s just a token!

Programs (Facets)

Programs are stateless Solidity contracts that contain the logic executed by the CTM. Each Program manages its own state within a unique storage slot to prevent collisions. Example Programs:
  • Voting and Governance
  • Staking and Rewards
  • DEX Integration
  • NFT Minting
  • Custom Game Logic
  • Automated Trading
  • On-chain AI Agents

Diamond Standard (EIP-2535)

CTM is built on the Diamond Standard, which allows a single contract to use multiple logic contracts (facets/programs). This pattern enables:
  • ✅ Unlimited contract size
  • ✅ Upgradability
  • ✅ Modular functionality
  • ✅ Single address persistence

Quick Start

1

Clone Repository

git clone https://github.com/dev-paxeer/Computable-Token-Machine-v1.0.0
cd Computable-Token-Machine-v1.0.0
npm install
2

Configure Network

Add Paxeer Network details to hardhat.config.js:
hardhat.config.js
networks: {
  paxeer: {
    url: "https://public-rpc.paxeer.app/rpc",
    chainId: 229,
    accounts: [process.env.PRIVATE_KEY]
  }
}
3

Deploy

npx hardhat run scripts/deploy.js --network paxeer
This deploys:
  • TokenVM.sol (the main proxy contract)
  • DiamondCutFacet (for adding/removing Programs)
  • DiamondLoupeFacet (for inspecting installed Programs)
  • OwnershipFacet (access control)
  • ERC20Facet (token functionality)

Creating Programs

Program Structure

Programs must be stateless and manage state within a unique storage slot to prevent collisions.
VotingProgram.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ERC20Facet} from "./ERC20Facet.sol";

contract VotingProgram {
    
    struct VotingStorage {
        mapping(uint256 => string) proposals;
        mapping(uint256 => mapping(address => uint256)) votes;
        uint256 proposalCount;
    }

    bytes32 constant VOTING_STORAGE_POSITION = 
        keccak256("ctm.program.storage.voting");

    function votingStorage() internal pure 
        returns (VotingStorage storage vs) 
    {
        bytes32 position = VOTING_STORAGE_POSITION;
        assembly {
            vs.slot := position
        }
    }

    function createProposal(string calldata _description) external {
        VotingStorage storage vs = votingStorage();
        vs.proposalCount++;
        vs.proposals[vs.proposalCount] = _description;
    }

    function vote(uint256 _proposalId) external {
        uint256 voterBalance = ERC20Facet(address(this))
            .balanceOf(msg.sender);
        require(voterBalance > 0, "Must hold tokens");

        VotingStorage storage vs = votingStorage();
        vs.votes[_proposalId][msg.sender] = voterBalance;
    }
    
    function getProposal(uint256 _proposalId) 
        external view returns (string memory) 
    {
        VotingStorage storage vs = votingStorage();
        return vs.proposals[_proposalId];
    }
    
    function getVotes(uint256 _proposalId, address _voter) 
        external view returns (uint256) 
    {
        VotingStorage storage vs = votingStorage();
        return vs.votes[_proposalId][_voter];
    }
}

Adding Programs to CTM

Use the diamondCut function to register new Programs:
const diamondCut = await ethers.getContractAt('IDiamondCut', ctmAddress);

await diamondCut.diamondCut(
  [{
    facetAddress: votingProgramAddress,
    action: FacetCutAction.Add, // 0 = Add, 1 = Replace, 2 = Remove
    functionSelectors: getSelectors(votingProgram)
  }],
  ethers.constants.AddressZero,
  '0x'
);

Helper Function for Selectors

function getSelectors(contract) {
  const signatures = Object.keys(contract.interface.functions);
  const selectors = signatures.reduce((acc, val) => {
    if (val !== 'init(bytes)') {
      acc.push(contract.interface.getSighash(val));
    }
    return acc;
  }, []);
  return selectors;
}

Program Best Practices

Always use keccak256 hashes for storage positions to avoid collisions:
bytes32 constant STORAGE_POSITION = keccak256("ctm.program.storage.myprogram");
Never use regular state variables at the contract level!
Programs should not hold funds or use constructors that set state:Wrong:
contract MyProgram {
    uint256 public count = 0; // Don't do this!
    
    constructor() {
        count = 10; // Don't do this!
    }
}
Correct:
contract MyProgram {
    bytes32 constant STORAGE_POSITION = keccak256("ctm.myprogram");
    
    struct MyStorage {
        uint256 count;
    }
    
    function myStorage() internal pure returns (MyStorage storage ms) {
        bytes32 position = STORAGE_POSITION;
        assembly { ms.slot := position }
    }
}
Protect diamondCut with ownership or governance controls:
modifier onlyOwner() {
    require(msg.sender == owner(), "Not authorized");
    _;
}
Only authorized addresses should be able to add/remove Programs.
Programs can call each other using address(this) and shared storage:
// Calling another program's function
uint256 balance = ERC20Facet(address(this)).balanceOf(user);

// Reading shared storage (if designed that way)
bytes32 sharedPosition = keccak256("ctm.shared.data");

Security Considerations

Critical Security Points:
  1. Storage Layout: Never use standard global state variables. Always use the diamond storage pattern with unique keccak256 slots.
  2. Access Control: The diamondCut function is extremely powerful. Ensure it’s protected by robust ownership or governance control.
  3. Stateless Logic: Programs are logic contracts and should not hold funds or have constructors that set state.
  4. Testing: Thoroughly test all Programs before deployment. Once added to a CTM, they have access to the token’s storage and capabilities.

Auditing Checklist

Before deploying CTM or adding new Programs:
  • Storage slots use unique keccak256 hashes
  • No global state variables in Programs
  • Access control properly configured
  • Programs don’t hold funds directly
  • All Programs thoroughly tested
  • diamondCut function is protected
  • Inter-program interactions tested
  • Gas optimization reviewed
  • Security audit completed (for production)

Advanced Examples

Staking Program

contract StakingProgram {
    struct StakingStorage {
        mapping(address => uint256) stakedAmount;
        mapping(address => uint256) stakingTimestamp;
        uint256 rewardRate; // rewards per second
    }

    bytes32 constant STAKING_STORAGE = keccak256("ctm.program.staking");

    function stakingStorage() internal pure 
        returns (StakingStorage storage ss) 
    {
        bytes32 position = STAKING_STORAGE;
        assembly { ss.slot := position }
    }

    function stake(uint256 amount) external {
        ERC20Facet token = ERC20Facet(address(this));
        require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed");
        
        StakingStorage storage ss = stakingStorage();
        ss.stakedAmount[msg.sender] += amount;
        ss.stakingTimestamp[msg.sender] = block.timestamp;
    }

    function unstake(uint256 amount) external {
        StakingStorage storage ss = stakingStorage();
        require(ss.stakedAmount[msg.sender] >= amount, "Insufficient stake");
        
        uint256 rewards = calculateRewards(msg.sender);
        ss.stakedAmount[msg.sender] -= amount;
        
        ERC20Facet token = ERC20Facet(address(this));
        require(token.transfer(msg.sender, amount + rewards), "Transfer failed");
    }

    function calculateRewards(address user) public view returns (uint256) {
        StakingStorage storage ss = stakingStorage();
        uint256 timeStaked = block.timestamp - ss.stakingTimestamp[user];
        return ss.stakedAmount[user] * ss.rewardRate * timeStaked / 1e18;
    }
}

Governance Program

contract GovernanceProgram {
    struct GovernanceStorage {
        mapping(uint256 => Proposal) proposals;
        uint256 proposalCount;
        uint256 votingPeriod;
    }

    struct Proposal {
        string description;
        uint256 forVotes;
        uint256 againstVotes;
        uint256 endTime;
        bool executed;
    }

    bytes32 constant GOVERNANCE_STORAGE = keccak256("ctm.program.governance");

    function governanceStorage() internal pure 
        returns (GovernanceStorage storage gs) 
    {
        bytes32 position = GOVERNANCE_STORAGE;
        assembly { gs.slot := position }
    }

    function propose(string calldata description) external returns (uint256) {
        GovernanceStorage storage gs = governanceStorage();
        gs.proposalCount++;
        
        gs.proposals[gs.proposalCount] = Proposal({
            description: description,
            forVotes: 0,
            againstVotes: 0,
            endTime: block.timestamp + gs.votingPeriod,
            executed: false
        });
        
        return gs.proposalCount;
    }

    function vote(uint256 proposalId, bool support) external {
        GovernanceStorage storage gs = governanceStorage();
        Proposal storage proposal = gs.proposals[proposalId];
        
        require(block.timestamp < proposal.endTime, "Voting ended");
        
        uint256 weight = ERC20Facet(address(this)).balanceOf(msg.sender);
        
        if (support) {
            proposal.forVotes += weight;
        } else {
            proposal.againstVotes += weight;
        }
    }
}

Resources

Next Steps