DEV Community

Eze
Eze

Posted on

Integrating With RustChain's Node API From Python — Real Requests, Real Outputs

RustChain is a Proof-of-Antiquity blockchain where vintage hardware earns higher mining rewards, and its node exposes a plain HTTPS JSON API you can integrate from any language. This tutorial walks through a complete first integration in Python against the live mainnet node — every output below is captured from an actual run, not mocked.

The one rule before any code

The node lives at https://50.28.86.131. It uses a self-signed TLS certificate, so standard clients will reject it until you configure trust. For local testing you can disable verification; the project's own docs recommend pinning the certificate for production.

First, verify the node is healthy before writing integration code:

import json, ssl, urllib.request

BASE = "https://50.28.86.131"
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE  # testing only — pin the cert in production

def get(path, params=None):
    url = BASE + path
    if params:
        url += "?" + "&".join(f"{k}={urllib.parse.quote(str(v))}" for k, v in params.items())
    req = urllib.request.Request(url, headers={"User-Agent": "my-integration/0.1"})
    with urllib.request.urlopen(req, timeout=20, context=ctx) as r:
        return json.loads(r.read().decode())

print(get("/health"))
Enter fullscreen mode Exit fullscreen mode

Actual output from my run:

{
  "backup_age_hours": 18.18,
  "db_rw": true,
  "ok": true,
  "tip_age_slots": 0,
  "uptime_s": 77012,
  "version": "2.2.1-rip200"
}
Enter fullscreen mode Exit fullscreen mode

db_rw: true and tip_age_slots: 0 tell you the database is writable and the chain tip is fresh — if either is off, stop and retry later rather than building on stale data.

Reading chain state

Two read endpoints cover most integration needs:

print(get("/epoch"))
Enter fullscreen mode Exit fullscreen mode
{
  "blocks_per_epoch": 144,
  "enrolled_miners": 10,
  "epoch": 264,
  "epoch_pot": 1.5,
  "slot": 38022,
  "total_supply_rtc": 8388608
}
Enter fullscreen mode Exit fullscreen mode

And balances, which are keyed by miner ID:

print(get("/wallet/balance", {"miner_id": "Ivan-houzhiwen"}))
Enter fullscreen mode Exit fullscreen mode
{"amount_i64": 45000000, "amount_rtc": 45.0, "miner_id": "Ivan-houzhiwen"}
Enter fullscreen mode Exit fullscreen mode

Note the two amount fields: amount_i64 is the integer internal unit (micro-RTC), amount_rtc is the human-readable value. Always do arithmetic on the integer field and format at the end — floating point balances are how accounting bugs start.

The gotcha that will bite you

Querying a wallet that does not exist does not return an error:

print(get("/wallet/balance", {"miner_id": "definitely-not-a-real-miner-xyz"}))
# HTTP 200 -> {"amount_i64": 0, "amount_rtc": 0.0, "miner_id": "definitely-not-a-real-miner-xyz"}
Enter fullscreen mode Exit fullscreen mode

HTTP 200, zero balance. If your integration treats "zero" as "valid empty account", you cannot distinguish this wallet has nothing from this wallet was never mined / typo'd. Wrap reads in a validation layer that checks the miner ID exists in whatever registry your application trusts before believing a zero.

This class of bug — success status, wrong effect, no error surfaced — is worth auditing for in any API client you write.

Sending transfers safely

Writes go through /wallet/transfer/signed and require an Ed25519 signature over a canonical payload:

transfer = {
    "from_address": "RTCaaaa...",
    "to_address":   "RTCbbbb...",
    "amount_rtc":   1.5,
    "nonce":        12345,
    "memo":         "",
    "public_key":   ed25519_pubkey_hex,
    "signature":    ed25519_sig_hex,
    "chain_id":     "rustchain-mainnet-v2",
}
# POST json=transfer to BASE + "/wallet/transfer/signed"
Enter fullscreen mode Exit fullscreen mode

Three details matter:

  1. Addresses must be RTC... strings — not miner IDs, not EVM/Solana addresses.
  2. Nonces exist for replay protection — never reuse one; track your last used nonce per wallet.
  3. chain_id prevents cross-chain replay — sign exactly rustchain-mainnet-v2 for mainnet.

The signing rules live in the repo's docs/API.md; use their canonical serialization rather than inventing your own JSON layout, or signatures will not verify.

A minimal production-ready client

Putting it together with retries and sane error handling:

class RustChainClient:
    def __init__(self, base=BASE, retries=3):
        self.base, self.retries = base, retries
        self.ctx = ssl.create_default_context()
        self.ctx.check_hostname = False
        self.ctx.verify_mode = ssl.CERT_NONE

    def get(self, path, params=None):
        last = None
        for i in range(self.retries):
            try:
                return get(path, params)
            except Exception as e:
                last = e
                time.sleep(1.5 * (i + 1))
        raise RuntimeError(f"node unreachable after {self.retries} attempts") from last

client = RustChainClient()
health = client.get("/health")
assert health["ok"] and health["db_rw"], "node unhealthy — refusing to proceed"
Enter fullscreen mode Exit fullscreen mode

Fail closed on health checks: an integration that keeps trading on a stale or read-only node is worse than one that stops.

Wrapping up

You now have working, verified code to read chain state, query balances correctly (including the silent-zero trap), and the shape of signed transfers. The full walkthrough lives in RustChain's API_WALKTHROUGH.md, and the explorer UI is at https://50.28.86.131/explorer for cross-checking anything your client reports.

All outputs above were captured from a live run on epoch 264. Nothing here is financial advice.

Top comments (0)