Liquid Asset Registry

The Liquid Asset Registry is an open source repo maintained by Blockstream that can be used to feed Liquid asset metadata (e.g. asset name, ticker...) to wallets, block explorers and other Liquid services

Using Blockstream's Liquid Asset Registry

Simple Issuance Example


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 three files:

  • A liquid-asset-proof-<asset-id> file that must be placed on the server of the registered domain over the entire lifecycle of the asset.

  • A register_asset_<asset-id>.sh file that, when run, will post the asset registration data to Blockstream's Liquid Asset Registry.

  • A delete_asset_<asset-id>.sh file that, when run, will delete the asset from the registry.

The use of the output files will be explained later.

There are seven fields that need setting within the script and one (collection) that can optionally be provided:

  • NAME The name of the asset as it will appear in the registry and applications that use asset registry data, such as the Blockstream Explorer, the Mempool Liquid Explorer or Liquid wallets like Blockstream Green. Length must be 5 to 255 ASCII characters.

  • TICKER The ticker you would like to assign to the asset or, in the case of a unique asset such as an NFT, a serial number or human-readable unique string. Length must be 3 to 24 characters. Valid characters are: a-z A-Z 0-9 . and -. If provided, ticker has to be unique when combined with the domain field. If the ticker is empty there is no such constraint.

  • DOMAIN The domain that will be used to verify the asset. Must be a valid domain name format and only supports (sub)domain names, for example: example.com or sub.example.com. Do not include the http/s or www prefixes. To verify you control the entity domain name, you'll need to place a file on your webserver. Note that serving the file with https is required, except for .onion hidden services. The file should be made available at https://<domain>/.well-known/liquid-asset-proof-<asset-id>, with the following contents (see the code example below for an example of file content generation): Authorize linking the domain name <domain> to the Liquid asset <asset-id>

  • 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. Represents the number of digits after the decimal point, i.e. 0 for non-divisible assets or 8 for BTC-like divisible assets. See the examples below.

Precision examples:

A PRECISION value of 0 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 100 when viewed in an app that uses the asset registry data, which shifts the decimal 0 places left from the sats value.

A PRECISION value of 2 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 1.00 when viewed in an app that uses the asset registry data, which shifts the decimal 2 places left from the sats value.

A PRECISION value of 8 for an ASSET_AMOUNT of 0.00000100 will create 0.00000100 when viewed from Liquid Core and 0.00000100 when viewed in an app that uses the asset registry data, which shifts the decimal 8 places left from the sats value. This equates to BTC units.

The default is 0 and the maximum value is 8.

Please note that precision is not taken into account when displaying reissuance token amounts. So if you issue your asset with 1 reissuance token it will always show as 100 000 000 (the amount 1.00 in sats) via Blockstream Explorer and other apps using the registry. In Elements it will show as 1.00000000.

  • PUBKEY Holds the public key value that goes into the issuer_pubkey registration data field. The code example derives this from a new address generated by a private key in the node's wallet.dat file. For safety, this file should be backed up after running the script. Any ECDSA secp256k1 public key could be used in its place, which may be of use when the issuing entity is not the same as the entity running the script. You should ensure that, whatever PUBKEY value you use to set the registration data's issuer_pubkey field, it is recorded somewhere and that the corresponding private key is backed up for potential future use in authorizing amendments to the registry data. Note that the script sets the PUBKEY variable's value by calling getaddressinfo with a new address generated by the node's wallet. The value of PUBKEY is then used to set the issuer_pubkey registration data field. The issuer_pubkey data field is used by the Asset Registry to verify ownership when removing or updating registry data in the future.

  • COLLECTION An optional field to relate a group of uniquely issued assets, as is often helpful for a collection of NFTs. Length must be 1 to 255 ASCII characters. If sub-categorization is helpful, consider a convention such as a / delimiter, e.g. "Top-level-collection/Sub-collection".

Issuing with a contract hash

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 and take a back up of the script before you run it as you may wish to refer to it in the future to track how you created the registry data.

#!/bin/bash
set -x

shopt -s expand_aliases

# ASSUMES elementsd IS ALREADY RUNNING

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

# Amend the following:
NAME="your asset name here"
TICKER="your ticker"
# Do not use a domain prefix in the following:
DOMAIN="domain.here"
# 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

# Optional collection parameter. Set to "" to ignore:
COLLECTION="Your-top-level-collection/Your-sub-collection"

# Asset registry url
# When using test-net replace to
# https://assets-testnet.blockstream.info/
ASSET_REGISTRY_URL="https://assets.blockstream.info/"

# Don't change the following:
VERSION=0

# Change the following to point to your elements-cli binary and liquid live data directory (default is .elements).
alias e1-cli="elements-cli -datadir=$HOME/.elements"

# We will hash using sha256sum if available, openssl otherwise (other options are available)
which sha256sum >/dev/null 2>&1 && alias sha256hash="sha256sum | sed 's/ .*//g'" || alias sha256hash="openssl dgst -sha256 | sed 's/.*= //g'"

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

# Exit on error
set -o errexit

# We validate characters in the domain
echo $DOMAIN| grep -q '[^a-z0-9\.-]' && RV=$? || RV=$?
if [ $RV -eq 0 ];then
    echo "invalid chars detected in the domain, exiting...."
    exit -1
fi

# We will be using the issueasset command and the contract_hash argument:
# issueasset <assetamount> <tokenamount> <blind> <contract_hash>

# As we need to sign the deletion request message later we need
# a legacy address. If you prefer to generate a pubkey and sign
# outside of Elements you can use a regular address instead.
NEWADDR=$(e1-cli getnewaddress "" legacy)

VALIDATEADDR=$(e1-cli getaddressinfo $NEWADDR)

PUBKEY=$(echo $VALIDATEADDR | jq -r '.pubkey')

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:
if [ "$COLLECTION" = "" ]; then
    CONTRACT='{"entity":{"domain":"'$DOMAIN'"},"issuer_pubkey":"'$PUBKEY'","name":"'$NAME'","precision":'$PRECISION',"ticker":"'$TICKER'","version":'$VERSION'}'
else
    CONTRACT='{"collection":"'$COLLECTION'","entity":{"domain":"'$DOMAIN'"},"issuer_pubkey":"'$PUBKEY'","name":"'$NAME'","precision":'$PRECISION',"ticker":"'$TICKER'","version":'$VERSION'}'
fi

CONTRACT_HASH=$(echo -n "${CONTRACT}" | sha256hash)

# Reverse the hash --- expects an even length
TEMP=$CONTRACT_HASH
LEN=${#TEMP}
until [ $LEN -eq "0" ]; do
    END=${TEMP:(-2)}
    CONTRACT_HASH_REV="$CONTRACT_HASH_REV$END"
    TEMP=${TEMP::$((${#TEMP} - 2))}
    LEN=$((LEN-2))
done

# Issue the asset and pass in the contract hash
IA=$(e1-cli issueasset $ASSET_AMOUNT $TOKEN_AMOUNT false $CONTRACT_HASH_REV)

# Details of the issuance...
ASSET=$(echo $IA | jq -r '.asset')
TOKEN=$(echo $IA | jq -r '.token')
ISSUETX=$(echo $IA | jq -r '.txid')

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

# Output the proof file - you need to place this on your domain.
echo "Authorize linking the domain name $DOMAIN to the Liquid asset $ASSET" > liquid-asset-proof-$ASSET

# Create the bash script to run after you have placed the proof file on your domain
# that will call the registry and request the asset is registered.
echo "curl $ASSET_REGISTRY_URL --data-raw '{\"asset_id\":\"$ASSET\",\"contract\":$CONTRACT}'" > register_asset_$ASSET.sh

# Create the bash script to delete the asset from the registry (if needed later)
PRIV=$(e1-cli dumpprivkey $ASSET_ADDR)
SIGNED=$(e1-cli signmessagewithprivkey $PRIV "remove $ASSET from registry")
echo "curl -X DELETE $ASSET_REGISTRY_URL$ASSET -H 'Content-Type: application/json' -d '{\"signature\":\"$SIGNED\"}'" > delete_asset_$ASSET.sh

# Stop the daemon
e1-cli stop
sleep 10

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-<asset-id> 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_<asset-id>.sh script.

For example, if your domain was 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:

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

Once that file is accessible you can then run the register_asset_<asset-id>.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.

You can remove the asset from the registry by running delete_asset_<asset-id>.sh.

Asset Registry Specifications


Contract JSON Fields

Required fields:

  • version: currently 0
  • issuer_pubkey: the hex-encoded public key of the issuer
  • name: 1-255 ASCII characters
  • entity: the online entity linked to this asset which currently only supports (sub)domain names in the form of a nested object with {"domain":"foobar.com"}

Optional fields:

  • ticker: 3-24 characters consisting of a-z, A-Z, 0-9, . and -.
    If provided, has to be unique within the entity (domain name) namespace.
  • precision: number of digits after the decimal point, i.e. 0 for non-divisible assets or 8 for BTC-like. defaults to 0.
  • collection: 1-255 ASCII characters.

Example:

  {  
    "version": 0,  
    "issuer_pubkey": "037c7db0528e8b7b58e698ac104764f6852d74b5a7335bffcdad0ce799dd7742ec",  
    "name": "Foo Coin",  
    "ticker": "FOO",  
    "entity": { "domain": "foo-coin.com" },  
    "precision":8  
  }

Contract Hash

The contract hash is the (single) sha256 hash of the contract json document, canonicalized to have its keys sorted lexographically.

The canonicalization can be done with Perl like so:

  $ perl -e 'use JSON::PP; my $js = JSON::PP->new; $js->canonical(1); print $js->encode($js->decode($ARGV[0]))' '{"version":0,"name":"FOO",...}'

Or with Python like so:

 $ python -c 'import json,sys; sys.stdout.write(json.dumps(json.loads(sys.argv[1]), sort_keys=True, separators=(",",":")))' '{"version":0,"name":"FOO",...}'

Or with JavaScript using the json-stable-stringify library.

The resulting sha256 hash needs to be reversed to match the format expected by elementsd, similarly to the reverse encoding of txids and blockhashes as originally implemented for bitcoin by Satoshi.
This can be done in a unix environment like so:

echo <hash> | fold -w2 | tac | tr -d "\\n"

All together:

  $  CONTRACT='{"version":0,"ticker":"FOO","name":"Foo Coin"}'  
  $  CONTRACT_HASH=$(python -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_HASH

Domain Ownership Proof

To verify you control the entity domain name, you'll need to make a file on your webserver available at https://<domain>/.well-known/liquid-asset-proof-<asset-id>, with the following contents:

Authorize linking the domain name <domain> to the Liquid asset <asset-id>

Note that serving the file with https is required, except for .onion hidden services.

Issuing & Registering Assets

Prepare your contract json and get your contract hash. You can verify their validity before issuing the asset using the validation endpoint:

 $  curl <https://assets.blockstream.info/contract/validate> -H 'Content-Type: application/json'  
        -d '{"contract": <your-contract-json>, "contract_hash": "<your-contract-hash>"}'

If everything seems good, issue the asset using elementsd's rawissueasset with your hash as the contract_hash parameter and take note of the resulting asset_id. Add the domain ownership proof (as described above) and, once the issuance transaction confirms, submit the asset to the registry:

$  curl <https://assets.blockstream.info/> -H 'Content-Type: application/json'  
        -d '{"asset_id": "<asset-id>", "contract": <contract-json>}'

Deleting Asset Registry Data

Deleting an asset from the Blockstream Asset Registry only removes its metadata from the registry. It does not, and can not, delete the asset itself from the Liquid Blockchain.

To delete an asset from the Blockstream Asset Registry, you need to sign and send a message authorizing the deletion. The message must be signed using the private key for the address associated with the issuer_pubkey used in registration.

You can use Elements to sign the message. First, dump the private key for the address that was used to get the pubkey:

elements-cli dumpprivkey <address>

Use the returned private key to sign a message of the format remove <asset-id> from registry.

The call to elements to sign the message is:

elements-cli signmessagewithprivkey <private key> "remove <asset-id> from registry"

The result of this is a base64 encoded signature. Post the signed message/signature to the asset registry like so:

$  curl -X DELETE https://assets.blockstream.info/<asset-id> -H 'Content-Type: application/json' \
    -d '{"signature":"<base64-encoded-signature>"}'

You will receive an Asset deleted message in response.

The asset will immediately be removed from the Liquid Asset Registry data. It may take a short while before the data deletion is replicated in the Blockstream Explorer itself.

Liquid Asset Registry - additional resources