I was following https://bitcointalk.org/index.php?topic=453086.0 for creating a message in OP_RETURN. I am able to decode my raw transaction, but on signrawtransaction it gives

error: {"code":-22,"message":"TX decode failed"}

I validated the address, is there something that is missing in that tutorial.

  • Best way would be to post the resulting tx. Is the data you're embedding less than 40 bytes?
    – Matthieu
    May 9, 2014 at 18:09
  • Thanks Matthieu, you were right about the size. But I still had to do more steps. So was looking for a complete Step by Step.
    – wizgot
    May 10, 2014 at 4:31
  • I got this message when I'd forgotten to replace the original script length with the new one for my OP_RETURN hex string.
    – Echelon
    May 22, 2015 at 11:28

3 Answers 3


I wrote a little demo program which puts a snippet of data into an OP_RETURN script. It requires a bitcoin instance that accepts RPC connections, though it could be implemented without that. You can find it on github here. It's been tested, but only on testnet. I'm going to go through the code and explain what it's doing.



This makes logging more verbose. It's handy because it shows what RPC calls are being made.

Connect to bitcoind

rpc_user = "bitcoinrpc"
rpc_password = "87Y9A2gs25E9HDPGc9axqSqzxMR2MyTtrMkYc5KiZk2Z"

rpc = AuthServiceProxy("http://%s:%[email protected]:18332/" % (rpc_user, rpc_password))

Note that your password will be different, and that you use port 8332 for mainnet instead of port 18332.

List unspent outputs

first_unspent = rpc.listunspent()[0]
txid = first_unspent['txid']
vout = first_unspent['vout']
input_amount = first_unspent['amount']
SATOSHI = Decimal("0.00000001")
change_amount = input_amount - Decimal("0.005") - SATOSHI

The - Decimal("0.005") part is so that we pay a transaction fee.

Create transaction

# Marker address we're going to replace
# Produces a pattern that's easy to search for
mainnet = 0
if mainnet:
    dummy_address = "1111111111111111111114oLvT2"
    dummy_address = "mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8"

These are two different encodings of a Pay to Public Key Hash of all zeros. The top one is the mainnet representation, and the bottom is the testnet representation.

# My change address
change_address = "mhZuYnuMCZLjZKeDMnY48xsR5qkjq7bAr9"

Remember, this is my change address. If you don't change it, you'll be sending money to me.

tx = rpc.createrawtransaction([{"txid": txid, "vout": vout}], \
                              {change_address: change_amount, \
                               dummy_address: SATOSHI})

And now we have an actual transaction. It doesn't contain any of our own data though, so we'll have to fix that.

Replace dummy output with our own output

# Pattern to replace
# Represents length of script, then OP_DUP OP_HASH160,
# then length of hash, then 20 bytes of zeros, OP_EQUALVERIFY OP_CHECKSIG
oldScriptPubKey = "1976a914000000000000000000000000000000000000000088ac"

This is a bit of a hack. Instead of creating our own output, we create a dummy output, then search for the pattern it makes and replace it. There's probably a better way to do this, but this seems easiest.

# Data to insert
data = "Melons."
if len(data) > 75:
    raise Exception("Can't contain this much data-use OP_PUSHDATA1")

newScriptPubKey = "6a" + hexlify(chr(len(data))) + hexlify(data)

Next we create the data we want to put into the blockchain. I'm using the string Melons., but you could use anything. (Above 40 bytes is nonstandard, though.) I covered most of this in my other answer.

This code will break if the data is longer than 75 bytes. If you need it for more than that, you could use OP_PUSHDATA1 instead of the single-byte pushdata that I'm using here.

#Append int of length to start
newScriptPubKey = hexlify(chr(len(unhexlify(newScriptPubKey)))) + newScriptPubKey

This part is a little different than my other answer, because we need to include the length of the scriptPubKey also. This code will break with data longer than 251 bytes. If you want it to work on data longer than that, encode a varint correctly.

if oldScriptPubKey not in tx:
    raise Exception("Something broke!")

Error checking for this very rickety method.

tx = tx.replace(oldScriptPubKey, newScriptPubKey)

Finally, a string replace swaps the new script for the old one.

Sign it

tx = rpc.signrawtransaction(tx)['hex']

Bitcoin handles the heaving lifting here.

Broadcast it to the network.


Done! Now just wait for your transaction to get into a block.

I ran the code (output), and I produced a transaction, which you can see in Block Explorer here. If you copy the string next to OP_RETURN, and paste it into a Hex to ASCII converter, you get...



Other resources

I found this webpage helpful while writing this.

  • Nice, as it's an OP_RETURN scriptsig (If I understands correctly), does that mean the tx will be removed / invalidated by the network?
    – Valmond
    Mar 13, 2015 at 13:28
  • 1
    @Valmond scriptsig scriptPubKey, actually. does that mean the tx will be removed / invalidated by the network? Not... really. It will be removed from the unspent transaction output set in some clients, but many clients will still remember it. (They need to, because they need to prove to new clients that it's just an OP_RETURN transaction.)
    – Nick ODell
    Mar 13, 2015 at 14:24
  • 1
    Note, the transaction you produced would not be a standard transaction on mainnet because the op_return output has a value > 0. It gets relayed/mined-by-default on testnet because testnet accepts almost all transactions as standard. Mar 17, 2015 at 22:31
  • 1
    @DavidA.Harding I did that because createrawtransaction wouldn't accept a value of 0. Can you provide a source to that claim? I can't find any check like that in Solver or IsStandard.
    – Nick ODell
    Mar 18, 2015 at 1:17
  • 1
    I got a few questions. 1. How did you calculate the dummy addresses with such precision? 2. If I enter a real address as dummy (getnewaddress) and then txhex = createrawtransaction. Then I get the script hex by decodedtx = decoderawtransaction txhex and oldScriptHex = decodedtx.scriptPubKey.hex. Then txhex.replace(oldScriptHex, newScriptHex) and this approach doesn't decode anymore (code -22; TX decode failed). why? newScriptHex is identical in your version and mine.
    – krivar
    Mar 29, 2018 at 10:35

You can make life easy by using one of our OP_RETURN libraries:

The code will also show you exactly how it's done.



Academic paper describing the different methods of data insertion for Bitcoin's blockchain.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.