> For the complete documentation index, see [llms.txt](https://xeqmlabs.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xeqmlabs.gitbook.io/docs/documentation/guides/xeq-legacy-swap-mainnet/service-node-startup-and-registration.md).

# Service Node Startup & Registration

End-to-end walkthrough: from spinning up your service node container to generating the registration command you'll paste into the wallet CLI.

> **Replace `YOUR_PUBLIC_IP`** with your server's public IP (from Step 1). **Replace `<MY_ADDRESS>`** with your XEQM wallet address.

***

### Part 1 — Start Your Service Node

#### Windows (PowerShell)

Stop and remove any existing service node container:

```powershell
docker stop sn01; docker rm sn01
```

Then run:

```powershell
docker run -dit --name sn01 `
    -p 9230:9230 `
    -p 9232:9232 `
    -p 127.0.0.1:9231:9231 `
    -v "${PWD}\data\sn01:/data" `
    ghcr.io/equilibriahorizon/equilibria-node:latest `
    --service-node `
    --data-dir=/data `
    --p2p-bind-ip=0.0.0.0 `
    --p2p-bind-port=9230 `
    --rpc-admin=0.0.0.0:9231 `
    --service-node-public-ip=YOUR_PUBLIC_IP `
    --quorumnet-port=9232 `
    --log-level=2
```

> **Why the asymmetric port mapping?** `-p 127.0.0.1:9231:9231` keeps the admin RPC bound to your host's loopback only — it's not reachable from the public internet. `--rpc-admin=0.0.0.0:9231` lets the daemon inside the container accept the connection that Docker forwards in. If you bind the daemon to `127.0.0.1` instead, Docker's port forwarding can't reach it and your RPC calls will fail.

#### Linux

Stop and remove any existing service node container:

```bash
docker stop sn01; docker rm sn01
```

Then run:

```bash
docker run -dit --name sn01 --network host \
    -v "$(pwd)/data/sn01:/data" \
    ghcr.io/equilibriahorizon/equilibria-node:latest \
    --service-node \
    --data-dir=/data \
    --p2p-bind-port=9230 \
    --rpc-admin=0.0.0.0:9231 \
    --service-node-public-ip=YOUR_PUBLIC_IP \
    --quorumnet-port=9232 \
    --log-level=2
```

#### Check it's running

```bash
docker logs -f sn01
```

Look for connection and sync messages. `Ctrl+C` stops watching the logs — the container keeps running. Proceed to starting your wallet RPC.

#### Port Reference (XEQM Mainnet)

| Port | Purpose               |
| ---- | --------------------- |
| 9230 | P2P                   |
| 9231 | Admin RPC             |
| 9232 | Quorumnet             |
| 9233 | Public OMQ (optional) |
| 9234 | Wallet RPC            |

***

### Part 2 — Multi-SN on One Host (Windows / Docker Desktop)

Each additional SN needs different host ports (+10 per SN) and a priority node pointing at sn01.

#### sn02

```powershell
docker run -dit --name sn02 `
    -p 9240:9230 `
    -p 9242:9232 `
    -p 127.0.0.1:9241:9231 `
    -v "${PWD}\data\sn02:/data" `
    ghcr.io/equilibriahorizon/equilibria-node:latest `
    --service-node `
    --data-dir=/data `
    --p2p-bind-ip=0.0.0.0 `
    --p2p-bind-port=9230 `
    --rpc-admin=0.0.0.0:9231 `
    --service-node-public-ip=YOUR_PUBLIC_IP `
    --quorumnet-port=9232 `
    --add-priority-node=host.docker.internal:9230 `
    --log-level=2
```

For sn03 use host ports `9250/9251/9252`, sn04 use `9260/9261/9262`, etc. (+10 per SN, both host port and admin RPC port).

**Container-internal ports always stay `9230/9231/9232`** — only the host-side port mapping changes.

***

### Part 3 — Get the Registration Command

Once your funds have been received and unlocked, generate the registration command for each service node.

The command queries the SN's daemon for its public key and signs the registration with your wallet address as operator.

> **Note:** Run this from your new mainnet folder directory, NOT from inside the CLI Wallet

#### ⚠️ Important: Two Different Encoding Schemes

The `get_service_node_registration_cmd` RPC accepts two different parameter formats depending on the chain's hard fork version:

| Era                                   | `contributor_amounts` encoding                         | `staking_requirement`                 |
| ------------------------------------- | ------------------------------------------------------ | ------------------------------------- |
| **Pre-HF16** (current Horizon, HF 15) | Portions of `2^64 - 4` (`18446744073709551612` = 100%) | Dynamic — query the daemon            |
| **Post-HF16** (after upcoming fork)   | Atomic units (e.g. `200000000000000` = 200k XEQ)       | Fixed at `200000000000000` (200k XEQ) |

**Use the wrong encoding and the daemon will reject the request** with errors like `No operator contribution given`.

Check which version your daemon is running with:

**PowerShell:**

```powershell
Invoke-RestMethod -Uri http://127.0.0.1:9231/json_rpc -Method Post -ContentType "application/json" -Body '{"jsonrpc":"2.0","id":"0","method":"hard_fork_info"}'
```

**Linux:**

```bash
curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"0","method":"hard_fork_info"}' | jq
```

If `version` is **15 or lower**, use the pre-HF16 commands. If **16 or higher**, use the post-HF16 commands.

***

#### Pre-HF16 (Current Horizon Chain, HF 15) — Portions Encoding

This is what you need **right now** on the live Horizon chain. `contributor_amounts` is expressed as a **portion of `STAKING_PORTIONS` (2^64 - 4 = `18446744073709551612`)**, where the full value represents 100% of the staking requirement.

The staking requirement itself is **dynamic** — it changes with block height. Always query it fresh before generating the registration.

**Reference portion values:**

| Operator stake | Portions value         |
| -------------- | ---------------------- |
| 100% (solo)    | `18446744073709551612` |
| 50%            | `9223372036854775806`  |
| 25%            | `4611686018427387903`  |

**Step 1: Get the current staking requirement**

**Windows (PowerShell):**

```powershell
$req = (Invoke-RestMethod -Uri http://127.0.0.1:9231/json_rpc -Method Post -ContentType "application/json" -Body '{"jsonrpc":"2.0","id":"0","method":"get_staking_requirement"}').result.staking_requirement; $req
```

**Linux:**

```bash
REQ=$(curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"0","method":"get_staking_requirement"}' \
  | jq -r '.result.staking_requirement'); echo $REQ
```

**Step 2: Generate the registration command**

**Windows (PowerShell)**

**Solo Node (100% operator stake) — 10% operator cut:**

```powershell
$result = Invoke-RestMethod -Uri http://127.0.0.1:9231/json_rpc -Method Post -ContentType "application/json" -Body "{`"jsonrpc`":`"2.0`",`"id`":`"0`",`"method`":`"get_service_node_registration_cmd`",`"params`":{`"operator_cut`":`"10`",`"contributor_addresses`":[`"<MY_ADDRESS>`"],`"contributor_amounts`":[18446744073709551612],`"staking_requirement`":$req}}"; $result.result.registration_cmd
```

> Both steps must be run in the same PowerShell session so `$req` carries over.

**Linux**

**Solo Node (100% operator stake) — 10% operator cut:**

```bash
curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"get_service_node_registration_cmd\",\"params\":{\"operator_cut\":\"10\",\"contributor_addresses\":[\"<MY_ADDRESS>\"],\"contributor_amounts\":[18446744073709551612],\"staking_requirement\":$REQ}}" \
  | jq -r '.result.registration_cmd'
```

> Both steps must be run in the same shell session so `$REQ` carries over.

***

#### Post-HF16 (After Upcoming Fork) — Atomic Units Encoding

After HF16 lands, the registration RPC will accept clean atomic-units encoding with a fixed staking requirement. `contributor_amounts` is in atomic units (1 XEQ = 1,000,000,000 atomic units), and the staking requirement is fixed at 200,000 XEQ.

* **Full Node:** operator contributes the full 200,000 XEQ
* **Pool Node:** operator contributes a minimum of 100,000 XEQ; remaining stake fills from contributors

**Windows (PowerShell)**

**Full Node — 200,000 XEQ:**

```powershell
$result = Invoke-RestMethod -Uri http://127.0.0.1:9231/json_rpc -Method Post -ContentType "application/json" -Body '{"jsonrpc":"2.0","id":"0","method":"get_service_node_registration_cmd","params":{"operator_cut":"100","contributor_addresses":["<MY_ADDRESS>"],"contributor_amounts":[200000000000000],"staking_requirement":200000000000000}}'; $result.result.registration_cmd
```

**Pool Node — 100,000 XEQ operator minimum:**

```powershell
$result = Invoke-RestMethod -Uri http://127.0.0.1:9231/json_rpc -Method Post -ContentType "application/json" -Body '{"jsonrpc":"2.0","id":"0","method":"get_service_node_registration_cmd","params":{"operator_cut":"10","contributor_addresses":["<MY_ADDRESS>"],"contributor_amounts":[100000000000000],"staking_requirement":200000000000000}}'; $result.result.registration_cmd
```

**Linux**

**Full Node — 200,000 XEQ:**

```bash
curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"0","method":"get_service_node_registration_cmd","params":{"operator_cut":"100","contributor_addresses":["<MY_ADDRESS>"],"contributor_amounts":[200000000000000],"staking_requirement":200000000000000}}' \
  | jq -r '.result.registration_cmd'
```

**Pool Node — 100,000 XEQ operator minimum:**

```bash
curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"0","method":"get_service_node_registration_cmd","params":{"operator_cut":"10","contributor_addresses":["<MY_ADDRESS>"],"contributor_amounts":[100000000000000],"staking_requirement":200000000000000}}' \
  | jq -r '.result.registration_cmd'
```

**If `jq` isn't installed:**

```bash
curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":"0","method":"get_service_node_registration_cmd","params":{"operator_cut":"100","contributor_addresses":["<MY_ADDRESS>"],"contributor_amounts":[200000000000000],"staking_requirement":200000000000000}}' \
  | grep -o '"registration_cmd":"[^"]*"' | cut -d'"' -f4
```

***

### Part 4 — Multi-SN Registration

Each daemon listens on a different host port. Adjust the URL accordingly:

| SN   | URL                              |
| ---- | -------------------------------- |
| sn01 | `http://127.0.0.1:9231/json_rpc` |
| sn02 | `http://127.0.0.1:9241/json_rpc` |
| sn03 | `http://127.0.0.1:9251/json_rpc` |
| sn04 | `http://127.0.0.1:9261/json_rpc` |

(+10 per additional SN.)

***

### Expected Output

A long single-line string starting with `register_service_node`:

```
register_service_node 100 XEQMXX... 200000000000000 17XXXXX200 <pubkey> <signature>
```

Copy the entire line. Paste it into your wallet CLI prompt to submit the registration transaction.

***

### Troubleshooting

#### "Connection closed" or "Unable to connect" when running `Invoke-RestMethod`

Your daemon was started with `--rpc-admin=127.0.0.1:9231` instead of `--rpc-admin=0.0.0.0:9231`. Docker's port forwarding can't reach a service bound to the container's internal loopback.

**Quick fix** — run the RPC from inside the container, bypassing port forwarding entirely. Replace `<STAKING_REQ>` with the value from `get_staking_requirement` (post-HF16 only):

```powershell
docker exec sn01 curl -s http://127.0.0.1:9231/json_rpc -X POST -H "Content-Type: application/json" -d '{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"get_service_node_registration_cmd\",\"params\":{\"operator_cut\":\"100\",\"contributor_addresses\":[\"<MY_ADDRESS>\"],\"contributor_amounts\":[18446744073709551612],\"staking_requirement\":<STAKING_REQ>}}'
```

The whole JSON response prints — copy the `registration_cmd` value out of it.

**Proper fix** — recreate the container with `--rpc-admin=0.0.0.0:9231` (as shown in Part 1). Apply this to all SNs going forward so you don't hit this again on every registration.

#### "No operator contribution given" / "Failed to make registration command"

You're using the wrong encoding for the chain version. On the **current Horizon chain (HF 15, pre-HF16)**, `contributor_amounts` must be expressed as portions of `2^64 - 4`, not atomic units. See the encoding table at the top of Part 3.

The daemon will log the parsed args at debug level — check `docker logs sn01` and look for the `=== convert_registration_args DEBUG ===` block to see exactly what it received.

***

### Notes

* The registration command is one continuous line — don't introduce line breaks when copying.
* The registration is timestamped and **expires after 14 days** if not submitted.
* Make sure your wallet has enough unlocked XEQ/XEQM to cover the operator stake plus tx fee.
* Each SN generates its own unique registration — re-run per SN, pointed at that SN's admin RPC port.
* **Pre-HF16 (current Horizon, HF 15):** staking requirement is dynamic and changes with block height — always query `get_staking_requirement` fresh before each registration. `contributor_amounts` is expressed as portions of `2^64 - 4` (`18446744073709551612` = 100% of the staking requirement).
* **Post-HF16 (after upcoming fork):** staking requirement is fixed at 200,000 XEQ. `contributor_amounts` is in atomic units. Pool node operator minimum is 100,000 XEQ.
