On-chain opreturn markers for protocols and smart contracts

When a smart contract has a parameters to be initialized, different instances of the contract will all be at unique addresses. Because of the p2sh standard the specific parameter and template will no longer be visible on-chain. P2sh is really working against developers here to some extent as it’s currently not possible to opt-out of the contract obfuscation it provides.

This creates 3 related issues:

  1. server requirement: the smart contract application needs to store contract params on a server when there are different unique instances so the contract can be found
  2. not interoperable: unique instances of p2sh smart contract utxos are not interoperable/findable across different front-ends
  3. no backups: there’s no standardized way to back-up smart contract params + their script logic, so the contract params/full script can be ‘forgotten’ for p2sh

Using opreturn this can be solved in a nice way as smart contracts can post an identifier for their fixed contract logic and then the values for the unique contract params.

I recently created a project called ‘opreturn scanner’ which can scan the BCH blockchain for these markers. It also includes a list of actively used on-chain markers.

Most markers use 4 bytes fixed encoding (according to the lokad-id spec) but other protocols like Cauldron use custom identifiers. So that is a point of consideration.

Related to opreturn marker discussion @2qx remarked:

The protocol op_return strings of these early dapps (hodl) were influenced by the api available in python.

Sometimes the numeric values are strings. Sometimes data is encoded as text.

In the past, there has been conflict with multiple projects using the same identifier but not coordinating or implementing it correctly.

This adds two interesting points

  1. anyone can (mis)use your marker for unrelated purposes so this data should always be carefully checked to be in accordance with the protocol/smart contract context
  2. encoding of data arguments is not always straightforward when there is room for mistakes

additionally, @Jonathan_Silverblood remarked that the 220 bytes limit for opretruns are too small to contain all the anyhedge params, even if they wanted to employ this method as Anyhedge has the following params:

contract AnyHedge_v0_12(
    //        Mutual redemption
    pubkey    shortMutualRedeemPublicKey,  // 33 B
    pubkey    longMutualRedeemPublicKey,   // 33 B
    int       enableMutualRedemption,      // 1 B

    //        Arbitrary output lock scripts for Short and Long.
    bytes     shortLockScript,  // 26 B for p2pkh, depends on script type
    bytes     longLockScript,   // 26 B for p2pkh, depends on script type

    //        Oracle
    pubkey    oraclePublicKey,  // 33 B, verifies message from oracle

    //        Money
    //        Note: All int types below must be minimally encoded.
    int       nominalUnitsXSatsPerBch,               // 1~8 B, nominal hedge value in Units, scaled by 1e8(sats/BCH)
    int       satsForNominalUnitsAtHighLiquidation,  // 1~8 B, cost sats for the nominal hedge at high liquidation
    int       payoutSats,                            // 1~8 B, total payout sats, miner fee not included
    int       lowLiquidationPrice,                   // 1~4 B, clamps price data to ensure valid payouts
    int       highLiquidationPrice,                  // 1~4 B, clamps price data to ensure valid payouts

    //        Time
    int       startTimestamp,     // 4 B, earliest redemption timestamp under liquidation conditions
    int       maturityTimestamp,  // 4 B, required redemption timestamp under maturity conditions
) {

which makes it harder to standardize on this method if it does not work for advanced contracts.

To address this, an increased maximum opreturn size could be considered.

1 Like

Some of the Electron Cash dapps used a to_ui_string() and str() methods to generate their protocol strings.

The licho and hodl plugin style serializer and parsers were converting bytecode hex to ascii in the record protocol. While it does make it easy to copy the address on a block explorer, there is no op_bin2ascii nor op_ascii2bin.

So, if the checksum of the contract (hash, locking bytecode, address) gets converted to ascii, it means that record can’t be written as a requirement within a contract itself.


Before it was possible to store state in NFT commitments, it was possible to force a contract to announce it’s changing state via protocol identifier.

For example, the mining covenant in Unspent Phi v1 required each winner of a successful nonce to announce the next version of the covenant of the contract.

Because anyone can write anything to an op_return and op_returns can’t be spent, it’s unlikely that a contract will ever need to parse an op_return.

HOWEVER, data in NFT commitments can be parsed, which is another good reason to avoid converting bytecode script to ascii.


In addition to the four byte identifier, a sub-protocol selector and versioning system are really useful in addition to the arguments and hash/locking bytecode/checksum.