This repository contains the official reference implementation of ERC-1450, a standard for compliant security tokens controlled by a Registered Transfer Agent (RTA).
This standard is backed by real-world production experience. StartEngine has tokenized over $1 billion in securities on the blockchain using the patterns and architecture formalized in ERC-1450. The standard captures lessons learned from years of operating compliant security token infrastructure under SEC regulations.
This reference implementation is part of a two-repository system:
-
Specification Repository: StartEngine/ERCs
- Fork of ethereum/ERCs
- Contains the formal ERC-1450 specification
- Location:
/Users/devendergollapally/StartEngineRepositories/ERCs - Spec file:
ERCS/erc-1450.md - Our pull request PR
-
Reference Implementation (this repository): StartEngine/erc1450-reference
- Solidity smart contracts implementing the spec
- Comprehensive test suite
- Location:
/Users/devendergollapally/StartEngineRepositories/erc1450-reference - Contracts:
contracts/ERC1450.sol,contracts/RTAProxy.sol
Important: Any changes to the contracts in this repository should be validated against the formal specification in the ERCs repository to ensure compliance.
ERC-1450 enables compliant securities offerings under SEC regulations by providing:
- Exclusive RTA Control: Only the designated transfer agent can execute token operations
- Multi-Signature Security: RTAProxy pattern prevents single key compromise
- Transfer Request System: Compliant transfer workflow with fees and approvals
- Regulatory Compliance: Built for SEC Rule 17Ad requirements
- Court Order Support: Forced transfers for legal compliance
- Account Restrictions: Freeze/unfreeze capabilities
- All transfers must go through the Registered Transfer Agent
- Direct ERC-20
transfer()andapprove()functions are disabled - Minting and burning controlled by RTA only
- Token holders or authorized brokers request transfers
- Fees collected at request time
- RTA reviews and approves/rejects requests
- Full audit trail of all transfer activities
- 2-of-3 multi-signature requirement for critical operations
- Protection against single point of failure
- Immutable transfer agent once set to RTAProxy
- Account freezing for regulatory compliance
- Court order execution capabilities
- Broker registration and management
- Configurable fee structures
- KYC/AML verification requirements
- Extended reason codes (0-14, 999) for detailed rejection tracking
This implementation now includes upgradeable versions of the contracts using OpenZeppelin's UUPS proxy pattern, allowing critical bug fixes without requiring token holder action.
ERC1450Upgradeable.sol- Upgradeable token implementationRTAProxyUpgradeable.sol- Upgradeable multi-sig RTA
- Bug Fixes: Deploy patches without changing contract addresses
- No Migration: Token holders keep the same addresses and balances
- Secure: Upgrades require multi-sig RTA approval
- Gas Efficient: UUPS pattern minimizes overhead
# Deploy standard (immutable) contracts
npx hardhat run scripts/deploy.js --network polygon
# Deploy upgradeable contracts (recommended for production)
npx hardhat run scripts/deploy-upgradeable.js --network polygon# Upgrade contracts (requires multi-sig approval)
npx hardhat run scripts/upgrade.js --network polygonFor detailed upgradeability documentation, see UPGRADEABILITY.md.
All contracts include a version() function that returns the current contract version. This version is automatically synced with package.json to ensure consistency between the npm package version and deployed contracts.
// All contracts expose this function
function version() external pure returns (string memory) {
return "1.10.1"; // Matches package.json version
}// Query version from a deployed contract
const rtaProxy = new ethers.Contract(proxyAddress, RTAProxyABI, provider);
const version = await rtaProxy.version();
console.log(`Deployed version: ${version}`); // e.g., "1.10.1"The version in contracts is automatically synchronized via:
- Pre-commit hook: Runs
scripts/sync-version.jsbefore every commit - Pre-compile hook: Runs before
npm run compile
This ensures:
- Contract
version()always matchespackage.jsonversion - No manual updates needed when releasing new versions
- Deployed contracts can be compared against local code
When you deploy a contract and later update the codebase, you need to know:
- What version is deployed on-chain?
- Is there a newer version available locally?
- Does the deployed contract need an upgrade?
The version() function enables automated tracking and comparison of deployed contracts against the current codebase.
// Compare deployed version with local artifacts
const deployedVersion = await contract.version(); // "1.10.0"
const localVersion = require('erc1450-reference/package.json').version; // "1.10.1"
if (deployedVersion !== localVersion) {
console.log(`Upgrade available: ${deployedVersion} → ${localVersion}`);
}┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐
│ Token Holder │────▶│ ERC1450 │◀────│ RTAProxy │
└─────────────────┘ └──────────────┘ └─────────────────┘
▲ ▲
│ │
┌──────┴──────┐ ┌──────┴──────┐
│ Brokers │ │ RTA Signers │
└─────────────┘ └─────────────┘
- Node.js v16+ (tested with v22.14.0)
- npm v7+ (tested with v10.9.2)
# Clone the repository
git clone https://github.com/StartEngine/erc1450-reference.git
cd erc1450-reference
# Install dependencies (also installs git hooks via Husky)
npm install
# Compile contracts
npx hardhat compile
# Run tests
npm test
# Check test coverage
npx hardhat coverage
# Run security analysis (requires Python + Slither)
slither . --print human-summaryNote: The npm install command automatically sets up git hooks via Husky. These hooks will run hardhat compile and npm test before each commit to ensure code quality.
For contributors and developers working on this project:
-
Clone & Setup
git clone https://github.com/StartEngine/erc1450-reference.git cd erc1450-reference npm install # Auto-installs Husky pre-commit hooks
-
Compile Contracts
npx hardhat compile
-
Run Tests
npm test # Runs all 643 tests
-
Check Coverage
npx hardhat coverage # Current: 86.2% branch coverage -
Security Analysis (optional, requires Slither)
pip install slither-analyzer slither . -
Pre-Commit Hooks (automatic)
- Husky automatically runs before each commit:
- ✅ Compiles all Solidity contracts
- ✅ Runs full test suite (643 tests)
- To bypass (not recommended):
git commit --no-verify
- Husky automatically runs before each commit:
# Start local Hardhat node
npx hardhat node
# Deploy contracts (in new terminal)
npx hardhat run scripts/deploy.js --network localhost# Set up environment variables
export PRIVATE_KEY="your_private_key"
export RPC_URL="your_rpc_url"
# Deploy
npx hardhat run scripts/deploy.js --network sepoliaRun these scripts to see the ERC-1450 system in action:
# Display token information and run basic demo
npx hardhat run scripts/info.js
# Demo minting tokens through multi-sig
npx hardhat run scripts/demo-mint.js
# Demo complete transfer request workflow
npx hardhat run scripts/demo-transfer.jsFor production deployments, use the deployment and operations scripts:
# Deploy contracts
npx hardhat run scripts/deploy.js --network localhost
# After deployment, manage operations using the deployment file
# (Requires deployment-{network}.json file from deploy.js)// RTA sets fee token (one-time setup, typically USDC)
await token.connect(rta).setFeeToken(usdcAddress);
await token.connect(rta).setFeeParameters(0, feeValue); // 0=flat, 1=percentage
// Token holder approves fee token spend
await feeToken.connect(holder).approve(tokenAddress, feeAmount);
// Request a transfer (single fee token - no ETH)
await token.requestTransferWithFee(
fromAddress,
toAddress,
amount,
feeAmount
);
// Check transfer request status
const request = await token.transferRequests(requestId);
console.log("Status:", request.status);// Submit operation (first signer)
const operationId = await rtaProxy.submitOperation(
targetContract,
encodedFunctionData,
ethValue
);
// Confirm operation (second signer)
await rtaProxy.confirmOperation(operationId);
// Auto-executes when threshold reached
// Check operation status
const op = await rtaProxy.getOperation(operationId);
console.log("Executed:", op.executed);The main security token interface extending ERC-20:
interface IERC1450 is IERC20, IERC165 {
// RTA Functions
function changeIssuer(address newIssuer) external;
function setTransferAgent(address newTransferAgent) external;
function mint(address to, uint256 amount) external returns (bool);
function burnFrom(address from, uint256 amount) external returns (bool);
// Fee Configuration (single ERC-20 fee token)
function setFeeToken(address token) external;
function getFeeToken() external view returns (address);
function setFeeParameters(uint8 feeType, uint256 feeValue) external;
function getTransferFee(address from, address to, uint256 amount) external view returns (uint256);
// Transfer Request System
function requestTransferWithFee(
address from,
address to,
uint256 amount,
uint256 feeAmount
) external returns (uint256 requestId);
function processTransferRequest(uint256 requestId, bool approved) external;
function rejectTransferRequest(uint256 requestId, uint16 reasonCode, bool refundFee) external;
// Fee Withdrawal
function withdrawFees(uint256 amount, address recipient) external;
// Compliance
function setAccountFrozen(address account, bool frozen) external;
function executeCourtOrder(address from, address to, uint256 amount, bytes32 documentHash) external;
}Multi-signature contract for RTA operations:
contract RTAProxy {
function submitOperation(address target, bytes memory data, uint256 value) external returns (uint256);
function confirmOperation(uint256 operationId) external;
function revokeConfirmation(uint256 operationId) external;
function executeOperation(uint256 operationId) external;
}The test suite covers all major functionality:
# Run all tests
npx hardhat test
# Run specific test file
npx hardhat test test/ERC1450.test.js
# Run with coverage
npx hardhat coverage
# Run with gas reporting
REPORT_GAS=true npx hardhat testTest coverage includes:
- ✅ Token deployment and initialization
- ✅ RTA-exclusive operations (mint, burn, transfer)
- ✅ Transfer request lifecycle
- ✅ Fee management
- ✅ Broker registration
- ✅ Account freezing
- ✅ Court order execution
- ✅ Multi-sig operations
- ✅ Interface detection (ERC-165)
The ERC-1450 specification includes comprehensive documentation for real-world securities operations:
- Stock Splits & Reverse Splits: Proportional mint/burn operations
- Dividends: Stablecoin distributions with off-chain calculations
- Mandatory Redemptions: Forced buybacks and bond calls
- Tender Offers: Voluntary redemption patterns
- Mergers & Acquisitions: Token swap mechanisms
- Record Dates: Off-chain snapshots or external snapshot contracts
- Proxy Voting: Vote recording with on-chain attestation
- Meeting Quorums: Threshold calculations and verification
- Document Management: Via ERC-1643 for proxy rules and notices
- W-9/W-8 Collection: Off-chain during KYC process
- Withholding Calculations: Per-jurisdiction off-chain processing
- 1099/1042-S Reporting: Annual tax form generation
- Document References: Encrypted storage via ERC-1643
- ATS Adapter Pattern: Integration with regulated trading venues
- Order Book Visibility: Via TransferRequested events
- Pre-Matched Trades: Through registered broker submissions
- Reason Code Analytics: Optimization using rejection reasons
- Similar to RTAProxy but optional for brokers
- Enables secure key rotation and multi-sig controls
- Provides business continuity for broker operations
- Private Key Management: RTA signers must secure their private keys
- Multi-Sig Threshold: Choose appropriate signature requirements
- Transfer Agent Lock: Once set to RTAProxy, cannot be changed
- Fee Token: Single ERC-20 fee token (e.g., USDC) configured by RTA
- Reentrancy Protection: All state-changing functions protected
- Access Control: Strict RTA-only modifier on critical functions
- Uses custom errors (ERC-6093) for gas efficiency
- Unchecked blocks where overflow impossible
- Efficient storage packing
- Minimal external calls
This implementation is designed to comply with:
- SEC Rule 17Ad (Transfer Agent regulations)
- Regulation S-T (Electronic filing requirements)
- Regulation A+ (Qualified offerings)
- Regulation D (Private placements)
- Regulation CF (Crowdfunding)
The implementation includes standardized reason codes for transfer rejections:
| Code | Constant | Description |
|---|---|---|
| 0 | REASON_INSUFFICIENT_BALANCE | Sender has fewer tokens than transfer amount |
| 1 | REASON_INVALID_SENDER | Sender address is invalid or blacklisted |
| 2 | REASON_INVALID_RECEIVER | Receiver address is invalid or zero |
| 3 | REASON_COMPLIANCE_FAILURE | Generic compliance check failure |
| 4 | REASON_TRANSFER_RESTRICTED | Transfer temporarily restricted |
| 5 | REASON_HOLDER_LIMIT_EXCEEDED | Would exceed maximum holder count |
| 6 | REASON_TRADING_HALT | Trading is currently halted |
| 7 | REASON_COURT_ORDER | Transfer blocked by court order |
| 8 | REASON_REGULATORY_FREEZE | Account frozen by regulator |
| 9 | REASON_LOCK_PERIOD | Tokens are in lock-up period |
| 10 | REASON_RECIPIENT_NOT_VERIFIED | Recipient hasn't completed KYC/AML |
| 11 | REASON_ADDRESS_NOT_LINKED | Address not linked to verified identity |
| 12 | REASON_SENDER_VERIFICATION_EXPIRED | Sender's KYC has expired |
| 13 | REASON_JURISDICTION_BLOCKED | Recipient in restricted jurisdiction |
| 14 | REASON_ACCREDITATION_REQUIRED | Recipient not accredited (Reg D) |
| 999 | REASON_OTHER | Other unspecified reason |
This is a reference implementation maintained by StartEngine. For questions or issues, please open a GitHub issue.
MIT License
For questions and support:
- Open an issue in this repository
- Join the discussion on Ethereum Magicians
- Contact the StartEngine team
This repository can be used as an npm package via git dependencies for JavaScript/TypeScript projects. The package provides both basic (immutable) and upgradeable contract implementations.
Add to your package.json:
{
"dependencies": {
"erc1450-reference": "git+https://github.com/StartEngine/erc1450-reference.git#v1.4.0"
}
}Or install directly:
npm install git+https://github.com/StartEngine/erc1450-reference.git#v1.4.0Import contract artifacts in your JavaScript/TypeScript project:
// Method 1: Import via main index.js (recommended)
const { ERC1450, RTAProxy, ERC1450Upgradeable, RTAProxyUpgradeable, ERC1967Proxy } = require('erc1450-reference');
// Method 2: Import specific artifacts directly
const RTAProxyUpgradeable = require('erc1450-reference/artifacts/contracts/upgradeable/RTAProxyUpgradeable.sol/RTAProxyUpgradeable.json');
const ERC1450Upgradeable = require('erc1450-reference/artifacts/contracts/upgradeable/ERC1450Upgradeable.sol/ERC1450Upgradeable.json');
// Use with ethers.js or web3.js
const abi = RTAProxyUpgradeable.abi;
const bytecode = RTAProxyUpgradeable.bytecode;
// Example: Deploy with ethers.js
const factory = new ethers.ContractFactory(abi, bytecode, signer);
const contract = await factory.deploy(...args);This package includes both basic and upgradeable versions:
Basic Contracts (Immutable)
ERC1450- Standard ERC1450 token implementationRTAProxy- Multi-sig RTA proxy
Upgradeable Contracts (UUPS Pattern)
ERC1450Upgradeable- Upgradeable ERC1450 tokenRTAProxyUpgradeable- Upgradeable multi-sig RTAERC1967Proxy- OpenZeppelin proxy for deployment
Interfaces & Libraries
IERC1450- ERC1450 interfaceERC1450Constants- Shared constants library
When updating contracts:
- Make contract changes and compile:
npm run compile - Update version in
package.json(follow semantic versioning) - Compile again to sync version to contracts:
npm run compile- This automatically runs
scripts/sync-version.jswhich updates theversion()function in all contracts
- This automatically runs
- Commit changes:
git commit -am "Release v1.10.1"- Pre-commit hook will verify version sync and run tests
- Create git tag:
git tag v1.10.1 - Push to GitHub:
git push origin main git push origin v1.10.1
- Update dependent projects to use the new version tag in their
package.json
Note: Contract versions are automatically synced from package.json - no manual contract edits needed!
- Slither Analysis: Completed (November 2024) - No critical vulnerabilities found
- Static Analysis: All high-priority issues resolved in commit 9805925
- Test Coverage: 643 comprehensive tests passing
- Security Score: 9.5/10 based on automated analysis
This implementation has completed a comprehensive security audit by Halborn Security (December 2025).
Audit Results:
| Severity | Count | Status |
|---|---|---|
| Critical | 1 | ✅ Solved |
| High | 0 | - |
| Medium | 0 | - |
| Low | 6 | 3 Solved, 3 Risk Accepted |
| Informational | 11 | 1 Solved, 10 Acknowledged |
100% of all findings have been addressed. The single critical finding (fee bypass vulnerability) was resolved by implementing a single ERC-20 fee token design.
Key fixes implemented:
- Single ERC-20 fee token design (commit
3901950) - Replaced
.transfer()with.call()for ETH transfers (commit1b273d8) - Added 7-day expiration for stale multisig operations (commit
42f000a) - Double rejection prevention (commit
2b7c52f) - Batch cleanup consistency (commit
3a25d8c)
The full audit report published by Halborn is available here.
Production deployment should proceed after:
- Thorough legal review for your jurisdiction
- Comprehensive integration testing
Built with ❤️ by StartEngine for the Ethereum community