Alkanes protocol docs

Walkthrough

Guide

Three things you will actually do: move a balance, call a contract, deploy a contract. Each one is the same shape, one OP_RETURN output carrying a protostone, and each is worked through to real bytes you can check.

The mental model

Four ideas cover almost everything.

1

Balances live on outputs

An alkane balance is recorded against a specific transaction output. To move it you must spend that output. If you spend it without a valid instruction, the balance does not follow.

2

The instruction is one output

A single zero-value OP_RETURN starting 6a 5d. Everything the protocol reads comes from it.

3

Everything unassigned has a home

Whatever your edicts do not explicitly place ends up at the protostone's pointer, or the default output when there is no pointer. There is no discard.

4

Contracts are ordinary WASM

A call is a cellpack: a target id, an opcode, and arguments. The contract sees the alkanes you sent it and returns the alkanes it wants to send on.

Transfer a balance

Goal: send 100000000 base units of alkane 2:1 to someone, keeping the change.

Step 1: choose inputs

Spend the outpoint or outpoints that hold the balance. All alkane balances on all inputs are pooled, so you can combine several. Add a plain funding input if you need more sats for the fee.

Step 2: lay out outputs

vout 0   recipient address        546 sats
vout 1   your change address      546 sats
vout 2   OP_RETURN                0 sats     <- the protostone
vout 3   fee change (optional)

The alkane outputs need enough value to be spendable later, so use your wallet's dust threshold rather than the minimum.

Step 3: build the protostone

const { encodedRunestone } = encodeRunestoneProtostone({
  protostones: [
    ProtoStone.edicts({
      protocolTag: 1n,
      edicts: [{
        id: new ProtoruneRuneId(2n, 1n),
        amount: 100000000n,
        output: 0,
      }],
    }),
  ],
});
The script this produces
6a5d0cff7f818a80909080a0e1d75f

That is the output: 0 variant, 15 bytes. The output: 1 variant worked through on the encoding page is 6a5d0dff7f818a80909080a0e1d7df04. Changing one small integer at the end of the protostone changes the tail of the payload and its length, which is a good reminder to verify rather than eyeball.

Step 4: know where the change goes

The edict places exactly 100000000 at output 0. Everything left over goes to the protostone's pointer. This protostone has no pointer, so the default output is used: the first non-OP_RETURN output, which here is output 0 as well. If you want change at output 1, say so:

ProtoStone.edicts({
  protocolTag: 1n,
  edicts: [
    { id: new ProtoruneRuneId(2n, 1n), amount: 100000000n, output: 0 },
    { id: new ProtoruneRuneId(2n, 1n), amount: 0n,          output: 1 },
  ],
})

The second edict has amount 0, which means the entire remaining balance, so it sweeps the change to output 1. Both edicts address the same id, so the delta encoding makes the second pair 0, 0, and the protostone integers become [1, 9, 0, 2, 1, 100000000, 0, 0, 0, 0, 1].

Script hex with the change sweep
6a5d11ff7f819280909080a0e1d7df8080808040

20 bytes.

Step 5: verify before signing

Paste the script hex into the decoder. Confirm the protocol tag is 1, the id is the alkane you meant, the amount is in base units, and the output index is the recipient. Then sign.

Call a contract

Goal: call alkane 2:1 with opcode 77, sending the result to output 0 and refunding to output 1 if it fails.

const cellpack = new Cellpack(2n, 1n, [77n]);

const { encodedRunestone } = encodeRunestoneProtostone({
  protostones: [
    ProtoStone.message({
      protocolTag: 1n,
      calldata: cellpack.serialize(),
      pointer: 0,
      refundPointer: 1,
    }),
  ],
});
Script hex
6a5d0eff7f818cec82d0abc0a88285d215

A real example

This mainnet payload calls the fr-BTC system contract at 32:0 with opcode 77:

6a5d101600ff7f818cec8ad0abc0a8a081d215

It decodes to a Runes-layer pointer of 0, then protostone tag 1 with pointer 1, refund pointer 1, and cellpack [32, 0, 77]. Run it through the decoder to see every field.

Deploy a contract

Deployment is a call to a reserved target. The binary itself does not go in the OP_RETURN; it goes in a witness envelope, the same way an inscription does.

TargetFormResult idBinary source
1:0CREATE2:<next sequence>Witness envelope on the first input
3:NCREATERESERVED4:NWitness envelope on the first input
5:NFactory from sequence template2:<next sequence>Pointer to the template's code
6:NFactory from reserved template2:<next sequence>Pointer to the template's code

A CREATE with the initializer at opcode 0:

const cellpack = new Cellpack(1n, 0n, [0n]);   // target 1:0, opcode 0

const { encodedRunestone } = encodeRunestoneProtostone({
  protostones: [
    ProtoStone.message({
      protocolTag: 1n,
      calldata: cellpack.serialize(),
      pointer: 0,
      refundPointer: 0,
    }),
  ],
});
Script hex
6a5d0bff7f818cec82d08bc0a801

14 bytes. The whole deployment instruction fits in fourteen bytes because the contract itself travels in the witness, which is also why the fuel accounting strips that witness before sizing the transaction.

A factory clone of the reserved template 65517:

const cellpack = new Cellpack(6n, 65517n, [0n]);
Script hex
6a5d0fff7f818cec82d0abc0a886b5ffff01

Deployment is not something to attempt against mainnet first. alkanes-rs ships a test harness that runs your contract against the same code path the mainnet indexer uses, with no funds and no live node. Use it.

Read balances and traces

State is derived, so reading it means asking an indexer, not the chain. alkanes-rs exposes view functions through metashrew. The ones you will use most:

ViewAnswers
protorunes_by_addressEvery outpoint an address controls and the alkane balances on each.
protorunes_by_outpointThe balances on one specific outpoint.
traceThe full execution trace for a transaction's protostone, including revert data.
traceblockEvery trace in a block.
simulateRuns a cellpack without broadcasting, so you can check a call before you pay for it.
getbytecodeThe decompressed WASM behind an alkane id.
getstorageatOne storage key of one contract.
metaThe contract's ABI, if it exports __meta.

When a transaction did not do what you expected, trace is the first place to look. A revert carries a readable message after the four-byte 08 c3 79 a0 prefix.

Support matrix

Only capability that is declared in this organisation's own code is listed as supported. Where something is absent, that is stated rather than implied.

Bitcoin Universe products

From the Core protocol registry and the ecosystem capability snapshot.
SurfaceActionsState
Core, mainview, discover, view collection, view activity, view transactionSupported
Walletview, send, receiveSupported
InscribemintSupported
Marketplace, readsview, view collection, view activityRead-only
Marketplace, mutationslist, update listing, unlist, buy, make offer, accept offer, cancel offer, sell, settle, reconcileNot supported
Why marketplace mutations are off, recorded verbatim

“Alkanes mutations remain read-only until exact Alkane state, transferability, builder, signed-transaction validation, broadcast, settlement, and reorg recovery are deployed and proven.”

The same snapshot records that no executable order authority is deployed, that ownership is read authority only with no complete transferability proof, that no executable settlement authority is deployed, that freshness is not enforced (no node-tip comparison, no maximum observation age), that no supported mutation reaches confirmation or settlement, and that reorg reconciliation is not automatic because there is no executable order state to roll back.

Developer tooling

ToolWhat it coversWhere
@alkanes/ts-sdkProtostone and runestone encoding, cellpacks, provider and wallet helpers, CLI commands for alkanes, protorunes, runestone, and metashrew.ts-sdk/ in alkanes-rs
alkanes-rs test harnessRuns contracts against the same code path as the mainnet indexer, without funds or a live node.alkanes-rs repository
alkanes-runtimeRust SDK for writing alkane contracts.crates/alkanes-runtime
The decoder on this siteChecking a payload before you sign it.tool.html

Third-party wallets and marketplaces are outside this organisation's code and are not listed. Their support is not something this documentation can verify, so it makes no claim either way.

Common mistakes

  1. Writing JSON into an OP_RETURN. Some Bitcoin metaprotocols work that way. Alkanes does not. Without 6a 5d nothing is read, and the balance is stranded on a spent outpoint.
  2. Using amount 0 as a placeholder. It means the entire remaining balance.
  3. Assuming decimals. Divisibility is asset-defined. The protocol moves raw base units and the registry records alkanes decimals as asset-defined, so never hardcode a scale.
  4. Pointing an edict at the OP_RETURN. Check your output indexes against the final output order, not the order you built them in.
  5. Omitting a cellpack argument. Zero padding fills it with 0, silently.
  6. Forgetting the refund pointer. A message without it aborts protostone processing for the transaction.
  7. Adding a second OP_RETURN. Only the first runestone counts, and a stray non-push opcode in it makes a cenotaph, which burns balances.
  8. Testing on mainnet. The regtest harness exists precisely so you do not have to.