Non-custodial signal execution client for Veskald. Your exchange API keys never leave your own server.
Veskald Execution Bridge is the open-source execution client for users of veskald.com. It runs on a server you control and acts as the bridge between your strategies on veskald.com and the official APIs of supported cryptocurrency exchanges.
- Project type: open-source software (AGPL-3.0), self-hosted by each user on their own infrastructure.
- What this client does: receives signed signals from a user's own Veskald account over an authenticated WebSocket, and relays them to that user's chosen exchange using API keys the user provides locally. It does not contain strategy logic, signal generation, or any decision-making — those are user-side operations performed inside the user's own Veskald account.
- What this client does not do: it does not run on any GitLab-provided infrastructure. GitLab CI is used in this repository only at release time, to build and publish Docker images and desktop installers. No signal routing or order placement happens on GitLab runners — see Continuous integration disclosure.
- Operator: currently operated as a sole proprietorship (jednoosobowa działalność gospodarcza) registered in Poland. We plan to incorporate as a Polish limited liability company (sp. z o.o.) as the project grows.
- Security contact: security@veskald.com
The Veskald platform is intentionally split into two halves:
- Strategy layer — strategy builder, backtesting, optimisation and signal generation — runs on
veskald.comunder your account. - Execution layer (this repository) — the part that holds your exchange API keys and places orders on your behalf — runs on a server you control.
The two halves communicate over a single authenticated WebSocket. Veskald sends signals (e.g. open long BTCUSDT, 0.1 contracts, market, stop at 67500) and this client relays them to your exchange using keys Veskald never sees.
This separation is non-custodial by design. The strategy logic stays with us so you do not have to maintain it. The exchange API keys stay with you, so we cannot lose them, leak them, or be compelled to use them.
If you prefer to receive signals as Telegram or email notifications and place orders manually, you do not need this repository — enable the corresponding notification channels in the Veskald app instead.
- Signals are signed and bound to your server's IP. A leaked or copied token cannot be replayed from another machine.
- Exchange API keys never leave your machine — they live only in the
.envfile on your server. - For the journal and execution log, you set up separate read-only keys in the Veskald app. Read-only keys cannot place orders or withdraw funds. They let Veskald compare what the strategy expected against what actually filled.
- Execution results flow back to Veskald over the same WebSocket so the journal stays accurate — no key material ever travels.
| Exchange | Status | Market type | Test mode available |
|---|---|---|---|
| Binance | Stable | USD-M Futures | — |
| Bybit | Stable | USDT Perpetual | Testnet |
| Kraken | Stable | Futures | Sandbox |
| OKX | In development | Swap (Perpetual) | Demo trading |
| Coinbase | In development | INTX Perpetuals | — |
| KuCoin | In development | USDT Perpetual | — |
Need another exchange (Bitget, BingX, Hyperliquid, dYdX, …)? Open an issue — new adapters typically ship within 1–3 weeks.
Three paths, pick the one that fits how you operate.
On a fresh Linux server (Ubuntu / Debian / Fedora / RHEL / CentOS):
bash <(curl -fsSL https://gitlab.com/veskald/veskald-execution-bridge/-/raw/main/installer/server-install.sh)The installer:
- Detects your distribution and installs Docker if missing (via the official
get.docker.comscript). - Prompts for your MACHINE token and the API keys for whichever exchanges you want to enable (skip the rest).
- Writes
.env(mode0600) and adocker-compose.ymlto/opt/veskald-execution-bridge/(or$HOME/veskald-execution-bridge/for non-root users). - Pulls the multi-architecture image
registry.gitlab.com/veskald/veskald-execution-bridge:latestfrom GitLab Container Registry (amd64+arm64). - Starts the container with
restart: unless-stopped.
After installation, manage the client via subcommands of the same script — see Updating and operations.
A double-clickable GUI for Windows, macOS and Linux. No Python install required. Use this if you run the client on the same machine you operate from.
| Platform | Format | File |
|---|---|---|
| Windows x86_64 | zip | Veskald-Execution-Bridge-<version>-windows-x86_64.zip |
| macOS Apple Silicon | tar.gz (.app) | Veskald-Execution-Bridge-<version>-macos-arm64.tar.gz |
| macOS Intel | tar.gz (.app) | Veskald-Execution-Bridge-<version>-macos-x86_64.tar.gz |
| Linux x86_64 | AppImage | Veskald-Execution-Bridge-<version>-x86_64.AppImage |
Latest builds and SHA-256 checksums live on the Releases page. The first launch opens a Settings dialog for the MACHINE token and exchange keys; values are saved to a .env next to the executable.
Platform-specific notes (Windows SmartScreen prompts, macOS quarantine, glibc requirements) ship as INSTALL.md inside each archive.
If you want to inspect every command or customise the Docker image, follow the step-by-step setup below. This path uses git clone + rebuild_latest.sh and is also the path our contributors use.
Detailed server setup
Use this if you want full control or are debugging an install.
- A Linux server — DigitalOcean at $4–6/month is enough, but any VPS works (Hetzner, Vultr, Linode, your own hardware).
- Ubuntu 22.04 LTS recommended.
- 2 GB RAM minimum, 4 GB recommended.
- A Veskald account at app.veskald.com.
- API keys with futures permission on at least one supported exchange. Disable withdrawal permission on the key.
Using DigitalOcean as the example because it is cheap:
- Sign up at digitalocean.com.
- Create → Droplets.
- Settings:
- Region: pick the one geographically closest to your chosen exchange's matching engine — typically Frankfurt or Amsterdam for European users, Singapore for Asian pairs.
- Image: Ubuntu 22.04 LTS.
- Size: 2 GB RAM minimum.
- Authentication: SSH key (strongly preferred) or password.
- After creation, note the droplet's public IP — you will need it.
The token is bound to your specific server IP, so it is useless on its own.
- Open
app.veskald.com/notifications. - Find the Socket channel and enable it.
- Enter your server's IP address.
- Save — the app shows a MACHINE token. Copy it.
SSH in:
ssh root@YOUR_SERVER_IPInstall dependencies:
apt update && apt upgrade -y
apt install -y docker.io docker-compose-plugin git nanogit clone https://gitlab.com/veskald/veskald-execution-bridge.git
cd veskald-execution-bridge
chmod +x rebuild_latest.sh
cp .env.example .env
nano .envFill in the variables you need. You only need keys for the exchanges you will actually use — leave the rest empty.
See the configuration reference for every variable.
./rebuild_latest.shThis builds the Docker image and starts the container in the background.
docker update --restart unless-stopped $(docker ps -q)The client will now come back automatically if the server reboots.
All variables go in your .env file.
| Variable | Required | Description |
|---|---|---|
HUB_URL |
yes | WebSocket URL. Default https://stream.veskald.com is correct. |
MACHINE |
yes | Token from app.veskald.com/notifications. Bound to your server IP. |
UNIQUE_ID |
optional | Override the auto-generated machine ID. Most users don't need this. |
| Variable | Description |
|---|---|
BINANCE_API_KEY |
API key with futures permission enabled. |
BINANCE_API_SECRET |
API secret. |
| Variable | Description |
|---|---|
BYBIT_API_KEY |
API key. |
BYBIT_API_SECRET |
API secret. |
BYBIT_TESTNET |
true for testnet, false for live (default). |
| Variable | Description |
|---|---|
KRAKEN_API_KEY |
API key. |
KRAKEN_API_SECRET |
API secret. |
KRAKEN_SANDBOX |
true for sandbox, false for live (default). |
| Variable | Description |
|---|---|
OKX_API_KEY |
API key. |
OKX_API_SECRET |
API secret. |
OKX_API_PASSPHRASE |
API passphrase (required by OKX). |
OKX_DEMO |
true for demo, false for live. |
OKX_DEFAULT_ISOLATED |
true for isolated margin by default, false for cross. |
| Variable | Description |
|---|---|
COINBASE_API_KEY |
API key. |
COINBASE_API_SECRET |
API secret. |
COINBASE_INTX_PORTFOLIO_UUID |
INTX portfolio UUID. |
| Variable | Description |
|---|---|
KUCOIN_API_KEY |
API key with Futures trade permission. |
KUCOIN_API_SECRET |
API secret. |
KUCOIN_API_PASSPHRASE |
API passphrase (required by KuCoin). |
The broker (partner) tag and key arrive with each signal; hardcoded fallbacks
live in lib/kucoin.py.
Check the container is running:
docker psYou should see one container in the Up state.
Follow the logs:
docker compose logs -fYou are looking for a successful WebSocket handshake and a connected message. If you have an active strategy in your Veskald account with full-auto enabled and a signal fires, the log will show the order being placed.
How you keep the client up-to-date depends on the install path you picked.
bash server-install.sh update # pull the latest image, restart container
bash server-install.sh status # docker compose ps
bash server-install.sh logs # tail -f the container logs
bash server-install.sh restart
bash server-install.sh stop
bash server-install.sh reconfigure # rerun the wizard, overwrite .env
bash server-install.sh uninstall # remove container + image + dirPin a specific image version: edit the generated docker-compose.yml and replace :latest with e.g. :v1.2.3, then bash server-install.sh restart.
./rebuild_latest.sh # git pull + rebuild image + restart
docker compose stop # full stop
docker compose logs -f # follow logs
docker compose logs --tail=100 # last 100 linesRe-download the latest archive from the Releases page and replace the binary. Your .env and logs are kept outside the bundle (in the OS user config directory) and survive upgrades.
This client handles your exchange API keys. Treat it accordingly.
- Never commit
.env. It is already in.gitignore— keep it that way. - Use API keys with futures permission only. Disable withdrawal at the exchange. Most exchanges (Binance, OKX, Bybit) also let you whitelist the client's server IP — do that.
- The MACHINE token is bound to your IP, but treat it as a secret anyway.
- Audit the code. That is the point of open source.
lib/is roughly 1,500 lines of readable Python. Read it. - Use SSH keys, not passwords. Disable root SSH login once you have created a non-root sudo user.
- Keep the server patched —
apt update && apt upgrade -yweekly is a healthy habit. - The Docker image and desktop binaries never contain your
.env. It stays on your host or in the OS user config directory.
Found a security issue? Please email security@veskald.com rather than opening a public issue.
Container won't start. Check docker compose logs. Almost always an empty or malformed value in .env — verify no trailing spaces and no quotes around values.
WebSocket disconnects immediately. The server's public IP must match what you entered at app.veskald.com/notifications. If you moved servers, regenerate the token.
Order rejected by exchange. Usually one of: (1) the API key is missing futures permission, (2) the exchange has IP whitelisting on and your server IP is not whitelisted, (3) margin mode mismatch (isolated vs cross). The exchange's rejection reason is logged verbatim — read it.
Client connects but no orders are placed. Confirm the strategy in your Veskald account has full-auto mode enabled (the checkbox on the strategy edit screen). Without it, you get notifications but the client waits for your manual confirmation.
docker compose not found after the one-liner. The get.docker.com script normally installs the Compose v2 plugin. If Docker was pre-installed without it (e.g. apt install docker.io or via Snap), install the plugin and re-run the installer:
sudo apt-get install -y docker-compose-plugin # Debian / Ubuntu
sudo dnf install -y docker-compose-plugin # Fedora / RHEL / CentOSSnap-installed Docker is not supported — sudo snap remove docker and let the installer set up the official package.
An exchange API changed and something broke. Exchanges occasionally ship breaking API changes. Run your update command (server-install.sh update or ./rebuild_latest.sh). If the fix is not out yet, open an issue — patches usually land within a day.
Anything else: open an issue, or contact support from inside the Veskald app.
veskald-execution-bridge/
├── .env.example # environment template
├── Dockerfile # production image (also pushed to registry.gitlab.com)
├── docker-compose.yml # dev / local compose
├── rebuild_latest.sh # manual git-clone deploy
├── run.py # headless entry point
├── config.py # config loader
├── BUILD.md # how to build desktop apps locally
├── installer/
│ ├── server-install.sh # ← server one-liner installer
│ ├── build-local.sh / build-local.ps1 # desktop builds (Linux/macOS, Windows)
│ ├── veskald-execution-bridge.spec # PyInstaller spec
│ ├── launcher.py + ui/ # PySide6 desktop app
│ ├── INSTALL.md # ships inside each desktop archive
│ └── assets/ # icon, .desktop, linux-install.sh
├── lib/ # exchange adapters + shared logic
│ ├── base_trader.py # shared adapter interface
│ ├── trader_factory.py # picks the right adapter based on the signal
│ ├── symbols.py # symbol mapping between Veskald and exchanges
│ ├── userdir.py # OS-specific config / log dirs
│ ├── logger.py
│ ├── binance.py / bybit.py / coinbase.py / kraken.py / kucoin.py / okx.py
├── .gitlab-ci.yml # CI: Docker multi-arch build & push to GitLab Container Registry
└── .gitlab/merge_request_templates/Default.md # MR template + format reminder
Adding a new exchange adapter is a single file in lib/ implementing the BaseTrader interface.
This repository uses GitLab CI only at release time. The .gitlab-ci.yml pipeline runs when a release tag is pushed and is responsible for:
- Building the multi-architecture Docker image (
linux/amd64andlinux/arm64) and publishing it to GitLab Container Registry. - Building desktop installers for Windows, macOS (Apple Silicon and Intel) and Linux, and uploading them as artifacts on the GitLab Release.
- Refreshing the public mirror used for desktop installer downloads.
The client itself does not run on GitLab-hosted runners. Signal routing, order placement and any other runtime behaviour happens exclusively on a user's own server or workstation, never on GitLab infrastructure. All workflow jobs declare explicit timeout-minutes values to prevent runaway runs, and multi-architecture builds are split into per-architecture jobs so that a stuck build cannot consume runner time beyond its configured bound.
- Additional exchange adapters (Bitget, BingX, Hyperliquid) — by request.
- Multi-account routing (run several strategies against several accounts from one client).
- Optional Telegram mirror — the client posts a copy of every action it takes to a Telegram channel of your choice.
Bug reports and pull requests are welcome.
- Open an issue first for non-trivial changes.
- Keep exchange-specific code in
lib/<exchange>.py. No exchange logic inrun.py. - Run
python test.pybefore submitting. - See
BUILD.mdfor building the desktop apps and Docker image locally.
PR titles must follow Conventional Commits — the title becomes a line in the auto-generated "What's Changed" section of the next release.
type: Subject in imperative mood, capitalized
Allowed types: feat, fix, perf, refactor, docs, test, build, ci, chore, revert.
Examples:
feat: Add Bybit testnet toggle in settings dialogfix: Reconnect socket after MACHINE token rotationchore: Bump pybit to 5.16.0
The PR title workflow blocks merges that do not conform and posts a comment explaining what is wrong. See .gitlab/merge_request_templates/Default.md.
AGPL-3.0. Use it freely, modify it, run it for your own purposes. If you build a hosted service on top of it, the AGPL requires you to publish your changes — that is the trade.
Veskald is currently operated as a sole proprietorship (jednoosobowa działalność gospodarcza) registered in Poland. As the project scales, we plan to incorporate as a Polish limited liability company (sp. z o.o.).
- 🌐 veskald.com
- 📱 app.veskald.com
- 💬 In-app support
- 🔒 security@veskald.com
- 🐛 GitLab issues