Blockstream AMP Issuer Authorization Example

The issuer_authorization_endpoint value can be set against an asset to make AMP call out to an external API for transaction authorization.

Below is an example in Node.js of how to write an asset issuer authorization endpoint and use the data passed to it by AMP. For production instances you may want to secure the serivce and validate the signature passed by AMP. Contact Blockstream for the specification of the endpoint and how to verify the AMP message signature.

The below is for example pusposes only.

First, install dependencies.

npm install express

Paste the following into a file named index.js

const express = require('express');

const app = express();

// Used for our two example rule checks:
const allowedGAIDs = ['GA114wqp9EXcPFELYrmgg2nmBpX4T'];
const minimumAmount = 10;

app.use(express.json());

/*
    /issuerauthorizer
    Your issuerauthorizer route. This endpoint has to be named like this.
    When setting the asset's issuer_authorization_endpoint the AMP server will
    append issuerauthorizer to it. For example, if https://example.com is set,
    https://example.com/issuerauthorizer' is what AMP will post requests to.
*/
app.post('/issuerauthorizer', (req, res) => {
    auth = req.body;

    // The JSON format you will return to AMP on completion of the authorization
    authResult = {
        'result': true,
        'error': ''
    };

    // We'll use two example rule checks on each output:
    // 1. The receiving GAID is in the allowed list.
    // 2. The amount is at least the minimum allowed.
    // Get the outputs from the transaction data AMP sent for authorization:
    outputs = auth.message.request.outputs;
    // Check each output for valid GAID/amount according to our example rules:
    outputs.forEach(function(output) {
        var gaid = output.gaid;
        var amount = output.amount;
        allowed = allowedGAIDs.includes(gaid);
        if (allowed) {
            allowed = (amount >= minimumAmount);
            if (!allowed) {
                authResult.result = false;
                authResult.error = `Amount ${amount} not great enough`;
            }
        } else {
            authResult.result = false;
            assetID = auth.message.request.asset_id;
            authResult.error = `GAID ${gaid} not allowed to receive asset ${assetID}`;
        }
    });
    // Return the authorization result to AMP:
    res.send(authResult);
});

// Start the example server on port 3001
app.listen(3001, () => {
    console.log('listening on port 3001');
});

Paste the following into a file named test.js

/*
    This .js file will act as the AMP server sending your issuerauthorizer
    endpoint a request to authorize a transaction.
*/


const request = require('request');

var HEADERS = { 'content-type': 'application/json' };

const AUTH_API_URL = 'http://localhost:3001/issuerauthorizer';

/*
    Example data that your issuerauthorizer endpoint will receive from AMP.
    Please note the following is not real data, it is just a representation of
    the data formats.
*/
const dataNeedingAuthorization = {
    'signature': 'ignoredinexample',
    'message': {
        'server_result': true,
        'request': {
            'asset_id': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
            'uuid': '17acf253-d77c-46b5-b4ef-9fa44f38c450',
            'tx_hex': '8f12865bf84cd256ce7b58854fe3ab118e16be3e67c1f81704233ba92be29cc3',
            'inputs': [{
                'vin': 0,
                'gaid': 'GA113GbfPmz7DkEASixWxn6igsJCC',
                'registered_user': 1,
                'is_treasury': false,
                'amount': 10
            }],
            'outputs': [{
                'vout': 0,
                'gaid': 'GA114wqp9EXcPFELYrmgg2nmBpX4T',
                'registered_user': 2,
                'is_treasury': false,
                'amount': 9
            }]
        }
    }
}

runExample();

async function runExample() {
    try {
        // AMP ran its own authorization test and set it in server_result.
        console.log(`AMP authorized: ${dataNeedingAuthorization.message.server_result}`)

        // Send the mocked AMP request to the example issuerauthorizer endpoint.
        // It should fail as the amount is less than the minimum (10).
        authResult = await postToIssuerAuthorizerEndpoint(dataNeedingAuthorization);
        console.log(`Example issuerauthorizer authorized: ${authResult.result}`);
    }
    catch(error) {
        console.log(`Error: ${error}`);
        process.exit(1);
    }
}

// WRAPPED API FUNCTION CALLS
async function postToIssuerAuthorizerEndpoint(data) {
    let { response, body } = await apiPost(AUTH_API_URL, data).catch((error) => { console.log('Error in postToIssuerAuthorizerEndpoint. ' + error); process.exit(1); });
    return body;
}

// REQUEST HANDLER
async function apiPost (api, data) {
    return new Promise((resolve, reject) => {
        request({ uri:api, method:'POST', json:true, body:data, headers:HEADERS }, (error, response, body) => {
            if (error) return reject(error)

            return resolve({ body, response })
        })
    })
}

Start the server and be ready to accept test authorization requests:

node src

This will start the express server and listen for requests on port 3001, which our test example will post to.

From another command line instance, run the test which mocks up AMP sending your endpoint an authorization request:

node test.js