Developer Guide

The code examples listed below show you how to make use of the most common features of Liquid. Each example uses the command line and the Liquid Client application (elements-cli) to issue commands to either the Liquid daemon (elementsd) or Liquid Core GUI (elements-qt), depending on which you are running.

The example code can be modified to work in other languages like Python, C#, Ruby, Go, Perl, and Java by following the steps in the App Examples section and issuing the appropriate RPC commands to Liquid using your chosen language instead of via elements-cli.

It is also worth noting that any examples on the Elements website will also work on Liquid, as Liquid is built using the Elements codebase. The Elements website also includes instructions on how to run all the basic and more advanced code examples listed here (and more) from within one file, that you can copy and paste and run from the terminal.


Basic Commands

The Liquid Client (elements-cli) application allows you to issue Remote Procedure Call (RPC) commands to the Liquid Daemon (elementsd) or Liquid Core GUI (elements-qt) from the terminal.

A full list of all the commands you can issue over RPC can be be seen by running:

elements-cli help

To see further information on how to use a certain command you can append the name of the command like this:

elements-cli help sendtoaddress

This returns examples of how to use the command, a list of arguments that can be passed to it and and the format of the results returned.

If you run the above code you will see that the sendtoaddress accepts 2 mandatory arguments and has 8 additional optional arguments.

For example, to use the sendtoaddress command to send an amount of 1 L-BTC and subtract the fee from the amount being sent we would run:

elements-cli sendtoaddress AzppUfWkYpjiThupf2t6Kn1yPTgseg3buBexuMSbZxpPWAsQcucHXFpr4HHGFQAiyBiddvcjAYyoVeMD 1 "" "" true

The result returned from sendtoaddress is the transaction id. For example:

466c7da2555f0e1e8eff0188cbf7ba0e44fae03a61b81a1226c34ed785e4bb58

The result was returned as a string value. Many results are returned as JSON formatted data however. Using getwalletinfo as an example:

elements-cli getwalletinfo

This returns JSON formatted data similar to that below:

{
  "walletname": "",
  "walletversion": 169900,
  "balance": {
    "bitcoin": 100.00000000,
    },
  "unconfirmed_balance": {
    "bitcoin": 0.00000000
  },
  "immature_balance": {
    "bitcoin": 0.00000000
  },
  "txcount": 10,
  "keypoololdest": 1557907592,
  "keypoolsize": 1000,
  "keypoolsize_hd_internal": 999,
  "paytxfee": 0.00000000,
  "hdseedid": "17cea58f08ed56e975216699061df4a75002fcf8",
  "hdmasterkeyid": "17cea58f08ed56e975216699061df4a75002fcf8",
  "private_keys_enabled": true
}

In the remaining code examples we will continue using the terminal and the elements-cli application to send commands to elementsd. Other languages can be used to send RPC commands as mentioned previously.


Confidential Transactions

All addresses in Liquid are, by default, blinded using Confidential Transactions. Blinding is the process by which the amount and type of asset being transferred is cryptographically hidden from everyone except the sending and receiving parties. This is done using a blinding key, which we will look at later.

Imagine that our cryptographic friends Alice and Bob are running Liquid nodes. We’ll have Bob send some assets to himself using a blinded Liquid address as the destination.

First Bob will generate a new address and store it in a variable named “ADDR” so he can recall it for use later:

Bob:~$ ADDR=$(elements-cli getnewaddress)

To see the new address Bob created he can print out the value that was stored in the “ADDR” variable.

Bob:~$ echo $ADDR

Tip

We will use the “store the value returned from an RPC command in a variable for future use” technique throughout the rest of the code examples. We’ll print out the contents of a variable every now and again when highlighting something that has been returned. You can always just echo out the contents of any others as we go along should you want to.

After running the echo command above you should see something similar to this:

Azpr7BwzjwdiB1pNZKcLkk6Esn5NWAE7wtrC4UzEsshpKe3eUZzPQBvfJ7q9wzJLbt9yn8hYZmZDayGG

Tip

As of Liquid v0.17, getnewaddress defaults to creating P2SH-P2WPKH addresses. You can create “CTE” prefixed addresses (the default for versions of Liquid previous to 0.17) by calling getnewaddress like this: elements-cli getnewaddress "" legacy. You can also set the addresstype=legacy argument on node startup, or set it in your config file to always get legacy addresses from “getnewaddress”. Some commands require a “legacy” style address in order to work, such as message signing.

Let’s look at the address in more detail to check that it is indeed a confidential one. To do this we can use the “getaddressinfo” command, passing in the address that we stored in the ADDR variable as a parameter:

Bob:~$ getaddressinfo $ADDR

You should see a long value for the “confidential_key” property within the JSON formatted results. It will look something like this:

"confidential_key": "030788da8d9ca229cbe57e346daaf8d94cba3ed548b41922a8abefaec91ff1abb1"

The confidential_key is the public blinding key, which has been added to the address and is the reason why a confdential address is so long. You will also see that the “getaddressinfo” command shows an associated “unconfidential” address, which can be used to receive assets if you don’t want to make use of the Confidential Transaction feature for some reason.

We’ll now send an amount of 1 “bitcoin” (L-BTC) from Bob’s wallet to the new address we generated for him:

Bob:~$ TXID=$(elements-cli sendtoaddress $ADDR 1)

In order to have the transaction confirm we need a block to be generated. As an aside, at this stage we can query the mempool of each of our Liquid nodes to see the transaction waiting to be added to a block, as well as the current block count of each node’s blockchain:

Alice:~$ elements-cli getrawmempool
Bob:~$ elements-cli getrawmempool
Alice:~$ elements-cli getblockcount
Bob:~$ elements-cli getblockcount

Both should display results including the transaction with the same ID as that stored in the “TXID” variable and the same block count value. Once a new block has been created, we can check the mempool again for each client to see that the transaction has been confirmed:

Bob:~$ elements-cli generatetoaddress 1 $(elements-cli getnewaddress)
Alice:~$ elements-cli getrawmempool
Bob:~$ elements-cli getrawmempool
Alice:~$ elements-cli getblockcount
Bob:~$ elements-cli getblockcount

Note that although Bob sent an amount of 1 L-BTC to himself the net effect is that he now has slightly less than he did before, this is because some of the transaction amount was spent on fees that have yet to mature and be seen as spendable.

The above shows that the client’s blockchains and mempools are in sync. If they are not, wait a few seconds and try the calls above again as it may take a moment for the nodes to synchronize. They display the same results because they are connected nodes on the same Liquid network and broadcast transactions and blocks between each other in very much the same was as Bitcoin nodes do.

Now let’s examine the transaction as it is seen by Bob’s wallet and also how it is seen from the point of view of Alice’s wallet. First the view from Bob’s wallet:

Bob:~$ elements-cli gettransaction $TXID

The output from that initially looks like just a huge random assortment of letters and numbers (the hex value of the transaction), but if you scroll up you will see some more readable content above that.

Looking in the “details” section near the top, you will see that there are two amount values:

"details": [
  {
    ...
    "category": "send",
    "amount": -1.00000000,
    ...
  },
  {
    ...
    "category": "receive",
    "amount": 1.00000000,
    ...
  }
]

And so we can confirm that Bob’s wallet can view the actual amounts being sent and received in this transaction. This is because the blinded transaction was sent from Bob’s own wallet and so it has access to the required data to unblind the amount values. You will also see two other properties and their values within the two details sections: “amountblinder” and “assetblinder”. These indicate that both the asset amount and the type of asset were blinded. This ensures that wallets without knowledge of the blinding key are prevented from viewing them.

Looking at the transaction from Alice’s wallet, we would expect both amount and type to be unknown as they were sent using a Confidential Transaction.

In order to check Alice’s view of the transaction, we need Alice’s node to use the value of the transaction id that we stored in the TXID variable in Bob’s terminal session. When our code examples use a variable that was set by the other node like this, we will assume that you will set the variable across terminal sessions. This can be done by using echo to print the value within one terminal session, copying the value, and then setting it within the other node’s terminal session, like so:

Bob:~$ echo $TXID

Copy the result, which will be similar to:

533533c5a382ccf14f4b432130f02871091b6a28594a9481da12f360f711685d

And then we can set a corresponding variable in Alice’s terminal session, similar to doing the following:

Alice:~$ TXID=533533c5a382ccf14f4b432130f02871091b6a28594a9481da12f360f711685d

Tip

You can use this technique whenever we need to use a variable set in one terminal session within another.

This then allows us to run the code below.

Alice:~$ elements-cli gettransaction $TXID

This causes an error. The reason is that Alice’s wallet will not contain wallet details of the transaction as it does not relate to an address contained in her wallet. We can get the raw transaction data from Alice’s node’s copy of the blockchain using the getrawtransaction command like this:

Alice:~$ elements-cli getrawtransaction $TXID 1

That returns raw transaction details. If you look within the “vout” section you can see that there are three instances. The first two instances are the receiving and change amounts and the third is the transaction fee. Of these three amounts, the fee is the only one in which you can see a value, as the fee itself is unblinded. For the first two instances you will see (amongst others) properties with values similar to this:

"value-minimum": 0.00000001,
"value-maximum": 11258999.06842624,
"amountcommitment": "0881c61d8a15ad26e6ef621ca99a188ccebbdb348d5285012393459b7e5b1e6113",
"assetcommitment": "0b1b7a1a4a604f4a68b3277e3a8926d74e86adce7b92e8e6ba67f9c5a8ad2cbcf4",

What this shows are the “blinded ranges” of the value amounts and the commitment data that acts as proof of the actual amount and type of asset transacted. The raw view of the transaction will be the same accross all nodes, regardless of if they hold the blinding key or not, only the results of gettransaction from a wallet aware of the blinding key used will show the actual amounts.

Even if we were to import Bob’s private key into Alice’s wallet it would still not be able to see the amounts and type of asset using gettransaction because it still has no knowledge of the blinding key used.

If we want to let Alice’s wallet view the actual amount details we’ll need to import the address as ‘watch only’ so gettransaction will work, and then import the blinding key so we can see the unblinded amounts. First, import and then view the transaction. Note the use of the second argument passed to gettransaction, set to true. This tells gettransaction to include watch only addresses, such as the one we imported. Remember to copy the value of $ADDR from Bob’s session and set it in Alice’s before running the code below.

Alice:~$ elements-cli importaddress $ADDR
Alice:~$ elements-cli gettransaction $TXID true

This time the call to gettransaction does not error but, because Alice still does not know the blinding key, the amount (towards the top of the output) will show as:

"amount": {
  "bitcoin": 0.00000000

Without knowledge of the Blinding Key, the amount and type of asset being transacted is still hidden.

In order for anyone else apart from the sender and receiver of a Confidential Transaction (such as an auditor) to view the amount and type of assets being transacted, they need to know the blinding key that was used to generate the blinded address. To show this, we can export the blinding key Bob’s wallet used for the related address, import it into Alice’s wallet and try to view the transaction again. Let’s export the key for that particular address from Bob’s wallet and import it into Alice’s.

Export Bob’s blinding key for the address:

Bob:~$ BOBBLINDINGKEY=$(elements-cli dumpblindingkey $ADDR)

Echo, copy and set the variable accross terminal sessions again like we did above (steps not shown) and then import Bob’s blinding key into Alice’s wallet:

Alice:~$ elements-cli importblindingkey $ADDR $BOBBLINDINGKEY

Now that Alice’s wallet has knowledge of the blinding key used on that address, we can run the checks we did above from Alice’s wallet, this time expecting to see the actual amount value:

Alice:~$ elements-cli gettransaction $TXID true

Magic! Alice’s wallet now shows the actual value sent in the transaction.

"amount": {
  "bitcoin": 1.00000000

We’ve seen that the use of a blinding key hides the amount and type of assets in an address and that by importing the right blinding key, we can reveal those values. In practical use, a blinding key may be given to an auditor, should there be a need to verify the amounts and types of assets held by a party. The Confidential Transactions feature of Liquid also allows for “range proofs” to be performed without the need to expose actual amounts. This allows statements such as “address abc holds at least an amount x of asset y” to be cryptographically proven as true or false.


Issued Assets

In this example we’ll issue our own asset, label it, look at the re-issuance token, and learn how to send the asset to other other Liquid nodes and wallet addresses. We’ll also take a look at how to keep track of what assets have been issued and re-issued.

First, let’s take a look at Alice’s wallet to see what it currently holds.

Alice:~$ elements-cli getwalletinfo

We see that Alice holds a lot of the “bitcoin” asset and nothing else:

"balance": {
  "bitcoin": 101.00000000
}

Every asset you issue within Liquid (including the “bitcoin” default) will be assigned its own hex value. This is used to uniquely identify it on the network. Notice how “bitcoin” is displayed with a readable asset name. This is because Liquid automatically associates the label “bitcoin” with the asset hex for that default asset. To find out its hex value we can run:

Alice:~$ elements-cli dumpassetlabels

Which returns:

"bitcoin": "cc5fb67403bee3b9a74e7518b7684d6cb64041e4156970a17aa653ee336b1097"

One of the main features of Liquid is the ability to issue your own assets.

Tip

There is nothing inherently different between assets in the way they are handled within the Liquid protocol.

Run the following to perform a blinded issuance of 100 of a new asset, along with 1 reissuance token for the asset.

Alice:~$ ISSUE=$(elements-cli issueasset 100 1)

Tip

To manually issue an asset using the rawissueasset command, please see the Creating Raw Issuances example. Additionally, the Proof of Issuance example shows how to prove that you were the one who issued the asset using the “contract hash” parameter.

That will create a new asset type, an initial supply of 100 and also 1 reissuance token. The reissuance token is used to prove authority to reissue more of the asset at a later date. We have issued one such token in the command above. The token is transferable and you can initially create as many as you think you will need based upon how many of the network participants will need to perform this duty. Each asset has its own reissuance token. We’ll look at this in more detail later. It is also possibly to create an asset with no reissuance tokens by using zero for that argument.

Tip

The minimum amount of an asset you can issue is 0.00000001. This is also the smallest amount of any issued asset that you can send. The minimum amount of the default asset that you can send (L-BTC) is higher, at 0.00001. These differ as the default asset considers the minimum send amount of the Bitcoin network’s dust limit. The maximum amount of an asset you can issue is 21,000,000 although more can be created by reissuing. When you create the reissuance token as above, where the amount was 1, you are actually creating 100,000,000 of them when measured in their smallest denomination. This is because they are divisible like every other asset on Liquid. The 1 is little more than a user interface concession. For ease of readability we will refer to this issuance as “one token”.

We have stored all of the returned data from the issuance command in a variable named “ISSUE”, which we’ll pull the hex of the new asset from, storing that value in another variable named “ASSET”. We’ll also store the “token” value (which we’ll explain and use later) and the “txid” and “vin” of the issuance, which will be used when we try and unblind the issuing transaction shortly.

In order to do this we can use a tool called jq (which we installed as part of the tutorial dependencies) to strip out and store only the parts returned and saved within “ISSUE” that we are interested in:

Alice:~$ ASSET=$(echo $ISSUE | jq '.asset' | tr -d '"')
Alice:~$ TOKEN=$(echo $ISSUE | jq '.token' | tr -d '"')
Alice:~$ ITXID=$(echo $ISSUE | jq '.txid' | tr -d '"')
Alice:~$ IVIN=$(echo $ISSUE | jq '.vin' | tr -d '"')

To see the hex identifier for the asset we issued run:

Alice:~$ echo $ASSET

Which will return something like this:

f0379482f9b77917670be0f060cc58debc6d93db0bf857458d5fb19080c8ab67

In order to view all asset issuances that have been made we run the “listissuances” command:

Alice:~$ elements-cli listissuances

That will show the asset we just issued. You’ll notice tha it has the following property:

"isreissuance": false,

This indicates that it was an original issuance and not a reissuances. You’ll also see that the newly issued asset does not have an “assetlabel”.

Tip

Asset labels are not part of network protocol consensus and are local only to each node. You should not rely on them for transaction processing but instead use the asset’s hex value, which is shared across the network.

You can set the label by assigning it against the hex identifier of the asset. This can be done in the relevant Liquid.conf file by adding a line:

assetdir=asset_hex_here:your_label_here

Or you can do this by passing in “assetdir” as a parameter when you start the node. We’ll do this now and call our new asset “demoasset”. We need to stop the node first.

Alice:~$ elements-cli stop
Alice:~$ elementsd -assetdir=$ASSET:demoasset
Alice:~$ elements-cli listissuances

This shows that the asset we issued has the label we assigned to its hex value:

"assetlabel": "demoasset",

Having labelled our asset for ease of reference, we will now look at the issuance data for “demoasset” in more detail. You will notice a “token” property similar to that below:

"token": "33244cc19dd9df0fd901e27246e3413c8f6a560451e2f3721fb6f636791087c7",

This is the hex of the token and it can be used to reissue the asset. Yours will likely differ from the actual value above. There is also a “tokenamount” property which corresponds to the amount we created:

"tokenamount": 1.00000000,

When the transaction has confirmed, we can take a look using Bob’s wallet to see if it is aware of Alice’s asset issuance:

Bob:~$ elements-cli listissuances

Bob’s wallet isn’t aware of the issuance, so we’ll import the address into his wallet.

In order to check Bob’s view of the issuance, we need Bob’s node to use the address used when Alice issued the asset. As that’s currently known to Alice’s node, we need to copy the information over to Bob’s node. This can be done by using echo to print the value within one terminal session, copying the value, and then setting it within the other node’s terminal session, like so:

Alice:~$ IADDR=$(elements-cli gettransaction $ITXID | jq '.details[0].address' | tr -d '"')
Alice:~$ echo $IADDR

Copy the result, which will be similar to:

AzpkDFjr6nNm4nncoBHuZr5S2zH4wPEpTvU63M8jNuU7JMXAVb5rVNCYmLB3fTmP9kLGgTAdCTwrrndu

And then we can set a corresponding variable in Bob’s terminal session, similar to doing the following:

Bob:~$ IADDR=AzpkDFjr6nNm4nncoBHuZr5S2zH4wPEpTvU63M8jNuU7JMXAVb5rVNCYmLB3fTmP9kLGgTAdCTwrrndu

Tip

You can use this technique whenever we need to use a variable set in one terminal session within another.

Bob can now import the address relating to Alice’s issuance:

Bob:~$ elements-cli importaddress $IADDR

If we try and view the list of issuances from Bob’s node now we’ll see the issuance, but notice that the amount of the asset and the amount of its associated token are hidden:

Bob:~$ elements-cli listissuances

The asset amount and the token amount are both blinded and show as -1:

"tokenamount": -1,
"assetamount": -1,

In the Confidential Transactions example, we were able to expose the amount and type of asset being sent in a regular Confidential Transaction by exporting the blinding key used to create the blinded address and importing it into another wallet. We can do the same type of thing with the issuance transaction using the issuance blinding key.

First, we need to export the issuance blinding key. We refer to issuances by their txid/vin pair. As there is only one per input it will be zero, but we’ll use the value we saved earlier as it is good practice to not rely on such things staying fixed. You will need to echo, copy and create the appropriate variables (ISSUEKEY, ITXID, IVIN) in Bob’s node as we did above for this to work (steps not shown).

Alice:~$ ISSUEKEY=$(elements-cli dumpissuanceblindingkey $ITXID $IVIN)
Bob:~$ elements-cli importissuanceblindingkey $ITXID $IVIN $ISSUEKEY

Now when we run the command to list known issuances from Bob’s wallet we should see the actual values:

Bob:~$ elements-cli listissuances

Which returns:

"tokenamount": 1.00000000,
"assetamount": 100.00000000,

Indeed, Bob’s wallet can now see both the amount of the asset and its reissuance token that were issued.

Just like any other asset in Liquid, we can send our “demoasset” from Alice’s address to Bob’s using the “sendtoaddress” command. This differs from its implementation in Bitcoin’s source code in that it accepts an additional parameter, which allows you to specify the type of asset to be sent. Be aware that the step above where we imported the issuance blinding key is not required in order to transact an issued asset between addresses and wallets. Importing the issuance blinding key just enables another wallet to view the issuance details in full.

You will need to echo, copy and create the appropriate variable (BOBDEMOADD) in Alice’s node as we did above for this to work (steps not shown).

Bob:~$ BOBDEMOADD=$(elements-cli getnewaddress)
Alice:~$ elements-cli sendtoaddress $BOBDEMOADD 10 "" "" false false 1 UNSET demoasset

After confirmation, Bob’s wallet will now show an amount of 10 “demoasset” and Alice’s will show 90:

Bob:~$ elements-cli getwalletinfo
Alice:~$ elements-cli getwalletinfo

As we didn’t assign a label in Bob’s node for the asset we created, it will be identified by its hex value instead. We will therefore have to use the hex identifier instead of the asset label when we send it from his node. Remember that asset labels are local only to each node and are not part of the network’s protocol rules. We’ll demonstrate how Bob can send the asset using the hex value by transferring the 10 “demoasset” back to Alice:

You will need to echo, copy and create the appropriate variable (ALICEDEMOADD) in Bob’s node as we did above for this to work (steps not shown).

Alice:~$ ALICEDEMOADD=$(elements-cli getnewaddress)
Bob:~$ elements-cli sendtoaddress $ALICEDEMOADD 10 "" "" false false 1 UNSET $ASSET
Bob:~$ ADDRGEN=$(elements-cli getnewaddress)
Bob:~$ elements-cli generatetoaddress 1 $ADDRGEN

We should see that Bob’s wallet has no “demoasset” in it anymore and Alice’s is back to 100:

Bob:~$ elements-cli getwalletinfo
Alice:~$ elements-cli getwalletinfo

We can see that is indeed the case.


Reissuing Assets

In this section we’ll look at reissuing an amount of the asset we previously issued. We’ll re-use the asset hex we stored in the “ASSET” variable. Reissuing just means “creating some more of” in this context. For future use, we’ll store the result of the “reissueasset” command in a variable named “RTRANS” and from that, strip out the transaction ID, storing it in another variable named “RTXID”:

Alice:~$ RTRANS=$(elements-cli reissueasset $ASSET 99)
Alice:~$ RTXID=$(echo $RTRANS | jq '.txid' | tr -d '"')

We’ve just created 99 more units of our new asset. As an aside, because we already labelled the asset in the previous example, we could have also passed “demoasset” in as the first parameter instead of the hex identifier and it would have worked exactly the same.

To check this issuance history of our asset (and ignore the “bitcoin” issuance) we can run the “listissuances” command and specify the asset we are interested in:

Alice:~$ elements-cli listissuances $ASSET

For the above command you may also pass the asset label in instead of the hex value. As has been noted before, be aware that labels are stored locally and are not network-wide.

Along with the original issuance you should see a new entry with the following property:

"isreissuance": true,

This property allows us to differentiate between initial issuances and reissuances. Note that the transaction id where amounts of the asset were created is also included in the returned data.

Let’s look at the details of the transaction where we reissued our asset:

Alice:~$ elements-cli gettransaction $RTXID

Scroll to the top of the returned transaction data as there are a few things worth noting here. The first is that within the “amount” section we can see that 0 “bitcoin” and 99 “demoasset” were transacted:

"amount": {
  "bitcoin": 0.00000000,
  "33244cc19dd9df0fd901e27246e3413c8f6a560451e2f3721fb6f636791087c7": 0.00000000,
  "demoasset": 99.00000000,

This information suggests that an Liquid transaction can transact more than one type of asset within the same transaction, which is indeed the case.

Tip

To send different types of asset in the same transaction, the “sendmany” command is used. The syntax is the same as in Bitcoin.

The “amount” section shows the net effect of the transaction as: 0 “bitcoin”, 99 “demoasset” and also another asset that is 0. That unlabelled asset is our reissuance token (the hex for which will differ from that above but the results are otherwise the same). What this shows is that once the sent and received amounts are totalled we have created 99 “demoasset”. You can see how the values in the “amount” section are derived by scrolling down the returned data and looking within the “details” section.

You will see that amounts of 99 “demoasset” and 1 reissuance token were sent:

"category": "send",
"amount": -99.00000000,

"category": "send",
"amount": -1.00000000,

And that further on the same amounts were received:

"category": "receive",
"amount": 99.00000000,

"category": "receive",
"amount": 1.00000000,

We can see how the “amount” section above therefore lists the net transfer of 0 (-1 +1) tokens. The reason why the net of this is the creation of 99 new “demoasset” is that the “reissueasset” command essentially spends from a zero balance address, and so the received amount has the effect of creating 99 new “demoasset”. The 99 new “demoasset” are basically spent into existence. It is worth highlighting again that in order to reissue an asset you must hold a related reissuance token. They must therefore be allocated wisely.

To check that the blinding works the same for a reissuance transaction as it does for a normal transaction we can check Bob’s view of Alice’s reissuance transaction. Wait until a block is confirmed to let the nodes sync, then run the following:

Bob:~$ RAWRTRANS=$(elements-cli getrawtransaction $RTXID)
Bob:~$ elements-cli decoderawtransaction $RAWRTRANS

We can see that the amounts and asset types are indeed blinded with results like this:

"value-minimum": 0.00000001,
"value-maximum": 42.94967296,

You could unblind these using the techniques we used for the initial issuance should you want to.

We have seen that reissuance is just a special kind of spending transaction whereby you can create more of the original asset, so long as you hold a valid reissuance token in your wallet. Next we will look at how to transfer the reissuance tokens.

Let’s send the reissuance token from Alice to Bob so that Bob can reissue some “demoasset” himself. Note that if there was always going to be a need for them both to reissue the asset at the same time, we could have just created two reissuance tokens and initially sent one to Bob, leaving Alice still holding the other. Either way, we would need to send from one wallet to the other, so let’s begin. First we’ll double check that Alice’s wallet currently holds the reissuance token and Bob’s does not:

Alice:~$ elements-cli getwalletinfo
Bob:~$ elements-cli getwalletinfo

Alice’s wallet has “bitcoin”, “demoasset” and the demo asset’s reissuance token whereas Bob’s only has “bitcoin”.

We’ll just prove that the token is needed to reissue by trying to reissue from Bob’s wallet without the token:

Bob:~$ elements-cli reissueasset $ASSET 10

That fails as expected and gives the following error message:

No available reissuance tokens in wallet.

So let’s send the reissuance token to Bob so that he can reissue some of our “demoasset”. Have Bob’s wallet generate a new address and save it in a variable:

Bob:~$ RITRECADD=$(elements-cli getnewaddress)

Send the token from Alice’s wallet to Bob’s new address as if it were any other asset. We’ll use the hex of the token to say what type of asset we are sending and also generate a block so the transaction confirms:

Alice:~$ elements-cli sendtoaddress $RITRECADD 1 "" "" false false 1 UNSET $TOKEN
Alice:~$ elements-cli generatetoaddress 1 $ADDRGEN

Check that Bob’s wallet now has the token and that Alice’s no longer does:

Alice:~$ elements-cli getwalletinfo
Bob:~$ elements-cli getwalletinfo

The token and right to reissue it provides is now Bob’s!

Tip

Remember from an earlier note that we can divide a reissuance token like any other asset in Liquid. Our send of “1” token in this instance actually transferred 100,000,000 of the smallest possible amount of the token. You can try sending something like “0.1” of the token back to Alice and check if she is again able to reissue (she will and so will Bob, who will still hold “0.9”).

Bob still doesn’t have any of the “demoasset” itself yet but, now that his wallet holds the reissuance token, he can reissue any amount of “demoasset” and it will show in his wallet:

Bob:~$ RISSUE=$(elements-cli reissueasset $ASSET 10)
Bob:~$ elements-cli getwalletinfo

Bob’s wallet now has “bitcoin”, the reissuance token for our new asset, and an amount of the new asset itself:

"balance": {
  "bitcoin": 10499998.99841940,
  "78ee1e3b9f2edf730e7f624e9d0f92d3e1d364c0ee91525bbccf56377dcd5033": 1.00000000,
  "600010d2a60cf0d9395dced79af3ccdb7c908e80cddf125ed1af80dc87186aae": 10.00000000
},

Remember that the new asset we issued will still only display using its hex value in Bob’s wallet as we didn’t assign it a label like we did in Alice’s wallet. In order for Alice to see this reissuance we need to make her wallet aware of it. Show that Alice’s wallet can’t see it:

Alice:~$ elements-cli listissuances

Import the address so that it can:

Bob:~$ RITXID=$(echo $RISSUE | jq '.txid' | tr -d '"')
Bob:~$ RIADDR=$(elements-cli gettransaction $RITXID | jq '.details[0].address' | tr -d '"')
Alice:~$ elements-cli importaddress $RIADDR

Now Alice’s wallet can see the reissuance:

Alice:~$ elements-cli listissuances

As expected though, the amounts are blinded. You can unblind by importing the blinding key as we did earlier should you want to.

In Liquid you can also carry out an unblinded asset issue:

Bob:~$ UBRISSUE=$(elements-cli issueasset 55 1 false)
Bob:~$ UBASSET=$(echo $UBRISSUE | jq '.asset' | tr -d '"')

Which shows up as normal in Bob’s wallet after he issues it:

Bob:~$ elements-cli getwalletinfo

And this time if we import the address into Alice’s wallet she should be able to see the amount issued, proving it was issued unblinded. Following the same process as before to import the address into Alice’s wallet:

Alice:~$ elements-cli listissuances
Bob:~$ UBRITXID=$(echo $UBRISSUE | jq '.txid' | tr -d '"')
Bob:~$ UBRIADDR=$(elements-cli gettransaction $UBRITXID | jq '.details[0].address' | tr -d '"')
Alice:~$ elements-cli importaddress $UBRIADDR

We can now see that Alice’s wallet can see both the issuance and the amount issued (55) without the need to import the blinding key:

Alice:~$ elements-cli listissuances

It may also be necessary to destroy asset amounts as well as create them in an Liquid based blockchain. This is easily done using the “destroyamount” command:

Bob:~$ elements-cli destroyamount $UBASSET 5

Check the amount has gone from the 55 issued down to 50:

Bob:~$ elements-cli getwalletinfo

It will show the amount as 50, proving that an amount of 5 were indeed destroyed:

"balance": {
  "4021bf6faac59d7ec593859a741318752f72e637e7d5ecfa54725dba1508771b": 50.00000000,

Creating, reissuing and destroying assets is a key feature of Liquid that can help you reflect the real world movement of assets or tokens on another blockchains etc.


Creating Raw Transactions

In this example we will create a raw transaction sending some of an issued asset.

Issue an asset and get the asset hex so we can send some:

Alice:~$ ISSUE=$(elements-cli issueasset 100 1)
Alice:~$ ASSET=$(echo $ISSUE | jq '.asset' | tr -d '"')

Check the asset shows up in our wallet:

Alice:~$ elements-cli getwalletinfo

Get a list of unspent we can use as inputs, referenced via transaction id, vout and amount:

Alice:~$ UTXO=$(elements-cli listunspent 0 0 [] true '''{"''asset''":"'''$ASSET'''"}''')
Alice:~$ TXID=$(echo $UTXO | jq '.[0].txid' | tr -d '"' )
Alice:~$ VOUT=$(echo $UTXO | jq '.[0].vout' | tr -d '"' )
Alice:~$ AMOUNT=$(echo $UTXO | jq '.[0].amount' | tr -d '"' )

Get an address to send the asset to, we’ll use an unconfidential address:

Alice:~$ ADDR=$(elements-cli getnewaddress)
Alice:~$ VALIDATEADDR=$(elements-cli validateaddress $ADDR)
Alice:~$ UNCONADDR=$(echo $VALIDATEADDR | jq '.unconfidential' | tr -d '"')

Build the raw transaction, sending an amount of 3 of the asset:

Alice:~$ SENDAMOUNT="3.00"
Alice:~$ RAWTX=$(elements-cli createrawtransaction '''[{"''txid''":"'''$TXID'''", "''vout''":'$VOUT', "''asset''":"'''$ASSET'''"}]''' '''{"'''$UNCONADDR'''":'$SENDAMOUNT'}''' 0 false '''{"'''$UNCONADDR'''":"'''$ASSET'''"}''')

Fund the transaction:

Alice:~$ FRT=$(elements-cli fundrawtransaction $RAWTX)

Blind and sign the transaction:

Alice:~$ HEXFRT=$(echo $FRT | jq '.hex' | tr -d '"')
Alice:~$ BRT=$(elements-cli blindrawtransaction $HEXFRT)
Alice:~$ SRT=$(elements-cli signrawtransactionwithwallet $BRT)
Alice:~$ HEXSRT=$(echo $SRT | jq '.hex' | tr -d '"')

Send the raw transaction:

Alice:~$ TX=$(elements-cli sendrawtransaction $HEXSRT)

After the transaction has confirmed, decode the raw transaction so we can see the amount of the asset sent and the address it went to:

Alice:~$ GRT=$(elements-cli getrawtransaction $TX)
Alice:~$ DRT=$(elements-cli decoderawtransaction $GRT)

Creating Raw Issuances

Get an address to issue the asset to:

Alice:~$ ASSET_ADDR=$(elements-cli getnewaddress "" legacy)

Get an address to issue the reissuance token to:

Alice:~$ TOKEN_ADDR=$(elements-cli getnewaddress "" legacy)

Create the raw transaction and fund it:

Alice:~$ RAWTX=$(elements-cli createrawtransaction '''[]''' '''{"''data''":"''00''"}''')
Alice:~$ FRT=$(elements-cli fundrawtransaction $RAWTX)
Alice:~$ HEXFRT=$(echo $FRT | jq '.hex' | tr -d '"')

Create the raw issuance:

Alice:~$ ASSET_AMOUNT=100
Alice:~$ TOKEN_AMOUNT=1
Alice:~$ RIA=$(elements-cli rawissueasset $HEXFRT '''[{"''asset_amount''":'$ASSET_AMOUNT', "''asset_address''":"'''$ASSET_ADDR'''", "''token_amount''":'$TOKEN_AMOUNT', "''token_address''":"'''$TOKEN_ADDR'''", "''blind''":false}]''')

The results of which include the following for reference:

Alice:~$ HEXRIA=$(echo $RIA | jq '.[0].hex' | tr -d '"')
Alice:~$ ASSET=$(echo $RIA | jq '.[0].asset' | tr -d '"')
Alice:~$ ENTROPY=$(echo $RIA | jq '.[0].entropy' | tr -d '"')
Alice:~$ TOKEN=$(echo $RIA | jq '.[0].token' | tr -d '"')

Blind, sign and send the transaction that creates the asset issuance:

Alice:~$ BRT=$(elements-cli blindrawtransaction $HEXRIA true '''[]''' false)
Alice:~$ SRT=$(elements-cli signrawtransactionwithwallet $BRT)
Alice:~$ HEXSRT=$(echo $SRT | jq '.hex' | tr -d '"')
Alice:~$ ISSUETX=$(elements-cli sendrawtransaction $HEXSRT)

Confirm and check it worked:

Alice:~$ elements-cli generatetoaddress 101 $(elements-cli getnewaddress)
Alice:~$ elements-cli listissuances
Alice:~$ elements-cli getwalletinfo

Proof of Issuance - Blockstream’s Liquid Asset Registry

This example shows how to use Blockstream’s Liquid Asset Registry. The asset registry allows you to register an asset and prove ownership against a domain name.

The code below creates an asset, an associated token, and outputs two files:

  • A ‘liquid-asset-proof-‘ file that must be placed on the server of the registered domain over the entire lifecycle of the asset.
  • A ‘register_asset.sh’ file that, when run, will post the asset registration data to Blockstream’s Liquid Asset Registry.

The use of the output files will be explained later.

There are six fields that need setting within the script:

  • NAME The name of the asset as it will apear in the registry and applications that use asset registry data, such as the Blockstream Explorer. Length must be 5 to 255 ascii characters.
  • TICKER The ticker you would like to assign to the asset. Length must be 3 to 5 characters (valid characters are a-z and A-Z).
  • DOMAIN The domain that will be used to verify the asset. Must be a valid domain name format, for example: example.com or sub.example.com. Do not include the http/s or www prefixes.
  • ASSET_AMOUNT The amount to be issued. It is preferable to consider one satoshi unit as representing one asset. So to issue an amount of 100 of an asset, a value of 0.00000100 should be used. Please note that how this value is displayed within applications that use asset registry data is affected by the PRECISION field.
  • TOKEN_AMOUNT The amount of reissuance tokens to be created. It is preferable to consider one satoshi unit as representing one reissuance token, so a value of 0.00000001 will create 0.00000001 reissuance tokens and when viewed from Liquid Core it will also show as 0.00000001. When viewed from an app that uses satoshi sized units, such as the Blockstream Explorer it will show as 1. This field is not affected by the PRECISION field.
  • PRECISION The precision used to display the asset amount within applications that use Liquid Asset Registry data, such as the Blockstream Explorer. Examples: a PRECISION value of 0 for an ASSET_AMOUNT of 0.00000100 will create a value of 0.00000100 when viewed from Liquid Core and 100 when viewed in an app that uses the asset registry data. A PRECISION value of 2 for an ASSET_AMOUNT of 0.00000100 will create a value of 0.00000100 when viewed from Liquid Core and 1.00 when viewed in the same app. The default is 0 and the maximum value is 8.

The code below uses Liquid in live mode. The code can easily be adapted for test use.

Save the code below in a file named issue_and_prepare_register.sh

#!/bin/bash
set -x
shopt -s expand_aliases

# ASSUMES elementsd IS ALREADY RUNNING

######################################################
#                                                    #
#    SCRIPT CONFIG - PLEASE REVIEW BEFORE RUNNING    #
#                                                    #
######################################################

# Amend the following:
NAME="Wintercoin"
TICKER="WTX"
# Do not use a domain prefix in the following:
DOMAIN="wintercooled.github.io"
# Issue 100 assets using the satoshi unit, dependant on PRECISION when viewed from
# applications using Asset Registry data.
ASSET_AMOUNT=0.00000100
# Issue 1 reissuance token using the satoshi unit, unaffected by PRECISION.
TOKEN_AMOUNT=0.00000001

# Amend the following if needed:
PRECISION=0

# Don't change the following:
VERSION=0

# If your Liquid node has not seen enough transactions to calculate
# its own feerate you will need to specify one to prevent a 'feeRate'
# error. We'll assume this is the case:
FEERATE=0.00003000

# Change the following to point to your elements-cli binary and liquid data directory.
alias e1-cli="$HOME/elements-0.17.0.1/bin/elements-cli -datadir=$HOME/.elements"

##############################
#                            #
#    END OF SCRIPT CONFIG    #
#                            #
##############################

# Exit on error
set -o errexit

# We need to get a 'legacy' type (prefix 'CTE') address for this:
NEWADDR=$(e1-cli getnewaddress "" legacy)

VALIDATEADDR=$(e1-cli getaddressinfo $NEWADDR)

PUBKEY=$(echo $VALIDATEADDR | jq '.pubkey' | tr -d '"')

ASSET_ADDR=$NEWADDR

NEWADDR=$(e1-cli getnewaddress "" legacy)

TOKEN_ADDR=$NEWADDR

# Create the contract and calculate the contract hash.
# The contract is formatted for use in the Blockstream Asset Registry
# Do not amend the following!

CONTRACT='{"entity":{"domain":"'$DOMAIN'"},"issuer_pubkey":"'$PUBKEY'","name":"'$NAME'","precision":'$PRECISION',"ticker":"'$TICKER'","version":'$VERSION'}'

# We will hash using openssl, other options are available
CONTRACT_HASH=$(echo -n $CONTRACT | openssl dgst -sha256)
CONTRACT_HASH=$(echo ${CONTRACT_HASH#"(stdin)= "})

# Reverse the hash. This will be calculated from the contract by the asset registry service to
# check validity of the issuance against the registry entry.
TEMP=$CONTRACT_HASH

LEN=${#TEMP}

until [ $LEN -eq "0" ]; do
    END=${TEMP:(-2)}
    CONTRACT_HASH_REV="$CONTRACT_HASH_REV$END"
    TEMP=${TEMP::-2}
    LEN=$((LEN-2))
done

RAWTX=$(e1-cli createrawtransaction '''[]''' '''{"''data''":"''00''"}''')

# If your Liquid node has seen enough transactions to calculate its
# own feeRate then you can switch the two lines below. We'll default
# to specifying a fee rate:
#FRT=$(e1-cli fundrawtransaction $RAWTX)
FRT=$(e1-cli fundrawtransaction $RAWTX '''{"''feeRate''":'$FEERATE'}''')

HEXFRT=$(echo $FRT | jq '.hex' | tr -d '"')

RIA=$(e1-cli rawissueasset $HEXFRT '''[{"''asset_amount''":'$ASSET_AMOUNT', "''asset_address''":"'''$ASSET_ADDR'''", "''token_amount''":'$TOKEN_AMOUNT', "''token_address''":"'''$TOKEN_ADDR'''", "''blind''":false, "''contract_hash''":"'''$CONTRACT_HASH_REV'''"}]''')

# Details of the issuance...
HEXRIA=$(echo $RIA | jq '.[0].hex' | tr -d '"')
ASSET=$(echo $RIA | jq '.[0].asset' | tr -d '"')
ENTROPY=$(echo $RIA | jq '.[0].entropy' | tr -d '"')
TOKEN=$(echo $RIA | jq '.[0].token' | tr -d '"')

# Blind, sign and send the issuance transaction...
BRT=$(e1-cli blindrawtransaction $HEXRIA true '''[]''' false)

SRT=$(e1-cli signrawtransactionwithwallet $BRT)

HEXSRT=$(echo $SRT | jq '.hex' | tr -d '"')

# Test the transaction's acceptance into the mempool
TEST=$(e1-cli testmempoolaccept '''["'$HEXSRT'"]''')
ALLOWED=$(echo $TEST | jq '.[0].allowed' | tr -d '"')

# If the transaction is valid
if [ "true" = $ALLOWED ] ; then
    # Broadcast the transaction
    ISSUETX=$(e1-cli sendrawtransaction $HEXSRT)
else
    echo "ERROR SENDING TRANSACTION!"
fi

#####################################
#                                   #
#    ASSET REGISTRY FILE OUTPUTS    #
#                                   #
#####################################

# Blockstream's Liquid Asset Registry (https://assets.blockstream.info/) can be used to register an asset to an issuer.
# We already have the required data and have formatted the contract plain text into a format that we can use for this.

# Write the domain and asset ownership proof to a file. The file should then be placed over the entire lifecycle of
# the asset in a directory within the root of your domain named ".well-known"
# The file should have no extension and just copied as it is created.

echo "Authorize linking the domain name $DOMAIN to the Liquid asset $ASSET" > liquid-asset-proof-$ASSET

# After you have placed the above file without your domain you can run the register_asset.sh script created below to post the asset data to the registry.

echo "curl https://assets.blockstream.info/ --data-raw '{\"asset_id\":\"$ASSET\",\"contract\":$CONTRACT}'" > register_asset.sh

# For reference, write some asset details. These are not needed by the asset registry.

echo "ISSUETX:$ISSUETX ASSET:$ASSET ENTROPY:$ENTROPY TOKEN:$TOKEN ASSET_AMOUNT:$ASSET_AMOUNT TOKEN_AMOUNT:$TOKEN_AMOUNT ASSET_ADDR:$ASSET_ADDR TOKEN_ADDR:$TOKEN_ADDR CONTRACT_HASH_REV:$CONTRACT_HASH_REV" > liquid-asset-ref-$ASSET

##################################################################

echo "Completed without error"

When you have saved the above to the file, edit the variables at the top and of the file and start elements-qt or elementsd using an argument of -server=1 to allow the Liquid client to communicate with it. Execute the script from the directory you created it in by opening a Terminal session and running:

bash issue_and_prepare_register.sh

In order to register the asset just created:

  • Wait a couple of minutes for the issuance transaction to confirm.
  • Place the ‘liquid-asset-proof-‘ file in a folder named ‘.well-known’ in the root of your domain. This proof should be stored there over the entire lifecycle of the asset.
  • Run the ‘register_asset.sh’ script.

For example, if your domain was www.your-example-domain-here.com and the asset id generated was 123abc (it will of course be much longer) then the file generated would be named:

liquid-asset-proof-123abc

The domain variable in the code above would be set to:

your-example-domain-here.com

So the path used to check asset to domain registry would end up being:

www.your-example-domain-here.com/.well-known/liquid-asset-proof-123abc

Once that file is accessible you can then run the ‘register_asset.sh’ script and, when the required checks against the domain and issuance transaction have been made, the registration will be found on Blockstream’s Liquid Asset Registry.