Computable Token Machine (CTM)
Revolutionary token standard where tokens are fully-fledged execution environments
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: 0x477A9f214c947e6D81b9d32b6b1883F4a4ffFb24
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
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 installConfigure Network
Add Paxeer Network details to hardhat.config.js:
networks: {
paxeer: {
url: "https://public-rpc.paxeer.app/rpc",
chainId: 125,
accounts: [process.env.PRIVATE_KEY]
}
}Deploy
npx hardhat run scripts/deploy.js --network paxeerThis 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
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
diamondCutfunction 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:
- 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
GitHub Repository
View source code and examples
Live Contract
Explore on PaxeerScan
EIP-2535 Specification
Learn about Diamond Standard
Developer Community
Join other CTM developers
Next Steps
How is this guide?