Easy Dynamics 365/AX Blockchain Integration

This post continues to explore Blockchain integration into Microsoft Dynamics 365 for Finance and Operations (AX). I’ve seen examples where developers did integration using the MetaMask Chrome extension, however I want something that looks and feels like pure AX.

For this post I will be using Xalentis Fusion which provides seamless Blockchain integration, whereby Blockchain I refer to Ethereum. I don’t see much use for Bitcoin, and apart from hundreds of other altcoins available, I see more enterprise-level movement towards Ethereum or its variants, including JP Morgans’ Quorum or Microsoft “Project Bletchley”.

Xalentis Fusion works by detecting new transactions in an Ethereum Blockchain and allows filtering to take place across those. Once a filter detects your specific requirements, it can fire off any number of associated rules, written in simple script. Fusion also includes a growing API allowing REST-based integration with the outside world, which is what we will be using.

Fusion provides a Transact API method allowing transactions to be made via a REST call. We can do this ourselves easily as well, but since we’ll be using Fusion for more than transacting (later on in this post) I figured I’ll just stick with it.

We’ll keep it very basic, and create a simple form accepting a number of parameters that we will use to perform a transaction. Our form design is shown below.

BeforeTransact

We’ve added fields for Node, Sender, Recipient, Sender’s account Password, and the amount of Wei to send. Depending on the Ethereum network you are connecting to, adjust the Node value accordingly, or simply hardcode it and remove the field. We are using the Fusion Test Net so that is shown. I’ve created two addresses in the Test Net, also shown in the form design, and loaded the first with some Ether. Perhaps customers want to trade in Dollars, so you can add code to convert Dollars entered into Wei or whatever token is in use. We’ll stick with Wei for now.

Let’s submit this transaction.

AfterTransact

As you can see, the transaction has been submitted to the Blockchain and an Infolog displayed showing success. The X++ code is shown below, including the form class and a utility class that performs our REST calls to Fusion. I’ve added a TransactionRequest class as the POST action we are performing requires JSON being passed, and wrapped the class members with the DataContract attributes to enable easy serialization. This particular POST call accepts the full JSON as part of the POST URL, wrapped as Base64, and that is done in the utility class. The body is required, so we set the content-length to 0.

[DataContractAttribute]
class TransactRequestClass
{
    str addressFrom;
    str addressTo;
    str node;
    str password;
    str wei;

    [DataMemberAttribute]
    public str AddressFrom(str _addressFrom = addressFrom)
    {
        addressFrom = _addressFrom;
        return addressFrom;
    }

    [DataMemberAttribute]
    public str AddressTo(str _addressTo = addressTo)
    {
        addressTo = _addressTo;
        return addressTo;
    }

    [DataMemberAttribute]
    public str Node(str _node = node)
    {
        node = _node;
        return node;
    }

    [DataMemberAttribute]
    public str Password(str _password = password)
    {
        password = _password;
        return password;
    }

    [DataMemberAttribute]
    public str Wei(str _wei = wei)
    {
        wei = _wei;
        return wei;
    }
}

class FusionUtilityClass
{
    public static str Transact(str addressFrom, str addressTo, str password, str node, str wei)
    {
        System.Net.WebClient webClient;
        System.Text.UTF8Encoding encoder;
        System.Text.UnicodeEncoding decoder;
        System.IO.Stream s;
        System.IO.StreamReader sr;
        System.Net.HttpWebRequest myRequest;
       
        try
        {
            TransactRequestClass request = new TransactRequestClass();
            request.AddressFrom(addressFrom);
            request.AddressTo(addressTo);
            request.Node(node);
            request.Password(password);
            request.Wei(wei);

            encoder = new System.Text.UTF8Encoding();
            str json = FormJsonSerializer::serializeClass(request);
            System.Byte[] encodedBytes = encoder.GetBytes(json);
            str encoded64 = System.Convert::ToBase64String(encodedBytes);
 
            str url = "http://fusionapi.azurewebsites.net/api/transact?bodyJson=" + encoded64;
            myRequest = System.Net.WebRequest::Create(url);
            myRequest.Method = "POST";
            myRequest.Timeout = 30000;
            myRequest.ContentLength = 0;

            System.Net.WebHeaderCollection headers = myRequest.Headers;
            headers.Add("API_KEY", "your fusion api key");

            s = myRequest.GetResponse().GetResponseStream();
            sr = new System.IO.StreamReader(s);
            str txnHash = sr.ReadToEnd();
            s.Close();
            sr.Close();
            return txnHash;
        }
        catch (Exception::Error)
        {
        }
        return "";
    }
}

class XalentisTestFormClass
{
    
    [FormControlEventHandler(formControlStr(XalentisTestForm, FormButtonControl1), FormControlEventType::Clicked)]
    public static void FormButtonControl1_OnClicked(FormControl sender, FormControlEventArgs e)
    {
        FormStringControl nodeControl = sender.formRun().control(sender.formRun().controlId("FormStringControl1"));
        FormStringControl addressSenderControl = sender.formRun().control(sender.formRun().controlId("FormStringControl2"));
        FormStringControl addressRecipientControl = sender.formRun().control(sender.formRun().controlId("FormStringControl3"));
        FormStringControl passwordControl = sender.formRun().control(sender.formRun().controlId("FormStringControl4"));
        FormStringControl weiControl = sender.formRun().control(sender.formRun().controlId("FormStringControl5"));

        str addressFrom = addressSenderControl.Text();
        str node = nodeControl.Text();
        str addressTo = addressRecipientControl.Text();
        str password = passwordControl.Text();
        str wei = weiControl.Text();

        str txnHash = FusionUtilityClass::Transact(addressFrom, addressTo, Password, node, wei);
        //todo: store txnHash for history purposes.

        info("Transaction Posted");
    }
}

That works pretty well, but users don’t understand Blockchain addresses, and it would be painful to maintain that somewhere in notepad or Excel to copy and paste each time a transaction is made. Luckily Fusion provides an Account Mapping facility, which is a customer-specific key/value table mapping Blockchain addresses to friendly names, or account numbers the rest of us can readily understand.

So instead of entering address for Sender and Recipient, let’s modify our form as shown below. We can use drop-downs to pull up a list of known accounts, or use an API call to Fusion to return a full list of mapped accounts which we can then allow users to select. I’ll keep it simple with a text field. Here we’ve entered two known friendly account names we can read and verify. These could come from your chart of accounts as well, whatever works best in your scenario. As long as the display text maps to an address in Fusion, it can be resolved.

BeforeTransact2

I’ve modified our form class and utility class to add two additional API calls to Fusion to resolve the friendly names to Ethereum addresses as shown in the code below. We simply make a GET call to Fusion passing across the friendly name, and Fusion will perform the lookup, returning the proper Ethereum address we need to use when performing the transaction. The updated code is shown below.

class XalentisTestFormClass
{
    
    [FormControlEventHandler(formControlStr(XalentisTestForm, FormButtonControl1), FormControlEventType::Clicked)]
    public static void FormButtonControl1_OnClicked(FormControl sender, FormControlEventArgs e)
    {
        FormStringControl addressSenderControl = sender.formRun().control(sender.formRun().controlId("FormStringControl2"));
        FormStringControl addressRecipientControl = sender.formRun().control(sender.formRun().controlId("FormStringControl3"));
        FormStringControl passwordControl = sender.formRun().control(sender.formRun().controlId("FormStringControl4"));
        FormStringControl weiControl = sender.formRun().control(sender.formRun().controlId("FormStringControl5"));

        str addressFrom = addressSenderControl.Text();
        str addressTo = addressRecipientControl.Text();
        str password = passwordControl.Text();
        str wei = weiControl.Text();

        str txnHash = FusionUtilityClass::Transact(addressFrom, addressTo, Password, wei);
        //todo: store txnHash for history purposes.

        info("Transaction Posted");
    }
}

class FusionUtilityClass
{
    public static str Transact(str accountFrom, str accountTo, str password, str wei)
    {
        System.Net.WebClient webClient;
        System.Text.UTF8Encoding encoder;
        System.Text.UnicodeEncoding decoder;
        System.IO.Stream s;
        System.IO.StreamReader sr;
        System.Net.HttpWebRequest myRequest;
       
        try
        {
            str addressFrom;
            str addressTo;

            str url = "http://fusionapi.azurewebsites.net/api/address/" + strReplace(accountFrom, " ","%20");
            myRequest = System.Net.WebRequest::Create(url);
            myRequest.Method = "GET";
            myRequest.Timeout = 30000;
            System.Net.WebHeaderCollection headers = myRequest.Headers;
            headers.Add("API_KEY", your fusion api key);
            s = myRequest.GetResponse().GetResponseStream();
            sr = new System.IO.StreamReader(s);
            addressFrom = sr.ReadToEnd();
            s.Close();
            sr.Close();

            url = "http://fusionapi.azurewebsites.net/api/address/" + strReplace(accountTo, " ","%20");
            myRequest = System.Net.WebRequest::Create(url);
            myRequest.Method = "GET";
            myRequest.Timeout = 30000;
            headers = myRequest.Headers;
            headers.Add("API_KEY", "your fusion api key");
            s = myRequest.GetResponse().GetResponseStream();
            sr = new System.IO.StreamReader(s);
            addressTo = sr.ReadToEnd();
            s.Close();
            sr.Close();

            TransactRequestClass request = new TransactRequestClass();
            request.AddressFrom(strReplace(addressFrom,"\"",""));
            request.AddressTo(strReplace(addressTo,"\"",""));
            request.Node("http://xaleth4kq.eastus.cloudapp.azure.com:8545"); // hardcoded now
            request.Password(password);
            request.Wei(wei);
            encoder = new System.Text.UTF8Encoding();
            str json = FormJsonSerializer::serializeClass(request);
            System.Byte[] encodedBytes = encoder.GetBytes(json);
            str encoded64 = System.Convert::ToBase64String(encodedBytes);
            url = "http://fusionapi.azurewebsites.net/api/transact?bodyJson=" + encoded64;
            myRequest = System.Net.WebRequest::Create(url);
            myRequest.Method = "POST";
            myRequest.Timeout = 30000;
            myRequest.ContentLength = 0;
            headers = myRequest.Headers;
            headers.Add("API_KEY", "your fusion api key");
            s = myRequest.GetResponse().GetResponseStream();
            sr = new System.IO.StreamReader(s);
            str txnHash = sr.ReadToEnd();
            s.Close();
            sr.Close();
            return txnHash;
        }
        catch (Exception::Error)
        {
        }
        return "";
    }
}

We’ll submit this transaction, and as shown we’ve got success.

AfterTransact2

I hope this was helpful. One final item to note is using Wei, which is a BigInteger. I’ve used strings to remove the need for dealing with BigInteger types in X++.

 

Advertisements