1. Copy Contract
Copy the ERC-1404 compliant smart contract code below
2. Deploy on Avalanche
Use Remix, Hardhat, or Foundry to deploy to Avalanche C-Chain
3. Set Secrets
Configure RPC URL, private key, and contract address in settings
📋 Contract Features
ERC-1404 Compliance
Transfer restriction detection and messaging
KYC/AML Verification
On-chain investor verification status
Lockup Periods
Reg D, Rule 144, Reg A+ restriction support
Corporate Actions
On-chain dividends, splits, and events
Access Control
Role-based permissions for admins and compliance
Emergency Pause
Pause all transfers in case of security issues
🚀 Deployment Instructions
Option 1: Remix IDE (Easiest)
- Copy the contract code below
- Go to remix.ethereum.org
- Create a new file: SecurityToken.sol
- Install OpenZeppelin: npm install @openzeppelin/contracts
- Compile with Solidity 0.8.19+
- Connect MetaMask to Avalanche C-Chain
- Deploy with constructor args: ("Chayton Security Token", "CST")
- Copy the deployed contract address
Option 2: Hardhat (Advanced)
- npx hardhat init
- npm install @openzeppelin/contracts
- Add contract to contracts/ folder
- Configure hardhat.config.js for Avalanche
- npx hardhat run scripts/deploy.js --network avalanche
🌐 Avalanche C-Chain Networks:
Mainnet: https://api.avax.network/ext/bc/C/rpc
Fuji Testnet: https://api.avax-test.network/ext/bc/C/rpc
Chain ID: 43114 (Mainnet) | 43113 (Testnet)
SecurityToken.sol
Solidity 0.8.19+
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
/**
* @title SecurityToken (ERC-1404 Compliant)
* @dev Security token with transfer restrictions for Reg D, 144A, Reg A+ compliance
*/
contract SecurityToken is ERC20, AccessControl, Pausable {
bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");
bytes32 public constant COMPLIANCE_ROLE = keccak256("COMPLIANCE_ROLE");
// Restriction codes (ERC-1404)
uint8 public constant SUCCESS = 0;
uint8 public constant TRANSFER_RESTRICTED = 1;
uint8 public constant SENDER_NOT_VERIFIED = 2;
uint8 public constant RECIPIENT_NOT_VERIFIED = 3;
uint8 public constant LOCKUP_PERIOD = 4;
string constant SUCCESS_MESSAGE = "SUCCESS";
string constant TRANSFER_RESTRICTED_MESSAGE = "Transfer restricted";
string constant SENDER_NOT_VERIFIED_MESSAGE = "Sender not KYC verified";
string constant RECIPIENT_NOT_VERIFIED_MESSAGE = "Recipient not KYC verified";
string constant LOCKUP_PERIOD_MESSAGE = "Token is in lockup period";
struct TokenMetadata {
string tokenId;
string certificateNumber;
uint256 issueDate;
uint256 restrictionEndDate;
string restrictionType; // "reg_d", "rule_144", "reg_a_plus", "unrestricted"
bool isRestricted;
}
struct InvestorProfile {
bool isVerified;
bool isAccredited;
uint256 verificationDate;
string investorType; // "individual", "entity", "trust"
}
// Mappings
mapping(address => InvestorProfile) public investors;
mapping(address => TokenMetadata) public tokenMetadata;
mapping(address => uint256) public lockupEndDate;
// Events
event TokenMinted(address indexed investor, uint256 amount, string tokenId, string certificateNumber);
event InvestorVerified(address indexed investor, bool isAccredited);
event TransferRestrictionUpdated(address indexed investor, uint256 restrictionEndDate);
event CorporateAction(string actionType, uint256 timestamp, string details);
constructor(
string memory name,
string memory symbol
) ERC20(name, symbol) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(ADMIN_ROLE, msg.sender);
_grantRole(COMPLIANCE_ROLE, msg.sender);
}
/**
* @dev Mint new security tokens
*/
function mintToken(
address investor,
uint256 amount,
string memory tokenId,
string memory certificateNumber,
string memory restrictionType,
uint256 restrictionEndDate
) external onlyRole(ADMIN_ROLE) {
require(investors[investor].isVerified, "Investor not verified");
_mint(investor, amount);
tokenMetadata[investor] = TokenMetadata({
tokenId: tokenId,
certificateNumber: certificateNumber,
issueDate: block.timestamp,
restrictionEndDate: restrictionEndDate,
restrictionType: restrictionType,
isRestricted: restrictionEndDate > block.timestamp
});
if (restrictionEndDate > 0) {
lockupEndDate[investor] = restrictionEndDate;
}
emit TokenMinted(investor, amount, tokenId, certificateNumber);
}
/**
* @dev Verify investor for KYC/AML compliance
*/
function verifyInvestor(
address investor,
bool isAccredited,
string memory investorType
) external onlyRole(COMPLIANCE_ROLE) {
investors[investor] = InvestorProfile({
isVerified: true,
isAccredited: isAccredited,
verificationDate: block.timestamp,
investorType: investorType
});
emit InvestorVerified(investor, isAccredited);
}
/**
* @dev Check if transfer is allowed (ERC-1404)
*/
function detectTransferRestriction(
address from,
address to,
uint256 amount
) public view returns (uint8) {
if (paused()) return TRANSFER_RESTRICTED;
if (!investors[from].isVerified) return SENDER_NOT_VERIFIED;
if (!investors[to].isVerified) return RECIPIENT_NOT_VERIFIED;
if (lockupEndDate[from] > block.timestamp) return LOCKUP_PERIOD;
if (balanceOf(from) < amount) return TRANSFER_RESTRICTED;
return SUCCESS;
}
/**
* @dev Get message for restriction code (ERC-1404)
*/
function messageForTransferRestriction(uint8 restrictionCode)
public
pure
returns (string memory)
{
if (restrictionCode == SUCCESS) return SUCCESS_MESSAGE;
if (restrictionCode == SENDER_NOT_VERIFIED) return SENDER_NOT_VERIFIED_MESSAGE;
if (restrictionCode == RECIPIENT_NOT_VERIFIED) return RECIPIENT_NOT_VERIFIED_MESSAGE;
if (restrictionCode == LOCKUP_PERIOD) return LOCKUP_PERIOD_MESSAGE;
return TRANSFER_RESTRICTED_MESSAGE;
}
/**
* @dev Override transfer to include compliance checks
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal override {
super._beforeTokenTransfer(from, to, amount);
if (from != address(0)) { // Skip check for minting
uint8 restriction = detectTransferRestriction(from, to, amount);
require(restriction == SUCCESS, messageForTransferRestriction(restriction));
}
}
/**
* @dev Record corporate action on-chain
*/
function recordCorporateAction(
string memory actionType,
string memory details
) external onlyRole(ADMIN_ROLE) {
emit CorporateAction(actionType, block.timestamp, details);
}
/**
* @dev Burn tokens (redemption)
*/
function burn(address account, uint256 amount) external onlyRole(ADMIN_ROLE) {
_burn(account, amount);
}
/**
* @dev Update lockup period
*/
function updateLockupPeriod(address investor, uint256 newEndDate)
external
onlyRole(COMPLIANCE_ROLE)
{
lockupEndDate[investor] = newEndDate;
tokenMetadata[investor].restrictionEndDate = newEndDate;
tokenMetadata[investor].isRestricted = newEndDate > block.timestamp;
emit TransferRestrictionUpdated(investor, newEndDate);
}
/**
* @dev Pause all transfers (emergency)
*/
function pause() external onlyRole(ADMIN_ROLE) {
_pause();
}
/**
* @dev Unpause transfers
*/
function unpause() external onlyRole(ADMIN_ROLE) {
_unpause();
}
/**
* @dev Get investor details
*/
function getInvestorProfile(address investor)
external
view
returns (InvestorProfile memory)
{
return investors[investor];
}
/**
* @dev Get token metadata
*/
function getTokenMetadata(address holder)
external
view
returns (TokenMetadata memory)
{
return tokenMetadata[holder];
}
}✅ After Deployment
- Copy your deployed contract address
- Go to Admin Settings → Environment Variables
- Set
AVALANCHE_RPC_URL - Set
BLOCKCHAIN_PRIVATE_KEY(admin wallet) - Set
SECURITY_TOKEN_CONTRACT_ADDRESS - Grant ADMIN_ROLE and COMPLIANCE_ROLE to your backend wallet address
- Return to Securities Registry to start minting tokens on-chain