Blockstream AMP API Tutorial (Python)

This is a full example of how to use the Blockstream AMP API with Python. The Python examples below can be modifed to work in other languages by using the template examples written in Ruby, Node.js, C#, and Go here.

The Issuer-Tracked Asset type doesn’t place any restrictions on the recipient and there is no need for the issuer to register users in advance. Ownership can be taken by anyone who receives the asset to their Blockstream Green wallet’s ‘Managed Assets’ account. Transfers of the asset between Blockstream Green users is tracked by Blockstream AMP using the user’s Blockstream Green Account ID (GAID). The issuer can view GAID balances for their asset, while the transactions remain private on the Liquid blockchain.

The Transfer-Restricted Asset type requires that the issuer registers the GAID of any potential owners within Blockstream AMP prior to the user being able to receive or transact it. This asset type can be used to issue and manage security tokens, amongst other things. This asset type also tracks and reports on ownership by GAID. This is the asset type we will use in this tutorial.

This tutorial outlines the most common process of creating and managing Transfer-Restricted assets using the Blockstream AMP API. The process for issuing and managing Issuer-Tracked assets differs only slightly; the issuance command itself is slightly different and there is no need to create category and registered user records. For the purpose of this tutorial, we will assume the issuer is issuing a security token, but there are many other use cases for Transfer-Restricted assets.

We’ll walk through the code and describe the process in more detail when needed. You can also reference the API Specification for a full list and description of all the Blockstream AMP API endpoints. The examples are in Python but can be adapted to work with any language.

An example usind C# and .NET can be found here.

Definitions of the terms used in this page, as well as an overview of how Blockstream AMP uses the Liquid network to issue and manage security tokens, can be found in the Liquid Asset Registry.

A brief overview of Blockstream AMP can also be found on the Blockstream website.

Managing security tokens as Transfer-Restricted assets

The simplest case of issuing an Transfer-Restricted Asset, and managing it as a security token using Blockstream AMP, is outlined below.

  • Issue a Liquid Asset representing a Security.

  • Create Asset Categories.

  • Associate categories with the asset to create asset requirements.

  • Create registered users.

  • Associate categories with registered users to define eligibility.

  • Assign amounts of the asset to eligible registered users.

  • Distribute the assigned amounts to the registered user’s wallets.

This process is followed by the tutorial code. A subset of the API can be used to manage Tracked Assets.

Frequently Used Terms

The most commonly used API endpoints are those which relate to the Asset. An asset in Blockstream AMP represents the actual Security, while amounts of the asset represent tokens for the Security. The ‘Asset’ terminology is inherited from the Liquid Network’s Issued Assets. It should be noted that there is another type of asset used by Liquid called the Reissuance Token. Reissuance Tokens allow the issuer to create more of the asset, and can only be created at the point of initial asset issuance. Reissuance Tokens and Security Tokens should not be confused with one another.

Setting up a Treasury Wallet

Issuers should first set up and configure Elements Core. The Elements wallet will be used to receive issued assets, reissuance tokens, and carry out actions such as distributions and reissuances. Elements will be default connect to the live Liquid network.

To connect to the Liquid Testnet see the Testnet section, which shows how to connect Elements to the Liquid Testnet and call the AMP Testnet instance, which itself performs all actions against Liquid Testnet.

Install elements from the Elements Core release page.

If it does not already exist, create a file named elements.conf within the hidden .elements directory within your home folder.

Paste the following into the elements.conf file, changing the 3 rpc* entries before saving. An example RPC port that can be used is 18885.

rpcuser=yourrpcusername
rpcpassword=yourrpcpassword
[liquidv1]
rpcport=yourrpcport
daemon=1
server=1
listen=1
txindex=1
validatepegin=0
fallbackfee=0.00000100

Start your Elements node, by starting either elements-qt (the graphical user interface) or elements-d (the daemon). The settings above allow you to use either elementsd or elements-qt and have the elements-cli client, and any scripts you need to run to perform actions like distributions, be able to call the node using RPC.

Note

All distributions, reissuances, and burns of Blockstream AMP managed assets that are held by the issuer’s Liquid node must be done using the Blockstream AMP API. This ensures that asset UTXOs remain tracked through Blockstream AMP. Sending the asset or the associated reissuance token outside of Blockstream AMP will result in lost, untracked outputs. Refer to the Using the distribution, reisuance and burn script section for details of how to use a script to perform these actions.

Accessing Blockstream AMP API

To use the Blockstream AMP API, a username and password must be provided so that the server can authenticate requests. You can request account access by emailing blockstream-amp-support@blockstream.com.

There are both Live and Testnet instances of the AMP API. Both connect to the relevant Liquid network and Green server backend.

For the Live API use:

API_URL='https://amp.blockstream.com/api'

For the Testnet API use:

API_URL='https://amp-test.blockstream.com/api'

API Authentication

Before any API calls can be made, you need to obtain an Authorization Token using the Blockstream AMP user account details you were provided with.

The authorization token can then be set in the header of subsequent requests. An example of how to obtain the Authorization Token is shown below.

def get_auth_token_header(username, password):
    # api/user/obtain_token

    url = f'{API_URL}/user/obtain_token'
    headers = {'content-type': 'application/json'}
    payload = {'username': username, 'password': password}
    response = requests.post(url, data=json.dumps(payload), headers=headers)
    assert response.status_code == 200
    json_data = json.loads(response.text)
    token = json_data['token']
    return {'content-type': 'application/json', 'Authorization': f'token {token}'}

Once we have an Authorization Token we can begin the process of managing registered users, issuing assets as Security Tokens, distribution and reporting on ownership etc.

Registered Users

Blockstream AMP allows for restricting asset transfers through the use of registered user records. Registered user records that have a Blockstream Green Managed Assets account ID (GAID) saved against them are able to receive the Security Token, although other restrictions might apply through Categories, explained later.

Note

To find your Managed Assets Account ID within the Blockstream Green mobile app, create a Managed Assets account within a wallet and click ‘Receive’.

Adding, Viewing, Updating and Listing Registered Users

Before you actually create registered user records you may want to validate the registered user data you have collected. The validation of the Blockstream Green Managed Assets Account ID (GAID) can be done using the api/gaids/{gaid}/validate as shown below. This can be useful if you want to validate registered user GAIDs before trying to create registered users.

gaid = 'INVALIDZZ6SqZZvEpAeVz9QmfHqhh'
url = f'{API_URL}/gaids/{gaid}/validate'
response = requests.get(url, headers=headers)
assert response.status_code == 200
gaid_validation = json.loads(response.text)
gaid_is_valid = gaid_validation['is_valid']
print(f'\nValidating GAID \'{gaid}\'')
print(f'GAID is Valid: {gaid_is_valid}')

Although validating the GAID before registered user record creation is useful it it not necessary as registered_users/add also validates the GAID before allowing the registered user record to be created. You must add a valid GAID before running the example add below.

Creating the registered user record:

url = f'{API_URL}/registered_users/add'
registered_user_name = f'User {rand_string()}'
payload = {'is_company': False,
    'name': registered_user_name,
    'GAID': '<gaid here>'
}
response = requests.post(url, data=json.dumps(payload), headers=headers)
assert response.status_code == 201
registered_user = json.loads(response.text)
registered_user_id = int(registered_user['id'])
print('\nNew Registered User (individual):')
print('---------------------------------')
print(json.dumps(registered_user, indent=4))

The use of rand_string in the full example code is just to ensure that multiple runs of the code block do not fail with duplicate registered user errors, it is not intended for use when importing real registered user data.

Issuers can also retrieve a list of registered users and the details of a specific registered user. You can also delete registered users (not shown).

# Registered user details
# api/registered_users/{registered_user_id}
url = f'{API_URL}/registered_users/{registered_user_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
registered_user_details = json.loads(response.text)
print('\nRegistered user details:')
print('------------------------')
print(json.dumps(registered_user_details, indent=4))

# Registered user update name
# api/registered_users/{registered_user_id}/edit
url = f'{API_URL}/registered_users/{registered_user_id}/edit'
payload = {'name': f'{registered_user_name} updated'}
response = requests.put(url, data=json.dumps(payload), headers=headers)
assert response.status_code == 200
registered_user_details = json.loads(response.text)
print('\nRegistered user details:')
print('------------------------')
print(json.dumps(registered_user_details, indent=4))

# Registered users list
# api/registered_users
url = f'{API_URL}/registered_users'
response = requests.get(url, headers=headers)
assert response.status_code == 200
registered_users = json.loads(response.text)
print('\nRegistered users list:')
print('----------------------')
for registered_user in registered_users:
    print(json.dumps(registered_user, indent=4))

Categories

Blockstream AMP is able to restrict the transfer of assets between sender and receiver using user-defined Categories. Categories can be viewed as asset requirements that registered users need to be associated with in order to qualify for assignment and distributions of the asset.

Registered users can be assigned to one or more categories. By associating categories with an asset you can define the requirements for a registered user to receive the asset. Categories can be used to classify registered users in various ways, such as by jurisdiction, accreditation status, or any other arbitrary classification that can be used to satisfy the requirements of the asset.

Issuers then create categories required for the issued asset. The simplest method of doing this is to define an individual whitelist for each asset, however multiple assets can share the same whitelist.

Adding, Viewing, Updating and Listing Categories

Creating a Category:

# Category add
# api/categories/add
url = f'{API_URL}/categories/add'
category_name = f'Category {rand_string()}'
payload = {'name': category_name,
    'description':'Category for example'}
response = requests.post(url, data=json.dumps(payload), headers=headers)
assert response.status_code == 201
category = json.loads(response.text)
category_id = int(category['id'])
print('\nNew category:')
print('-------------')
print(json.dumps(category, indent=4))

Issuers can also retrieve a list of categories and the category details for a specific category. You can also edit a category and delete a category (not shown).

# Category details
# api/categories/{category_id}
url = f'{API_URL}/categories/{category_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
category_details = json.loads(response.text)
print('\nCategory details:')
print('-----------------')
print(json.dumps(category_details, indent=4))

# Category update
# api/categories/{category_id}/edit
url = f'{API_URL}/categories/{category_id}/edit'
payload = {'name': f'{category_name} updated', 'description':'Category example updated'}
response = requests.put(url=url, data=json.dumps(payload), headers=headers)
assert response.status_code == 200
category_details = json.loads(response.text)
print('\nCategory details:')
print('-----------------')
print(json.dumps(category_details, indent=4))

# Category list
# api/categories
url = f'{API_URL}/categories'
response = requests.get(url, headers=headers)
assert response.status_code == 200
categories = json.loads(response.text)
print('\nCategories list:')
print('----------------')
for category in categories:
    print(json.dumps(category, indent=4))

Associating Registered Users with Categories

The issuer can then associate registered users with the categories. If multiple categories have been associated with the asset, registered user membership to all categories is required. Registered users can be added to the category and removed from the category at any point.

In order to receive security tokens, a registered user will need to provide a Blockstream Green Managed Assets Account ID (GAID). This can be provided during registered user creation or by editing the registered user.

Note

A wallet using a Blockstream Green Managed Assets account holds assets in a multi-signature scheme that requires the Blockstream Green server to act as co-signer when authorizing transfers. Registered users who do not have wallets set up can still be added to Blockstream AMP, but cannot receive any distributions or transfers of assets until their wallet is registered. Each wallet is able to use multiple addresses to avoid privacy leaks about other holdings.

# Registered user category - add registered user to category
# api/categories/{categoryId}/registered_users/{registeredUserId}/add
url = f'{API_URL}/categories/{category_id}/registered_users/{registered_user_id}/add'
response = requests.put(url, headers=headers)

Note that the registered users list and registered user details views both show the categories associated with the registered user/s.

# Registered user details
# api/registered_users/{registered_user_id}
url = f'{API_URL}/registered_users/{registered_user_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
registered_user_details = json.loads(response.text)
print('\nRegistered user details:')
print('------------------------')
print(json.dumps(registered_user_details, indent=4))

Categories also need to be associated with Assets before amounts of the asset can be assigned to any registered users, this is covered within the next section.

Issuing Assets as Security Tokens

Security Tokens will be referred to as assets within this tutorial, as the term ‘token’ also refers to a special type of asset, the ‘reissuance token’, that permits the reissuance of an asset on the Liquid network.

Issuers choose the initial asset supply, the destination address is where the asset and any reissuance tokens will be received, and use the Blockstream AMP API to issue the asset. When the asset is issued, Blockstream AMP will issue it directly to the treasury addresses provided.

Issuing an Asset

If the asset contains registration data (ticker, domain, pubkey) later updates will not be possible as they are committed to the issuance transaction. For more information on the use of these three fields, please see the Liquid Asset Registry section.

If is_reissuable is true then reissuance_amount and reissuance_address must also be provided, and reissuance_address must be different from destination_address.

You will need to amend the following values from the example values given below:

name: The name of the Asset as it will appear in Blockstream AMP. Length must be 5 to 255 ascii characters.

amount: The amount of the asset to issue. Integer, minimum: 1, maximum: 2100000000000000.

is_confidential: If true, the issuance amount will not be readable on the Liquid blockchain. For most issuances it is expected that this will be False.

destination_address: An address generated by your Liquid node that will receive the issued asset.

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.

ticker: The ticker you would like to assign to the asset. Length must be 3 to 5 characters. Valid characters are a-z, A-Z, 0-9, . and -.

precision: The precision that will be applied by apps that use the Liquid Asset Registry. To help explain how all the various apps show amounts using the precision, an amount of 10000 issued through Blockstream AMP API with a precision of 2 would show as:

  • 10000 through any Blockstream AMP API, which always shows amounts as undivisible and at sats level.

  • 100.00 on Blockstream Green Mobile, Blockstream Green Desktop, and Blockstream Explorer, all of which use the precision held in the registry.

  • 0.00010000 in elements-cli RPC commands, where BTC is the base unit.

The default precision when issuing an asset using Blockstream AMP is 0.

Please note that when an asset is issued via Blockstream AMP with a reissuance token, the reissuance token is always seen in sats. So 1 reissuance token will show in elements as 0.00000001. Precision is not taken into account when displaying reissuance token amounts in apps using the Liquid Asset Registry, which will always show amounts in sats (1 in this example).

pubkey: Pubkey for the Liquid Asset Registry, must be a compressed pubkey in hex. You can obtian the pubkey from an Elements address using the Elements getaddressinfo rpc command.

is_reissuable: If true, the asset will be created as reissuable.

reissuance_address: The address that will receive the reissuance token if is_reissuable is set to True.

reissuance_amount: The amount of reissuance tokens to create if is_reissuable is set to True.

destination_address: An address generated by your Liquid node that will receive the issued asset for the example.

reissuance_address: An address generated by your Liquid node that will receive the reissuance token for the example.

transfer_restricted: If true, the asset can be transferred only between registered users registered with Blockstream AMP by the issuer, with a Blockstream Green Managed Assets account associated to them. If false, the asset can be transferred only between Blockstream Green Managed Assets accounts and no registered user record is needed. This second option enforces weaker rules, as anyone could create a Blockstream Green Managed Assets account, and then receive the asset. In both cases, the asset can also be received by or sent to the treasury wallet.

asset_destination_address = ''
reissuance_token_destination_address = ''

url = f'{API_URL}/assets/issue'
payload = {'name': 'An example asset with registration data',
            'amount': 17000000,
            'is_confidential': True,
            'destination_address': asset_destination_address,
            'domain': 'yourdomainforregistrationproof.com',
            'ticker': 'LSEXA',
            'precision': 0,
            'pubkey': '02' * 33,
            'is_reissuable': True,
            'reissuance_address': reissuance_token_destination_address,
            'reissuance_amount': 1,}
response = requests.post(url=url, data=json.dumps(payload), headers=headers)
assert response.status_code == 201
asset = json.loads(response.text)
asset_uuid = asset['asset_uuid']
print('\nNewly issued (registration enabled) asset:')
print('------------------------------------------')
print(json.dumps(asset, indent=4))

# Actions such as Reissuance, Authorized asset registration, Assignment
# require more than one confirmation of the issuance transaction:
time.sleep(3*60)

Note that in order for Blockstream AMP to track and control asset ownership, the asset must first be registered as an Authorized Asset with Blockstream Green.

It is worth noting that there is an assets/{asset_uuid}/delete API which does not destroy the asset on the Liquid network, instead it marks it as deleted within Blockstream AMP, so that it does not show up in any subsequent API calls. Some details of the asset cannot be edited as they are linked to either Liquid blockchain data or the Liquid Asset Registry.

Note

Blockstream AMP never holds the asset on the issuer’s behalf. The issuer must take precautions to safely store the private keys that control the treasury balance.

Other useful Asset related API to note:

# List issued assets
# api/assets
url = f'{API_URL}/assets'
response = requests.get(url, headers=headers)
assert response.status_code == 200
assets = json.loads(response.text)
print('\nIssued assets:')
print('--------------')
for asset in assets:
    print(json.dumps(asset, indent=4))

# Asset details
# api/assets/{asset_uuid}
url = f'{API_URL}/assets/{asset_uuid}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
asset = json.loads(response.text)
print(f'\nAsset details:')
print('--------------')
print(json.dumps(asset, indent=4))

Liquid Asset Registry

During issuance, the issuer can provide information that allows the asset to later be added to the Liquid Asset Registry (domain, tikcer, pubkey). By doing this, it can be made available to services that use the data, such as Blockstream Green. To enable registration, the issuer chooses a ticker, the domain name to associate and validate ownership of the asset registration, and a public key that can be used to update the registration data. After issuance, and after the domain proof has been set up, the assets/{asset_uuid}/register API can then be called.

The steps you need to follow are:

  1. Create a file named liquid-asset-proof-{asset_id} with no file extension. For example: liquid-asset-proof-a28d04f3e243a9a187f4a8b797be2f19a9c01b6ef4e1d65bfb6abbd6a2042097. The file contents should be of the format:

    Authorize linking the domain name {domain} to the Liquid asset {asset_id}

    For example: Authorize linking the domain name blockstream.com to the Liquid asset a28d04f3e243a9a187f4a8b797be2f19a9c01b6ef4e1d65bfb6abbd6a2042097

  2. Place the file so it is accessible via https://{domain}/.well-known/liquid-asset-proof-{asset_id}.

    For example https://blockstream.com/.well-known/liquid-asset-proof-a28d04f3e243a9a187f4a8b797be2f19a9c01b6ef4e1d65bfb6abbd6a2042097.

  3. Make a call to /assets/{asset_uuid}/register. Blockstream AMP will call the registration service for you and return the results. Any error will likely be because the proof file cannot be read correctly.

The asset will then be registered.

Register the asset with Blockstream Green

The asset must also be registered with Blockstream Green in order for the Blockstream Green server to authorize and co-sign valid transfers. If you do not register the asset with Blockstream Green you will not be able to track and control ownership of your asset. The code to register an asset with Blockstream Green is shown below. Note that this can only be done after the transaction in which the asset was issued has > 2 confirmations.

# Register the asset as an Authorized Asset
# api/assets/{asset_uuid}/register-authorized
url = f'{API_URL}/assets/{asset_uuid}/register-authorized'
response = requests.get(url, headers=headers)
assert response.status_code == 200
register_authorized = json.loads(response.text)
print('\nRegister authorized summary:')
print('----------------------------')
print(json.dumps(register_authorized, indent=4))

Associating Assets with Categories

Issuers then add categories to the asset, so that they become requirements that registered users will need to satisfy in order to be valid for assignments of the asset. Categories can be added to the asset or removed from the asset at any point.

# Add the Category to the Asset as a requirement
# api/categories/{categoryId}/assets/{assetUuid}/add
url = f'{API_URL}/categories/{category_id}/assets/{asset_uuid}/add'
response = requests.put(url=url, headers=headers)
category = json.loads(response.text)
category_assets = category['assets']
print('\nAssets in Category:')
print('-------------------')
print(json.dumps(category_assets, indent=4))

Reissuing an Asset

To issue more of an asset, the reissue request endpoint is called. This returns reissuance data that must be run against the treasury’s node using the blockstream-amp-confirm.py Python client, which is provided as part of account creation. The client will perform the reissuance, wait for sufficient blocks, and then confirm the reissuance transaction by sending transaction information back to Blockstream AMP. This ensures that asset ownership is tracked properly. Refer to the Using the distribution, reisuance and burn script section for details of how to use the script.

# Reissue some of the asset.
# Request the data needed to carry out a reissuance.
# api/assets/{assetUuid}/reissue-request
# Returns json data that should be saved to file. The path to the file should be passed as
# an argument to a Python client that carries out the reissuance itself. The client will
# be supplied to users of the api and will handle the reissuance transaction and post back
# the resulting transaction data to the /assets/{assetUuid}/reissue-confirm endpoint after
# it has sufficient confirmations.
# NOTE: Before the reissuance can be made, the transaction in which the asset was issued
# needs to have had at least 2 confirmations on the Liquid blockchain or the reissuance
# will fail.
url = f'{API_URL}/assets/{asset_uuid}/reissue-request'
payload = {'amount_to_reissue':1}
response = requests.post(url=url, data=json.dumps(payload), headers=headers)
assert response.status_code == 200
reissuance_data = json.loads(response.text)
# The json data in reissuance_data would then be saved to a file for use in the Python client
# that carries out the reissuance itself.
print(json.dumps(reissuance_data, indent=4))

Issuers can also retrieve a list of reissuances using the /assets/{assetUuid}/reissuances API, which shows transaction details for reissuances that have been confirmed on the Liquid network.

Destroying (burning) amounts of the asset

As well as being able to increase the circulating supply of an asset (through a reissuance), the issuer can also destroy, or ‘burn’, amounts of the asset. This is initiated using the /assets/{assetUuid}/burn-request API in conjunction with the blockstream-amp-confirm.py script. Refer to the Using the distribution, reisuance and burn script section for details of how to use the script.

Assignment

Assignment is the act of pre-allocating amounts of an asset to registered users for future distribution on the Liquid network. Issuers are free to modify an assignment as many times as necessary before it is marked for distribution. This is done by deleting the existing assignment and recreating it with amended amounts. Assignment does not require that registered users have a wallet registered but, without it, any subsequent distribution cannot begin. Assignment does not change Liquid wallet balances and is only recorded within Blockstream AMP.

# Assign some of the asset to the registered user
# api/assets/{asset_uuid}/assignments/create
# The assignment will be created as ready for distribution
# NOTE: Before the assignments can be made, the transaction in which the asset was issued
# needs to have had at least 2 confirmations on the Liquid blockchain or the assignment
# will fail with a warning about insufficient confirmed funds.
url = f'{API_URL}/assets/{asset_uuid}/assignments/create'
payload = {'assignments': [{
        'registered_user': registered_user_id,
        'amount': 1,
        'ready_for_distribution': True}]}
response = requests.post(url=url, data=json.dumps(payload), headers=headers)
assert response.status_code == 200
assignment = json.loads(response.text)
assignment_id = assignment[0]['id']
print('\nAsset Assignment:')
print('-----------------')
print(json.dumps(assignment, indent=4))

# Assignment details
# api/assets/{asset_uuid}/assignments/{assignment_id}
url = f'{API_URL}/assets/{asset_uuid}/assignments/{assignment_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
assignment = json.loads(response.text)
print('\nAssignment details:')
print('-------------------')
print(json.dumps(assignment, indent=4))

# Registered user details
# api/registered_users/{registered_user_id}
url = f'{API_URL}/registered_users/{registered_user_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
registered_user_details = json.loads(response.text)
print('\nRegistered user details:')
print('------------------------')
print(json.dumps(registered_user_details, indent=4))

# Asset assignments list
# api/assets/{asset_uuid}/assignments
# The assignments for the asset. Expect this to be empty unless you have
# already created assignments.
url = f'{API_URL}/assets/{asset_uuid}/assignments'
response = requests.get(url, headers=headers)
assert response.status_code == 200
assignments = json.loads(response.text)
print('\nAsset assignments:')
print('------------------')
for assignment in assignments:
    print(json.dumps(assignment, indent=4))

Distribution

To allow distribution to begin, the asset must have first been registered with Blockstream Green as an Authorized Asset. The code to do this has already been shown above.

To start the distribution of the token on the Liquid network to registered user wallets, a distribution is created, as shown in the code below. This marks the assignments as pending distribution and returns distribution data that must be run against the treasury’s node using the provided Python client. The client will perform the distribution, wait for sufficient blocks, and then confirm the distribution transaction by sending transaction information back to Blockstream AMP. This ensures that asset ownership is tracked properly. Refer to the Using the distribution, reisuance and burn script section for details of how to use the script.

An Issuer can cancel the distribution at any point after the script is requested, as long as the script has not been executed and the distribution transaction has not been broadcast on the Liquid Network.

# Request the data needed to carry out a distribution.
# api/assets/{asset_uuid}/distributions/create/
# Returns json data that should be saved to file. The path to the file should be passed as an
# argument to a Python client that carries out the distribution itself. The client will be
# supplied to users of the api and will handle the distribution transactions and post back
# the resulting transaction data to the distributions/confirm endpoint after it has
# sufficient confirmations.
# NOTE: The url intentionally ends with a '/'
url = f'{API_URL}/assets/{asset_uuid}/distributions/create/'
response = requests.get(url, headers=headers)
assert response.status_code == 200
distribution_data = json.loads(response.text)
# The json data in distribution_data would then be saved to a file for use in the Python client
# that carries out the distribution itself.
print(json.dumps(distribution_data, indent=4))

Issuers can view distribution details of a specific distribution.

# The Assignment will now have a distribution_uuid:
# Assignment details
# api/assets/{asset_uuid}/assignments/{assignment_id}
url = f'{API_URL}/assets/{asset_uuid}/assignments/{assignment_id}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
assignment = json.loads(response.text)
print('\nAssignment details:')
print('-------------------')
print(json.dumps(assignment, indent=4))

# Distribution details
# api/assets/{asset_uuid}/distributions/{distribution_uuid}
# Displays details for a distribution that has been distributed with a valid transaction using
# the Python client. Until the distribution transaction has confirmed the details api will
# return a 'not found' error.

Note

The following views show transaction details for distributions that have been confirmed by transactions on the Liquid network. For a list of pending distributions use the assignments list api.

url = f'{API_URL}/assets/{asset_uuid}/distributions/{distribution_uuid}'
response = requests.get(url, headers=headers)
assert response.status_code == 200
distribution = json.loads(response.text)
print('\nDistribution details:')
print('---------------------')
print(json.dumps(distribution, indent=4))

# Distributions list
# api/assets/{asset_uuid}/distributions
# Displays distributions that have been distributed with valid transactions
url = f'{API_URL}/assets/{asset_uuid}/distributions'
response = requests.get(url, headers=headers)
assert response.status_code == 200
distributions = json.loads(response.text)
print('\nDistribution:')
print('-------------')
for distribution in distributions:
    print(json.dumps(distribution, indent=4))

Balance, Summary, Ownership and Activities

A number of summary and balance type API are also available to help you manage asset treasury balances, ownership, and activities reporting.

# Asset summary
# api/assets/{assetUuid}/summary
# Balances and stats of an Asset
url = f'{API_URL}/assets/{asset_uuid}/summary'
response = requests.get(url, headers=headers)
assert response.status_code == 200
asset_summary = json.loads(response.text)
print('\nAsset summary:')
print('--------------')
print(json.dumps(asset_summary, indent=4))

# Asset activities
# api/assets/{assetUuid}/activities
url = f'{API_URL}/assets/{asset_uuid}/activities'
response = requests.get(url, headers=headers)
assert response.status_code == 200
asset_activities = json.loads(response.text)
print('\nAsset activities:')
print('-----------------')
print(json.dumps(asset_activities, indent=4))

# Asset ownerships
# api/assets/{assetUuid}/ownerships
url = f'{API_URL}/assets/{asset_uuid}/ownerships'
response = requests.get(url, headers=headers)
assert response.status_code == 200
asset_ownerships = json.loads(response.text)
print('\nAsset ownerships:')
print('-----------------')
print(json.dumps(asset_ownerships, indent=4))

# Asset balance
# api/assets/{assetUuid}/balance
url = f'{API_URL}/assets/{asset_uuid}/balance'
response = requests.get(url, headers=headers)
assert response.status_code == 200
asset_balance = json.loads(response.text)
print('\nAsset balance:')
print('--------------')
print(json.dumps(asset_balance, indent=4))

You can reference the API Specification for a full list and description of all the Blockstream AMP API endpoints.

Using the distribution, reisuance and burn script

To ensure that asset ownership is tracked properly by Blockstream AMP, actions that involve the issuer’s Elements node need to follow a set process. For each action (distribution, reissuance, burn) the steps below apply:

  1. Request the data from the API that the Elements node needs to carry out the action. The path will always end in create, for example /assets/{assetUuid}/distributions/create/ for a distribution.

  2. Use the returned data to call Elements and broadcast the relevant transaction on the Liquid network.

  3. Wait for the transaction to confirm (requires 2 confirmations).

  4. Send the unblinded transaction data back to the API. The path will always end in confirm, for example /assets/{assetUuid}/distributions/{distributionUuid}/confirm/ for a distribution.

Note

In order to make this process easier to follow, you should use the blockstream-amp-confirm.py Python script. This should have been sent to you when you requested a Blockstream AMP account. The script accepts a number of arguments and handles steps 2 to 4 above for you.

Using a distribution as an example of how to call the script:

  1. The issuer calls the /assets/{assetUuid}/distributions/create/ API and saves the returned JSON data to a file, e.g. distribution.json.

  2. The issuer calls the script from the command line, passing in their Blockstream AMP username and password, their Elements node’s RPC username, password, and port, the action to perform, and the path to the file where the JSON data was saved. Note that the possible actions are distribute, reissue, and burn.

$ python3 blockstream-amp-confirm.py -u yourampusername -p youramppassword -n http://yourrpcusername:[email protected]:yourportnumber distribute -f distribution.json

The script will read the data from the JSON file (which includes the URL of the API to send the transaction data back to), call the relevant method/s against the node using RPC, wait for two confirmations, send the unblinded transaction data back to the API.

You don’t have to use the blockstream-amp-confirm.py script if you would prefer to write your own solution, but it is recommended that you do. If you don’t use the provided script, please make sure that all of the steps and checks taken within the script are used as a template.

Warning

If you decide to write your own solution for the process please note: if the unblinded transaction data is not sent back to the relevant confirming API endpoint correctly Blockstream AMP will show incorrect balances and will not be able to track further movements of the asset balances affected by the action. It is possible to catch up any such ‘lost’ transaction data, please email blockstream-amp-support@blockstream.com if you find yourself in this situation.

Issuer Authorization Override

If the standard category-based transfer logic does not meet your requirements, or you have issued a tracked asset that you would like to control the flow of in some way, you can implement your own logic for approval or rejection of transfers. To do this, edit the asset using the assets/{asset_uuid}/edit endpoint and provide your own issuer_authorization_endpoint.

When this is set, Blockstream AMP will hand-off all transfer approvals to the specified API endpoint and action the returned result to either approve or reject the transfer.

Following a transaction from one Blockstream Green Managed Assets account enabled wallet to another with an asset’s authorization endpoint set against the asset:

  • A Blockstream Green Managed Assets account user attempts to send the asset to the address of another Blockstream Green Managed Assets account user.

  • The Blockstream Green server notices that the asset is one that has been registered as an Authorized Asset and passes the unblinded transaction details on to the Blockstream AMP server for approval.

  • Blockstream AMP notices that an issuer_authorization_endpoint has been set against the asset.

  • If the asset is an Transfer-Restricted asset, Blockstream AMP will run its normal (category-based) authorization and will include the result of this in the data it sends to the issuer.

  • The Blockstream Green transaction data and the result of the category-based authorization (if applicable) are passed to the issuer’s API.

  • Blockstream AMP gets the result (approve/reject) back from the issuer’s API, records the change in ownership if needed, and sends the approve/reject message back to Blockstream Green server.

  • The Blockstream Green server either co-signs and broadcasts the transaction, or notifies the user that it has been rejected.

An example of how to implement your authorization endpoint in Node.js is provided for reference.

There are a few additional rules to be aware of:

  • If the asset is a transfer restricted type and the destination address is one that has been derived from a Blockstream Green GAID but is not from a GAID that is recorded against a registered user, Blockstream AMP will reject the transfer and will not call the issuer’s API.

  • If the destination address is not one that has been derived from a Blockstream Green GAID, i.e. it is a regular Liquid address, Blockstream AMP will check if the address is one that has been recorded using the assets/{asset_uuid}/treasury-addresses/add endpoint. If it has, the data that is sent to the issuer’s API will have the is_treasury output field set to True. If the address is not found in that list it will mark is_treasury as False but will record the destination as being one owned by the treasury if the issuer approves the transaction.

  • When setting the issuer_authorization_endpoint against an asset, please note that Blockstream AMP will append /issuerauthorizer to the path provided. So if your endpoint is ultimately https://example.com/issuerauthorizer an issuer should set it as https://example.com against the asset.

  • Using an endpoint that encrypts the channel, like HTTPS, is strongly recommended.

  • Note that if the asset has is_locked set to True, the issuer’s endpoint will not be queried and all transactions will be rejected.

The specification YAML for the issuer authorization endpoint process is available on request.

User Management and User Roles

Resetting your password and API token

To reset your password you can call the /user/change-password API. Note that resetting your password also refreshes the API token.

Your API Token itself can be refreshed without changing your password by calling /user/refresh-token.

Allowing a third party limited access to your data

Blockstream AMP allows an issuer to create additional user accounts that have limited access to the issuer’s data. This type of user is referred to as a manager. Currently this is limited to the creation of a user of type ‘Investment Manager’, although other user types may be made available in the future. Manager access is limited to certain API endpoints and the issuer can further choose which assets the manager can view using /managers/{managerId}/assets/{assetUuid}/add.

An investment manager can perform registered user and registered user assignment tasks. This allows the issuer to pass the management of registered user KYC and investment registrations to a third party.

An issuer can view the list of such user accounts using /managers.

A manager can view their own asset permission using /managers/me.

Note that after calling /managers/create the manager account is locked and cannot be used until unlocked by the issuer. The unlocking of the account is done using /managers/{managerId}/unlock. The issuer may want to provide the manager with just the API token needed to access the API and not the username and password. This can be done by the issuer by creating the manager and then obtaining the token using /user/obtain_token.

Other manager API paths that are not mentioned above can be found in the API Specification.

Only the following paths can be accessed by an Investment Manager:

/info
/changelog
/user/obtain_token
/registered_users/
/registered_users/{registeredUserID}
/registered_users/add
/registered_users/{registeredUserID}/edit
/registered_users/{registeredUserID}/delete
/registered_users/{registeredUserID}/summary [*1]
/registered_users/{registeredUserID}/gaids
/registered_users/{registeredUserID}/gaids/add
/registered_users/{registeredUserID}/gaids/set-default
/registered_users/validate-gaid
/registered_users/{registeredUserID}/categories/add
/registered_users/{registeredUserID}/categories/delete
/categories
/categories/{categoryId}
/assets/{assetUuid} [*1]
/assets/{assetUuid}/activities [*1]
/assets/{assetUuid}/balance [*1]
/assets/{assetUuid}/summary [*1]
/assets/{assetUuid}/assignments [*1]
/assets/{assetUuid}/assignments/{assignmentId} [*1]
/assets/{assetUuid}/assignments/create [*1]
/assets/{assetUuid}/assignments/{assignmentId}/lock [*1]
/assets/{assetUuid}/assignments/{assignmentId}/unlock [*1]
/assets/{assetUuid}/assignments/{assignmentId}/delete [*1]
/gaids/{gaid}/validate

*1 Only for assets the Investment Manager has been associated with by the issuer.

Liquid Testnet and AMP Testnet API

To connect your Elements node to the Liquid Testnet you need to include the settings shown at liquidtestnet.com in your elements.conf file. The initial sync will speed up as your node connects to other peers on the network.

In order to issue assets on Liquid Testnet you need to connect to the AMP API Testnet instance using the URL:

https://amp-test.blockstream.com/api

When using Blockstream Green to obtain a GAID/AMP ID you need to first select Testnet Liquid from the create new wallet screen. This network option can be found under the Additional Networks drop down.