Skip to content

Examples

clothic edited this page Aug 17, 2024 · 21 revisions

The examples below constitute meaningful coverage over the scope of protorunes and its interactions with native runes. The final example on this page demonstrates a complete example where we compose protocol messages into one transaction script, constructed for a familiar AMM use case. The compactness of the final OP_RETURN buffers we construct is a proof on-paper that runes extensibility CAN work and it CAN enable a DeFi-like experience on Bitcoin, when we simply define constraints and leverage recursion of leb128 message structures on OP_RETURN.

protoburn

For inputs containing 500 UNCOMMON•GOODS, let's protoburn 100 of them to subprotocol ID 0x4e20, and we want it to be spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2. Outputs should be split to OP_RETURN on vout 0, 100 UNCOMMON•GOODS (subprotocol 0x4e20) on vout 1, and 400 UNCOMMON•GOODS runes refunded to vout 2.

Inputs:

  • 500 UNCOMMON•GOODS
  • Fees

Outputs:

  1. OP_RETURN Runestone output with 2 edicts and a Protostone
  2. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats
  3. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats

OP_RETURN output:

[ OP_RETURN, OP_13, Runestone bytes as data push]

Protostone layout:

{
  [Burn (83)]: u128(0x4e20),
  [Pointer (91)]: u128(2)
}

Protocol tag leb128[]:

(Protocol ID -> number of leb128s for Protostone -> leb128[length])

[ u128(13), u128(4), u128(83), u128(0x4e20), u128(91), uint128(2) ]

Protocol field u128(0x0d0453a09c015b02)

Runestone layout:

{
  [Edict (0)]: [{
    Height: 0,
    Txindex: 1,
    Amount: 100,
    Output: 0
  }],
  [Pointer (22)]: 0x01,
  [Protocol (16383)]: u128(0x0d0453a09c015b02)
}

Encoded:

0x1601ff7f82b685e089f494820d0001006400 (18 bytes) Full OP_RETURN output:

0x6a5d121601ff7f82b685e089f494820d0001006400

protomessage

In this example, we will use a hypothetical AMM with protocol ID 0x4e20 to trade 100 units of UNCOMMON•GOODS for a minOut of 150000 RUN•OF•THE•MILL•GOODS (RuneId 1000000:1) and for this transaction our AMM runtime interprets a Message bytearray of [ UTF-8('S') . leb128[3] (output RuneId . output minOut) ] or 0x535741508094ebdc03f09309.

We'll say our inputs already hold a balance of UNCOMMON•GOODS as protorunes and the Runestone thus only holds a Protocol field with the Protostone representing the transaction. Our outputs simply represent a success output which is the target of Protostone Pointer and a failure output which is the target of Protostone Refund.

We are transacting from bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 and outputs will be spendable by this address as well.

Inputs:

  • 100 UNCOMMON•GOODS (subprotocol 0x4e20)
  • Fees

Outputs:

  1. OP_RETURN Runestone output with 1 Protostone
  2. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats
  3. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats

OP_RETURN output:

[ OP_RETURN, OP_13, Runestone bytes as data push]

Protostone layout:

{
  [Message (81)]: u8[] = 0x535741508094ebdc03f09309,
  [Pointer (91)]: u128(1),
  [Refund (93)]: u128(2)
}

Protocol field leb128[]:

(Protocol ID -> number of leb128s for Protostone -> leb128[length]) (Message field u128s as well as Protocol field u128s have a max size of 15 bytes and are concatenated once decoded)

[ u128(0x4e20), u128(6), u128(81), u128(0x535741508094ebdc03f09309), u128(91), uint128(1), u128(93), u128(2) ]

Protocol field u8[] = 0xa09c01065189a6c29fc0fbbaca80a185bab50a5b015d02

Chunks are taken in sets of 15 bytes as so:

Message u128[] = [ u128(0xa09c01065189a6c29fc0fbbaca80a1, u128(0x85bab50a5b015d02) ]

Runestone layout:

{
  [Protocol (16383)]: [ u128(0xa09c01065189a6c29fc0fbbaca80a1, u128(0x85bab50a5b015d02) ]
}

Encoded:

0xff7fa181aad6bb9ff0cfc2cda68ce5a080cea001ff7f82ba85d8a5a1addd8501 (32 bytes)

Full OP_RETURN output:

0x6a5d20ff7fa181aad6bb9ff0cfc2cda68ce5a080cea001ff7f82ba85d8a5a1addd8501

Protoburn into a protomessage example

Inputs:

  • 100 UNCOMMON•GOODS (subprotocol 0x4e20)
  • Fees

Outputs:

  1. OP_RETURN Runestone output with 2 Protostones (first protostone is a protoburn, second is the protomessage)
  2. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats
  3. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats

protomessage

Note: these are described by the protomessage tests

Transaction Scripts on our AMM

In the following example, we will perform the following actions:

  1. protoburn 100 UNCOMMON•GOODS
  2. protoburn 100 RUN•OF•THE•MILL•GOODS
  3. swap 100 RUN•OF•THE•MILL•GOODS to UNCOMMON•GOODS
  4. pair 100 RUN•OF•THE•MILL•GOODS and swap output of UNCOMMON•GOODS to mint LP token

We will say UNCOMMON•GOODS is RuneId 2000000:1 and the Message bytearray to mint LP is [ UTF-8('M') . leb128 minOut) ]

The Message bytearray to swap is still [ UTF-8('S') . leb128[3] (output RuneId . output minOut) ]

The subprotocol ID is 0x4e20 for our AMM, and we want outputs to be spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2. Outputs should be split to OP_RETURN on vout 0, surplus runes above what we want to pay into our trade should end up on vout 1, LP token should be output to vout 2 on success, result of LP mint failure should end up on vout 3, and result of swap failure should be output to vout 4.

Example Inputs:

  • 1000 UNCOMMON•GOODS
  • 1000 RUN•OF•THE•MILL•GOODS
  • Fees

Outputs:

  1. OP_RETURN Runestone output with Pointer field set, 3 Edicts (one is a 0 transfer to skip a protoburn), and 4 Protostones in Protocol field
  • Protostones encode in the following order i. protoburn for RUN•OF•THE•MILL•GOODS directly to protomessage for SWAP (S) ii. protoburn for UNCOMMON•GOODS directly to protomessage for MINT (M) iii. protomessage for MINT (M) (shadow vout 7) iv. protomessage for SWAP (S) (shadow vout 8)
  • Order of edicts is such that SWAP (S) message is evaluated first, then MINT (M) is evaluated with result of SWAP (S) as well as parent protoburn
  1. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats (Runestone Pointer)
  2. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats (LP protorunes on success)
  3. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats (protomessage mint failure refund)
  4. p2tr output spendable by bc1pkgdwl8qlcxxc06ezsqans2x379t7drqh8u95k8wax8wr6ww5d4ssxu03q2 with value of 546 sats (protomessage swap failure refund)

OP_RETURN output:

[ OP_RETURN, OP_13, Runestone bytes as data push]

Protostone 0 layout, target of RUN•OF•THE•MILL•GOODS edict:

{
  [Burn (83)]: u128(0x4e20),
  [Pointer (91)]: u128(8)
}

Protostone 1 layout, target of UNCOMMON•GOODS edict:

{
  [Burn (83)]: u128(0x4e20),
  [Pointer (91)]: u128(7)
}

Protostone 2 layout (SWAP):

We use a hypothetical minOut of 99 EVERYDAY•CARRY•GOODS output from and construct a Message bytearray of [ UTF-8('S') . leb128[3] (output RuneId . output minOut) ] or 0x5357415080a8d6b9070163.

{
  [Message (81)]: u8[] = 0x5357415080a8d6b9070163,
  [Pointer (91)]: u128(7),
  [Refund (93)]: u128(3)
}

Message for Protostone 3 we will construct with an example minOut of 100 LP tokens, encoded as leb128 and appended to UTF-8 "M"

This gives us:

0x4d494e5410

Protostone 3 layout (MINT):

{
  [Message (81)]: u8[] = 0x4d494e5410,
  [Pointer (91)]: u128(2),
  [Refund (93)]: u128(3)
}

Recall that the format for the preimage for the bytearray stored in the Runestone Protocol field is:

Protocol ID -> number of leb128s for Protostone -> leb128[length]

This list is consumed in entirety.

The leb128[] representation of our Protocol field for a Runestone then, is:

[ u128(13), u128(4), u128(83), u128(0x4e20), u128(91), u128(7), u128(13), u128(4), u128(83), u128(0x4e20), u128(91), u128(8), u128(0x4e20), u128(6), u128(81), u128(0x5380897a019901), u128(91), u128(7), u128(93), u128(3), u128(0x4e20), u128(6), u128(81), u128(0x4d64), u128(91), u128(2), u128(93), u128(3) ]

As an array of leb128s, the bytearray in hexdecimal is:

0x0d0453a09c015b070d0453a09c015b08a09c01065181b286d09791e0295b075d03a09c010651e49a015b025d03

Runestone layout:

{
  [Edict (0)]: [{
    Height: 0,
    Txindex: 1,
    Amount: 0,
    Output: 0
  }, {
    Height: 0,
    Txindex: 1,
    Amount: 100,
    Output: 0
  }, {
    Height: 1000000,
    Txindex: 1,
    Amount: 100,
    Output: 0
  }], 
  [Pointer (22)]: 0x01,
  [Protocol (16383)]: [ u128(0x0d0453a09c015b070d0453a09c015b), u128(0x08a09c01065181b286d09791e0295b), u128(0x075d03a09c010651e49a015b025d03) ]
}

Encoded:

0xc0843d0164001601ff7fdb82f084ba8ac18687b685e089f494820dff7fdbd2808ff992b4c3b283c6b29080a7d008ff7f83ba89d895c0a6f2d18c84e089f4c0ae070001000000006400 (73 bytes) Full OP_RETURN output:

0x6a5d49c0843d0164001601ff7fdb82f084ba8ac18687b685e089f494820dff7fdbd2808ff992b4c3b283c6b29080a7d008ff7f83ba89d895c0a6f2d18c84e089f4c0ae070001000000006400