This guide explains how to set up the import_wave service in your Docker Compose configuration.
The investor wave system imports whitelisted investors from a CSV file and generates merkle proofs for on-chain verification. The import runs as a one-time job during deployments, after database migrations.
Add the following service to your compose.production.yaml:
import_wave:
image: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/flying-tulip/ft-api:${IMAGE_TAG}
command: ["/bin/import-wave"]
env_file:
- .env.prod
environment:
# Optional: Override defaults
# INVESTOR_CSV_PATH: /assets/whitelist/investors.csv # Default path
# INVESTOR_CHAIN_IDS: 1 # Default: Ethereum (1)
depends_on:
- postgres
networks:
- backendThe GitHub Actions workflow already includes the import step:
migrate → import_wave → up -d
This sequence ensures:
- Database migrations run first (creates tables)
- Investor wave import runs (loads CSV, creates merkle tree)
- API starts with wave data already loaded
| Variable | Default | Description |
|---|---|---|
INVESTOR_CSV_PATH |
/assets/whitelist/investors.csv |
Path to the investor CSV file |
INVESTOR_CHAIN_IDS |
1 |
Comma-separated chain IDs to import for |
The CSV file is baked into the Docker image at /assets/whitelist/investors.csv. To use a different file, mount a volume or set INVESTOR_CSV_PATH.
- Idempotent: If the CSV hasn't changed (same SHA256 hash), no reimport occurs
- Versioned: Each new CSV creates a new "wave" with its own merkle root
- Archives: Previous waves are marked as archived with timestamp
- Graceful: If CSV file doesn't exist, exits successfully (no error)
On successful import, you'll see:
level=INFO msg="Starting investor wave import" csvPath=/assets/whitelist/investors.csv chainIds=[1]
level=INFO msg="Parsed investor CSV" path=/assets/whitelist/investors.csv chainId=1 entries=100 hash=abc123...
level=INFO msg="Created new wave" waveId=uuid waveNumber=1 merkleRoot=0x...
level=INFO msg="Wave import complete" chainId=1 waveId=uuid waveNumber=1 merkleRoot=0x... entryCount=100 isCurrent=true
level=INFO msg="All wave imports completed successfully"
If the CSV hasn't changed:
level=INFO msg="Wave with this CSV hash already exists" waveId=uuid waveNumber=1 isCurrent=true
- "pg not initialized": Database connection failed. Check
POSTGRES_URLin env. - "failed to parse CSV": Invalid CSV format. Check file has format:
wallet,token,amount - "No investor CSV file found, skipping import": File not found at path. Check mount/path.