A comprehensive security token implementation on Solana using Token-2022 extensions, providing KYC/AML compliance, transfer restrictions, and corporate actions support.
Before you begin, ensure you have the following installed:
-
Rust 1.88.0 (pinned via
rust-toolchain.toml)- Install from https://rustup.rs/
- Required components:
rustfmt,clippy - Note: This repository includes a
rust-toolchain.tomlfile; when usingrustup, the correct Rust version (1.88.0) will be selected automatically. Newer Rust versions are not guaranteed to be compatible.
-
Solana CLI (recommended: v2.2.0)
- Install (pinned):
sh -c "$(curl -sSfL https://release.anza.xyz/v2.2.0/install)" - Verify:
solana --version - Warning: Newer Solana CLI versions (e.g., 3.x) may not be compatible with the local test harness yet. If you see flaky or failing tests, downgrade to v2.2.0.
- Install (pinned):
-
Node.js (v18 or later) and pnpm
- Install Node.js from https://nodejs.org/
- Install pnpm:
npm install -g pnpm
-
Cargo Tools (optional but recommended):
cargo-deny- Security vulnerability scanning & Dependency lintingcargo-expand- Macro expansion for debugging
Run the automated setup script to install all dependencies and configure your environment:
./scripts/setup.shThis script will:
- Verify Rust and Solana CLI installations
- Install required Rust components (
rustfmt,clippy) - Install recommended cargo tools (
cargo-deny,cargo-expand) - Configure Solana CLI for devnet
- Generate a keypair if one doesn't exist
- Build both the main program and transfer hook
If you prefer manual setup:
# Install Rust components
rustup component add rustfmt clippy
# Install cargo tools (pinned for Rust 1.88 compatibility)
cargo install cargo-deny --version 0.19.0 --locked
cargo install cargo-expand --version 1.0.118 --locked
# Configure Solana for devnet
solana config set --url https://api.devnet.solana.com
# Generate keypair (if needed)
solana-keygen new --no-bip39-passphrase
# Install Node.js dependencies
pnpm install
# Build programs
./scripts/build.shBuild both the main security token program and the transfer hook:
./scripts/build.shThis compiles:
- Security Token Program (
program/) - Transfer Hook Program (
transfer_hook/)
For SBF-only builds (skip host build):
./scripts/build.sh sbfGenerate IDL and client libraries:
# Build everything including clients
pnpm build-all
# Or step by step:
pnpm build # Build programs
pnpm generate-idl # Generate IDL from program
pnpm generate-clients # Generate Rust and TypeScript clientsFormat all Rust code in the project:
./scripts/format.shThe project includes comprehensive unit and integration tests.
./scripts/test.shThis will:
- Clean previous builds
- Build all workspace components
- Run program unit tests
- Run client library tests
- Build programs for SBF
- Run integration tests
- Run BPF program tests
Unit Tests (Program):
cargo test --manifest-path program/Cargo.tomlClient Tests:
cargo test --manifest-path clients/rust/Cargo.tomlIntegration Tests:
# Build programs first
./scripts/build.sh sbf
# Run integration tests
SBF_OUT_DIR=$(pwd)/target/deploy cargo test --manifest-path tests/Cargo.tomlQuick Test (via npm):
pnpm testRun with Code Quality Checks:
The test script automatically runs additional quality checks:
- Code formatting (
cargo fmt --check) - Linting (
cargo clippy) - Security audit (
cargo deny check advisories -c deny.toml- if installed)
These checks run automatically and don't require any parameters.
For license compliance checks, run cargo deny check manually (if installed).
Ensure you have SOL in your wallet for deployment fees:
# Check your balance
solana balance
# Request airdrop on devnet (2 SOL)
solana airdrop 2Note: If the airdrop fails (common on devnet due to rate limits), use the Solana Faucet to request devnet SOL manually.
Environment variables (deployment):
DEPLOYER_KEYPAIR— fee-payer keypair (defaults toSOLANA_KEYPAIRor~/.config/solana/id.json)SOLANA_KEYPAIR— legacy override for deployer keypairSBF_OUT_DIR/BPF_OUT_DIR— directory containing.soand program keypairs (defaulttarget/deploy)PROGRAM_KEYPAIR_PATH— override main program keypair pathTRANSFER_HOOK_KEYPAIR_PATH— override transfer hook keypair pathPROGRAM_PATH— override main program.sopathTRANSFER_HOOK_PROGRAM_PATH— override transfer hook.sopath
By default, programs deploy to devnet. To deploy to a different cluster (testnet or mainnet), pass the cluster name as an argument.
If you plan to deploy with custom program IDs (vanity or not), you must set those IDs in code before building and deploying. The program IDs are baked into:
program/src/lib.rs(declare_id!for the main program)transfer_hook/src/lib.rs:declare_id!for the transfer hook program IDSECURITY_TOKEN_PROGRAM_ID(must match the main program ID)
program/src/constants.rs(TRANSFER_HOOK_PROGRAM_ID)idl/security_token_program.json(program address)
After updating the IDs in code, regenerate IDL + clients:
pnpm generate-idl
pnpm generate-clientsGenerate vanity keypairs:
# Example: find a keypair starting with "SSTS"
solana-keygen grind --starts-with SSTS:1 --ignore-caseMove them into place (recommended):
# Main program keypair
mkdir -p target/deploy
mv <GENERATED_KEYPAIR.json> target/deploy/security_token_program-keypair.json
# Transfer hook keypair
mkdir -p target/deploy
mv <GENERATED_KEYPAIR.json> target/deploy/security_token_transfer_hook-keypair.jsonAlternative: keep them elsewhere and point the deploy scripts at them:
PROGRAM_KEYPAIR_PATH=/path/to/security_token_program-keypair.json \
TRANSFER_HOOK_KEYPAIR_PATH=/path/to/security_token_transfer_hook-keypair.json \
pnpm run deploy:allDeploy both programs (recommended):
# Devnet (default)
pnpm deploy:all
# Other clusters
pnpm deploy:all -- testnet
pnpm deploy:all -- mainnetDeploy individual programs:
# Main program only
./scripts/deploy.sh [devnet|testnet|mainnet]
# Transfer hook only
./scripts/deploy-transfer-hook.sh [devnet|testnet|mainnet]After deployment, the program IDs are saved to:
program_id.txt- Main security token program IDtransfer_hook_program_id.txt- Transfer hook program ID
Verify deployment:
solana program show <PROGRAM_ID>- Program Instructions - Complete instruction reference, account structures, and authorization model
- IDL - Codama IDL for client generation
- Security Policy - Disclosure process and security reporting expectations
| Script | Description |
|---|---|
./scripts/setup.sh |
Initial environment setup |
./scripts/build.sh |
Build programs |
./scripts/test.sh |
Run all tests |
./scripts/format.sh |
Format code |
./scripts/deploy.sh |
Deploy main program |
./scripts/deploy-transfer-hook.sh |
Deploy transfer hook |
pnpm build # Build programs
pnpm build-all # Build + generate IDL + generate clients
pnpm test # Run tests
pnpm generate-idl # Generate IDL from program
pnpm generate-clients # Generate client libraries
pnpm deploy:all # Deploy both programs
pnpm deploy:program # Deploy main program
pnpm deploy:transfer-hook # Deploy transfer hook├── program/ # Main security token program
├── transfer_hook/ # Transfer hook program
├── clients/
│ ├── rust/ # Rust client library
│ └── typescript/ # TypeScript client library
├── tests/ # Integration tests
├── scripts/ # Build and deployment scripts
├── idl/ # Generated IDL
└── docs/ # Documentation
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
./scripts/test.sh - Format code:
./scripts/format.sh - Submit a pull request