Previously, when we joined the Linux Foundation to work on LF Edge and move towards open collaboration we announced that we have been working on an internal bridge to Hyperledger Fabric for quite some time.
We have seen that Hyperledger and the entire Hyperledger ecosystem have done a tremendous job in progressing the state of Distributed Ledger Technology(DLT) in a permissioned context.
The data capabilities and the respective utilities coming out of the ecosystem have been exciting and progressive in many regards. At IOTA we’ve been working to progress the state of DLTs in a permissionless context with flexible capabilities surrounding both data and value transfer.
However, this does not mean we don’t see the value in permissioned systems as well. To further enable the mutual growth and value that both permissioned and permissionless systems can provide, we’re moving forward today with officially open-sourcing our bridge to Hyperledger Fabric.
This bridge will enable direct integrations of both Hyperledger Fabric and IOTA capabilities together by functioning as an IOTA Connector. By doing this it allows the IOTA Tangle to act as connective tissue between permissioned Hyperledger Fabric-based systems and allows for more fluid data sharing and validation with siloed permissioned systems.
As the Hyperledger Fabric project becomes increasingly popular for supply chain and asset tracking projects, we introduce the IOTA Connector which allows data to be mirrored into the Tangle, benefitting from all the features available, such as fee-less payments, encrypted transaction payload, and public/private message chains.
At this point, we are considering the Hyperledger Fabric DLT - in which all data are initially stored and managed - as the primary source of truth.
Each smart contract (chaincode) execution can now trigger a request to the IOTA Tangle using the provided software. This process allows us to store/update results of the Hyperledger Fabric smart contract execution on the Tangle and to perform payments between IOTA wallet holders.
It is also possible to read data from the Tangle and trigger Hyperledger Fabric smart contract execution based on the fetched transaction data or payment confirmation.
The code samples provided below showcase how you can implement multiple scenarios of the IOTA integration in a common supply chain project based on Hyperledger Fabric.
We will not go into details of the Hyperledger Fabric architecture, setup, and configuration in this blog post. We assume that you are already familiar with a typical Hyperledger Fabric project and most likely have one such project running.
You can download the source code files for Hyperledger Fabric chaincode IOTA connector from this public GitHub repository.
We will start with a simple chaincode file suitable for a supply chain project and will add a few IOTA connectors one by one with a short usage description.
The plain Hyperledger Fabric chaincode written in Go has a namespace, which can be defined as the docker-compose.yml file under volumes:
In our case, the chaincode folder is mapped to the github.com namespace. All external packages, including the IOTA connector, need to be imported using the same namespace.
We start by adding the IOTA connector code into the chaincode folder.
In addition, you will need to add three other complementary packages.
You can do this either by adding an instruction to the shell script that installs and starts the Hyperledger instance or by manually downloading and copying the projects into the chaincode folder.
To add instructions to the shell script, add the following three lines before the “chaincode install” command
This will ensure that required packages are downloaded and placed into the chaincode folder, mapped asgithub.com
Depending on your version of Go, the last command that downloads the iota.go library might fail. In this case, you can download it manually and place it into the chaincode folder.
Next, add import of the IOTA connector library to the list of imports of your chaincode.go file. Please note, gibhub.com namespace in from of the iota package name. If your organization uses a different namespace, please adjust accordingly.
Hyperledger chaincode contains a function called initLedger, where you can define an initial structure of the ledger and store data as key/value pairs. For complex types, you would typically use structures to describe object fields.
In our example, the initial ledger structure consists of a number of container definitions, where each container includes the Holder field among many others
Once the structure is defined, it is stored on a ledger one by one
This is one possible way to store data on a ledger. We do not claim to develop a best possible option.
We will add the IOTA connector here, to ensure that the digital twin is stored on the Tangle in MAM message streams. We will create an individual message stream for each container, where all changes to the container data, location, holder etc. will be tracked.
First, define the structure of the IOTA MAM stream. It should contain randomly generated Seed, MamState object, Root address to fetch data from, stream mode and sideKey(encryption key).
Then, define the mode and encryption key for every asset, that will get a digital twin on the Tangle.
You can use one of the following modes: “public”, “private”, or “restricted”.
SideKey (encryption key) is only required for the “restricted” mode. Otherwise, it can remain an empty string.
We provide two helper functions for the sideKey. Both functions are accessible from the iota namespace:
- iota.GenerateRandomSeedString(length)will generate a random string of the given length. It can be used as a seed or encryption key.
- iota.PadSideKey()will automatically adjust the length of the short key to 81 characters.
If you do not want to define your own values for mode and sideKey, you can use default values iota.Mam Mode and iota.MamSideKey, which you can inspect and modify under chaincode/iota/config.go.
After that, you can call a function iota.PublishAndReturnState(), which will publish the message payload as a new MAM channel. This function returns the mamState, root and seed values.
MamState and seed are needed for further appends to the same channel. Root value is used to read data from the channel.
These values need to be stored on the ledger and communicated to each peer of the organization.
If you do not want to append new messages to the same channel in the future, you can call iota.Publish()function instead, which won’t return the MamState.
iota.PublishAndReturnState()requires the following parameters:
- Message payload (string)
- Existing MAM stream flag (bool)
- Existing seed value (string)
- Existing MamState value (string)
- Mode (string)
- sideKey (string)
If you create a new MAM stream, set values for the existing MAM stream, seed and mamState to false, “”, “”
Once the message was published, it is time to persist values on the ledger. Create a new object of type IotaPayload and put it on the ledger, similarly as you created records for container assets.
Please note that we recommend adding a prefix like “IOTA_” in front of the asset ID.
APIstub.PutState(“IOTA_” + strconv.Itoa(i+1), iotaPayloadAsBytes)
If your smart contract contains a function to add new records to the ledger, please update this function by adding the IOTA connector code to it.
Please note that the ID of the new asset is used for the new IOTA object
APIstub.PutState(“IOTA_” + args, iotaPayloadAsBytes)
Similarly, if your smart contract contains a function to modify existing records, please update this function by adding the IOTA connector code to it as well.
Please note that in this case, we are appending the modified asset data to the existing MAM stream of this asset. Therefore we retrieve the iotaPayload for specific assets from the ledger and communicate this information to the iota.PublishAndReturnState()function, along with the existing MAM stream flag set to true.
Also note, at this point, the mode and encryption key of the existing MAM stream can not be changed. Please use existing values stored on the ledger.
Once the new message was added to the existing stream, the new MamState should replace the previous state on the ledger. All other values should remain the same as before.
If your smart contract contains a function to query a specific record from the ledger, you might want to also fetch and return the state stored on the Tangle. To perform the query from the Tangle, you can use the following function available from the IOTA connector code:
iota.Fetch(root, mode, sideKey)
Please note that the ID of the asset is used to retrieve the corresponding IOTA object from the ledger.
iotaPayloadAsBytes, _ := APIstub.GetState(“IOTA_” + args)
Fetched messages are returned as an array of strings. If you want to join them together into one string and add them to the output object, you will also need to import the “strings” Go package.
In addition, if you want to output the MamState values, to be able to perform other actions on the UI, like confirm and compare data from the ledger with data stored on the Tangle, you can add the MamState object to the output
Supply chain projects might benefit from the built-in payment solution, where payments for certain services and goods can be sent between supply chain participants.
One possible use case could be when retailers or end consumers send payments to the producers, logistics and fulfillment providers for the ordered assets.
Since none of the Hyperledger projects support cryptocurrency or any other type of payment, the IOTA connector can be used to perform fee-less payments between participants at the moment when a smart contract confirms a successful transaction.
To send payment using the IOTA wallet, you will need to store the wallet seed and keyIndex on the ledger. Seed is used to initiate a transaction, and keyIndex is specific for IOTA implementation and represents the index of the current wallet address, which holds the tokens.
After every outgoing payment transaction, the remaining tokens are transferred to the next address in order to prevent double-spending. The index of the new address (called remainderAddress) should be stored on the ledger and used for the next outgoing payment. Incoming payments do not trigger address or index change.
In the example, we will maintain only one wallet for outgoing payments. This wallet will be assigned to the retailer, who is the end consumer of the asset in this supply chain project.
The payment will be sent to the previous asset holder each time the holder is changed, which indicates asset movement toward the end consumer. In other words, once a producer prepares a container for shipment and transfers it over to a freight forwarder, the retailer will pay the producer in IOTA tokens. Then, once a freight forwarder delivers the container to the next destination, the retailer will transfer IOTA tokens to pay for this service.
All possible participants in this sample of a supply chain project are defined upon initialization of the ledger. For simplicity, we assume that there is only one participant with the role of “Producer”, “Shipper” and so on.
We will start with the definition of the structure of the wallet object.
This structure contains seed and keyIndex as described above. In addition, it also contains the actual address where tokens are currently stored. You can perform a balance check to ensure a sufficient balance of the wallet. Enter your wallet address on this page to check the current balance.
Next, we will extend the existing Participant structure by adding the IotaWallet part into it.
And then we will generate a new empty wallet and add wallet information to every participant record.
To generate a new wallet, you can use a function from the IOTA connector:
walletAddress, walletSeed := iota.CreateWallet()
Since we generate a new wallet for every record, we set the keyIndex value to 0. If you are about to use existing wallets, please adjust keyIndex values accordingly.
The generated wallets are empty and currently can only receive IOTA tokens.
In order to send tokens, you need to maintain at least one wallet funded with IOTA tokens.
Wallet data of this wallet should be stored on the ledger.
You can provide wallet data upon ledger initialization, or you can modify values in the configuration file under chaincode/iota/config.go
To store a wallet on the ledger, please update the initLedger() function by adding the following code. You can replace values for iota.DefaultWalletSeed and iota.DefaultWalletKeyIndex with respective values of your wallet.
Once the wallets are configured, we can add functionality to perform payments. This consists of 3 simple steps:
- Identify the function in your smart contract which should trigger payment.
- Identify the payment recipient. Retrieve the wallet address of the recipient.
- Identify the payment sender. Retrieve the wallet seed and keyIndex of the sender. Perform token transfer, then update keyIndex to the new value and store it on the ledger.
In our example, we will perform payments once the asset holder was changed. So, the function that triggers payments is called changeContainerHolder(). The payment recipient is the previous container holder. So, we need to preserve the holder value before changing, to be able to retrieve the corresponding wallet data from the ledger.
In the screenshot below you see the required updates to the function. Container data is retrieved based on the provided ID. Before the holder is reassigned, we store the original holder value. Later we query the ledger in order to get the wallet address of the original container holder.
For step 3 we will request the IOTA wallet data of the retailer. Then we will trigger the following function and submit seed and keyIndex values of the sender and the address value of the recipient.
iota.TransferTokens(seed, keyIndex, address)
Once this is done, we just need to update keyIndex to the new value and store it on the ledger.
The token transfer usually requires a few seconds to be confirmed. Please do not attempt to trigger multiple transfers from the same wallet within a very short timeframe (less than 10 seconds), as it will result in an “Invalid balance” error, and wallet keyIndex should be manually reset to the previous value.
As always, you can check the status of the token transfer on this page by entering the wallet address.
Following the open-sourcing of the IOTA Connector, we will be seeding the capabilities as a Hyperledger Bridge to the Linux Foundation. We’re working with the Linux Foundation to ensure the code is being seeded into the project that it will be most relevant for and look to finalize that decision in the coming weeks.
- The source code of the IOTA Connector can be downloaded from this open-source GitHub repository.
- The supplementary iota.go library, which needs to be located within the same chaincode folder, can be downloaded from this open-source GitHub repository .
- The demo project used to showcase the IOTA Connector integration is open-source and can be found in this GitHub repository .
Feel free to stop by on Discord— every project mentioned here has a channel (or more) for discussions with the devs!