Comsats
  • Comsats
  • Apps
    • Inscribe
      • Rune
      • BRC-20 (4-byte)
        • Deploy
        • Mint
        • Transfer
      • BRC-20 (5-byte)
      • ORC-20
        • Deploy, Upgradable, Migration
        • Mint
        • Transfer (Updating)
      • Comparing BRC-20 to ORC-20 Features
      • Fee Description
    • Telegram Bot
      • BRC-20 Trending Bot
      • Inscribe Bot
      • BRC-20 Buy Bot
      • Swap Bot πŸ”œ
    • Launchpad
      • PSBT
      • Launchpad πŸ”œ
    • BRC-20 Bridge
    • Inscription Game
    • Runes
      • Viking Runes Collection (β—ˆ)
      • Convert to Runes Protocol
  • More
    • Comsats Point
    • $CSAS Token
    • Roadmap
    • Official Links
  • FAQ
    • BRC-20 Token Minting Instructions
    • BRC-20δ»£εΈι“Έι€ θ―΄ζ˜Ž
Powered by GitBook
On this page
  • Introduction
  • Implementation
  • Issue tokens
  • Mint tokens
  • Transfer tokens
  • Burn tokens
  • Difference with Inscriptions
  1. Apps

Runes

Last updated 1 year ago

Introduction

is a fungible token protocol for Bitcoin.

It allows to issue/mint/transfer/burn tokens in a utxo1 way by client side validation, which means the Bitcoin itself is not aware of these tokens.

The token owner must be some non- tx output, tokens allocated to OP_RETURN output are considered burned.

The overall process is:

In order to spend(transfer/burn) tokens, the user just spends the corresponding tx output, but the spending details are encoded into the structure in a OP_RETURN output.

In order to issue/mint tokens, the user also has to encode the intent into the Runestone structure in a OP_RETURN output, but this time no need to spend any tx output.

A single tx can only host a single RuneStone object, only the matching tx output will be effective.

Implementation

Here's the above mentioned structure:

pub struct Runestone {
  pub edicts: Vec<Edict>,
  pub etching: Option<Etching>,
  pub default_output: Option<u32>,
  pub burn: bool,
}
  1. The etching field is used for issuing tokens.

  2. The edicts field is used for minting/transfering tokens.

  3. The default_output field is used for specifying a default output for "unallocated" tokens.

  4. The burn field is used for burning tokens.

"unallocated" deserves some more explanations here.

Let's go through the implementation details of each operation, more specically: issue/mint/transfer/burn tokens.

Issue tokens

We'll need to make use of the Runestone.etching field above, let's take a closer look at the Etching structure:

pub struct Etching {
  pub divisibility: u8,
  pub mint: Option<Mint>,
  pub rune: Option<Rune>,
  pub spacers: u32,
  pub symbol: Option<char>,
}

pub struct Mint {
  pub deadline: Option<u32>,
  pub limit: Option<u128>,
  pub term: Option<u32>,
}

pub struct Rune(pub u128);
  1. divisibility represents the precision of amount, corresponding to decimals in ERC20.

  2. mint contains 3 optional parameters for minting:

    • deadline configures the deadline timestamp if specified.

    • limit configures the max amount of token each single tx can mint if specified.

    • term configures the termination block if specified.

  3. symbol is an optional single character symbol for this token.

Here's an example to issue an token:

let runestone = Runestone {
    etching: Some(Etching {
    divisibility: 3u8,
    mint: None,
    rune: None,
    spacers:0,
    symbol: Some('$'),
    }),
    ..Default::default()
};
let script_pubkey = runestone.encipher();

ensure!(
    script_pubkey.len() <= 82,
    "runestone greater than maximum OP_RETURN size: {} > 82",
    script_pubkey.len()
);

let unfunded_transaction = Transaction {
    version: 2,
    lock_time: LockTime::ZERO,
    input: Vec::new(),
    output: vec![
        TxOut {
            script_pubkey,
            value: 0,
        },
        TxOut {
            script_pubkey: destination.script_pubkey(),
            value: TARGET_POSTAGE.to_sat(),
        },
    ],
};

let inscriptions = wallet
    .get_inscriptions()?
    .keys()
    .map(|satpoint| satpoint.outpoint)
    .collect::<Vec<OutPoint>>();

if !bitcoin_client.lock_unspent(&inscriptions)? {
    bail!("failed to lock UTXOs");
}

let unsigned_transaction =
    fund_raw_transaction(&bitcoin_client, self.fee_rate, &unfunded_transaction)?;

let signed_transaction = bitcoin_client
    .sign_raw_transaction_with_wallet(&unsigned_transaction, None, None)?
    .hex;

let transaction = bitcoin_client.send_raw_transaction(&signed_transaction)?;

Mint tokens

We'll need to make use of the Runestone.edicts field above, let's take a closer look at the Edict structure:

pub struct Edict {
  pub id: u128,
  pub amount: u128,
  pub output: u128,
}

Basically this structure represents the intention to mint some amount of the id token to some tx output of the minting tx.

Here's an example to mint an existing token:

let runestone = Runestone {
    edicts: vec![Edict {
        amount: 10u128,
        id: issued_id  | CLAIM_BIT,
        // mint it to tx output 1
        output: 1,
    }],
    ..Default::default()
};
let script_pubkey = runestone.encipher();
// The remaining logic is the same as before

Here's an example to both issue and mint at the same time:

let runestone = Runestone {
    etching: Some(Etching {
        divisibility: 3u8,
        mint: None,
        rune: None,
        spacers:0,
        symbol: Some('$'),
    }),
    edicts: vec![Edict {
        amount: 10u128,
        // mint the issuing token
        id: 0,
        // mint it to tx output 1
        output: 1,
    }],
    ..Default::default()
};
let script_pubkey = runestone.encipher();
// The remaining logic is the same as before

Transfer tokens

The transfer function is coerced into Runestone.edicts field.

In order to transfer some token, the user has to spend the corresponding tx output and allocate the tokens with Runestone.edicts.

Unallocated tokens are either allocated to the tx output specified by Runestone.default_output if it's a non OP_RETURN output, or the first non OP_RETURN output if there is no default, or burned otherwise.

Here's an example to transfer tokens from some tx output:

let runestone = Runestone {
    edicts: vec![Edict {
        amount: 10u128,
        id: id_to_transfer,
        // mint it to tx output 1
        output: 1,
    }],
    ..Default::default()
};
let script_pubkey = runestone.encipher();
ensure!(
    script_pubkey.len() <= 82,
    "runestone greater than maximum OP_RETURN size: {} > 82",
    script_pubkey.len()
);

let unfunded_transaction = Transaction {
    version: 2,
    lock_time: LockTime::ZERO,
    input: vec![
        tx_input_to_spend,
    ],
    output: vec![
        TxOut {
            script_pubkey,
            value: 0,
        },
        TxOut {
            script_pubkey: destination.script_pubkey(),
            value: TARGET_POSTAGE.to_sat(),
        },
    ],
};

// The remaining logic is the same as before

Basically it's very similar to mint an existing token, except that one has to spend the tx output by specifying corresponding tx_input_to_spend as tx input.

Burn tokens

In order to burn tokens, one just needs to set Runestone.burn to true and spend the corresponding tx output.

Here's an example to burn tokens from some tx output:

let runestone = Runestone {
    burn: true,
    ..Default::default()
};
let script_pubkey = runestone.encipher();
ensure!(
    script_pubkey.len() <= 82,
    "runestone greater than maximum OP_RETURN size: {} > 82",
    script_pubkey.len()
);

let unfunded_transaction = Transaction {
    version: 2,
    lock_time: LockTime::ZERO,
    input: vec![
        tx_input_to_spend,
    ],
    output: vec![
        TxOut {
            script_pubkey,
            value: 0,
        },
    ],
};

// The remaining logic is the same as before

Basically it's very similar to transfer tokens in that one needs to spend the corresponding tx output, but no need for Runestone.edicts.

Difference with Inscriptions

  1. No dependency for taproot.

  2. Each operation only needs 1 tx on Bitcoin.

  3. Assets are allocated to utxo instead of sat.

The tx input specifies the tokens that the user wants to spend, these tokens are considered unallocated unless allocated by .

rune is an optional id for the token, an internal id will be if it's None.

The generated id won't conflict with user provided id because of .

spacers is the bit positions of "β€’" characters, follow for an example.

Basically one only needs to tweak the runestone object, all other codes are template codes copied from .

The canonical id of the issued token is computed , which uniquely locates the etching tx with block height and tx index within the block.

In order to mint an existing token, one just needs to set Edict.id to "u128::from(id) | CLAIM_BIT", where id is the canonical id of the issued token, refer for details.

In order to both issue and mint a token at the same time, one needs to make sure to specify a valid Runestone.etching and then set Edict.id to 0, refer for details.

Source:

Runes
OP_RETURN
Runestone
first
Runestone
initially
RuneStone.edicts
automatically generated
this check
here
here
here
here
here
https://github.com/zhiqiangxu/btc_notes/blob/main/ordinal_runes.md