Pixa
GitHub

Become a Witness

Status: Live

21 witnesses scheduled on 2026-11-03; up to 21 elected, 1 required. For the witness repository at commit 704a68f, with pixadock/pixagram:1.30.1 and pixarex/bigmac-feed:v1.3.0. Steps 1 to 4 were run on 2026-10-05 with 1.30.0, which 1.30.1 replaced on 2026-10-15 without a hardfork; the feed's dry runs on 2026-11-03. Commands that need a registered witness were not run.

A witness runs a node that produces blocks in turn, publishes a price feed every hour and asks stakeholders for their votes. What witnesses decide, how they are elected and how they are paid is on Witnesses and DPoS. This page is the procedure: keys, node, registration, price feed and upkeep. pixa.org's node operator guide walks through the same setup, together with an API node, from renting the server onward; this page pins the versions and gives the expected output of each step.

What you commit to

  • A node that never sleeps. Every block you miss is public and permanent in your total_missed count, and missed blocks are not paid.
  • An exact clock. A slow clock costs the next witness its block (below).
  • A price feed every hour. Feeds older than 7 days stop counting.
  • Upgrades before hardforks. After activation, a witness on an older version can no longer produce valid blocks (Protocol Upgrades).
  • A public page. Stakeholders vote for people they can identify: say who runs the node, where, and how to reach you.

Before you start

  • A server as in Run a Node: 2 vCPU, 4 GB, 50 GB SSD, x86-64. Prefer a provider and a region the other witnesses do not use: a chain is only as resilient as its least diverse failure.
  • An account with its active key at hand. Create it in the app if you have none (Create an Account).
  • A page to register as your witness URL, for example a post on Pixagram.

The two keys

Key Signs Lives in
Block-signing key, new, made in step 1 Blocks, and witness_set_properties updates to your witness's own settings (URL, price feed, parameter votes). It cannot move funds. pixagram/config.ini on the witness server
Active key of your account The registration (witness_update), the price feed (feed_publish) and every transfer Your password manager, and the feed's .env file
  • Do not mix them up. The active key in config.ini produces no blocks; the signing key in .env publishes no feed.
  • Guard both. Whoever holds the signing key can produce blocks and change your witness's settings in your name. Whoever holds the active key can move your funds. Both files get chmod 600, and neither is ever committed or pasted into a chat.
  • The owner key never goes on a server.

1. Create a signing key

Generate it offline, with the wallet inside the node image. Nothing is sent anywhere.

Shelldocker run --rm -it --entrypoint /home/hived/bin/cli_wallet pixadock/pixagram:1.30.1 -o

At the new >>> prompt, type:

suggest_brain_key

Expected output (your values differ):

JSON{
  "brain_priv_key": "<16 words>",
  "wif_priv_key": "5…",
  "pub_key": "PIX…"
}

Type exit to leave the wallet. Store all three values with your other keys: wif_priv_key goes into config.ini in step 2, pub_key goes on chain in step 4, and the 16 words recreate both. More on the wallet: CLI Wallet.

2. Set up the node

Shellgit clone https://github.com/pixagram-blockchain/witness.git
cd witness
grep 'image: pixadock/pixagram' docker-compose.yml

Expected output: the line image: pixadock/pixagram:1.30.1, indented. The repository pins every image version; this page describes its commit 704a68f.

Fill in pixagram/config.ini. Near the end, two lines ship commented out, because hived stops with unable to parse private key when private-key = is present but empty. Open the file with nano pixagram/config.ini, remove the # from both lines and fill them in:

INIwitness = "your-account"
private-key = 5…the wif_priv_key from step 1

Protect the file. It now holds the signing key, and git tracks it as a template. Mark it so that git leaves your copy alone:

Shellchmod 600 pixagram/config.ini
git update-index --skip-worktree pixagram/config.ini
git ls-files -v pixagram/config.ini
grep -E '^(witness|private-key)[[:space:]]*=' pixagram/config.ini

Expected output: S pixagram/config.ini, then your two lines.

  • With the mark, git status no longer lists the file, git add refuses it, and git reset --hard keeps your copy.

  • Without it, git reset --hard replaces your copy with the template. hived reads the file only when it starts, so the node keeps producing until its next restart, perhaps days later, and then stops.

  • When the template changes upstream, git pull stops with Aborting and updates nothing, image versions included. Take the change this way, which keeps your key out of git:

    Shellcp pixagram/config.ini ~/config.ini.mine            # your copy, outside the repository
    git update-index --no-skip-worktree pixagram/config.ini
    git checkout -- pixagram/config.ini                 # back to the template
    git pull
    nano pixagram/config.ini                            # fill in the two lines again, from ~/config.ini.mine
    chmod 600 pixagram/config.ini
    git update-index --skip-worktree pixagram/config.ini
    rm ~/config.ini.mine                                # once the node runs on the new file

Create .env for the price feed, with your account's active key. Use an editor rather than a command line, so the key does not land in your shell history: run nano .env, enter these two lines, save, then chmod 600 .env.

INIWITNESS_ACCOUNT=your-account
WITNESS_WIF=5…your active key

Leave two settings out. The node that started the chain needed enable-stale-production = true and required-participation = 0 to produce alone. On a node joining a live network, they remove the checks that stop it from building on a stale chain or producing during a network split. The shipped config.ini contains neither; keep it that way.

Check the clock to the millisecond

hived decides when to produce from the system clock, and each slot lasts 3 seconds. If your clock runs a second or two slow, your block reaches your peers during the next witness's slot. That witness has not seen it, produces a competing block at the same height, loses the fork, and the miss is recorded against it, not you. Your own count stays clean while you cost the network blocks. On this network, one witness running 2.672 seconds slow caused 121 of 123 same-height collisions.

Install a time daemon as in Run a Node, then measure the real offset from the server itself. Comparing date over SSH measures your connection, not the clock.

Shellpython3 - <<'PY'
import socket, struct, time, statistics
def probe(host):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.settimeout(4)
    try:
        t1 = time.time(); s.sendto(b'\x1b' + 47 * b'\0', (host, 123))
        d, _ = s.recvfrom(1024); t4 = time.time()
    finally:
        s.close()
    u = struct.unpack('!12I', d[:48])
    t2 = u[8] + u[9] / 2**32 - 2208988800
    t3 = u[10] + u[11] / 2**32 - 2208988800
    return ((t2 - t1) + (t3 - t4)) / 2
offsets = []
for host in ('pool.ntp.org', 'time.google.com', 'time.cloudflare.com'):
    for _ in range(3):
        try: offsets.append(probe(host))
        except Exception: pass
print(f'clock offset: {statistics.median(offsets):+.4f}s  ({len(offsets)} samples)'
      if offsets else 'could not reach any NTP server')
PY

Expected output: a line such as clock offset: +0.0031s (9 samples). Under about 50 ms is fine. Past about half a second, you cost your neighbours in the schedule their blocks.

3. Start the node and let it sync

Start the node alone; the feed comes in step 5.

Shelldocker compose up -d pixagram
docker compose logs -f pixagram

Wait for entering live mode, followed by one Got … transactions on block … line every 3 seconds (what the log shows). Register only once the node is in live mode.

4. Register on chain

Registration is one witness_update operation, signed with your active key. This script sends it with dpixa through api.pixagram.com. Run it on any machine with Node.js 18 or newer:

Shellmkdir witness-tools && cd witness-tools
npm init -y
npm install @pixagram/dpixa@1.5.4

Save as register-witness.js, with your values in the first three constants:

JavaScript// register-witness.js: run once, and again whenever the URL, key or properties change
const { Client, PrivateKey } = require("@pixagram/dpixa/dist/dpixa.js");

const ACCOUNT = "your-account";
const SIGNING_PUB = "PIX…";                          // pub_key from step 1
const URL = "https://pixagram.com/…";                // your witness page

const client = new Client(["https://api.pixagram.com"]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function main() {
  const key = PrivateKey.fromString(process.env.ACTIVE_WIF || ""); // from the environment, never from a file
  const { id } = await client.broadcast.sendOperations([
    ["witness_update", {
      owner: ACCOUNT,
      url: URL,
      block_signing_key: SIGNING_PUB,
      props: { account_creation_fee: "20.000 PIXA", maximum_block_size: 2097152, pxs_interest_rate: 0 },
      fee: "0.000 PIXA",
    }],
  ], key);
  console.log("accepted, transaction", id);
  await sleep(9000);
  const w = await client.call("condenser_api", "get_witness_by_account", [ACCOUNT]);
  console.log("on chain:", w.owner, "signs with", w.signing_key, "url", w.url);
}

main().catch((e) => { console.error(e.message); process.exit(1); });

Run it with the active key read from the keyboard, so the key stays out of your shell history:

Shellread -rsp 'Active key: ' ACTIVE_WIF && export ACTIVE_WIF && echo
node register-witness.js
unset ACTIVE_WIF

Expected output:

accepted, transaction <40 hexadecimal characters>
on chain: your-account signs with PIX… url https://pixagram.com/…
Field What to put
url Your witness page. It cannot be empty.
block_signing_key The pub_key from step 1
account_creation_fee Your vote for the fee to open an account. The median was 20.000 PIXA on 2026-10-05; the floor is 0.001 PIXA.
maximum_block_size Your vote for the block size, from 131072 to 2097152 bytes. Every witness voted 2097152 on 2026-10-05.
pxs_interest_rate 0. The chain rejects any other value.
fee 0.000 PIXA

Your votes for the fee and the block size count through the median of the scheduled witnesses (account fee, block size).

5. Publish the price feed

PXS promises no price. Its feed is each witness's statement of how many PIXA one Big Mac costs. The chain takes the middle feed of the scheduled witnesses once an hour, conversions use the median of the last 84 samples, and a feed older than 7 days stops counting (the rules). How the feed is computed, and why, is on Oracle and Price Feed.

The bigmac-feed service in the repository runs bigmac-feed v1.3.0. Every hour it:

  1. prices one Big Mac in the local currency of the country your server runs in, from The Economist's index, or uses the country or the price you set;
  2. values PIXA at the fair value of the PIXA/USDT order book on Coinstore, not at its last trade;
  3. converts that value into the Big Mac's currency at the day's rate, when the two differ;
  4. publishes 1.000 PXS = <Big Mac ÷ PIXA> PIXA through your own node, over Docker's internal network, which is why config.ini loads database_api, condenser_api and network_broadcast_api.

It needs no placeholder price. If the market gives no fair value, it publishes nothing that hour, and the chain keeps your previous feed.

Check the version

In the witness directory:

Shellgrep 'bigmac-feed:' docker-compose.yml
ls docker-compose.override.yml

Expected output: the line image: pixarex/bigmac-feed:v1.3.0, indented, then ls: cannot access 'docker-compose.override.yml': No such file or directory. Until 2026-10-30 the repository pinned v1.0.2 with a fixed 0.12 USD price for PIXA.

  • An older version means an older copy of the repository: update it as in step 2, with git pull.
  • An override file may hold a feed pinned by an earlier version of this page, pixadock/bigmac-feed:v1.0.3 or bigmac-feed-agnostic:local, and it overrides the repository. Delete its bigmac-feed section, or the whole file if that is all it holds.

Try it without a key

Shelldocker run --rm pixarex/bigmac-feed:v1.3.0 --dry-run --once --witness=your-account

A dry run computes everything, broadcasts nothing and needs no key. Expected output from a server in the United States on 2026-11-03. Each line starts with the date and time, and the numbers follow the order book and the day's exchange rate:

Big Mac country: USA (detected from public IP)
Big Mac Feed — witnesses=your-account rpc=https://api.pixagram.com markets=[coinstore PIXAUSDT] impact-size=1000 PIXA max-spread=25% interval=1h0m0s dry-run=true
Big Mac in United States = 6.22 USD (as of 2026-07-01) | PIXA = 0.138513 USDT [coinstore PIXAUSDT: bid 0.1349 / ask 0.142, 1000-PIXA impact 0.13402 / 0.143006] → 1 PXS = 44.906 PIXA
Dry run: would publish 1.000 PXS = 44.906 PIXA for your-account

Read the third line before going further: it names the Big Mac your feed will use and the market it read.

Choose your Big Mac

The detected country is your server's, which may not be yours: a server rented abroad reports the Big Mac of its data centre's country. Two settings change that:

Setting Effect Example
--country=CH The local price of that country in the latest survey of the index. Two- or three-letter ISO codes; the euro area is EUZ, and any member country maps to it. A country missing from the survey makes the feed publish nothing. Switzerland: 7.30 CHF
--bigmac-price="7.30 CHF" A price you observe yourself, with its currency. It overrides --country. Nobody can check it on chain, so say on your witness page where it comes from. 7.30 CHF

Try a setting before you keep it:

Shelldocker run --rm pixarex/bigmac-feed:v1.3.0 --dry-run --once --witness=your-account --bigmac-price="7.30 CHF"

Expected output on 2026-11-03, after the first two lines:

Big Mac = 7.3 CHF (--bigmac-price) | PIXA = 0.138513 USDT [coinstore PIXAUSDT: bid 0.1349 / ask 0.142, 1000-PIXA impact 0.13402 / 0.143006] = 0.113529 CHF | FX USD→CHF 0.81963 (ECB 2026-11-02) → 1 PXS = 64.301 PIXA
Dry run: would publish 1.000 PXS = 64.301 PIXA for your-account

To keep it, create docker-compose.override.yml in the witness directory; git pull leaves that file alone. Compose replaces the whole command list, so repeat the repository's three lines before yours:

YAMLservices:
  bigmac-feed:
    command:
      - --witness=${WITNESS_ACCOUNT}
      - --rpc=http://pixagram:7777
      - --interval=1h
      - --country=CH

Start it

Shelldocker compose up -d bigmac-feed
docker compose logs -f bigmac-feed

After any change to its settings, run docker compose up -d bigmac-feed again; docker compose restart keeps the old command. Expected output, once an hour, with your Big Mac and that hour's numbers:

Big Mac in United States = 6.22 USD (as of 2026-07-01) | PIXA = 0.138513 USDT [coinstore PIXAUSDT: …] → 1 PXS = 44.906 PIXA
Feed published by your-account: 1.000 PXS = 44.906 PIXA

Until your registration in step 4 is in a block, every publish fails with an error from the node. When the feed publishes nothing, it says why:

Log line Meaning What to do
WARNING: no fair value: coinstore PIXAUSDT: spread … is wider than --max-spread 25.0%, no fair value The book is too thin to read Nothing. The chain keeps your previous feed for up to 7 days.
ERROR: no PIXA fair value from any of the 1 market(s); nothing published, the chain keeps the previous feed No market gave a fair value this hour The same. If it lasts for days, raise it with the other witnesses before you set --token-price.
ERROR: fx USD→…: … No exchange rate for your Big Mac's currency, and no saved rate younger than 96 hours Check the server's outgoing connections.
ERROR: … is not in the latest Big Mac survey The country set with --country is not in the index Choose another country, or declare a price.

Keep it healthy

  • When your witness is scheduled, which needs votes from 1% of all Pixa Power (below), condenser_api.get_witness_schedule lists it in current_shuffled_witnesses, and the node logs Generated block #<n> with timestamp <time> at time <time> at each of its turns. If it logs Won't produce block because I don't have the private key for PIX… instead, the key in config.ini does not match the one you registered: fix one of them and run docker compose restart pixagram.

  • Look daily at the witness status page: your missed blocks, your feed's age, your version against the majority.

  • Let a machine watch. The status project's check exits 0 when everything is healthy, 1 on a warning, 2 on a critical finding and 3 if it cannot fetch. It judges the whole network, so a warning may concern another witness. Run it on a machine that holds no keys, pinned to a version you have read, with Node.js 20 or newer. A cron line can alert you:

    Shell*/10 * * * * npx github:pixagram-blockchain/witness-status#a376149 --check >/dev/null 2>&1 || <your alert command>
  • Your own numbers, from any machine:

    Shellcurl -s -X POST https://api.pixagram.com -H 'Content-Type: application/json' \
      -d '{"jsonrpc":"2.0","method":"condenser_api.get_witness_by_account","params":["your-account"],"id":1}'

    total_missed should stay flat; running_version should match the other witnesses.

  • Short maintenance. Restart right after your node has produced a block, with docker compose restart pixagram, so it is back within seconds.

  • Longer maintenance: a standby node. Run a second synced node with its own signing key. Move to it by running register-witness.js with the standby's public key, do the work, and move back the same way. Never let two nodes sign with the same key at the same time: that can harm the chain, not just you.

  • Upgrades follow Run a Node. Running the new version is how your node signals support for its hardfork.

  • Back up keys, not the chain. config.ini, .env and your key store cannot be downloaded again; the chain can.

Before you announce

  • <input class="task-list-item-checkbox" type="checkbox"> get_witness_by_account shows your signing key and URL, and once you are scheduled, your missed count stays flat.
  • <input class="task-list-item-checkbox" type="checkbox"> Your feed is published, less than an hour old, and its log line names the Big Mac and the market you expect.
  • <input class="task-list-item-checkbox" type="checkbox"> config.ini and .env are chmod 600, backed up, and in no git commit.
  • <input class="task-list-item-checkbox" type="checkbox"> timedatectl reports a synchronized clock, and the offset is under 50 ms.
  • <input class="task-list-item-checkbox" type="checkbox"> Your witness URL says who runs the node, where, and how to reach you.
  • <input class="task-list-item-checkbox" type="checkbox"> Something other than you checks the node every ten minutes.

Then ask for votes: tell stakeholders why you run a witness and what you run besides it. They vote as shown in Witnesses and DPoS. Since hardfork 30 your witness is scheduled only once accounts holding at least 1% of all Pixa Power have voted for it, so registering alone produces no blocks (Witnesses and DPoS).

Sources

  • Repository pixagram-blockchain/witness at commit 704a68f, 2026-10-30: README.md (keys, clock incident, verification, the 1.30.1 upgrade), docker-compose.yml, pixagram/config.ini. Since commit e3b8841, which the first version of this page described, only the image versions, the feed's settings and the README changed.
  • Feed: bigmac-feed v1.3.0 at commit 11f8ad1, 2026-10-30: README and main.go for the settings and log lines, chain.go:280-290 for the line logged after each publish. The dry runs above were made on 2026-11-03 between 08:49 and 08:58 UTC with the program built from that commit, which its README calls v1.3.0, and reached api.pixagram.com, Coinstore and the exchange-rate services read-only. Docker Hub refused anonymous pulls from the test machine that day, so the image itself was not run. The superseded bigmac-feed-agnostic is at commit 96b9d4a.
  • Registration: the witness_update above, signed by dpixa, matched the public node's serialization (condenser_api.get_transaction_hex) on 2026-10-05. Validation of the properties: hive_operations.cpp:312-336 at commit 48f75a2.
  • Log lines: witness_plugin.cpp:342, 427 and 625.
  • Live values, 2026-10-05: condenser_api.get_witnesses_by_vote and condenser_api.get_chain_properties; the number of scheduled witnesses, condenser_api.get_witness_schedule at block 1,722,911 on 2026-11-03.
  • Monitoring: witness-status at commit a376149.
  • The same procedure on the developer site: pixa.org, Run a Pixagram node, node operator guide version 1.0, 2026-09-05.