- 1. Requirements
- 2. TL;DR
- 2.1. Clone the code and implement your model
- 2.2. Check that your model generates a valid response
- 2.3. Backtest your model
- 2.4. Get a VM and open port
8091 - 2.5. Create or import a Bittensor wallet
- 2.6. Launch your miner with PM2
- 2.7. Verify the port is open
- 2.8. Track your miner performance
- 2.9. Check your prediction validation
- 2.10. More information
- 3. Getting started
- 4. Protect your predictions
- 5. Set up your public miner profile
- Clone the Synth subnet repository.
- Know what you are asked to predict: the subnet runs three competitions —
crypto-1h,crypto-24h, andcom-equ-24h— with different assets, horizons, and points per path. See the competition table in the README before writing your model. - The exact response format (paths array, points per path, price precision) is specified in the miner reference FAQ.
- Modify the implementation of this function to run your own model: simulations.py#L25
- Use all parameters from the prompt except
sigma, which you may ignore.
- Run this command to test your model locally:
python synth/miner/run.py- If your format is correct, you’ll see the output:
start_time <timestamp>
CORRECT
⚠️ run.pyexercises a single BTC / 24h prompt with 100 paths. A passing check does not cover thecrypto-1hshape (61 points per path) or the 1,000 paths requested in production — see the miner reference FAQ for the per-competition parameters.
The open-source synth-lib backtester lets you measure how your model would actually score: it reads your prediction files and replays the live validator's own logic — CRPS per prompt, smoothed scores, reward weights, and rank — against real historical prices, then charts your rank evolution, CRPS distribution, and estimated earnings per competition.
git clone https://github.com/synthdataco/synth-lib.git
cd synth-lib && uv sync
uv run synth_lib/backtester/scripts/run_backtest.py --miner-name my_model --competition crypto-24h --asset BTC- Full guide (prediction file format, CLI flags, Python API, chart outputs): synth-lib README.
- Mind the earliest supported backtest window — see Known caveats.
- For a live end-to-end rehearsal of the network plumbing, you can also run a miner on testnet (netuid
247) — see 3.4.2. Register the wallet.
- Ensure port 8091 is open in your cloud provider's ingress rules.
- Configure your VM's firewall to allow inbound traffic on this port.
- Use
btclito:- Create or import a wallet.
- Add funds.
- Register your hotkey (this will purchase a UID).
- Create a new file called
miner.local.config.jsusing the config from this link: miner.config.js#L1 - Modify the wallet name and hotkey name as needed.
- Start the miner with PM2:
pm2 start miner.local.config.js- Your port will only be accessible if:
- The miner is actively running.
- Port 8091 is open on the VM and network level.
- You can verify using this tool: https://www.yougetsignal.com/tools/open-ports.
- View miner performance and stats on:
- View validation status of your last submission:
https://api.synthdata.co/validation/miner?uid=<your UID>
- Explore the full Synth API documentation: https://api.synthdata.co
To ensure a miner can successfully connect to the network, the port 8091 MUST be open.
Before the beginning, check what ports are open:
nmap localhostwhich should output:
$ nmap localhost
Starting Nmap 7.80 ( https://nmap.org ) at 2025-07-15 12:43 CEST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000079s latency).
Not shown: 998 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
631/tcp open ipp
Nmap done: 1 IP address (1 host up) scanned in 0.04 seconds
⚠️ NOTE: You can installnmapviasudo apt install nmap.
It is RECOMMENDED that ufw (Uncomplicated Firewall) is used to handle port connections.
ufw is a minimal front-end for managing iptables rules. It allows you to easily open ports with simple commands
First, enable ufw using:
sudo ufw enableNext, allow incoming traffic on the correct port:
sudo ufw allow 8091To ensure the port is accessible and the rule is active, execute:
sudo ufw statuswhich should output:
$ sudo ufw status
Status: active
To Action From
-- ------ ----
8091 ALLOW Anywhere
8091 (v6) ALLOW Anywhere (v6)
Using nmap you can check if the port is open using:
nmap -p 8091 localhostwhich should output:
$ nmap -p 8091 localhost
Starting Nmap 7.80 ( https://nmap.org ) at 2025-07-15 12:50 CEST
Nmap scan report for localhost (127.0.0.1)
Host is up (0.000073s latency).
PORT STATE SERVICE
8091/tcp open jamlink
Nmap done: 1 IP address (1 host up) scanned in 0.03 seconds
If you have set up your miner on a remote server/VM using a cloud provider (GCP, AWS, Azure, e.t.c.), you will also need to add an ingress rule on port TCP/8091 to allow for incoming connections.
Please refer to your cloud provider's documentation on adding ingress rules to your server.
Install rust:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shInstall Node/npm and build tooling (Python itself is provisioned by uv in 3.3.3):
sudo apt update && \
sudo apt install nodejs npm pkg-configInstall PM2 via npm:
sudo npm install pm2 -gClone the synth subnet repository:
git clone https://github.com/synthdataco/synth-subnet.gitChange directory to the project root
cd ./synth-subnetInstall uv if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
⚠️ NOTE: The second command putsuvon thePATHof the current shell; new shells pick it up automatically.
Create the virtual environment and install all locked dependencies:
uv syncActivate and switch to the newly created Python virtual environment:
source .venv/bin/activate
export PYTHONPATH=.
⚠️ NOTE: This should activate the.venvenvironment, and you will see the command line prefixed with(.venv).
💡 TIP: For a more extensive list of the Bittensor CLI commands see here.
You will need to create the cold and hot wallets:
btcli wallet create \
--wallet.name miner \
--wallet.hotkey default🚨 WARNING: You must ensure your wallets have enough TAO (0.25 should be enough) to be able to start mining. For testnet, check out the faucet on the Discord.
Next, register the wallets by acquiring a slot on the Bittensor subnet:
btcli subnet register \
--wallet.name miner \
--wallet.hotkey default \
--netuid 50if you want to try it on testnet first, run the following command:
btcli subnet register \
--wallet.name miner \
--wallet.hotkey default \
--network test \
--netuid 247You can verify the wallet registration by running:
btcli wallet overview \
--wallet.name miner \
--wallet.hotkey defaultAnd, you can also check the network metagraph:
btcli subnet metagraph \
--netuid 50for testnet it's:
btcli subnet metagraph \
--network test \
--netuid 247💡 TIP: You can backtest your model to estimate the rank and earnings it would achieve.
Create your local copy of the PM2 config (as in 2.6, adjusting the wallet and hotkey names), then start it:
pm2 start miner.local.config.jsfor testnet, base your local copy on miner.test.config.js instead:
pm2 start miner.test.local.config.jsYou can check if the miner is running by using:
pm2 listYour predictions are valuable. To stop unauthorized parties from querying your miner and copying your responses, make sure only legitimate validators can reach your miner port.
We RECOMMEND configuring your firewall with an allow-list for the ingress IPs on your miner port (8091).
Legitimate validators publish their IP on the Bittensor chain, and these IPs are visible on the metagraph:
https://taostats.io/subnets/50/metagraph?order=validator_trust%3Adesc
Your miner should only answer requests from a validator that:
- Stakes at least 65000.
- Publishes its IP on chain.
- Holds the validator permit.
Maintaining this allow-list by hand is tedious, since validator IPs may change over time. We provide a chain-sync script that keeps your ufw allow-list in sync with the eligible validators on the metagraph automatically:
It admits only validators that hold the validator permit and stake at least --blacklist.validator_min_stake (default 65000), then adds/removes per-IP rules on each cycle. Use --dry-run to preview which IPs would be allowed without touching ufw:
python synth/miner/chain_sync.py --axon_port 8091 --dry-runTo run it for real (as root, so it can edit ufw):
python synth/miner/chain_sync.py --axon_port 8091We ask all miners to run their miners with the blacklist function:
neurons/miner.py#L58 In particular, it includes a security fix that rejects unsigned requests:
if not synapse.dendrite.signature:
bt.logging.warning(
f"Blacklisting unsigned request claiming hotkey "
f"{synapse.dendrite.hotkey}"
)
return True, "Missing signature"The new blacklist function applies a minimum of 65000 stake by default. This is why you MUST update your blacklist function and restart all your miners.
--blacklist.validator_min_stake 65000— gates requests by validator stake. The blacklist enforces a minimum of65000, even if you configure a lower value. Back to top ^
You can attach public identity metadata to your coldkey — a display name, an avatar image and social handles — shown on your miner profile page on the dashboard. Ownership is proven with an sr25519 signature made with your coldkey: no account or API key is needed.
Run the one-shot submission script on whatever machine holds your coldkey (the key never leaves the machine; only the signature is sent). It prompts for your wallet password to sign, then submits:
python scripts/submit_miner_profile.py \
--wallet_name my_wallet \
--display-name "My Mining Co" \
--avatar-url https://example.com/logo.png \
--twitter my_handle \
--discord my_handle \
--linkedin https://www.linkedin.com/in/my-handle \
--website https://example.comEvery submission replaces all six fields, so pass all the values you want shown each time — an omitted field is cleared. To remove your metadata entirely:
python scripts/submit_miner_profile.py --wallet_name my_wallet --deleteField constraints:
--display-name— at most 40 printable characters.--twitter/--discord— bare handles (no URL), at most 64 characters from letters, digits and_ . - #.--avatar-url/--website/--linkedin—https://URLs of at most 300 characters; the linkedin URL must be onlinkedin.com(the website can be any host).
Your coldkey must be (or have been) registered on the subnet, and the signed message expires after 5 minutes — the script signs and submits in one go, so this only matters if your machine's clock is badly off.