Vultisig CLI for local development and testing of Vultisig plugins.
The Vultisig stack is tightly coupled:
- vcli depends on verifier (TSS protocols)
- verifier depends on recipes (chain abstraction)
- app-recurring depends on recipes + verifier (policy execution)
- All depend on go-wrappers (cryptographic primitives)
Changes in one repo often require changes in others. Docker images create version drift - the local vcli binary may be incompatible with pre-built Docker images due to protocol or signature changes.
Local development lets you:
- Edit any repo and test immediately
- Debug with IDE breakpoints
- Use native architecture (no emulation issues on ARM Macs)
- Go 1.24+
- Docker (for postgres, redis, minio only)
- Git
Clone all repos as siblings:
mkdir vultisig && cd vultisig
git clone https://github.com/vultisig/vcli.git
git clone https://github.com/vultisig/verifier.git
git clone https://github.com/vultisig/app-recurring.git
git clone https://github.com/vultisig/recipes.git
git clone https://github.com/vultisig/go-wrappers.gitDirectory structure:
vultisig/
├── vcli/ # This tool
├── verifier/ # Policy verification + TSS
├── app-recurring/ # DCA plugin
├── recipes/ # Chain abstraction layer
└── go-wrappers/ # Rust crypto (auto-downloaded, but useful to have)
cd vcli
# Build vcli (REQUIRED - do this first!)
cd local && go build -o vcli ./cmd/vcli && cd ..
# Start all services
make start # Starts postgres/redis/minio in Docker, services run natively
⚠️ IMPORTANT: You MUST build the vcli binary before using any./local/vcli.shcommands. If you see "No such file or directory" errors, run the build command above.
make start:
- Starts infrastructure in Docker (postgres, redis, minio)
- Runs verifier (API + worker) natively with
go run - Runs app-recurring (server + worker + scheduler) natively with
go run
Logs: tail -f local/logs/*.log
Edit code in any sibling repo, then:
make stop && make startOr restart individual services manually.
You need a Fast Vault (vault with cloud backup) exported from the Vultisig mobile app:
- Create a vault in the Vultisig mobile app with "Fast Vault" enabled
- Export the vault backup (Settings -> Export -> Backup file)
- Transfer the
.vultfile tolocal/keyshares/directory
# Put your vault file in the keyshares directory
cp ~/Downloads/MyVault.vult local/keyshares/You MUST follow the E2E testing flow exactly as documented below. The flow is:
START → IMPORT → INSTALL → [ GENERATE → ADD → MONITOR ] ─┐
│
(repeat as needed) ←───┘
The bracketed steps (policy testing) can be repeated as many times as needed. Everything else runs once per test cycle. When completely done testing, proceed to cleanup (DELETE → UNINSTALL → STOP).
After IMPORT: Run vcli vault details to view your wallet addresses and balances before proceeding to INSTALL.
Do NOT:
- Restart mid-way through a test cycle
- Move keyshare files manually between directories
- Edit database records directly (no manual SQL inserts/updates)
- Skip steps (except repeating the policy loop)
- Re-use state from a previous failed run
If something fails: Run make stop (cleans all state) and start fresh from Step 1.
Follow these steps in order, every time. Do not skip steps.
Start all services (infrastructure + application services).
make startThis starts:
- Infrastructure in Docker: PostgreSQL, Redis, MinIO
- Services natively: Verifier API/worker, DCA plugin server/worker/scheduler
Validation:
make status✅ Expected: Infrastructure containers show as "running" (postgres, redis, minio)
❌ If validation fails: Check logs with tail -f local/logs/*.log. Fix the issue and restart with make stop && make start.
Import your vault into the local environment.
# Recommended: auto-find vault in local/keyshares/
./local/vcli.sh vault import --password "password"
# Or specify a file with ABSOLUTE path:
./local/vcli.sh vault import --file /full/path/to/vault.vult --password "password"Note: When using --file, you must provide an absolute path (starting with /). Relative paths will not work because the vcli.sh wrapper changes directories internally.
Validation:
./local/vcli.sh vault list
./local/vcli.sh report✅ Expected:
vault listshows your imported vaultreportdisplays vault name, public keys (ECDSA/EdDSA), and signers
❌ If validation fails: Verify your .vult file is in local/keyshares/ and password is correct. The vault must be a Fast Vault.
Next step: Run vcli vault details to view your wallet addresses and balances before proceeding.
Inspect your vault (recommended for first-time users):
Before creating policies, view your wallet addresses and token balances:
# Show all addresses and balances
./local/vcli.sh vault details
# Or check a specific chain
./local/vcli.sh vault details --chain ethereumThis displays:
- Wallet addresses for each supported chain (Ethereum, Bitcoin, Solana, etc.)
- Native token balances (ETH, BTC, SOL, etc.)
- Common ERC20 token balances (USDT, USDC, DAI, etc.)
Use this information when creating swap policies in Step 4.
Install a plugin. This performs a 4-party TSS reshare.
./local/vcli.sh plugin install vultisig-dca-0000 --password "password"What happens: A 4-party reshare occurs between:
- CLI (your local vault share)
- Fast Vault Server (production cloud backup)
- Verifier Worker (running locally)
- DCA Plugin Worker (running locally)
Validation:
./local/vcli.sh report✅ Expected:
- Report shows plugin installation in database
- Report shows keyshare files stored in MinIO (4 parties)
- Signers list now includes verifier and plugin parties
❌ If validation fails: Check logs with tail -f local/logs/*.log. Do not attempt to fix manually - run make stop && make start and restart from Step 1.
Once a plugin is installed, you can create and test multiple policies without restarting. Repeat Steps 4-6 as many times as needed:
┌─────────────────────────────────────────────────┐
│ GENERATE → ADD → MONITOR (repeat for more) │
└─────────────────────────────────────────────────┘
This is the only valid shortcut. You may:
- Test different policy configurations (different assets, amounts, frequencies)
- Create multiple policies simultaneously
- Monitor execution across different policies
- Test edge cases and error conditions
Continue testing more policies by repeating Steps 4-6. When completely done with all testing, proceed to cleanup (Steps 7-9).
Generate a policy configuration file using the vcli policy generate command.
# Generate a swap policy (use ABSOLUTE path for --output)
./local/vcli.sh policy generate --from eth --to usdc --amount 0.01 --output /full/path/to/vcli/local/policies/my-policy.json
# Generate with custom frequency
./local/vcli.sh policy generate --from usdt --to btc --amount 10 --frequency daily --output /full/path/to/vcli/local/policies/my-policy.json
⚠️ IMPORTANT: The--outputflag requires an absolute path (starting with/). Relative paths will fail becausevcli.shchanges directories internally. Use$(pwd)/local/policies/my-policy.jsonor the full path.
Why use policy generate:
- Auto-derives wallet addresses from your imported vault
- Converts amounts to smallest units automatically
- Validates the recipe with the plugin server
- Supports asset shortcuts:
eth,btc,sol,usdc,usdt,dai, etc.
Asset shortcuts:
eth, btc, sol, rune, bnb, avax, matic - Native tokens
usdc, usdt, dai - Stablecoins (Ethereum)
usdc:arbitrum - Specify chain explicitly
Frequency options: one-time (default), minutely, hourly, daily, weekly, bi-weekly, monthly
Example output:
Policy written to my-policy.json
Policy Summary:
From: 0.01 eth (Ethereum)
0x2d63088Dacce3a87b0966982D52141AEe53be224 [FastPlugin1]
To: usdc (Ethereum)
0x2d63088Dacce3a87b0966982D52141AEe53be224 [FastPlugin1]
Amount: 10000000000000000 (smallest unit)
Frequency: one-time
✅ Expected: Policy JSON file created with addresses auto-filled.
❌ If validation fails: Check that your vault is imported and the asset names are valid.
Add the policy to the installed plugin.
./local/vcli.sh policy add --plugin vultisig-dca-0000 --policy-file local/policies/my-policy.json --password "password"Validation:
./local/vcli.sh policy list --plugin vultisig-dca-0000✅ Expected: Your policy appears in the list with a policy ID.
❌ If validation fails: Check that the plugin is installed (Step 3). Check logs with tail -f local/logs/verifier.log.
Monitor the policy execution and check its status.
# Check policy status and next execution time
./local/vcli.sh policy status <policy-id>
# View executed transactions
./local/vcli.sh policy transactions <policy-id>
# View transaction history
./local/vcli.sh policy history <policy-id>
# List all policies for the plugin
./local/vcli.sh policy list --plugin vultisig-dca-0000
# Watch logs in real-time
tail -f local/logs/dca-worker.log
tail -f local/logs/dca-scheduler.logNext steps:
- Test another policy: Go back to Step 4 (GENERATE) to create a new policy
- Monitor existing policies: Use
policy status,policy transactions, orpolicy historycommands - When done testing: Proceed to cleanup steps (DELETE → UNINSTALL → STOP)
✅ Expected: Policy shows execution history, pending/completed transactions, and next scheduled execution time.
❌ If validation fails: Check scheduler and worker logs for errors. Verify the policy frequency and chain configuration.
When you're completely finished testing all policies, follow these cleanup steps:
Delete individual policies if you want to clean up specific ones.
./local/vcli.sh policy delete <policy-id> --password "password"Note: You can skip this step if you want to keep policies for future testing. Policies will be cleaned up when you uninstall the plugin.
Validation:
./local/vcli.sh policy list --plugin vultisig-dca-0000✅ Expected: The deleted policy no longer appears in the list.
Uninstall the plugin. This will remove all policies associated with the plugin.
./local/vcli.sh plugin uninstall vultisig-dca-0000Validation:
./local/vcli.sh report✅ Expected: Plugin installation no longer appears in the report.
❌ If validation fails: Check for remaining policies - all policies will be automatically removed when uninstalling.
Stop all services and clean all state (vaults, logs, docker volumes).
make stopValidation:
make status✅ Expected: All services show as stopped or not running.
Note: Only run this when you're completely done with testing. If you want to test more policies, stay in the policy testing loop (Steps 4-6).
| Bad Practice | Why It Breaks Things |
|---|---|
| Restarting mid-test | TSS sessions are stateful; partial state causes reshare failures |
| Moving keyshare files | Keyshares are tied to specific party IDs and sessions |
| Manual DB edits | Breaks consistency between DB records and MinIO keyshares |
| Skipping UNINSTALL | Leaves orphaned keyshares that conflict with future installs |
| Re-using failed state | Corrupted state propagates; always clean start |
The only recovery from a failed test is: make stop then start fresh from Step 1.
# Vault management (put .vult file in local/keyshares/ first)
./local/vcli.sh vault import --password "password"
./local/vcli.sh vault list
./local/vcli.sh vault details
# Plugin management
./local/vcli.sh plugin list
./local/vcli.sh plugin install <plugin-id> --password "password"
./local/vcli.sh plugin uninstall <plugin-id>
# Policy management (use absolute paths for file arguments)
./local/vcli.sh policy generate --from <asset> --to <asset> --amount <amount> --output $(pwd)/local/policies/<file.json>
./local/vcli.sh policy add --plugin <plugin-id> --policy-file $(pwd)/local/policies/<config.json> --password "password"
./local/vcli.sh policy list --plugin <plugin-id>
./local/vcli.sh policy status <policy-id> # Check status and next execution
./local/vcli.sh policy transactions <policy-id> # View executed transactions
./local/vcli.sh policy history <policy-id> # View transaction history
./local/vcli.sh policy delete <policy-id> --password "password" # Cleanup only
# Status and reporting
./local/vcli.sh report
./local/vcli.sh status| Service | Port | Notes |
|---|---|---|
| PostgreSQL | 5432 | Docker container |
| Redis | 6379 | Docker container |
| MinIO | 9000 | Docker container |
| MinIO Console | 9090 | Docker container |
| Verifier API | 8080 | Native (go run) |
| Verifier Worker | - | Native (go run) |
| DCA Server | 8082 | Native (go run) |
| DCA Worker | - | Native (go run) |
| DCA Scheduler | - | Native (go run) |
| DCA TX Indexer | - | Native (go run) |
When installing a plugin, a 4-party TSS reshare occurs:
- CLI (vcli)
- Fast Vault Server (production)
- Verifier Worker (running locally)
- DCA Plugin Worker (running locally)
The workers use separate task queues to prevent task stealing.
make start # Start infra in Docker + services natively
make stop # Stop all services and clean state
make status # Show infrastructure container status
make logs # Show how to view service logs
make help # Show all available commandsvcli/
├── local/
│ ├── cmd/vcli/ # vcli source code
│ ├── scripts/
│ │ ├── vcli.sh # vcli wrapper script
│ │ └── run-services.sh # Runs services natively
│ ├── keyshares/ # Put your .vult files here
│ ├── policies/ # Policy JSON templates
│ ├── configs/ # Service environment files (*.env)
│ ├── logs/ # Service logs
│ ├── docker-compose.yaml # Infrastructure only (postgres, redis, minio)
│ └── cluster.yaml # Cluster configuration
├── Makefile # Main entry point: make start/stop/status
└── README.md
Sibling repos (required):
vultisig/
├── vcli/
├── verifier/
├── app-recurring/
├── recipes/
└── go-wrappers/
# Check service logs
tail -f local/logs/verifier.log
tail -f local/logs/worker.log
tail -f local/logs/dca-server.log
# Check infrastructure containers
docker compose -f local/docker-compose.yaml pslsof -i :5432 # Check PostgreSQL
lsof -i :8080 # Check Verifier
make stop # Force stop everythingOn shared machines: If another user has processes running on required ports, you'll need to coordinate with them or use sudo to kill the processes:
# Check what's using a port
lsof -i :8080
# If owned by another user, coordinate with them or:
sudo pkill -9 -f "go-build.*/verifier"Check queue isolation - both workers must use separate queues:
docker exec vultisig-redis redis-cli -a vultisig KEYS "asynq:*queue*"# View specific service logs
tail -f local/logs/verifier.log # Verifier server
tail -f local/logs/worker.log # Verifier worker
tail -f local/logs/dca-server.log # DCA plugin server
tail -f local/logs/dca-worker.log # DCA plugin worker
tail -f local/logs/dca-scheduler.log # DCA scheduler
# View all logs
tail -f local/logs/*.logThe go-wrappers CGO library is automatically downloaded on first run. If you encounter library loading issues:
# Check if libraries are downloaded
ls -la ~/.vultisig/lib/
# Force re-download by removing the marker file
rm ~/.vultisig/lib/darwin/.downloaded-master # macOS
rm ~/.vultisig/lib/linux/.downloaded-master # Linux
# Then run any vcli command to trigger download
./local/vcli.sh --helpFor production Kubernetes deployment on Hetzner Cloud, see infrastructure/DEPLOYMENT.md.
The K8s deployment guide covers:
- Terraform-based infrastructure provisioning
- K3s cluster setup
- Service deployment with kustomize overlays
- E2E testing in Kubernetes
- Server type and region selection (AMD64 required for GHCR images)
- Troubleshooting guide with common errors and fixes