Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Teoría ordinal, el sobre de una inscripción campo a campo, commit y reveal, el runestone byte a byte, etching con commitment, edicts, cenotaph y los errores caros.
Bitcoin no tiene contratos inteligentes. No tiene estado global, no tiene bucles, no tiene almacenamiento persistente por cuenta. Aun así, desde 2023 lleva imágenes, textos, colecciones y tokens fungibles.
Eso no ocurrió por un cambio de consenso. Ocurrió porque dos funciones creadas con otra finalidad — el descuento de peso de SegWit y el script path de Taproot — abrieron, sin querer, espacio barato para datos arbitrarios. Ordinals y Runes son lo que se construyó encima de eso.
Esta guía es la documentación que me habría gustado encontrar: el formato de los bytes, las reglas exactas, los comandos y los errores que cuestan dinero.
Tres conceptos tienen que estar firmes, si no el resto no tiene sentido.
Bitcoin no guarda saldo por dirección. Guarda salidas no gastadas — cada una con un valor en satoshis y una condición de gasto. Una transacción consume salidas enteras y crea otras nuevas.
bitcoin-cli listunspent
# [{ "txid": "abc...", "vout": 0, "amount": 0.00015000, "confirmations": 12 }]
bitcoin-cli gettxout "abc..." 0Como cada salida tiene un origen rastreable, es posible seguir fracciones concretas de moneda a lo largo de la historia. De eso se aprovecha la teoría ordinal.
SegWit (2017) separó la firma del cuerpo de la transacción y creó la unidad de peso (weight unit, WU):
El tamaño virtual (vsize), que es lo que define la comisión, es el peso dividido entre 4. En la práctica: un dato en el witness cuesta una cuarta parte.
bitcoin-cli decoderawtransaction "<hex>" | jq '{size, vsize, weight}'
# size: tamaño total en bytes
# weight: 4 * (bytes del cuerpo) + 1 * (bytes del witness)
# vsize: weight / 4 <- esto es lo que multiplica la comisión en sats/vBTaproot (2021) permite gastar una salida revelando un script guardado en un árbol. Ese script va en el witness — y no tiene límite práctico de tamaño impuesto por el consenso.
Junta las dos cosas: un script Taproot puede llevar un archivo entero, pagando una cuarta parte de la comisión. Ninguna de las dos actualizaciones se propuso con ese objetivo. La consecuencia fue emergente — y es la raíz de toda la discusión que vino después.
La teoría ordinal, creada por Casey Rodarmor, es una convención de numeración. No altera Bitcoin en nada: es una regla que cualquiera puede aplicar sobre la cadena y llegar al mismo resultado.
Regla 1 — asignación. Los satoshis se numeran de 0 a 2.099.999.997.690.000, en el orden en que se minan. Los satoshis del subsidio de un bloque reciben los siguientes números disponibles.
Regla 2 — transferencia. Los satoshis pasan por las transacciones en orden primero en entrar, primero en salir (FIFO). Concatena las entradas en el orden en que aparecen; distribúyelas a las salidas en el orden en que aparecen.
# Entradas: [ A: sats 100-199 ] [ B: sats 500-549 ]
# Salidas: [ X: 120 sats ] [ Y: 30 sats ]
# X recibe: 100-199 (100 sats) + 500-519 (20 sats)
# Y recibe: 520-549 (30 sats)Regla 3 — comisiones. Los satoshis pagados como comisión van al minero y entran en la salida de la coinbase, justo después de los satoshis del subsidio. Por eso gastar un sat inscrito como comisión entrega tu inscripción al minero.
Conviene insistir: nada de esto lo valida la red. Un nodo Bitcoin no sabe qué es un ordinal. Quien lo sabe es el indexador que ejecuta esa regla. Dos implementaciones divergentes producirían saldos divergentes — la convención solo vale porque todos usan la misma.
El mismo satoshi se puede escribir de cinco formas. Todas identifican el mismo número.
| Notación | Ejemplo | Qué expresa |
|---|---|---|
| Entero | 2099994106992659 | El número de serie puro |
| Decimal | 3891094.16797 | bloque.offset — bloque de origen y posición dentro de él |
| Grado | 1°111094′214″16797‴ | Ciclo, época, periodo de dificultad y posición |
| Percentil | 99.99971949060254% | Posición relativa en el supply total |
| Nombre | satoshi | Codificación en base 26 (letras a–z) |
La notación de grado es la que revela la rareza, porque expone los cuatro ciclos de Bitcoin de una vez:
A°B′C″D‴
# A = ciclo (cada 6 halvings, cuando el halving y el ajuste coinciden)
# B = índice del bloque en la época de halving (0 a 209.999)
# C = índice del bloque en el periodo de dificultad (0 a 2.015)
# D = índice del satoshi dentro del bloqueDe ahí sale la escala de rareza:
| Rareza | Condición | Cantidad aproximada |
|---|---|---|
| Common | Cualquier sat que no sea el primero del bloque | ~2,1 cuatrillones |
| Uncommon | El primer sat de cada bloque | ~6,9 millones |
| Rare | El primer sat de cada ajuste de dificultad | ~3.400 |
| Epic | El primer sat de cada época de halving | 32 |
| Legendary | El primer sat de cada ciclo | 5 |
| Mythic | El primer sat del bloque génesis | 1 |
ord find 2099994106992659 # en qué UTXO está este sat
ord list <outpoint> # qué sats hay en este UTXO
ord traits 2099994106992659 # rareza, ciclo, época, notacionesLa rareza es una propiedad de la convención, no del protocolo. Un sat "rare" es indistinguible de cualquier otro para el consenso de Bitcoin — vale lo que el mercado que acepta la convención decida que vale.
Una inscripción adjunta contenido a un satoshi. El contenido va dentro de un sobre en el script Taproot revelado en el gasto:
OP_FALSE
OP_IF
OP_PUSH "ord" # marcador del protocolo
OP_PUSH 0x01 # tag 1 = content-type
OP_PUSH "image/png"
OP_PUSH 0x00 # tag 0 = cuerpo (body)
OP_PUSH <bytes ...> # contenido, en trozos de hasta 520 bytes
OP_PUSH <bytes ...>
OP_ENDIFTres detalles explican por qué esto funciona:
OP_FALSE OP_IF ... OP_ENDIF crea un bloque que nunca se ejecuta. Para el consenso, es un dato inerte — no cuesta validación, no rompe nada.La inscripción se atribuye al primer satoshi de la primera salida de la transacción de reveal — salvo que un puntero diga otra cosa (tag 2, más adelante).
El identificador de una inscripción es el txid del reveal seguido del índice:
6fb976ab49dcec017f1e201e84395983204ae1a7c2abf7ced0a85d692e442799i0
# <txid del reveal> + "i" + <índice de la inscripción en esa transacción>Después del marcador "ord", el sobre lleva pares de tag y valor. Los tags son números:
| Tag | Campo | Para qué sirve |
|---|---|---|
| 0 | body | El contenido en sí (siempre el último) |
| 1 | content-type | Tipo MIME: image/png, text/plain;charset=utf-8, text/html |
| 2 | pointer | En qué sat de la transacción debe caer la inscripción |
| 3 | parent | Id de la inscripción padre (procedencia) |
| 5 | metadata | Metadatos en CBOR |
| 7 | metaprotocol | Nombre de un protocolo que corre por encima (p. ej. BRC-20) |
| 9 | content-encoding | Codificación del cuerpo, como gzip |
| 11 | delegate | Id de otra inscripción que aporta el contenido |
La convención de paridad importa: los tags pares son esenciales — un indexador que no reconozca uno de ellos debe tratar la inscripción como inválida; los tags impares se pueden ignorar con seguridad en las versiones antiguas. Es el mecanismo que permite evolucionar el protocolo sin romper a quien no haya actualizado.
# Contenido comprimido, para que salga más barato
OP_PUSH 0x09 OP_PUSH "gzip"
OP_PUSH 0x01 OP_PUSH "text/html;charset=utf-8"
OP_PUSH 0x00 OP_PUSH <html comprimido>Inscribir exige dos transacciones. Entenderlo evita la mayor parte de los errores operativos.
# 1) COMMIT
# Crea una salida Taproot cuya dirección se compromete con el árbol de scripts,
# y una de las ramas de ese árbol es el sobre con tu contenido.
# En esta etapa NADA del contenido aparece en la cadena.
# 2) REVEAL
# Gasta esa salida por el script path, revelando la rama del sobre.
# Aquí es donde los bytes entran en la blockchain, en el witness.Consecuencias prácticas:
ord wallet inscribe --fee-rate 15 --file arte.png
# {
# "commit": "e2f1...",
# "reveal": "6fb9...",
# "inscriptions": [{ "id": "6fb9...i0", "location": "6fb9...:0:0" }],
# "total_fees": 24310
# }Límite de relay. Por defecto, un nodo solo retransmite transacciones de hasta 400.000 unidades de peso (-maxstandardtxweight). Por encima de eso, la transacción es válida pero no circula por la red: solo entra en un bloque si se entrega directamente a un minero.
Límite de bloque. 4.000.000 WU. Una sola inscripción puede, en teoría, ocupar casi un bloque entero.
La cuenta de la comisión. Como el witness cuesta 1 WU por byte:
# Estimación para un archivo de 100 KB en el witness
# 100.000 bytes * 1 WU = 100.000 WU
# vsize = 100.000 / 4 = 25.000 vB
# a 20 sats/vB -> 25.000 * 20 = 500.000 sats de comisión (solo el reveal)
bitcoin-cli estimatesmartfee 6 | jq '.feerate' # BTC/kvB
bitcoin-cli getmempoolinfo | jq '{size, bytes, mempoolminfee}'Regla aproximada: el mismo archivo en el cuerpo de la transacción costaría cuatro veces más. Es el descuento del witness lo que hace la práctica económicamente viable — y es exactamente por eso que parte de la comunidad considera el descuento un error de diseño.
Reducir el coste, en la práctica:
# 1. Comprime antes (tag 9 = content-encoding)
gzip -9 -c pagina.html > pagina.html.gz
# 2. Prefiere formatos ligeros: SVG y HTML suelen ganarle a PNG
# 3. Usa recursión: referencia bibliotecas ya inscritas en lugar de reinscribirlas
# 4. Inscribe por lotes: una transacción para varias inscripciones
ord wallet batch --fee-rate 8 --batch lote.yamlEl tag 3 apunta a una inscripción padre. Para que la filiación sea válida, la inscripción padre tiene que gastarse en la transacción de reveal — es decir, solo quien controla el padre puede crear un hijo. Así es como una colección demuestra autenticidad sin registro central.
# lote.yaml — colección con procedencia
mode: separate-outputs
parent: 6fb9...i0
inscriptions:
- file: 001.png
- file: 002.png
- file: 003.pngEl tag 11 hace que una inscripción apunte al contenido de otra. Mil elementos que comparten la misma imagen pueden ser mil inscripciones diminutas delegando en una sola — el coste se desploma.
Una inscripción puede buscar otra a través del endpoint /content/<id>. Eso permite inscribir una biblioteca una vez y reutilizarla:
<script src="/content/6fb9...i0"></script>
<img src="/content/a1b2...i0">Otros endpoints de recursión exponen datos de la propia cadena, lo que permite arte generativo que reacciona al estado de Bitcoin:
/r/blockheight # altura actual
/r/blockhash # hash del bloque
/r/blocktime # timestamp
/r/sat/<numero> # inscripciones en ese sat
/r/children/<id> # hijos de una inscripciónEl tag 5 lleva metadatos en CBOR — binario, más compacto que JSON:
# {"nome": "Peça 001", "autor": "Jhonatan", "edicao": 1}
# se convierte en unos pocos bytes de CBOR en el sobre
ord wallet inscribe --fee-rate 10 --file arte.png --json-metadata meta.jsonUn satoshi puede recibir más de una inscripción. Las posteriores se reconocen, pero quedan claramente marcadas como reinscripciones — la primera es la canónica.
En los primeros meses, algunas inscripciones se crearon de formas que la implementación de referencia no había previsto: sobres duplicados en la misma entrada, tags desconocidos, estructuras fuera del estándar.
En lugar de descartarlas, ord pasó a numerarlas con números negativos — las cursed inscriptions. Era una señal de "esto existe, pero no es canónico".
En el bloque 824.544, el llamado jubileo puso fin a esa distinción: a partir de ahí, las formas antes malditas pasaron a recibir numeración positiva normal. Las antiguas mantuvieron sus números negativos como registro histórico.
ord list <outpoint> | jq '.inscriptions'
# número negativo = inscripción anterior al jubileo, creada de forma no canónicaLa lección de ingeniería aquí es buena: en un protocolo basado en convención, cambiar la regla es una decisión social, no técnica. No hubo fork — hubo un acuerdo entre quienes ejecutan el indexador.
# Requisito previo: nodo completo con índice de transacciones
bitcoind -daemon -txindex=1
bitcoin-cli getblockchaininfo | jq '{blocks, initialblockdownload}'
# Indexador (la primera indexación tarda horas y ocupa decenas de GB)
ord --index-sats server --http-port 8080
# Monedero
ord wallet create # genera y muestra la seed — guárdala offline
ord wallet receive # dirección para depositar
ord wallet balance # separa cardinal (gastable) de ordinal
ord wallet outputs # todos los UTXOs
ord wallet inscriptions # lo que tienes inscrito# Inscribir
ord wallet inscribe --fee-rate 12 --file arte.png
ord wallet inscribe --fee-rate 12 --file arte.png --destination bc1p...
ord wallet inscribe --fee-rate 12 --file arte.png --postage 10000sat
# Enviar
ord wallet send --fee-rate 10 bc1p... 6fb9...i0 # por id de la inscripción
ord wallet send --fee-rate 10 bc1p... 2099994106992659 # por número de sat
# Consultar
ord wallet transactions
ord decode --txid <txid> # muestra el sobre decodificadoEl parámetro --postage merece atención: es cuántos satoshis acompañan a la inscripción en la salida. El valor por defecto son 10.000 sats. Valores muy bajos pueden caer por debajo del límite de polvo y bloquear futuras transferencias.
Antes de Runes, crear un token fungible en Bitcoin significaba usar BRC-20: inscripciones con un JSON que decía lo que se quería hacer.
{"p":"brc-20","op":"deploy","tick":"ordi","max":"21000000","lim":"1000"}
{"p":"brc-20","op":"mint","tick":"ordi","amt":"1000"}
{"p":"brc-20","op":"transfer","tick":"ordi","amt":"100"}Funciona, pero con tres defectos serios:
Runes, lanzado por Casey Rodarmor en el bloque 840.000 (el halving de abril de 2024), se diseñó para resolverlo siendo nativo al modelo UTXO: el saldo vive en el propio UTXO, y la instrucción va en una única salida OP_RETURN.
Un runestone es una salida OP_RETURN así:
OP_RETURN
OP_13 # marcador del protocolo Runes (OP_PUSHNUM_13)
<push de datos> # payload
<push de datos> # (si hay más de uno, concaténalos en orden)El payload es una secuencia de enteros LEB128 (varint de tamaño variable), leídos como pares tag → valor:
| Tag | Nombre | Función |
|---|---|---|
| 0 | Body | A partir de aquí vienen los edicts |
| 1 | Divisibility | Decimales (0 a 38) |
| 2 | Flags | Bits que activan etching, terms y turbo |
| 3 | Spacers | Dónde van los separadores visuales del nombre |
| 4 | Rune | El nombre, codificado en base 26 |
| 5 | Symbol | Un carácter Unicode |
| 6 | Premine | Cantidad reservada al creador |
| 8 | Cap | Número máximo de mints |
| 10 | Amount | Cantidad entregada por mint |
| 12 / 14 | HeightStart / HeightEnd | Ventana de mint por altura absoluta |
| 16 / 18 | OffsetStart / OffsetEnd | Ventana de mint relativa al etching |
| 20 | Mint | Id de la rune que se está acuñando |
| 22 | Pointer | Salida que recibe el saldo no asignado |
La misma regla de paridad de las inscripciones vale aquí: un tag par desconocido invalida el runestone (se convierte en cenotaph); un tag impar desconocido se ignora. Es lo que permite añadir funciones en el futuro sin romper los indexadores antiguos.
# Decodificar un runestone de la cadena
ord decode --txid <txid>
bitcoin-cli getrawtransaction <txid> 1 | jq '.vout[] | select(.scriptPubKey.type=="nulldata")'¿Por qué OP_RETURN y no el witness? Porque OP_RETURN es demostrablemente no gastable — los nodos pueden descartar esas salidas del conjunto de UTXOs, sin hinchar la memoria de la red. Cuesta más por byte (está en el cuerpo, sin descuento), y precisamente por eso el formato es binario y escueto.
Etching es el acto de crear la rune. Define el nombre, el símbolo, la divisibilidad, el premine y las reglas de acuñación.
# etching.yaml
mode: separate-outputs
etching:
rune: MINHA•PRIMEIRA•RUNE
divisibility: 2
premine: 1000
symbol: ¤
supply: 21000
terms:
amount: 100
cap: 200
height: [840000, 900000] # ventana absoluta (opcional)
turbo: true
inscriptions:
- file: logo.png # opcional: un "cenotaph visual" de la runeord wallet batch --fee-rate 20 --batch etching.yamlAquí está el detalle que más confunde a quien hace su primer etching: el nombre de la rune tiene que comprometerse antes, y el compromiso necesita al menos 6 confirmaciones antes de la transacción de etching.
El motivo es anti-front-running: sin eso, cualquiera vería tu nombre en el mempool y publicaría la misma rune con una comisión mayor. Con el commitment, quien intente copiarlo tendría que esperar seis bloques — tiempo suficiente para que el original confirme.
# En la práctica, ord lo hace por ti y espera:
# "Waiting for rune commitment ... to mature (6 confirmations)"
# Es alrededor de una hora entre empezar y que la rune exista. No interrumpas el proceso.premine + (amount × cap).# premine 1000 + (100 * 200) = 21.000 unidades
# Cuanto mayor es la porción del premine, más centralizada es la distribución.El premine es el primer número que cualquiera debería mirar antes de tocar una rune. Un premine del 90% significa que el creador tiene casi todo.
El campo Flags (tag 2) es un conjunto de bits:
| Bit | Nombre | Significado |
|---|---|---|
| 0 | Etching | Esta transacción crea una rune |
| 1 | Terms | El etching define reglas de mint abierto |
| 2 | Turbo | La rune adopta automáticamente los futuros cambios del protocolo |
Los nombres de rune usan solo letras mayúsculas de la A a la Z, codificadas como un entero en una base 26 modificada.
MINHA•PRIMEIRA•RUNE
# La identidad real es MINHAPRIMEIRARUNE.
# Los "•" son spacers (tag 3): decoración visual, no forman parte del nombre.
# No existen dos runes con el mismo nombre, aunque tengan spacers distintos.Si todos los nombres hubieran estado disponibles en el lanzamiento, los cortos se habrían tomado en los primeros bloques. El protocolo lo evita liberando los nombres a lo largo del tiempo:
# Longitud mínima según la altura actual, en la práctica:
# 840.000 -> 13 letras
# 857.500 -> 12 letras
# 875.000 -> 11 letras
# ...
# 1.050.000 -> 1 letraLas runes etchadas antes del bloque 840.000 no existen: la primera rune posible es la del propio bloque de lanzamiento.
Cada rune se identifica por el bloque y la posición de la transacción dentro de él:
840000:1 # bloque 840.000, transacción 1
# Es este id el que usan los edicts — no el nombre.Si el etching definió terms, cualquiera puede acuñar hasta alcanzar el cap, dentro de la ventana permitida.
ord wallet mint --fee-rate 15 --rune MINHA•PRIMEIRA•RUNE
# Un mint es un runestone con el tag Mint (20) apuntando al rune id:
# OP_RETURN OP_13 <20> <bloque> <tx>Reglas que conviene conocer:
amount. No existe el mint parcial.height u offset) no es un error de transacción: simplemente no produce nada, y la comisión se pagó igual.cap, los mints dejan de producir. En runes populares eso se convierte en una carrera por bloque, y es lo que suele disparar la comisión de la red en las ventanas de mint.height) usa números de bloque absolutos; la ventana por offset (offset) cuenta desde el bloque del etching.Una transferencia es un edict dentro del runestone: una tripleta (id, cantidad, salida).
ord wallet send --fee-rate 12 bc1p... 500:MINHA•PRIMEIRA•RUNEDos convenciones marcan toda la diferencia:
OP_RETURN". Es el mecanismo de airdrop en una sola transacción.# Transacción con 3 salidas (índices 0, 1, 2) más el OP_RETURN:
# edict (840000:1, 0, 3) -> reparte todo el saldo entre las salidas 0, 1 y 2Lo que sobra sin un edict explícito va a la primera salida que no sea OP_RETURN. El tag Pointer (22) cambia ese destino:
# Sin pointer: el cambio de runes cae en la salida 0
# Con pointer = 2: el cambio cae en la salida 2Olvidarlo es una de las formas más fáciles de mandar saldo a la dirección equivocada — incluida la del destinatario, cuando solo querías mandar el cambio.
Un cenotaph es un runestone mal formado. El nombre no es casual: un cenotafio es una tumba vacía.
Un runestone se convierte en cenotaph cuando:
Y la consecuencia es dura, deliberadamente:
| Situación | Qué ocurre |
|---|---|
| Runes que entran en la transacción | Se queman |
| El runestone contenía un etching | La rune se crea con supply cero y no puede acuñarse |
| El runestone contenía un mint | El mint cuenta contra el cap, pero las unidades se queman |
¿Por qué tan severo? Porque la alternativa — ignorar el error y seguir — abriría espacio a divergencias entre indexadores. Quemar es determinista e igual para todos. El coste de esa elección es que un bug en tu implementación destruye saldo real, sin recurso.
# Antes de firmar cualquier transacción de runes construida a mano:
ord decode --txid <txid> # ord señala el cenotaph
# En caso de duda, prueba en signet primero. Siempre.Como ni Ordinals ni Runes los valida el consenso, todo saldo depende de un indexador. Ejecutar el tuyo es la única forma de no confiar en terceros.
# Requisitos aproximados de ord con índice de sats
# - nodo Bitcoin completo con -txindex=1 (~700 GB y creciendo)
# - decenas de GB adicionales para el índice de ord
# - un SSD es prácticamente obligatorio; la indexación inicial lleva horas
ord --index-sats --index-runes server --http-port 8080
curl -s localhost:8080/status | jqEndpoints útiles de la API local:
curl -s localhost:8080/inscription/<id> -H "Accept: application/json"
curl -s localhost:8080/output/<txid>:<vout> -H "Accept: application/json"
curl -s localhost:8080/rune/MINHA•PRIMEIRA•RUNE -H "Accept: application/json"
curl -s localhost:8080/sat/2099994106992659 -H "Accept: application/json"Existen APIs de terceros que te ahorran el trabajo de indexar. Son cómodas y legítimas, pero fíjate en lo que estás aceptando: su respuesta es la fuente de verdad de tu aplicación. Para mostrar, está bien; para decidir sobre la custodia de valor, ejecuta tu propio índice.
El riesgo aquí rara vez es criptográfico — es operativo.
# 1. Separa los monederos. Los sats inscritos y las runes NUNCA en el monedero del día a día.
ord wallet balance # cardinal = gastable | ordinal = protegido
# 2. Bloquea los UTXOs que llevan valor
bitcoin-cli lockunspent false '[{"txid":"<txid>","vout":0}]'
bitcoin-cli listlockunspent
# 3. Comprueba el destino antes de enviar
ord wallet send --dry-run --fee-rate 10 bc1p... 6fb9...i0Reglas que evitan casi todas las pérdidas:
bitcoind -signet -daemon
ord --signet --index-sats server
ord --signet wallet inscribe --fee-rate 1 --file teste.txtOrdinals y Runes son, técnicamente, un caso de estudio notable: dos protocolos completos construidos sin alterar una línea del consenso, aprovechando funciones que existían con otra finalidad.
Eso trae una característica que debe quedar clara antes que nada: Bitcoin no conoce a ninguno de los dos. No existe validación de saldo, no existe un mensaje de error amigable, no existe reversión. Existe una convención que vale mientras los indexadores estén de acuerdo — y un conjunto de reglas implacables cuando te equivocas en la codificación.
Para quien programa, el valor de estudiar esto va más allá del asunto: es un ejemplo raro de protocolo bien documentado, con decisiones de diseño explícitas (por qué OP_RETURN y no el witness, por qué quemar en lugar de ignorar, por qué la paridad de los tags) y consecuencias verificables en la cadena. Se puede leer el código, reproducir cada byte en signet y comprobarlo.
Y, si vas a tocar mainnet: signet primero, monedero separado siempre, y nada de valor sin hardware wallet.