Skip to main content

Runes

Advanced
Bitcoin
Tutorial

Overview

Bitcoin Runes are a type of fungible asset deployed on the Bitcoin network. Runes are not reliant on the Ordinals protocol like other Bitcoin asset standards such as BRC-20 and SRC-20. Runes are designed to be an efficient and simple asset that utilizes Bitcoin's UTXO model and the OP_RETURN opcode. Although Runes aren't reliant on Ordinals, the Rune protocol was created and implemented by the same creator, and is part of the same open source project that Ordinals are.

The UTXO transaction model enables each transaction's output to hold information and be treated as digital currency. To initiate a transaction, you must use those outputs as your transaction's input. For Runes specifically, each UTXO can hold a different amount or type of Rune, simplifying the management of tokens on Bitcoin.

The Bitcoin opcode OP_RETURN enables additional information to be attached to a Bitcoin transaction, up to 80 bytes of data. Runes use this OP_RETURN value to store the token's data, such as name, symbol, commands, or ID. This token data is referred to as the 'Runestone'.

Creating a Rune is done through a process called 'etching', which refers to submitting a transaction to the Bitcoin network that specifies the Rune's data in the OP_RETURN output of a transaction. Once a Rune has been etched, it can be minted through open or closed mints. Open minting allows anyone to mint an instance of the Rune through a mint transaction, while closed minting refers to a set of requirements that must be met in order for the Rune to be minted. When a Rune is initially etched, the creator can set aside a portion of the Runes for themselves before others are minted.

Runes on ICP

Canisters deployed on ICP can sign and submit transactions directly to the Bitcoin network through ICP's Bitcoin integration and threshold signatures. Specifically, for a canister to etch Runes, it must sign transactions using threshold Schnorr signatures and have a Bitcoin taproot address.

Etching and minting Runes

To etch a Rune through a canister on ICP, first you will need to call the schnorr_public_key API to obtain a Schnorr public key. This public key will be used to generate a Bitcoin taproot (P2TR) address for your canister. A taproot address is required to sign and submit Rune transactions, both etching and minting, on Bitcoin. Learn more about Bitcoin taproot addresses.

Once you have generated a taproot address for your canister, you will need to write a canister method that creates an etching transaction, then signs that transaction with your canister's Schnorr public key. Here's an example of an etching transaction written in Rust:

#[update]
pub async fn etch_rune(mut args: EtchingArgs) -> (String, String) {
let caller = ic_cdk::id();
args.rune = args.rune.to_ascii_uppercase();
let derivation_path = generate_derivation_path(&caller);
let ecdsa_public_key = get_ecdsa_public_key(derivation_path.clone()).await;
let schnorr_public_key = get_schnorr_public_key(derivation_path.clone()).await;
let caller_p2pkh_address = public_key_to_p2pkh_address(&ecdsa_public_key);
let balance = btc_api::get_balance_of(caller_p2pkh_address.clone()).await;
if balance < 1000_0000 {
ic_cdk::trap("Not enough balance")
}
let utxos_response = btc_api::get_utxos_of(caller_p2pkh_address.clone()).await;
check_etching(utxos_response.tip_height, &args);
let (commit_tx_address, commit_tx, reveal_tx) = build_and_sign_etching_transaction(
&derivation_path,
&utxos_response.utxos,
&ecdsa_public_key,
&schnorr_public_key,
caller_p2pkh_address,
args,
)
.await;
let commit_txid = btc_api::send_bitcoin_transaction(commit_tx).await;
let id = STATE.with_borrow_mut(|state| {
let id = state.queue_count;
state.queue_count += 1;
id
});
let time = STATE.with_borrow(|state| state.timer_for_reveal_txn as u64 * 60);
let timer_id = ic_cdk_timers::set_timer_interval(Duration::from_secs(time), move || {
ic_cdk::spawn(confirm_min_commitment_and_send_reveal_txn(id))
});
let queue_txn = QueuedRevealTxn {
commit_tx_address: commit_tx_address.to_string(),
reveal_txn: reveal_tx.clone(),
timer_id: timer_id.data(),
};
STATE.with_borrow_mut(|state| state.reveal_txn_in_queue.insert(id, queue_txn));
(commit_txid, reveal_tx.txid().encode_hex())
}
   
View the full example.

Querying Rune information

To query information about a Rune, you can specify a query call that returns the Rune information for a given UTXO. Here is an example using the Rust Ordinals crate:

The Rust Ordinals crate is not created or maintained by DFINITY. The ICP management canister does not currently provide endpoints to query Ordinal data.

#[query]
pub fn get_runes_by_utxo(txid: String, vout: u32) -> Result<Vec<RuneBalance>, OrdError> {
  let k = OutPoint::store(OutPoint {
    txid: Txid::from_str(&txid).map_err(|e| OrdError::Params(e.to_string()))?,
    vout,
  });
  let v =
    crate::outpoint_to_rune_balances(|b| b.get(&k).map(|v| v.deref().iter().map(|i| *i).collect()))
      .unwrap_or_default();
  Ok(v)
}
   
View the full example.

Resources