Normative
Alkanes specification
Each rule below states behaviour that alkanes-rs 2.2.1-rc.4 implements, with the source file that implements it. Where the reference implementation and the upstream prose disagree, this page follows the code, because the code is what determines the indexed state.
Scope and conformance
This document specifies how a conforming indexer derives alkanes state from Bitcoin blocks. It does not specify wallet behaviour, RPC surfaces, or contract semantics beyond the protocol boundary.
The words must, must not, and may describe required behaviour of a conforming indexer. An implementation that disagrees with any rule here will fork away from the reference index, which in a derived-state protocol means it is simply wrong.
Alkanes is a sub-protocol of protorunes, which is an extension of Runes. Runes and protorunes originated outside Bitcoin Universe. Rules tagged ENV and parts of EDT describe behaviour inherited from those layers as alkanes-rs implements it.
ENV. The carrier
- ENV-1Alkanes data is carried in a Bitcoin transaction output whose
scriptPubKeybegins withOP_RETURN(0x6a) immediately followed byOP_PUSHNUM_13(0x5d). alkanes-rs matches this as the little-endianu16constant0x5d6a. - ENV-2An output that does not match ENV-1 is not a runestone. An indexer must not attempt to read protocol data out of it, and must not treat any other payload format, including JSON, as an alkanes instruction.
- ENV-3If several outputs match ENV-1, the first in output order is the runestone.
- ENV-4Everything after
OP_PUSHNUM_13must be data pushes. Push payloads are concatenated in order to form the runestone payload. Any non-push opcode makes the runestone a cenotaph. - ENV-5A single push carries at most 520 bytes (
MAX_SCRIPT_ELEMENT_SIZE). Longer payloads must be split across consecutive pushes. - ENV-6The payload is a sequence of LEB128 varints. A varint longer than 18 bytes, or one whose final byte sets disallowed bits, is a flaw and makes the runestone a cenotaph.
- ENV-7Varints are read as tag and value pairs until tag
0(Body) appears, after which all remaining values are Runes-layer edicts. Protostone data is carried in fields with tag16383, repeated as needed. - ENV-8The concatenated tag
16383values are expanded to the protostone byte stream by writing each value as 16 little-endian bytes and discarding the 16th, so every value contributes exactly 15 bytes. The inverse operation, used when encoding, reads 15 bytes at a time little-endian.
Source: crates/ordinals/src/runestone.rs,
crates/ordinals/src/varint.rs, crates/ordinals/src/runestone/tag.rs,
crates/protorune-support/src/protostone.rs.
PST. Protostone records
- PST-1The protostone byte stream is LEB128-decoded into a flat integer list and read as consecutive records of the form
[protocol_tag, length, value × length]. - PST-2A
protocol_tagof0terminates the record list. This absorbs the zero padding produced by ENV-8. - PST-3If
lengthexceeds the number of remaining values, decoding of the entire protostone list fails. - PST-4Alkanes is protocol tag
1. Records with any other tag are decoded but are not alkanes records. - PST-5Within a record, values are tag and value pairs until tag
0(Body), after which all remaining values are edict data. Recognised tags are Message81, Burn83, ProtoPointer91, Refund93, and From95. Unrecognised tags are consumed as pairs and ignored. - PST-6Each protostone occupies a virtual output. For a transaction with
Nreal outputs, the protostone at indexi(zero based) occupies virtual outputN + 1 + i. Edicts may target these indexes. - PST-7All alkane balances carried by the transaction's inputs are assigned to the virtual output of the first record whose
protocol_tagis1. If no record carries tag1, no such assignment happens. - PST-8Protoburns (Burn
83and From95) are parsed but not processed by the released indexer. The call toprocess_burnsis compiled only undercfg(test), with a source comment deferring activation to a future block. An implementation must not process protoburns on mainnet today.
Source: crates/protorune-support/src/protostone.rs,
crates/protorune/src/lib.rs (index_protostones).
ID. Identifiers
- ID-1An alkane is identified by an
AlkaneId, a pair ofu128values writtenblock:txand serialised as 32 bytes, two little-endianu128s. - ID-2An id with
block == 0andtx > 0is not constructible and must be rejected. - ID-3The
blockvalue carries meaning:block Meaning tx 0Null, non-contract caller 01CREATE: deploy the binary in the transaction witness 02Sequentially numbered contract sequence number 3CREATERESERVED: deploy to reserved id reserved number 4Reserved-number contract reserved number 5Factory clone of a sequence-numbered template template sequence 6Factory clone of a reserved-number template template reserved number 32System precompiled contract 0fr-BTC,1fr-Sigil800000000Virtual precompile 0block header,1coinbase tx,2diesel mint count,3total miner fee - ID-4Sequence numbers are allocated from a single global counter at
/alkanes/sequence, incremented once per successful CREATE or factory deployment.
Source: crates/alkanes-support/src/id.rs, src/vm/utils.rs.
EDT. Edicts
- EDT-1The edict body is a flat run of values read in groups of four:
block_delta, tx_delta, amount, output. - EDT-2Ids are delta encoded from
0:0. The next id isprevious.block + block_delta, and itstxisprevious.tx + tx_deltawhenblock_deltais0, otherwisetx_deltataken absolutely. - EDT-3A body whose length is not a multiple of four produces the error edict values did not appear in sets of four. The reference implementation discards this error and substitutes an empty edict list, so the protostone remains valid and the transfers do not happen. A conforming indexer must reproduce this, including the silence.
- EDT-4
amountis a rawu128in the alkane's own base units. The protocol layer does not apply divisibility. - EDT-5An
amountof0transfers the entire remaining balance of that alkane at that point in edict processing. - EDT-6An
outputequal to the number of transaction outputs spreads the amount across all non-OP_RETURNoutputs. Values above that address protostone virtual outputs per PST-6. - EDT-7Edicts are applied in encoded order, each drawing from the running unallocated balance. An edict for more than the remaining balance transfers what remains.
- EDT-8Within a protostone, the message is processed before the edicts. Edicts then operate on the balance sitting at the protostone's pointer output. If the message failed and refunded, the edicts of that protostone are skipped entirely.
- EDT-9Balance arithmetic is checked. Overflow on addition is an error that rolls the enclosing scope back.
Source: crates/protorune-support/src/protostone.rs,
crates/protorune/src/lib.rs (process_edicts,
handle_transfer_runes_to_vout).
MSG. Messages and cellpacks
- MSG-1A record whose Message field (
81) is non-empty is a message. Its chunks are expanded per ENV-8 into the calldata byte string. - MSG-2Calldata is LEB128-decoded into a
u128list. The first two values aretarget.blockandtarget.tx; the remainder are the inputs. By conventioninputs[0]is the opcode the contract dispatches on. - MSG-3A calldata list of fewer than two values is rejected before execution. This guard is explicit in the reference implementation because the alternative was a panic that would halt indexing network wide.
- MSG-4Because ENV-8 always emits whole 15-byte chunks, calldata arrives zero-padded and the padding decodes to additional zero-valued inputs. Encoders must pass every argument explicitly.
- MSG-5A message record must carry both ProtoPointer (
91) and Refund (93). A missing one is the error Missing pointer or Missing refund pointer, which aborts protostone processing for the transaction rather than refunding. - MSG-6Both pointers must be no greater than
num_outputs + num_protostones. Otherwise the error is Invalid output pointer. - MSG-7The protostone's virtual output must be strictly less than
num_outputs + 100. This bounds the number of message-bearing protostones in one transaction. - MSG-8A message is only executed when its record's
protocol_tagequals the indexer's protocol tag. For alkanes that is1.
Source: crates/protorune/src/protostone.rs
(process_message), crates/alkanes-support/src/cellpack.rs,
src/message.rs.
DEP. Deployment
- DEP-1A cellpack whose target is
1:0is a CREATE. The WASM binary is read from the envelope in the transaction's first input witness, stored gzip-compressed at/alkanes/<id>, and the contract is assigned the id2:<next_sequence>. - DEP-2A cellpack whose target has
block == 3is a CREATERESERVED. The contract is deployed at4:<tx>. It fails if a binary already exists at that id. - DEP-3A cellpack whose target has
block == 5orblock == 6is a factory deployment. The new contract receives2:<next_sequence>and stores a 32-byte pointer to the template's id instead of a copy of the binary. It shares the template's code but has its own storage and balances. - DEP-4Deployment maps the new id to its creation outpoint at
/alkanes_id_to_outpoint/<id>. - DEP-5For fuel accounting, transactions containing a deployment cellpack have their first witness stripped before virtual size is computed, so a large binary does not consume a disproportionate share of the block's fuel.
Source: src/vm/utils.rs (run_special_cellpacks),
src/vm/fuel.rs.
EXE. Execution
- EXE-1Contracts execute in
wasmi, a deterministic interpreter, with fuel metering enabled. Given the same binary, fuel, and context, execution always produces the same result. - EXE-2A contract must export
__execute. It may export__metafor ABI description. It reads its context through host functions and returns a pointer to a response buffer. - EXE-3The sandbox provides no network, no filesystem, and no randomness. The only interface is the fixed set of host functions in the
envnamespace. - EXE-4Memory is bounded by
MEMORY_LIMIT= 43,554,432 bytes. Allocation beyond it traps. - EXE-5Nested contract calls are limited to a checkpoint depth of 75.
- EXE-6Three call forms exist.
__callsetscallerto the current contract andmyselfto the target, committing state on success.__delegatecallleavescallerandmyselfunchanged.__staticcallalways rolls back state changes. - EXE-7A nested call that reverts returns a negative value to its caller and does not abort it. The caller may inspect the result and continue.
- EXE-8Revert data begins with the four bytes
08 c3 79 a0followed by a UTF-8 error message. - EXE-9Each block has a total fuel budget, split across transactions in proportion to virtual size with a per-transaction floor. Unused fuel returns to the block pool. On failure all fuel allocated to the transaction is drained rather than returned.
| Network | Initial block fuel | After change height | Change height |
|---|---|---|---|
| Bitcoin mainnet | 100,000,000 | 1,000,000,000 | 899,087 |
| Regtest | 100,000,000 | 1,000,000,000 | 0 |
Source: src/vm/instance.rs, src/vm/extcall.rs,
src/vm/host_functions.rs, src/vm/fuel.rs, src/vm/constants.rs.
BAL. Balances
- BAL-1Balances are held per outpoint, namespaced by protocol tag, at
/runes/proto/1/byoutpoint/<outpoint>. Contract-held balances live at/alkanes/<what>/balances/<who>. - BAL-2Incoming balances are credited to the target contract before execution and debited after, according to the transfers the contract returns.
- BAL-3A contract may transfer more of a token than it holds only when that token is the contract itself. This is how an alkane mints its own supply. Any other shortfall is a balance underflow error. Supply control is the contract author's responsibility, not the protocol's.
- BAL-4After a successful message the indexer reconciles: the virtual output's sheet is removed, outgoing transfers are added to the pointer output, and runtime balances are stored at virtual index
u32::MAX. - BAL-5When protostones are present, every transaction input's stored balance sheet is cleared at the end of processing. The source comment is explicit that all inputs must be used up, even in cenotaphs.
Source: src/utils.rs,
crates/protorune/src/balance_sheet.rs, crates/protorune/src/lib.rs.
ST. State transitions
- ST-1All state changes for one message are wrapped in a checkpoint. On failure the checkpoint is rolled back and the in-memory balance map is restored from a snapshot taken before the message, so the two stores unwind together.
- ST-2On message failure, balances are directed to the refund pointer and the protostone's edicts are skipped.
- ST-3Blocks are processed in order. Each block's fuel tank is initialised fresh.
- ST-4One transaction is hardcoded as blacklisted and skipped entirely:
5cbb0c466dd08d7af9223d45105fbbf0fdc9fb7cda4831c183d6b0cb5ba60fb0.
INV. Invalid conditions
These are the outcomes an implementer most often gets wrong. None of them produce a visible failure on chain.
| Condition | Outcome | Effect on alkane balances |
|---|---|---|
No output matches 6a 5d | No runestone; protostone processing never runs | Untouched in the index, so stranded at the now-spent outpoint |
| Runestone present, no tag 16383 field | No protostones decoded | Untouched, stranded |
| Protostone present but no record has tag 1 | Protostone processing runs; nothing claims the balances | Cleared from the inputs, so destroyed |
| Record length exceeds remaining values | less values than expected; whole protostone list rejected | Untouched, stranded |
| Edict body not a multiple of four | Empty edict list, no error surfaced | Follow the default assignment, not the intent |
| Message without pointer or refund pointer | Missing pointer; protostone processing aborts for the transaction | Untouched, stranded |
Pointer above num_outputs + num_protostones | Invalid output pointer | Untouched, stranded |
| Cellpack shorter than two varints | Rejected before execution; protostone skipped and refunded | Refunded to the refund pointer |
| Contract traps, reverts, or runs out of fuel | Rollback, revert trace saved, remaining transaction fuel drained | Refunded to the refund pointer |
| Non-push opcode or oversized varint in the payload | Cenotaph | Cleared from the inputs, so destroyed |
ACT. Activation
- ACT-1Alkanes is inactive below the network's genesis height. On Bitcoin mainnet that height is 880,000. On regtest it is
0. - ACT-2Below the genesis height the indexer still processes the Runes layer, which activates earlier, but performs no alkanes setup or execution.
- ACT-3Three system contracts are bootstrapped: the genesis alkane at
2:0, fr-BTC at32:0, and fr-Sigil at32:1. - ACT-4The genesis alkane is upgraded at fixed mainnet heights 908,888 and 917,888. Fuel accounting changes at mainnet height 899,087.
Source: src/network.rs, src/indexer.rs,
src/vm/fuel.rs.