[brc-20] The runes module proposal

Abstract

This proposal describes a module for sending brc-20 token balances using a method similar to Runes.

Using the brc-20 module, we have established a simple method that allows users to put their balances within the module to UTXOs and transfer them to multiple recipients in a manner similar to Runes.

Motivation

Runes is an intriguing tiny payment solution that utilizes the limited OP_RETURN script space in transaction outputs to design two operations for token issuance and transfers.

Token protocols designed based on the OP_RETURN generally have significant limitations, but this doesn’t deter us from adopting this interesting approach to send brc-20 tokens within a module.

Runes Mechanism

To utilize the Runes transfer mechanism in brc-20, we first need to create a runes module, where all runes transfer actions can only use brc-20 balances within the module. Issuing new ticks within the module is not allowed.

When users deposit their brc-20 balances into the runes module, they need to attach an output to any address, indicating the binding of the deposited balance to this UTXO. Subsequently, balances will be sent according to the runes transfer rules, which means that when spending UTXOs with deposited balances within the module, the protocol description in OP_RETURN will be used to assign token balances in the output.

Similar to Runes, a UTXO can contain balances of any number of ticks, with each tick’s balance not exceeding the total amount of that tick deposited into the module.

Creating a New Runes Module Instance

{ 
  "p": "brc20-module",
  "op": "deploy",
  "name": "runes",    
  "source": "2b3f8ab85b813916091b4ed753c09df97e2c584eed525155115a92e831c0a289i0", 
  "init": {
  }
}

Runes Transfer

Similar to the transfer defined in Rune, we use an uppercase ‘R’ at the beginning of the OP_RETURN in a transaction, followed by a series of triplets of data (ID, OUTPUT, AMOUNT) representing the assignment of tick balances to outputs. When there are multiple OP_RETURN starting with ‘R’ within a transaction, we simply concatenate the triplets of data from each OP_RETURN in order.

However, there are some differences in the triplet definitions compared to the original Rune definition:

  • ID is defined as the current transaction’s input tick index since the inputs are known and ordered within each transaction, rather than a global tick number.

  • OUTPUT indicating the output number where the balance is assigned.

  • Amount is serialized directly as a string, rather than being represented as a 128-bit integer.

The AMOUNT 0 is shorthand for “all remaining amount”.

After processing all tuple assignments, any unassigned amounts are assigned to the first non-OP_RETURN output, if any.

Excess assignments are ignored.

If an OUTPUT is an OP_RETURN, the corresponding balance is converted into withdrawable module balance and is no longer eligible for Runes-style transfers. However, only after becoming a white module does it support withdrawals.

Source Code for Inscription

contract Runes {
    // input tick amount
    mapping(string => amt) public tickBalancesFromInput;
    // output tick amount
    mapping(string => amt)[] public tickBalancesToOutput;

    function transfer(string[] ticks, varint[][] runes) external {
        Output output = getFirstNonOpReturnOutput();
        for (uint i = 0; i < runes.length; i++) {
            varint[] rune = runes[i];
            require(rune.length == 3, "Each rune should be a 3-tuple");
            varint tickIdx = rune[0];
            varint outputIdx = rune[1];
            amt amount = rune[2];

            require(tickBalancesToOutput.length > outputIdx, "outputIdx too large");

            if(tickIdx >= ticks.length) {
                // Excess assignments are ignored.
                continue;
            }
            string tick = ticks[tickIdx];
            uint256 tickBalance = tickBalancesFromInput[tick];
            if (amount == 0) {
                // The AMOUNT 0 is shorthand for "all remaining amount".
                amount = tickBalance;
            }
            if (tickBalance < amount) {
                // Excess assignments are ignored.
                continue;
            }

            // reduce from input
            tickBalancesFromInput[tick] -= amount;
            // increase output
            tickBalancesToOutput[outputIdx][tick] += amount;

            // Runes may be available for withdrawal by assigning them 
            //   to the OP_RETURN output containing the protocol message.
            if (isOpReturnRuneByOutputIdx(outputIdx)) {
                if (output.idx >= 0) {
                    module[tick].increaseWithdrawal(output.address, amount);
                }
            }
        }

        // After processing all tuple assignments, any unassigned amounts are 
        //   assigned to the first non-OP_RETURN output, if any.
        if (output.idx < 0) {
            return
        }
        for (tick amount in tickBalancesFromInput) {
            if (amount > 0) {
                tickBalancesToOutput[output.idx][tick] += amount;
            }
        }
    }
}

Acknowledgments

Most of our inspiration comes from Casey’s Runes protocol.

2 Likes

maybe we can have a gov token for every module

it is a proved way to get tvl and liquidity