Bitcoin inscription service development
Ordinals and inscriptions changed the understanding of what can be stored in Bitcoin. Technically they are not smart contracts and not tokens in EVM sense — they are data embedded in the witness part of SegWit transactions and tied to specific satoshis through Ordinals numbering protocol. If you want a service for minting, transferring or displaying inscriptions — this is a separate infrastructure task not solvable with standard Bitcoin libraries.
How inscriptions work at protocol level
An inscription is created through commit-reveal pattern from two transactions:
Commit TX: creates P2TR (Pay-to-Taproot, BIP-341) output, the script of which contains inscription data in envelope — special format inside Tapscript:
OP_FALSE
OP_IF
OP_PUSH "ord" # protocol marker
OP_PUSH 1 # field: content-type
OP_PUSH "image/png"
OP_PUSH 0 # field: content
OP_PUSH <data_chunk_1>
OP_PUSH <data_chunk_2> # data in 520-byte chunks
...
OP_ENDIF
<pubkey> OP_CHECKSIG
Data resides inside OP_IF...OP_ENDIF branch which never executes — this makes them witness data, not executable code. Witness data limit: ~400KB per transaction (after Taproot lifting witness size restrictions).
Reveal TX: spends commit output, revealing the script and embedding inscription data into blockchain.
Inscription is tied to the first satoshi of the first input of reveal transaction according to Ordinals ordinal theory rules.
Service architecture
Components
Bitcoin node — required, can't do without it. Bitcoind with txindex=1 for transaction lookup by hash. For working with Ordinals you also need ord indexer (https://github.com/ordinals/ord) — separate process that synchronizes its SQLite database with node state. Alternative: Hiro Ordinals API or OrdinalsBot API — if you don't want to maintain your own infrastructure.
Wallet management. Each user gets Bitcoin address (P2TR — taproot, starts with bc1p). Libraries: bitcoinjs-lib (JavaScript/TypeScript), rust-bitcoin (Rust), python-bitcoinlib. For HD wallets — BIP-32/84/86 derivation.
Fee estimation. Bitcoin fees are volatile. Service should use current fee rates: estimatesmartfee through RPC or external APIs (mempool.space API, Blockstream API). For inscriptions with large data, fee is main cost: at 50 sat/vByte and 200KB inscription — ~0.1 BTC.
Inscription creation flow
import * as bitcoin from 'bitcoinjs-lib';
import { ECPairFactory } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
const ECPair = ECPairFactory(ecc);
async function createInscription(
content: Buffer,
contentType: string,
feeRate: number, // sat/vByte
recipientAddress: string,
): Promise<{ commitTx: string; revealTx: string }> {
const keypair = ECPair.makeRandom({ network: bitcoin.networks.bitcoin });
// Build envelope script
const envelope = bitcoin.script.compile([
bitcoin.opcodes.OP_FALSE,
bitcoin.opcodes.OP_IF,
Buffer.from('ord'),
bitcoin.opcodes.OP_1,
Buffer.from(contentType),
bitcoin.opcodes.OP_0,
// data split into 520-byte chunks
...chunkData(content, 520),
bitcoin.opcodes.OP_ENDIF,
keypair.publicKey.slice(1), // x-only pubkey for Taproot
bitcoin.opcodes.OP_CHECKSIG,
]);
// Create P2TR output with inscription in tapscript
const scriptTree = { output: envelope };
const p2tr = bitcoin.payments.p2tr({
internalPubkey: keypair.publicKey.slice(1),
scriptTree,
network: bitcoin.networks.bitcoin,
});
// Commit TX sends funds to p2tr.address
// Reveal TX spends this output, revealing script
// ...
}
PSBT and signing
For user wallets (Unisat, Xverse, OKX Wallet) transactions are built as PSBT (Partially Signed Bitcoin Transaction, BIP-174) and passed to wallet for signing:
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
witnessUtxo: {
script: p2tr.output!,
value: utxo.value,
},
tapLeafScript: [{
leafVersion: 0xc0,
script: envelope,
controlBlock: p2tr.witness![p2tr.witness!.length - 1],
}],
});
psbt.addOutput({
address: recipientAddress,
value: 546, // dust limit
});
// Serialization for wallet transmission
const psbtBase64 = psbt.toBase64();
Working with existing inscriptions
Indexing and search
Ordinals ord indexer provides JSON API (ord server):
# Inscriptions by address
GET /inscriptions/{address}
# Specific inscription
GET /inscription/{inscription_id}
# Sat with number
GET /sat/{sat_number}
For production under high load: custom indexer on top of ord database (SQLite or PostgreSQL migration) + Redis caching.
Inscription transfer
Transfer is a normal Bitcoin transaction that spends UTXO containing inscription. Critically important: don't mix regular UTXOs with inscription-bearing UTXOs when forming transaction inputs — otherwise inscription will be destroyed (burned). This is called inscription burning and happens if inscribed sat lands in position where ordinal theory considers it spent.
Safety rule: always check UTXO through ord API before using as input.
Formats and limitations
Size: theoretically up to ~4MB (block limit), practically up to ~380KB for reliable block inclusion.
Content types: arbitrary MIME-type. Standard: image/png, image/webp, text/plain;charset=utf-8, application/json, text/html;charset=utf-8 (recursive inscriptions).
Recursive inscriptions: inscription can reference another through /content/{inscription_id}. Used for on-chain generative art.
BRC-20: JSON inscriptions with special format ({"p":"brc-20","op":"deploy",...}). Not validated by node — only by indexers. When developing BRC-20 integration you need to understand that rule consensus is off-chain.
Stack and timeline
| Component | Technology |
|---|---|
| Node | Bitcoin Core 26+ with txindex=1 |
| Ordinals indexer | ord 0.18+ |
| Backend | TypeScript/Node.js + bitcoinjs-lib |
| Wallet integration | Unisat API / Xverse Provider API |
| Fee estimation | mempool.space API |
| Database | PostgreSQL (metadata) + Redis (cache) |
Basic service (mint + transfer + view collection): 3-4 weeks. BRC-20 support, recursive inscriptions, marketplace functionality — separate estimate.







