Розробка інфраструктури для Bitcoin Ordinals
Ordinals — це не смарт-контракти. Коли розробники приходять з EVM досвідом та очікують стандартний стек (ABI, événements, RPC), перші два тижні йдуть на переосмислення базових речей. Bitcoin не емітує события, у нього немає state, немає address абстракції у звичному сенсі. Ordinals працюють поверх witness даних у SegWit транзакціях — це принципово інша інфраструктурна задача.
Як Ordinals та Inscriptions технічно влаштовані
Ordinal theory присвоює кожному satoshi порядковий номер на основі порядку майнингу. Номер сатоші детермінований — він обчислюється з номера блоку та позиції в coinbase транзакції. Передача ordinals — це передача конкретного сатоші у транзакції з правильним порядком inputs/outputs.
Inscriptions — довільні дані, записані в witness поле транзакції через envelope паттерн:
OP_FALSE
OP_IF
OP_PUSH "ord" // маркер
OP_PUSH 1 // tag: content-type
OP_PUSH "image/png" // MIME тип
OP_PUSH 0 // tag: content
OP_PUSH <data_chunk1> // дані (до 520 байт на чанк)
OP_PUSH <data_chunk2> // продовження
...
OP_ENDIF
OP_FALSE OP_IF створює гілку, яка ніколи не виконується, але дані записуються у witness. Після Taproot (BIP 341) witness дані дешевші за звичайні дані транзакції в ~4 рази (discount factor). Саме це зробило Ordinals економічно доцільними.
Commit-reveal схема: Inscription створюється в дві транзакції:
- Commit tx — містить P2TR output з commitment до скрипту з inscription
- Reveal tx — витрачає цей output, розкриваючи скрипт з даними inscription
Це захищає від front-running: до reveal транзакції вміст inscription невідомий.
Настройка node інфраструктури
Bitcoin Core + ord індексер
Мінімальний production стек:
Bitcoin Core (повна нода, pruned НЕ підходить) → ord індексер → PostgreSQL/RocksDB → API
Bitcoin Core вимагає архівний режим (unpruned) — Ordinals потребують доступ до witness даних усіх історичних транзакцій. Розмір на початок 2025 року: ~650GB та росте. SSD обов'язковий, HDD неприпустимий для production.
# bitcoin.conf
txindex=1 # індекс усіх транзакцій за hash
server=1 # RPC
rpcuser=rpc
rpcpassword=strong_password
rpcallowip=127.0.0.1
zmqpubrawblock=tcp://127.0.0.1:28332
zmqpubrawtx=tcp://127.0.0.1:28333
ord — референцева реалізація індексера від Casey Rodarmor:
# Первинна синхронізація (займає 12-48 годин)
ord --bitcoin-data-dir /data/bitcoin \
--data-dir /data/ord \
index update
# Запуск server
ord --bitcoin-data-dir /data/bitcoin \
--data-dir /data/ord \
server --http-port 8080
ord надає REST API: /inscription/{id}, /sat/{sat_number}, /output/{outpoint}. Для production — за nginx з кешуванням.
Вимоги до серверу
| Компонент | CPU | RAM | Диск |
|---|---|---|---|
| Bitcoin Core (mainnet) | 4+ cores | 8GB | 700GB+ NVMe SSD |
| ord індексер | 8+ cores | 16GB | 100GB+ NVMe SSD |
| Всього | 12 cores | 24GB | 800GB+ |
Первинна синхронізація Bitcoin Core — 1–3 дні. ord індекс поверх — ще 12–48 годин. Оновлення блоками — real-time (секунди після підтвердження).
Розробка кастомних індексерів
ord сервер покриває базові запити, але для складних продуктів (маркетплейс, аналітика коллекцій, parent-child inscriptions) потрібен кастомний індексер.
Парсинг транзакцій через Bitcoin RPC
from bitcoinrpc.authproxy import AuthServiceProxy
import json
rpc = AuthServiceProxy("http://rpc:[email protected]:8332")
def parse_inscription_from_tx(txid: str) -> dict | None:
"""Витягує inscription з reveal транзакції"""
raw = rpc.getrawtransaction(txid, True)
for vin in raw.get("vin", []):
witness = vin.get("txinwitness", [])
for item in witness:
script_bytes = bytes.fromhex(item)
inscription = try_parse_inscription_script(script_bytes)
if inscription:
return inscription
return None
def try_parse_inscription_script(script: bytes) -> dict | None:
"""Парсит ord envelope з witness script"""
# Шукаємо маркер: OP_FALSE(0x00) OP_IF(0x63) ... "ord" ...
try:
idx = script.index(b"\x00\x63") # OP_FALSE OP_IF
except ValueError:
return None
# Парсимо content-type та content
# ... (повний парсер envelope ~100 рядків)
pass
Parent-child Inscriptions (рекурсивні)
З версії ord 0.6+ підтримуються parent inscriptions — NFT колекції з провенансом. Дочірня inscription посилається на parent через pointer у envelope. Для маркетплейсу колекцій потрібно індексувати ці зв'язки:
CREATE TABLE inscriptions (
id TEXT PRIMARY KEY, -- inscription id (txid + i)
sat BIGINT NOT NULL, -- ordinal number
content_type TEXT,
content_length INTEGER,
block_height INTEGER NOT NULL,
parent_id TEXT REFERENCES inscriptions(id),
created_at TIMESTAMP NOT NULL
);
CREATE INDEX ON inscriptions(parent_id); -- для швидкої виборки колекції
CREATE INDEX ON inscriptions(sat);
BRC-20 та Runes: токени поверх Ordinals
BRC-20
BRC-20 використовує JSON-вміст у text/plain inscriptions як операції (deploy, mint, transfer). Немає смарт-контракту — індексер має сам інтерпретувати послідовність операцій:
// Deploy
{"p":"brc-20","op":"deploy","tick":"ordi","max":"21000000","lim":"1000"}
// Mint
{"p":"brc-20","op":"mint","tick":"ordi","amt":"1000"}
// Transfer (двохшаговий: inscribe transfer → send)
{"p":"brc-20","op":"transfer","tick":"ordi","amt":"500"}
Критичний момент: балансу повністю визначаються індексером. Немає on-chain state — баланс це результат застосування усіх операцій до початкового стану. Різні індексери можуть давати різні результати при edge cases (double-spend спроби, невалідні послідовності). Для production — суворе слідування специфікації l1brc20 indexer.
Runes (Casey Rodarmor, квітень 2024)
Runes — офіційний стандарт токенів від автора Ordinals. На відміну від BRC-20, Runes зберігають state у UTXO: кожен UTXO носить баланс конкретного Rune. Це ближче до Bitcoin UTXO моделі, менше навантаження на індексер.
OP_RETURN OP_13 <encoded_runestone>
Runestone — CBOR-подібна структура у OP_RETURN output. Містить: Etching (деплой), Mint (мінтинг), Edicts (трансфери). ord індексер підтримує Runes нативно з версії 0.17.
Кастодіальні операції: робота з UTXO
Для маркетплейсу або платформи, яка управляє користувацькими inscriptions, потрібна PSBT (Partially Signed Bitcoin Transactions) схема:
# PSBT логіка для продажу inscription
# Покупець та продавець підписують свої частини незалежно
# Продавець підписує: input (inscription UTXO) + output (ціна в BTC)
seller_psbt = create_psbt_seller(
inscription_outpoint=outpoint,
price_sats=price_sats,
seller_payment_address=seller_addr
)
# Покупець додає: input (BTC) + output (inscription йому)
final_psbt = buyer_sign_and_complete(
seller_psbt,
buyer_address=buyer_addr,
funding_utxos=buyer_utxos
)
# Broadcast
rpc.sendrawtransaction(final_psbt.extract_transaction().serialize().hex())
PSBT (BIP 174) — стандарт для багатосторонніх транзакцій. Для маркетплейсу: продавець листингує inscription з PSBT-підписом на продажу, покупець додає свої inputs та фіналізує.
Sat-контроль при трансферах: потрібно точно контролювати, який сатоші (та його номер) потрапляє в який output. Порядок inputs та outputs це визначає. Бібліотека ord містить логіку sat-tracking, яку можна переиспользовать.
Мониторинг та алерти
ZMQ від Bitcoin Core — правильний спосіб отримувати real-time сповіщення про нові блоки та транзакції:
import zmq, asyncio
async def watch_new_blocks():
ctx = zmq.asyncio.Context()
sock = ctx.socket(zmq.SUB)
sock.connect("tcp://127.0.0.1:28332")
sock.setsockopt_string(zmq.SUBSCRIBE, "rawblock")
while True:
topic, body, seq = await sock.recv_multipart()
block_hash = body[:32].hex()
await process_new_block(block_hash)
Це швидше за RPC polling та не перевантажує ноду лишніми запитами.
Часові рамки та обсяг
| Фаза | Зміст | Тривалість |
|---|---|---|
| Інфраструктура | Настройка Bitcoin Core + ord, server, мониторинг | 3–5 днів |
| Кастомний індексер | Парсинг inscriptions, BRC-20/Runes, PostgreSQL схема | 1–2 тижні |
| API шар | REST API для фронтенду/партнерів, кешування | 1 тиждень |
| Маркетплейс механіка | PSBT листинг/покупка, кастодіальні операції | 2–3 тижні |
| Тестування | Testnet (signet), edge cases, нагрузочне тестування | 1 тиждень |
Повна інфраструктура для маркетплейсу Ordinals: 5–8 тижнів. Просто індексер + API для існуючого продукту: 2–3 тижні.







