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"))
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"
}
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"))
{
"blocks_per_epoch": 144,
"enrolled_miners": 10,
"epoch": 264,
"epoch_pot": 1.5,
"slot": 38022,
"total_supply_rtc": 8388608
}
And balances, which are keyed by miner ID:
print(get("/wallet/balance", {"miner_id": "Ivan-houzhiwen"}))
{"amount_i64": 45000000, "amount_rtc": 45.0, "miner_id": "Ivan-houzhiwen"}
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"}
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"
Three details matter:
-
Addresses must be
RTC...strings — not miner IDs, not EVM/Solana addresses. - Nonces exist for replay protection — never reuse one; track your last used nonce per wallet.
-
chain_idprevents cross-chain replay — sign exactlyrustchain-mainnet-v2for 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"
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)