Token Architecture
CapSign tokens are ERC-7752 securities tokens with lot-based accounting.
Overview
Features:
ERC-20 compatibility
ERC-7752 lot tracking
Transfer restrictions
Compliance modules
Administrative controls
Diamond Structure
TokenDiamond
├── DiamondCutFacet
├── DiamondLoupeFacet
├── AccessControlFacet
├── TokenERC20Facet - ERC-20 functions
├── TokenBalancesFacet - Balance tracking
├── TokenLotsFacet - Lot management (ERC-7752)
├── TokenTransferFacet - Transfer logic
├── TokenComplianceFacet - Compliance checks
├── TokenMetadataFacet - Token metadata
└── TokenAdminFacet - Admin functionsKey Interfaces
ERC-20
Standard ERC-20 functions:
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}ERC-7752 Lots
Lot-based accounting:
interface ITokenLots {
// Create lot (issuance)
function createLot(
address owner,
uint256 quantity,
address paymentCurrency,
uint256 costBasis,
uint256 acquisitionDate,
string memory uri,
bytes memory data,
uint256 customId
) external returns (bytes32 lotId, uint256 assignedCustomId);
// Transfer lot
function transfer(
bytes32 lotId,
address to,
uint256 quantity,
TransferType tType,
uint256 newCostBasis,
string memory uri,
bytes memory data
) external returns (bytes32 newLotId);
// Get lot info
function getLot(bytes32 lotId) external view returns (
bytes32 parentLotId,
bool isValid,
uint256 quantity,
address paymentCurrency,
uint256 costBasis,
uint256 acquisitionDate,
uint256 lastUpdate,
TransferType tType,
string memory uri,
bytes memory data
);
// Get user's lots
function getLotsOf(address account) external view returns (bytes32[] memory);
}Compliance
Transfer restrictions:
interface ITokenCompliance {
// Add compliance condition
function addComplianceCondition(
address condition,
string memory conditionName
) external;
// Validate transfer
function validateTransfer(
address from,
address to,
bytes32 lotId,
uint256 quantity
) external view returns (bool);
}Lot Structure
Each lot tracks:
struct Lot {
bytes32 parentLotId; // Parent lot (for tracking splits)
uint256 quantity; // Number of tokens
address paymentCurrency; // Currency used (USDC, ETH, etc.)
uint256 costBasis; // Cost per token
uint256 acquisitionDate; // When acquired
uint256 lastUpdate; // Last modification
bool isValid; // Is lot active?
address owner; // Current owner
TransferType tType; // How acquired
string uri; // Metadata URI
bytes data; // Additional data
}Transfer Types
enum TransferType {
INTERNAL, // Internal transfer
SALE, // Sale/purchase
GIFT, // Gift
INHERITANCE, // Inheritance
INCOME // Income/compensation
}Compliance Modules
Condition Contracts
Separate contracts that check transfers:
interface IComplianceCondition {
function checkTransfer(
address from,
address to,
bytes32 lotId,
uint256 quantity
) external view returns (bool allowed, string memory reason);
}Built-in Conditions
LockupCondition - Time-based lockup
VestingCondition - Linear vesting
Rule144Condition - SEC Rule 144 holding period
ROFRCondition - Right of first refusal
Example: Lockup
contract LockupCondition is IComplianceCondition {
mapping(bytes32 => uint256) public lockupExpiry;
function checkTransfer(
address,
address,
bytes32 lotId,
uint256
) external view returns (bool, string memory) {
if (block.timestamp < lockupExpiry[lotId]) {
return (false, "Tokens locked");
}
return (true, "");
}
}Deployment
Via TokenFactory
IToken token = ITokenFactory(TOKEN_FACTORY).createToken({
name: "Class A Common Stock",
symbol: "CSA",
decimals: 0,
baseURI: "https://capsign.com/tokens/",
admin: msg.sender
}, facetCuts, MULTI_INIT_FLAG, initData, complianceConditions);Issuance (Minting)
Create lots for recipients:
token.createLot({
owner: investorAddress,
quantity: 1000,
paymentCurrency: USDC_ADDRESS,
costBasis: 1e6, // $1.00 (6 decimals)
acquisitionDate: block.timestamp,
uri: "",
data: "",
customId: 0
});Transfers
ERC-20 Transfer
Simple transfers (uses oldest lot FIFO):
token.transfer(recipient, amount);ERC-7752 Transfer
Specify which lot:
token.transfer({
lotId: lotId,
to: recipient,
quantity: amount,
tType: TransferType.SALE,
newCostBasis: 2e6, // $2.00
uri: "",
data: ""
});Storage
TokenBalancesStorage
library TokenBalancesStorage {
struct Layout {
mapping(address => uint256) balances;
uint256 totalSupply;
}
}TokenLotsStorage
library TokenLotsStorage {
struct Layout {
mapping(bytes32 => Lot) lots;
mapping(address => bytes32[]) userLots;
mapping(uint256 => bytes32) customIdToLotId;
uint256 nextCustomId;
}
}Events
event LotCreated(bytes32 indexed lotId, uint256 indexed customId, address indexed owner, uint256 quantity);
event LotTransferred(bytes32 indexed newLotId, address indexed from, address indexed to, uint256 quantity, ...);
event LotInvalidated(bytes32 indexed lotId);
event ComplianceConditionAdded(address indexed condition, string name);Security
Reentrancy protection on all transfers
Access control on admin functions
Pausable for emergencies
Compliance checks before every transfer
Gas Costs
On Base:
Token deployment:
6M gas ($0.10)Create lot:
200k gas ($0.002)ERC-20 transfer:
100k gas ($0.001)ERC-7752 transfer:
300k gas ($0.003)
Testing
function testCreateLot() public {
(bytes32 lotId,) = token.createLot({
owner: alice,
quantity: 1000,
paymentCurrency: address(usdc),
costBasis: 1e6,
acquisitionDate: block.timestamp,
uri: "",
data: "",
customId: 0
});
assertEq(token.balanceOf(alice), 1000);
}Resources
Last updated
Was this helpful?