Blockstream AMP API Tutorial

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.

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 Elements Core, as their Blockstream AMP Treasury Wallet. The treasury wallet is used to receive issued assets, reissuance tokens, and carry out actions such as distributions and reissuances. To install and configure Elements Core, follow the node setup guide.

Note

All distributions, reissuances, burns, and transfers 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.

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.

Note that both the live and test versions of the Blockstream AMP API connect to the live Liquid Network, which enables transfer testing with Blockstream Green Managed Assets account enabled wallets.

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. This api also accepts a list of registered user. If a list is provided and one or more of the registered users fails validation, none of the registered users will be created and a list of errors will be returned. 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': ''
}
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.

Note that the registered_users/add API also accepts a list of registered users. If a list is provided and one or more of the registered users fails validation, none of the registered users will be created and a list of errors will be returned.

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 to z, A to Z, ‘.’ 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 and Blockstream Green

During issuance, the issuer can provide information that allows the asset to later be added to the Liquid Asset Registry. 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. This is not shown in the example code as the process involves steps outside the API to be carried out first, details of which can be found in the Liquid Asset Registry section.

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)
asset = json.loads(response.text)
requirements = asset['requirements']
print('\nAsset Requirements (Categories ids):')
print('------------------------------------')
print(json.dumps(requirements, 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.

# 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.

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.

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.

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.

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.