Reference
Reference
Vocabulary, how state is actually derived, what happens when the chain reorganises, and what to check before you ship an implementation.
Terminology
- Alkane
- A contract, and by extension the token it issues. Identified by an
AlkaneId. - AlkaneId
- A pair of
u128values writtenblock:tx. Theblockhalf also encodes the id's kind, see ID-3. - Cellpack
- The call payload: a target
AlkaneIdfollowed by inputs, LEB128 encoded.inputs[0]is the opcode by convention. - Cenotaph
- A malformed runestone. Balances on the inputs are burned rather than transferred.
- Edict
- One transfer instruction: id, amount, destination output.
- Fuel
- The metering unit for contract execution. Budgeted per block and shared out per transaction by virtual size.
- Message
- The calldata field of a protostone. Carries the cellpack.
- Metashrew
- The indexing framework alkanes-rs builds on. A WebAssembly indexer program plus a key/value store plus view functions.
- Outpoint
- A transaction output, identified by txid and index. Balances are recorded against outpoints.
- Pointer
- The output index that receives a protostone's result on success. The Runes layer has its own separate pointer, tag 22.
- Protoburn
- A protostone that bridges Runes-layer balances into a sub-protocol. Parsed but not processed in the released indexer.
- Protorune
- The sub-protocol layer over Runes that protostones belong to. Originated outside this organisation.
- Protostone
- One record inside the runestone's tag 16383 field. Names a protocol tag and carries edicts, a message, or both.
- Refund pointer
- The output index that receives balances when a message fails.
- Runestone
- The
OP_RETURNpayload format that Alkanes rides inside. Originated outside this organisation, with the Runes protocol. - Sequence
- A single global counter that assigns ids of the form
2:nto newly deployed contracts. - Trace
- The recorded execution of a protostone message, retrievable per outpoint or per block.
- Virtual output
- The shadow output index a protostone occupies,
N + 1 + ifor a transaction withNreal outputs.
Indexer semantics
Alkanes has no chain and no nodes of its own. State exists only because indexers derive it. alkanes-rs is
compiled to wasm32-unknown-unknown and run inside metashrew.
- Entry point. The indexer program exports
_start. metashrew calls it once per block with the block data as input. - Host interface. The program sees only
__host_len,__load_input,__get,__get_len,__flush, and__log. That is the entire surface, which is why indexing is deterministic. - Storage. Writes are height-indexed and committed through a sparse Merkle tree, so every height has a verifiable state root and historical state stays addressable.
- View functions. Reads run the same WASM program in a read-only mode against the store at a chosen height. This is why a query can be answered "as of block N" rather than only as of the tip.
JSON-RPC surface
| Method | Purpose |
|---|---|
metashrew_view | Call a named view function with input data at a height. |
metashrew_height | The height the indexer has processed to. |
metashrew_getblockhash | The block hash the indexer recorded for a height. |
metashrew_stateroot | The state root at a height, for verifying two indexers agree. |
metashrew_preview | Run a view against supplied block data without persisting anything. |
The alkanes view functions themselves are listed in the guide.
Confirmation
- Alkanes state changes when a block containing the transaction is indexed. There is no separate protocol-level confirmation count, and no notion of a pending alkanes transfer at the protocol layer.
- A transaction that is in the mempool has not changed alkanes state at all. It has no effect until it is mined and indexed.
- Because state is derived, "confirmed" means two things that can differ: the transaction has Bitcoin
confirmations, and an indexer has processed that height. Compare
metashrew_heightagainst the node's tip before trusting a read. - The one confirmation constant in the protocol code belongs to the Runes layer: an etching commitment must
be at least
COMMIT_CONFIRMATIONS= 6 confirmations old. It does not apply to alkanes transfers or calls. - The Bitcoin Universe capability snapshot records no listing or settlement confirmation minimum for alkanes, with the policy stated as: no supported mutation reaches confirmation or settlement.
Reorg behaviour
metashrew detects and repairs reorgs itself; alkanes-rs does not carry reorg logic.
- On each sync step the engine compares local block hashes against the node's, walking back over a
configurable
reorg_check_thresholdwindow. - At the first height where the hashes differ, the store is rolled back to that height.
- Indexing resumes from the height after the common ancestor and replays the new chain.
Rollback removes the indexed data written above the target height, not only the metadata. An earlier version deleted the height-to-hash and state-root keys but left the data written by the indexer program in place, which could leave chain A rows visible while chain B was being indexed. That was fixed with a shared SMT rollback implementation that identifies every key modified after the target height and restores it.
An alkanes balance is exactly as final as the Bitcoin block that produced it. A reorg that removes the block removes the balance change with it, cleanly. What is not automatic is anything an application built on top of that read: the Bitcoin Universe snapshot records automatic reconciliation as false for alkanes, because there is no executable order state to roll back.
Mempool
- alkanes-rs indexes blocks. It has no mempool ingestion path, so unconfirmed transactions do not appear in indexed state.
metashrew_previewaccepts hex block data that may be assembled from mempool transactions, and runs a view against it without persisting. That is the supported way to answer "what would this do", and it is a simulation, not state.- alkanes-rs also exposes
simulateviews that execute a cellpack without broadcasting. Use them to check a call before paying for it. - Chained spends of unconfirmed alkanes outputs are therefore not visible to the index until the parent confirms.
Fee and size considerations
- A transfer protostone is small. The worked single-edict example is a 16-byte script; a
contract call is 17 bytes; a CREATE deployment instruction is 14 bytes.
The
OP_RETURNis almost never what makes an alkanes transaction expensive. - Deployment cost is dominated by the WASM binary in the witness. Witness data is discounted, and alkanes-rs strips the first witness before computing virtual size for fuel purposes, so a large contract does not consume a disproportionate share of the block's fuel budget.
- Each alkane-bearing output must hold enough sats to be economically spendable later. An output at the dust limit can become uneconomic to spend at higher fee rates, which is a slow way to strand a balance.
- Relay policy for large
OP_RETURNoutputs is set by node operators, not by this protocol. If you are pushing an unusually large payload, confirm your relay path rather than assuming it. - Fuel is not paid for in a token. There is no alkanes network token; fees are Bitcoin fees and compute is bounded by the fuel budget.
Limits
| Limit | Value | Where it comes from |
|---|---|---|
| Bytes per script data push | 520 | MAX_SCRIPT_ELEMENT_SIZE |
| Payload bytes per protostone chunk | 15 | The 15-byte rule, to stay inside varint limits |
| Maximum varint length | 18 bytes usable | Longer is a flaw and makes a cenotaph |
| Value range | u128 | Ids, amounts, and cellpack inputs |
| Protostone virtual output ceiling | num_outputs + 100 | Bounds message-bearing protostones per transaction |
| Pointer and refund pointer ceiling | num_outputs + num_protostones | process_message validation |
| Nested call depth | 75 | Checkpoint depth guard |
| Contract memory | 43,554,432 bytes | MEMORY_LIMIT |
| Block fuel, Bitcoin mainnet | 100,000,000, then 1,000,000,000 from height 899,087 | src/vm/fuel.rs |
| Minimum cellpack values | 2 | Target block and tx must both be present |
Security considerations
Payload correctness is the main risk
The failure modes that actually cost people assets are all encoding mistakes, and none of them is visible on chain. A wrong envelope strands the balance; a wrong protocol tag destroys it; a malformed edict body silently does nothing. This is covered in full on the encoding page, and it is worth building payload verification into your signing flow rather than trusting your encoder.
Contracts can mint their own token without limit
The protocol permits a contract to transfer more of its own token than it holds. That is how issuance works, and there is no protocol-level cap. Supply control lives entirely in contract code. Before treating an alkane as scarce, read the contract, not the metadata.
Code is not a guarantee of behaviour
Factory deployments share a template's code but keep their own storage and balances, and upgradeable patterns exist in the standard library. An id that shares bytecode with a contract you trust is not thereby trustworthy. Check the id, the deployment form, and any admin authority the contract exposes.
Determinism is the security property
The sandbox has no network, no filesystem, no clock, and no randomness, and wasmi is a
deterministic interpreter. Any divergence between two indexers running the same version is a bug, and
comparing metashrew_stateroot at a height is the cheapest way to detect one. Do not run a
modified indexer and expect its state to be interoperable.
Denial of service is bounded by fuel, not by goodwill
Fuel budgets, the memory limit, the call depth guard, and the virtual output ceiling all exist to bound what one transaction can cost an indexer. On failure the transaction's remaining fuel is drained rather than returned, which removes the incentive to fail cheaply on purpose.
Reporting
Report a vulnerability in this documentation or in the site itself privately through GitHub security advisories. Issues in the protocol implementation belong in alkanes-rs. Do not open a public issue for a vulnerability.
Implementation checklist
For anyone writing an indexer, a wallet, or an encoder.
Encoding
- Emit
6a 5d, then data pushes only, chunked at 520 bytes. - LEB128 twice, with the 15-byte little-endian chunking in between.
- Record length is a value count, not a byte count.
- Sort and delta-encode edict ids from
0:0. - Keep the edict body a multiple of four.
- Round-trip through your own decoder before signing.
Decoding
- Reject a runestone with a non-push opcode or an over-length varint as a cenotaph.
- Stop the record loop on a protocol tag of
0. - Fail the whole protostone list when a record length exceeds the values remaining.
- Swallow the edict-body error into an empty list, matching the reference implementation exactly.
- Expand tag 16383 values to exactly 15 bytes each, including the last one.
- Ignore unrecognised protostone tags rather than failing.
Indexing
- Assign all input balances to the first record with protocol tag 1, and only then process records in order.
- Run each protostone's message before its edicts, and skip its edicts if the message refunded.
- Clear every input's balance sheet once protostones were present, including for cenotaphs.
- Do not process protoburns; the released indexer does not.
- Honour the activation height, 880,000 on Bitcoin mainnet.
- Skip the blacklisted transaction
5cbb0c466dd08d7af9223d45105fbbf0fdc9fb7cda4831c183d6b0cb5ba60fb0. - Verify against
metashrew_staterootrather than trusting your own output.
Display
- Never assume a decimal scale. Divisibility is asset-defined and the registry records it that way.
- Show the alkane id, not just a name. Names come from contract calls and are not unique.
- Show what a payload will actually do, decoded, before a user signs it.