Solum (SOLUM) — Canonical Contract Specification (Zipvilization)
0) Scope
This document describes all on-chain rules of the canonical Solum.sol contract (Solidity ^0.8.20).
- Source of truth: the contract code.
- Purpose: make the system auditable and unambiguous for both developers and AI.
- If a rule is not in the contract, it does not exist operationally.
1) Token Identity (Metadata)
string public constant name = "Solum";
string public constant symbol = "SOLUM";
uint8 public constant decimals = 18;
2) Supply: Fixed, With Real Burn (True Deflation)
2.1 Initial Supply (100T)
uint256 private _tTotal = 100_000_000_000_000 * 10**decimals; // 100T
- No mint path exists (no
mint,ownerMint, or inflation switch). - Supply can only decrease through real burn.
2.2 Dual-supply (Reflection Base)
uint256 private _rTotal = type(uint256).max - (type(uint256).max % _tTotal);
mapping(address => uint256) private _rOwned;
_tTotalis the “real” supply users see._rTotaland_rOwnedare internal reflected balances enabling passive redistribution without claim/staking.
2.3 Real Burn: reduces _tTotal (and _rTotal proportionally)
Within _transfer:
_tTotal -= tBurn;
_rTotal -= tBurn * rate;
emit Transfer(from, address(0), tBurn);
This is real burn because totalSupply decreases (not just a transfer to a “dead wallet”).
3) ERC-20 Standard Functions + Reflection Conversions
3.1 totalSupply()
function totalSupply() external view returns (uint256) { return _tTotal; }
3.2 balanceOf()
function balanceOf(address account) public view returns (uint256) {
return tokenFromReflection(_rOwned[account]);
}
3.3 tokenFromReflection() and rate
function tokenFromReflection(uint256 rAmount) public view returns (uint256) {
uint256 currentRate = _getRate();
return rAmount / currentRate;
}
function _getRate() internal view returns (uint256) {
return _rTotal / _tTotal;
}
4) Denominators (Fees & Slippage)
uint256 private constant FEE_DENOM = 1_000_000; // ppm fees
uint256 private constant BPS_DENOM = 10_000; // basis points
- Fees are expressed in ppm (1,000,000 = 100%).
- Slippage uses bps (10,000 = 100%).
5) Taxes: Transaction-Type Totals + Deterministic Split
This contract uses total fees per tx type (buy/sell/transfer), plus hardcoded split rules.
5.1 Fee Totals (initial values)
uint256 public constant BUY_FEE_INITIAL = 10_000; // 1.0%
uint256 public constant SELL_FEE_INITIAL = 100_000; // 10.0%
uint256 public constant TRANSFER_FEE_INITIAL = 50_000; // 5.0%
uint256 public buyFeeTotal;
uint256 public sellFeeTotal;
uint256 public transferFeeTotal;
5.2 BUY fee split: 50% LP / 50% Treasury
Representative logic:
uint256 tBuyFee = (tAmount * buyFeeTotal) / FEE_DENOM;
tLP = tBuyFee / 2;
tTreasury = tBuyFee - tLP;
5.3 SELL fee split: 4/3/2/1 (Burn/Reflection/LP/Treasury) as 10 parts
Representative logic:
uint256 tSellFee = (tAmount * sellFeeTotal) / FEE_DENOM;
tBurn = (tSellFee * 4) / 10;
tReflection = (tSellFee * 3) / 10;
tLP = (tSellFee * 2) / 10;
tTreasury = tSellFee - tBurn - tReflection - tLP;
5.4 TRANSFER fee split: 2/3 (Burn/Reflection) as 5 parts
Representative logic:
uint256 tTransferFee = (tAmount * transferFeeTotal) / FEE_DENOM;
tBurn = (tTransferFee * 2) / 5;
tReflection = tTransferFee - tBurn;
6) Fee Governance: Only-Decreasing, 24h Timelock, Max 5 Changes, Auto-Freeze
6.1 Timelock
uint256 public constant FEE_TIMELOCK = 24 hours;
Each fee type has:
- pending value
- available-at timestamp
- changes-used counter
- frozen flag
Example (BUY):
uint256 public pendingBuyFeeTotal;
uint256 public buyFeeChangeTime;
uint8 public buyFeeChangesUsed;
bool public buyFeeFrozen;
(Similar fields exist for SELL and TRANSFER.)
6.2 Only-decreasing rule
Proposals must satisfy:
require(newTotalFee <= buyFeeTotal, "ONLY_DECREASING");
(Analogous for SELL/TRANSFER.)
6.3 Max changes: 5
Each fee can be reduced up to 5 times. It auto-freezes when:
- the fee reaches 0, or
- changes used reaches 5.
Representative behavior:
changesUsed++;
if (feeTotal == 0 || changesUsed >= FEE_MAX_CHANGES) {
feeFrozen = true;
}
6.4 Exact flow
For each fee type: 1) propose*FeeReduction(newTotalFee) → sets pending value + FEE_TIMELOCK 2) wait FEE_TIMELOCK 3) confirm*FeeReduction() → applies, increments counter, may freeze
Events provide audit trace:
*FeeProposed(newFee, availableAt)*FeeApplied(newFee, changesUsed)*FeeFrozen(changesUsed)
7) Trading Gate (Pre-Trading Discipline)
7.1 State
bool public tradingEnabled = false;
7.2 One-time activation (owner-only)
function enableTrading() external onlyOwner {
require(!tradingEnabled, "TRADING_ON");
tradingEnabled = true;
emit TradingEnabled();
}
7.3 Pre-trading restriction
if (!tradingEnabled) {
require(isFeeExempt[from] || isFeeExempt[to], "TRADING_OFF");
}
8) Anti-Whale: MAX_TX + Dynamic MAX_WALLET
8.1 MAX_TX (fixed 10B)
uint256 public constant MAX_TX_AMOUNT = 10_000_000_000 * 10**decimals; // 10B
Enforced as:
if (!isLimitExempt[from] && !isLimitExempt[to]) {
require(tAmount <= MAX_TX_AMOUNT, "MAX_TX");
}
8.2 Dynamic MAX_WALLET
Constants:
uint256 public constant MAX_WALLET_INITIAL = 30_000_000_000 * 10**decimals; // 30B
uint256 public constant MAX_WALLET_GROWTH_DELAY = 180 days;
uint256 public constant MAX_WALLET_WEEKLY_GROWTH_PPM = 110_000; // +10% weekly
Core behavior:
- strict limit for the first 180 days
- then weekly compounding growth
- safety cap to avoid unbounded loops; beyond a horizon it becomes effectively uncapped
Applied on receive:
if (!isLimitExempt[to] && to != pair && to != router) {
uint256 newBal = balanceOf(to) + tAmount;
require(newBal <= _maxWalletNow(), "MAX_WALLET");
}
9) Treasury: Address + Timelocked Change
address public treasury;
address public pendingTreasury;
uint256 public treasuryChangeTime;
uint256 public constant TREASURY_CHANGE_DELAY = 48 hours;
Propose / Confirm flow
function proposeTreasury(address newTreasury) external onlyOwner {
pendingTreasury = newTreasury;
treasuryChangeTime = block.timestamp + TREASURY_CHANGE_DELAY;
emit TreasuryProposed(newTreasury, treasuryChangeTime);
}
function confirmTreasury() external onlyOwner {
require(block.timestamp >= treasuryChangeTime, "TIMELOCK");
treasury = pendingTreasury;
pendingTreasury = address(0);
treasuryChangeTime = 0;
isFeeExempt[treasury] = true;
isLimitExempt[treasury] = true;
emit TreasuryConfirmed(treasury);
}
10) DEX Wiring (router/pair/weth)
address public immutable router;
address public immutable pair;
address public immutable weth;
- Router and pair are injected at deploy.
wethis read from the router’sWETH().
The contract expects a V2-style router compatible with:
swapExactTokensForETHSupportingFeeOnTransferTokensaddLiquidityETHgetAmountsOut(best-effort; if unavailable the contract falls back safely)
11) SwapBack: Threshold, Cooldown, Caps, Slippage (MEV-aware)
11.1 Public controls
bool public swapBackEnabled = true;
uint256 public swapThreshold;
uint256 public swapBackMaxAmount;
uint256 public swapBackCooldown;
uint256 public slippageBps;
uint256 public lastSwapBackTime;
11.2 Owner guardrails for config
- slippage bounded (e.g., 50..800 bps)
- maxAmount >= threshold
- cooldown bounded (e.g., <= 15 minutes)
11.3 Fee buckets
uint256 private _tokensForLiquidity;
uint256 private _tokensForTreasury;
Buckets increase during fee collection, then are processed in _swapBack().
11.4 Trigger conditions (sells only)
_swapBack() is triggered primarily on sells (to == pair), with cooldown and threshold checks.
11.5 Best-effort minOut with slippage
The contract attempts getAmountsOut; if unavailable, minOut becomes 0. This avoids DoS across different router implementations while still applying:
- cooldown
- threshold
- per-swap cap
11.6 Liquidity + Treasury ETH routing
SwapBack:
- swaps a portion to ETH
- adds liquidity (token + ETH)
- sends remaining ETH to treasury
LP tokens are typically minted to a specific recipient (often owner), enabling external locking.
12) Launch Access Controls (Whitelist + Buy Cooldown, Phase-0 Only)
These rules exist only to stabilize the first 48 hours after trading is enabled.
They are designed to:
- prevent rapid buy-looping during the most hostile phase,
- provide a short, explicit preference window to an early-access set,
- keep the privilege limited to MAX_TX only (not MAX_WALLET),
- keep sells always possible (no exit restrictions).
12.1 Launch window duration (48h)
- The contract defines a launch window starting at the moment trading is enabled.
- For the first 48 hours, extra buy rules apply.
- After the window expires, these extra buy rules are inactive.
12.2 Whitelist preference window (first 60 minutes)
- For the first 60 minutes after trading is enabled:
- only whitelisted addresses are allowed to buy from the pool (pair).
- buys are still capped by
MAX_TX_AMOUNT.
- This is a preference window, not a special allocation.
12.3 Per-wallet buy cooldown (60 minutes)
During the same 48h launch window:
- A wallet may execute a buy (from the pair) at most once per 60 minutes.
- This applies to:
- whitelisted wallets during the first hour
- all wallets after the first hour (public phase)
Cooldown rules apply only to buys. Sells and normal transfers are not blocked by cooldown.
Canonical intent: “buy privilege = time to enter once”, not “ability to accumulate”.
12.4 Max buy = MAX_TX (no special maxWallet)
Whitelist preference does not bypass:
MAX_TX_AMOUNT(it is the only allowed purchase size for fair entry; equal or lower).
Whitelist preference does not change:
MAX_WALLETrules (still enforced).
13) Exemptions (fees & limits)
mapping(address => bool) public isFeeExempt;
mapping(address => bool) public isLimitExempt;
Initial exemptions typically include deployer, contract, treasury, router, pair.
Owner can update exemptions:
function setFeeExempt(address account, bool exempt) external onlyOwner;
function setLimitExempt(address account, bool exempt) external onlyOwner;
14) Ownership
A minimal Ownable pattern is embedded:
owneronlyOwnertransferOwnership(newOwner)
15) Events (Audit Trace)
Core events include:
- ERC20:
Transfer,Approval - Launch:
TradingEnabled - SwapBack:
SwapBackPaused,SwapBackConfigUpdated - Treasury:
TreasuryProposed,TreasuryConfirmed - Fee governance:
*FeeProposed,*FeeApplied,*FeeFrozen - Ownership:
OwnershipTransferred
If launch access controls have explicit events (recommended):
- whitelist updates (address added/removed)
- launch window parameters (if configurable at deploy)
16) ETH handling
The contract can receive ETH (swap proceeds / liquidity operations):
receive() external payable {}
17) What can change vs what cannot
Immutable / cannot increase
- Supply cannot increase (no mint).
- MAX_TX fixed (10B).
- MaxWallet growth rules are hardcoded.
Changeable with hard constraints
- Fee totals (buy/sell/transfer): only-decreasing, 24h timelock, max 5 changes each, auto-freeze.
- SwapBack parameters: bounded by guardrails.
- Treasury: timelocked change.
- Exemption lists: owner-controlled.
- Launch access controls: fixed Phase-0 posture (48h window, 60m whitelist preference, 60m cooldown) unless explicitly coded otherwise.
18) Zipvilization interpretation (no denial of blockchain reality)
- On-chain: holders, transfers, market activity are real.
- Zipvilization layer: holders are interpreted as colonists; balances as territory.
- Fees and protections are structural mechanics:
- defend early-phase liquidity
- reduce shock extraction
- reward holding via reflection
- fund evolution via treasury
END OF SPEC