Разработка инфраструктуры для Bitcoin Ordinals
Ordinals — это не смарт-контракты. Когда разработчики приходят с опытом EVM и ожидают стандартный стек (ABI, события, RPC), первые две недели уходят на переосмысление базовых вещей. Bitcoin не эмитит события, у него нет state, нет адресной абстракции в привычном смысле. 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 данные дешевле обычных данных транзакции в ~4x (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 | Disk |
|---|---|---|---|
| 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 попытки, invalid sequences). Для 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-control при трансферах: нужно точно контролировать, какой сатоши (и его номер) попадает в какой 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 и не нагружает node лишними запросами.
Сроки и что входит
| Фаза | Содержание | Срок |
|---|---|---|
| Инфраструктура | Настройка 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 недели.







