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
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! Internally, the CTM acts as a proxy that routes function calls to various logic contracts called “Programs” (or Facets). These Programs can be:
Added without redeployment
Replaced to fix bugs or add features
Removed when no longer needed
Composed together for complex behavior
All while maintaining the same contract address!
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
Clone Repository
git clone https://github.com/dev-paxeer/Computable-Token-Machine-v1.0.0
cd Computable-Token-Machine-v1.0.0
npm install
Configure Network
Add Paxeer Network details to hardhat.config.js: networks : {
paxeer : {
url : "https://public-rpc.paxeer.app/rpc" ,
chainId : 229 ,
accounts : [ process . env . PRIVATE_KEY ]
}
}
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.
// 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.
4. Inter-Program Communication
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:
Storage Layout: Never use standard global state variables. Always use the diamond storage pattern with unique keccak256 slots.
Access Control: The diamondCut function is extremely powerful. Ensure it’s protected by robust ownership or governance control.
Stateless Logic: Programs are logic contracts and should not hold funds or have constructors that set state.
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:
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