RustChain Developer Quickstart: First API Calls

April 3, 2026 · View on GitHub

Purpose: Get developers making successful RustChain API calls in under 5 minutes.
Related: Tracks Scottcjn/Rustchain#701 | Bounty: rustchain-bounties#1494


Base URL & Setup

NODE_URL="https://rustchain.org"

⚠️ Self-Signed Certificate: The node uses a self-signed TLS certificate. Always use -k or --insecure with curl.


1. First Read Call: Health Check

Verify the node is running:

curl -k "$NODE_URL/health"

Response:

{
  "ok": true,
  "version": "2.2.1-rip200",
  "uptime_s": 3966,
  "backup_age_hours": 20.74,
  "db_rw": true,
  "tip_age_slots": 0
}

Field Explanations:

FieldTypeDescription
okbooleanNode health status
versionstringNode software version
uptime_sintegerSeconds since last restart
backup_age_hoursfloatHours since last database backup
db_rwbooleanDatabase read/write capability
tip_age_slotsintegerSlots behind chain tip (0 = synced)

2. Check Network Epoch

Get current epoch and network stats:

curl -k "$NODE_URL/epoch"

Response:

{
  "epoch": 96,
  "slot": 13845,
  "blocks_per_epoch": 144,
  "enrolled_miners": 16,
  "epoch_pot": 1.5,
  "total_supply_rtc": 8388608
}

Field Explanations:

FieldTypeDescription
epochintegerCurrent epoch number
slotintegerCurrent slot within epoch
blocks_per_epochintegerTotal slots per epoch
enrolled_minersintegerActive miners in network
epoch_potfloatTotal RTC rewards for this epoch
total_supply_rtcintegerTotal RTC in circulation

3. Balance Lookup

Query a wallet balance with its RustChain address:

curl -k "$NODE_URL/wallet/balance?miner_id=YOUR_RTC_ADDRESS"

A placeholder value also returns the response shape, which is useful for onboarding:

curl -k "$NODE_URL/wallet/balance?miner_id=YOUR_WALLET_ID"

Tested response (2026-03-09):

{
  "amount_i64": 0,
  "amount_rtc": 0.0,
  "miner_id": "YOUR_WALLET_ID"
}

Field Explanations:

FieldTypeDescription
miner_idstringThe wallet address that was queried
amount_i64integerRaw amount in micro-RTC (6 decimal places)
amount_rtcfloatHuman-readable RTC amount

💡 For signed transfers, the server validates from_address / to_address as RTC... addresses with a fixed length. Do not use an ETH / SOL / Base address here.


4. Signed Transfer: Complete Guide

⚠️ Critical: RustChain Addresses vs External Addresses

RustChain transfer addresses are not Ethereum / Solana / Base addresses.

The current server validation expects:

  • from_address starts with RTC
  • to_address starts with RTC
  • both addresses are fixed-length RustChain addresses derived from an Ed25519 public key
ChainAddress FormatExample
RustChainRTC + 40 hex charsRTC0123456789abcdef0123456789abcdef01234567
Ethereum0x + 40 hex chars0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
SolanaBase58, 32-44 chars7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU
BaseSame as Ethereum0x...

In the codebase, RustChain addresses are derived as:

"RTC" + sha256(public_key_hex)[:40]

Transfer Endpoint

POST /wallet/transfer/signed

Required Fields

FieldTypeDescription
from_addressstringSender RustChain address (RTC...)
to_addressstringRecipient RustChain address (RTC...)
amount_rtcnumberAmount to send in RTC
memostringOptional memo; if omitted, the server treats it as an empty string
nonceinteger or numeric stringUnique positive nonce; current examples use a timestamp
public_keystringSender Ed25519 public key as hex
signaturestringEd25519 signature as hex

What Gets Signed

The server does not verify the signature over the outer request body directly. It reconstructs this canonical JSON object and signs/verifies that exact byte sequence:

{
  "amount": 1.0,
  "from": "RTC...",
  "memo": "Payment for services",
  "nonce": "1709942400",
  "to": "RTC..."
}

Canonicalization rules from the server implementation:

  • keys are sorted alphabetically
  • separators are compact: (",", ":")
  • nonce is verified as a string inside the signed message, even if submitted as a number in the request body

Equivalent Python used by the server:

message = json.dumps(tx_data, sort_keys=True, separators=(",", ":")).encode()

Payload Structure Sent to the Endpoint

{
  "from_address": "RTC0123456789abcdef0123456789abcdef01234567",
  "to_address": "RTC89abcdef0123456789abcdef0123456789abcdef",
  "amount_rtc": 1.0,
  "memo": "Payment for services",
  "nonce": 1709942400,
  "public_key": "a1b2c3d4e5f6...",
  "signature": "9f8e7d6c5b4a..."
}

Step-by-Step: Create and Sign Transfer

Step 1: Generate an Ed25519 key pair and derive the RustChain address

import hashlib
from nacl.signing import SigningKey

signing_key = SigningKey.generate()
verify_key = signing_key.verify_key

private_key_hex = signing_key.encode().hex()
public_key_hex = verify_key.encode().hex()
rustchain_address = "RTC" + hashlib.sha256(bytes.fromhex(public_key_hex)).hexdigest()[:40]

print("Address:", rustchain_address)
print("Public key:", public_key_hex)

Step 2: Create the canonical signed message and submit the outer payload

import hashlib
import json
import time
import requests
from nacl.signing import SigningKey

NODE_URL = "https://rustchain.org"
PRIVATE_KEY_HEX = "your_private_key_hex_here"
TO_ADDRESS = "RTC89abcdef0123456789abcdef0123456789abcdef"
AMOUNT_RTC = 1.0
MEMO = "Test transfer"
NONCE = int(time.time())

signing_key = SigningKey(bytes.fromhex(PRIVATE_KEY_HEX))
public_key_hex = signing_key.verify_key.encode().hex()
from_address = "RTC" + hashlib.sha256(bytes.fromhex(public_key_hex)).hexdigest()[:40]

# This exact structure is what the server reconstructs and verifies.
tx_data = {
    "from": from_address,
    "to": TO_ADDRESS,
    "amount": AMOUNT_RTC,
    "memo": MEMO,
    "nonce": str(NONCE),
}

message = json.dumps(tx_data, sort_keys=True, separators=(",", ":")).encode()
signature_hex = signing_key.sign(message).signature.hex()

payload = {
    "from_address": from_address,
    "to_address": TO_ADDRESS,
    "amount_rtc": AMOUNT_RTC,
    "memo": MEMO,
    "nonce": NONCE,
    "public_key": public_key_hex,
    "signature": signature_hex,
}

response = requests.post(
    f"{NODE_URL}/wallet/transfer/signed",
    json=payload,
    verify=False,
    timeout=15,
)

print(response.status_code)
print(response.json())

Complete Bash Example (with openssl)

#!/bin/bash

NODE_URL="https://rustchain.org"
FROM_ADDRESS="RTC1234567890123456789012345678901234567890"
TO_ADDRESS="RTC0987654321098765432109876543210987654321"
AMOUNT=1.0
MEMO="Test transfer"
NONCE=$(date +%s%3N)

# Generate Ed25519 key (one-time setup)
# openssl genpkey -algorithm Ed25519 -out private_key.pem
# openssl pkey -in private_key.pem -pubout -out public_key.pem

# Extract public key
PUBLIC_KEY=$(openssl pkey -in public_key.pem -pubout -outform DER 2>/dev/null | tail -c 32 | xxd -p -c 64)

# Create the canonical message the node verifies.
# The signed bytes use legacy keys {from,to,amount,memo,nonce}
# even though the outer request body uses {from_address,to_address,amount_rtc,...}.
MESSAGE=$(cat <<EOF
{"amount":${AMOUNT},"from":"${FROM_ADDRESS}","memo":"${MEMO}","nonce":"${NONCE}","to":"${TO_ADDRESS}"}
EOF
)

# Sign message
SIGNATURE=$(echo -n "$MESSAGE" | openssl pkeyutl -sign -inkey private_key.pem -rawin | xxd -p -c 128)

# Send transfer
curl -k -X POST "$NODE_URL/wallet/transfer/signed" \
  -H "Content-Type: application/json" \
  -d "{
    \"from_address\": \"${FROM_ADDRESS}\",
    \"to_address\": \"${TO_ADDRESS}\",
    \"amount_rtc\": ${AMOUNT},
    \"memo\": \"${MEMO}\",
    \"nonce\": \"${NONCE}\",
    \"public_key\": \"${PUBLIC_KEY}\",
    \"signature\": \"${SIGNATURE}\"
  }" | jq .

Common Errors

ErrorCauseSolution
invalid_from_address_formatfrom_address is not a valid RTC... addressDerive the address from the Ed25519 public key; do not use 0x... or a nickname
invalid_to_address_formatRecipient is not a valid RTC... addressUse the recipient's RustChain address
missing_required_fieldsMissing one of the required outer payload fieldsInclude from_address, to_address, amount_rtc, nonce, signature, and public_key
Invalid signatureThe server-reconstructed canonical message does not match what you signedSign {from,to,amount,memo,nonce} with sorted keys and compact separators
insufficient_balanceWallet has insufficient RTCCheck balance first via /wallet/balance
REPLAY_DETECTEDNonce already used for that senderUse a fresh nonce for every transfer

Testing Checklist

Before submitting your transfer:

  • Verified node health with /health
  • Checked sender balance with /wallet/balance?miner_id=YOUR_ID
  • Generated valid Ed25519 key pair
  • Public key is 64 hex characters
  • Signature is 128 hex characters
  • Nonce is unique (not reused)
  • Wallet IDs are RustChain format (not ETH/SOL)
  • Using -k flag for self-signed cert

Next Steps


References

  • Product Issue: Scottcjn/Rustchain#701
  • Bounty Issue: Scottcjn/rustchain-bounties#1494
  • Node: https://rustchain.org
  • Tested: 2026-03-09

Last Updated: 2026-03-09
Tested Against: Node v2.2.1-rip200