web/docs/liquid/liquid-part2.md
2024-01-27 10:09:46 -06:00

13 KiB

DeepDive into Liquid: Part II - Asset Creation and Configuration

This is a multipart DeepDive that will focus on the Liquid Sidechain. It will be released in 3 Parts:

  • Part I: Overview, Installation, and first Peg-In
  • Part II (this): Asset Creation and Configuration
  • Part III: Advanced Topics and Peg-out

Assets

In Part I, we successfully Pegged-In our BTC and received L-BTC, now lets use that L-BTC to do some interesting things with Asset Issuance:

Before we jump straight into creating an asset, let us first look at the assets we already have inside our pegged-in wallet1:

$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getwalletinfo

"balance": {"bitcoin": 0.00149155}`

$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS dumpassetlabels`

{"bitcoin": "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d"}

So our wallet already has an asset called 'bitcoin' and there is an associated UUID of 6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d. Everything in Liquid is an 'Asset' (including L-BTC) and each of these Assets can have different features.

Looking up this value on https://blockstream.info/liquid/assets we can see some info about it:

This shows us the total amount of L-BTC in circulation, additionally we can see how much was Pegged-In, Out, and Burned. Most notably, this confirms that everything is in balance. The Circulating Supply = (PEG_IN - PEG_OUT - BURNED), so no debasement has happened.

Issuing our own Asset

Issuing assets is pretty straight forward, it can be roughly broken down into 3 main steps2:

  1. Generate a Legacy Address3 and PUBKEY for that address
  2. Generate a Contract Hash using that PUBKEY
  3. Issue the Asset
  • STEP 1: Let's generate our legacy address and set the output to a variable and get its public key
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getnewaddress "" legacy)
lq1qq03fq9jz20qnfqw4utjhdh3feasg3rtzf7l2qd9snrkctjm8g4ey4wvu225kq79wqclq9qg7ak8ycnhuekwuw9r38t94qr60a
$ export NEWADD='lq1qq03fq9j....'
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getaddressinfo $NEWADD | jq '.pubkey'
02fc3b404d9785d2dc26ea1867e25cf047702e45d29559a4657fafe8c0dd53877e
  • STEP 2: In order to generate the contract hash, it requires a few steps - so I've simplified it by making a bash script. Copy this into a file named gen_asset_contract.sh and mark as executable
#!/bin/bash
#set -x

shopt -s expand_aliases

### FILENAME: gen_asset_contract.sh
### USAGE: ./gen_asset_contract.sh PUBKEY

if [ -z "$1" ]; then
	echo "Missing PUBKEY: Usage ./gen_asset_contract.sh PUBKEY"
	exit 1
fi

###EDIT THESE VALUES###
DOMAIN="coins.b0xxy.net"
PUBKEY=$1
NAME="StackerNewsDemo-1"
PRECISION=8
TICKER="SND-1"
### END EDITING HERE ###

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

CONTRACT_HASH=$(python3 -c 'import json,sys; sys.stdout.write(json.dumps(json.loads(sys.argv[1]), sort_keys=True, separators=(",",":")))' "$CONTRACT" | sha256sum | head -c64 | fold -w2 | tac | tr -d "\\n")

echo CONTRACT=$CONTRACT
echo CONTRACT_HASH=$CONTRACT_HASH
  • Now we can use the script we saved before to generate the contract:
$ ./gen_asset_contract.sh 02fc3b404d9785d2dc26ea1867e25cf047702e45d29559a4657fafe8c0dd53877e

CONTRACT={"entity":{"domain":"coins.b0xxy.net"},"issuer_pubkey":"02fc3b404d9785d2dc26ea1867e25cf047702e45d29559a4657fafe8c0dd53877e","name":"StackerNewsDemo-1","precision":8,"ticker":"SND-1","version":0}
CONTRACT_HASH=a742ef224aedad0b26902822abd05f163ab965a1f5195d06ca8429b6d2a9ffdc
  • Finally export those variables via bash to use later:
export CONTRACT='{"entity":{"domain":"coins.b0xxy.net"},"issuer_pubkey":"02fc3b404d9785d2dc26ea1867e25cf047702e45d29559a4657fafe8c0dd53877e","name":"StackerNewsDemo-1","precision":8,"ticker":"SND-1","version":0}'

export CONTRACT_HASH=a742ef224aedad0b26902822abd05f163ab965a1f5195d06ca8429b6d2a9ffdc
  • STEP 3: Now Issuing our own asset is straight-forward, as its a single line command in the client. Let's issue 10 new tokens and set the supply to be fixed (no re-issuance)
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS issueasset 10 0 true $CONTRACT_HASH
  
{
  "txid": "8a12dd64c43de200cc7addb6c59f67bdbc6481ef1cc8b24253c7c1daba3c4e06",
  "vin": 0,
  "entropy": "3b8ded63f872d53d93b40ea415f3473b50c033a8e9abc1f0e7418e7f16e98dec",
  "asset": "8aa889f0bd16bab7d236ab6f3583481382bf976433360ff240cf22b79181a50f",
  "token": "2eb4da7c84a7b4db2944ead6b05041a5d8d8f4a73cd1413b140b44c4ec42fbdf"
}

Exploring our new Asset

  • Checking out our wallet we see:
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getwalletinfo

"balance": {"8aa889f0bd16bab7d236ab6f3583481382bf976433360ff240cf22b79181a50f": 10.00000000,
"bitcoin": 0.00140447,}

  • So publicly we know the following about our new asset:
    • We can see our CONTRACT_HASH has been saved
    • It cannot be re-issued (fixed supply token)
    • It has only been issued once
    • However both the issued amount and the circulating supply has been hidden from us.

It's nice to have hidden assets, but practically speaking they might not be useful in lots of situations. For instance, suppose that we wanted users to be able to verify the circulating amounts (i.e. to prevent debasement).

It is possible to 'unblind' this information to clients, but that would involve distributing an unblinding key to each client. Luckily there is a simple solution: we can simply issue the asset as unblinded - which will permit this information to be publicly available without affecting the security of the actual transactions:

Issuing a Non-Confidential Asset

  • Let's create another address5 and another asset and this time choose a different option (specifically, lets create this asset unblinded):
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getnewaddress "" legacy
lq1qqfgyq8zfyrsr296tqj7y23f9y4rz2q63hmhdf2mffwvg5yk9jxl8f0q4r7m0szy9m0wwe8224d856sas6enx6dp4w8ar7j06e

$ export NEWADD='lq1qqfgy...'
$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS getaddressinfo $NEWADD | jq '.pubkey'
02ada219afbd424afca568e01370161d68ab3aa0697cd759a6105c23b81d20b397

$ ./gen_asset_contract.sh 02ada219afbd424afca568e01370161d68ab3aa0697cd759a6105c23b81d20b397
CONTRACT={"entity":{"domain":"coins.b0xxy.net"},"issuer_pubkey":"02ada219afbd424afca568e01370161d68ab3aa0697cd759a6105c23b81d20b397","name":"StackerNewsDemo-1","precision":8,"ticker":"SND-1","version":0}
CONTRACT_HASH=2b14c6c7ff4009ce6cf4dfa59099fdda4f27459d153b45fe91790e9d1bca42a0

$ export CONTRACT='{"entity":{"domain":"coins.b0xxy.net"},"issuer_pubkey":"02ada219afbd424afca568e01370161d68ab3aa0697cd759a6105c23b81d20b397","name":"StackerNewsDemo-1","precision":8,"ticker":"SND-1","version":0}'
$ export CONTRACT_HASH=2b14c6c7ff4009ce6cf4dfa59099fdda4f27459d153b45fe91790e9d1bca42a0

$ sudo ./scripts/app compose elements exec node elements-cli -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS issueasset 10 0 false $CONTRACT_HASH

{
  "txid": "225f3ed16457467673fd64f3577031b91be370615b3feba53e1cc0b256768944",
  "vin": 0,
  "entropy": "fc4b02ee3892faaef2ddb673d437910ca75ca4d4e5ff4c1cb6ef1e3a4667a942",
  "asset": "41c19a473c71298a28342ccbf6fcbd3042cae8607b3b79d336b4c02e89ba2c66",
  "token": "10178003de4d2141fc318e3a61a7c8e1d43d3842a87fb8fb18fc1c1b09d081cb"
}

...and checking again what the Liquid network knows about it

  • Now we can see the total issued amount / supply4

  • Let's try sending 2 of these unblinded assets to another wallet (my Blockstream Green mobile wallet) and see what is visible on the network.

    • $sudo ./scripts/app compose elements exec node elements-cli -named -rpcuser=$E_RPCUSER -rpcpassword=$E_RPCPASS sendtoaddress address="VJLJZQ...." amount=2.0 assetlabel="409b1d0...."

1122b7ef721864b0682120d474cc5a68e6c58fc62a2482bdc5195782f4503e7a

This is interesting: So although the transaction is visible, we can't see what was actually transferred (even though the details of the asset are visible, it is still hidden inside the transaction). Further, where is my Green Wallet address (ie. VJLJZQ...)? We can see 3 output addresses, presumably these are: Change, Fee, and Recipient...but which is my Green Wallet?

So now this shows us that the Confidential Address: VJLJZQ2... corresponds to the Unconfidential Address: H53F2D.... Through the magic of one-way hashes, if we know the "Confidential" address we can then derive its public address....however just by looking at the public blockchain, there is no way to reverse that hash.

Now, lets look at how things look on the Green mobile wallet:

We can see that we've received 200,000,000 'sats' of our custom asset. However, notice how there is no label. Whereas, for L-BTC and Tether it shows complete with name and ticker symbol. Showing just the contract address is not very user friendly. Let's see if we can remedy.

Registering our Asset

The publication of asset metadata info on Liquid works on a .well-known system where specific files are published on a webserver that you control. There are a few different steps to accomplish this, so lets begin

  • Lets set a few environmental variables to make things easier:

    $ ASSET_REGISTRY_URL="https://assets.blockstream.info/"

    $ export DOMAIN="nulldata.org" (NOTE: Needs to match what you set in Bash script above)

    $ export ASSET="409b1d0cb614822ea703576b71958ab051d66ff6708b368f378f2ffea458d15a" (NOTE: Needs to match the asset you are registering)

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

Running the above will result in a file named liquid-asset-proof-409b1d0c... being created, and inside that file it will say Authorize linking the domain name nulldata.org to the Liquid asset 409b1d0c...

First you need to place this liquid-asset-proof file in the root directory of your domain under a folder named .well-known

Before you proceed to the next step, please verify that they above file is reachable. Go to an external machine and issue:

$ curl https://nulldata.org/.well-known/liquid-asset-proof-409b1d0cb614822ea703576b71958ab051d66ff6708b368f378f2ffea458d15a

...and it should output the Authorize linking the domain text.

  • Now we can push the data to blockstreams server, lets create a script: $ echo "curl $ASSET_REGISTRY_URL --data-raw '{\"asset_id\":\"$ASSET\",\"contract\":$CONTRACT}'" > register-asset.sh

    Verify that this script looks sane, mark it as executable and run it.


  1. The values E_RPCUSER and E_RPCPASS, where environmental variables we set in Part I ↩︎

  2. Technically just running elements-cli issueasset 10 0 is enough to issue an asset, however there will be limitations later on when trying to register the asset, burn, remove, etc. ↩︎

  3. Using a legacy address for this task imposes no real implications to your security since its only used to remove assets from the registry. Its possible to do this with a non-legacy address, but more steps are involved and we will need to use other tools rather than what's already built-in to Elements node, so we will just use a legacy address for this. ↩︎

  4. Like in bitcoin, 1 integer unit is 100,000,000 base units (not sats, but equivalent concept) - So in this case issuing "10" assets is really issuing 1,000,000,000 units (10 x 100,000,000). ↩︎

  5. So each asset is held in a separate address ↩︎