Copyright (c) 2024, ASX Operations Pty Ltd. All rights reserved.
SPDX-License-Identifier: Apache-2.0
A solution that demonstrates
- Tokenization of assets using Daml/Canton.
- Asset settlement on Canton network.
- Wallet (user portal) to support the asset tokenization and settlement processes.
This diagram shows the overall intended deployment structure of the wallet. Each participant is expected to host their own instance of the wallet API and wallet UI. The wallet API is read-only, therefore UI users must use the JSON API to issue any commands which update the ledger state. In future, this could be migrated to the Daml 3.0 application architecture in which the wallet application service provider would host the read-only wallet API on its participant, while users would submit commands through their own participants.
It is important to note that this solution is designed to work in conjunction with other applications. For example, there may be another application that implements a customized token with bespoke lifecycling or other functions, but users could still authorise and complete the settlement processes through the wallet user interface. Due to the fact that this application makes use of the Daml Finance interfaces (rather than specific implementations), the settlement instructions can be initiated by a variety of other applications but responded to by users via this solution.
The following examples showcase the functionality provided by the application. One of the primary usecases for this solution is back office integration, i.e. the backend components and Daml contracts could be consumed by back office systems and then displayed as desired by an organisation through its own UI. Therefore the UI provided here is only a basic sample to illustrate what could be achieved using the underlying contracts and API which are included in this repository.
The sample UI allows the user to authenticate using Auth0. Once authenticated, the user will be able to issue commands
via the JSON API to their participant node using their primary party. They can also read data from the wallet views API.
Once logged in, the user can see the Daml Finance accounts and assets they own. In the below example, the user has one
non-transferable asset - their "soul-bound token" (SBT). This is a special type of asset issued to the investor which
assigns them a human-readable name so that others can identify them. Clicking on the balance brings up detailed
information about the asset that they hold - taken from the Instrument
and
Metadata
contracts.
The user's name is also displayed by looking up their SBT.
login.webm
The user can see both their total balance as well as their available balance - the latter being equal to the total
balance minus the sum of any Holding
contracts that are locked. As shown above, the user can also view transactions
(Daml Finance settlements) for their accounts. In this case, we have one transaction in which the user was issued the
soul-bound token. Each transaction is implemented using the settlement Batch
workflow provided by Daml Finance.
The user can open new Daml Finance accounts to hold other types of assets. They are provided non-consuming choices to create new accounts with the given custodian. In this example, they create an account for transferable, fungible assets:
open_account.webm
In this UI, the custodian is referred to as a "register". This party is responsible for keeping a register of ownership of an asset. They onboard investors and issuers but do not play an active role in the settlement workflows. Their other primary responsibility is operating their participant node which must provide confirmations for all transactions on their register. There is nothing to stop this application from allowing the investor to use multiple custodians/registers to hold their assets, as all interactions the user has to go via their own participant, not the custodians'.
Through the Offers tab, the user has the ability to take up settlement offers provided by the asset issuers or other
parties. These offers are open until such time as the signatories of the
OpenOffer
contract
choose to archive it. The OpenOffer
s are not restricted to any particular type of settlement. Depending on the payload
of the OpenOffer
any Daml Finance settlement Batch
and Instruction
(s) can be created. In the following example,
a stablecoin issuer has provided an OpenOffer
for the investors through which they can request issuance of the
stablecoin - assuming the issuer has implemented an off-ledger payment process triggered by the generation of the
settlement instruction.
stablecoin_onramp_offer.webm
The user can input a quantity of the asset. The OpenOffer
payload can (optionally) specify an increment size - in this
case, cents are the smallest increment. Upon submitting the form, an exercise command is submitted to the ledger API to
exercise the Take
choice on the OpenOffer
, resulting in the creation of the settlement Instruction
s. The
settlement Instruction
s then require a response from the investor and issuer to execute the settlement.
The stablecoin issuer also sees the same transaction and can respond to it. In this case, in order to settle they must
elect to create a new Holding
and issue it to the investor - as is done below. In this case, the register/custodian
has created a
MinterBurner
contract to delegate the ability to create the Holding
to the issuer. After both the issuer and investor have selected
their preferences, any of the settlers can click the execute button to perform the settlement. In this case, there is
only one settler - the issuer - as they would need to check that they have received the off-ledger payment before the
settlement should be executed.
stablecoin_onramp_execute.webm
Finally, the settled transaction and updated balance are visible to the investor:
stablecoin_onramp_complete.webm
This application is designed to complement other applications. Various contracts, such as the settlement OpenOffer
s
shown previously, could be generated by other usecase-specific applications (such as a stablecoin application or a
fund application) and then responded to by the users using the wallet. The exact details of how such contracts are
created are left out of this project. Alternatively, a single end-to-end application could also be developed with all
desired functionality which may simplify the process for the users, but limit options for the users to engage in other
applications.
For example, here we have a 3-way settlement between fund issuer, fund manager and investor, in which the fund manager receives a commission payment from the investor:
fund_issuance_offer.webm
The user can select different accounts for each settlement Instruction
if required. Upon applying the account
preferences, the user's Holding
s will be pledged for the settlement and the available balances on their account(s) will
decrease accordingly.
The fund manager can then view and respond to the settlement instructions:
fund_issuance_manager_accept.webm
Finally, the fund issuer can execute the settlement:
fund_issuance_execute.webm
The application also allows users to engage in secondary market transactions with other users. The simplest example is a free-of-payment transfer. First, the sender must instruct the settlement for this:
transfer_instruct.webm
Note that the sender must enter the full party ID of the receiver but in future this could be made easier using an SBT to identify the counterparty. Next, the receiver can choose to accept the transaction:
transfer_execute.webm
The application also has a "requests" tab where users can see any outstanding settlement
OneTimeOffer
s.
The settlement OneTimeOffer
contracts are similar to the OpenOffer
contracts except that they can only be used
once. It is possible to use a OneTimeOffer
to propose a Delivery-versus-Payment (DvP) transaction with another party.
In the example below, InvestorB accepts an offer from InvestorA and selects their account preferences.
dvp_accept.webm
Subsequently, InvestorA would then need to apply their preferences and the settlement can be executed in the same way as has been demonstrated in the previous examples.
As with the OpenOffer
, a OneTimeOffer
can be used to instruct any type of settlement desired by altering the
contract payload.
Users can alter who else on the network can view Instrument
s and associated Metadata
. This functionality is allowed
for the asset issuers and also other parties (if the issuer has delegated this to them). In the following example, the
issuer of the SBT is a Know Your Customer (KYC) service provider, who then delegates the ability to share/unshare the
SBTs to the SBT holders. The SBT contents (such as name) are stored on a Metadata
contract. Each holder is also
provided a unique Instrument
contract. Access can be controlled as shown:
sbt_share.webm
Note that some parties cannot be removed as observers as they have been tagged on the contract with a different on "context" (purpose for including them as observer). This helps to prevent different applications from interfering with eachother when adding/removing observers. In this case, the "WalletOperator" party cannot be removed via the UI - the purpose of sharing with this party is so that the contract payloads can be written by this party into the Wallet Views database (read by the backend API).
After sharing, the other user can see the SBT contents through the "Directory" tab:
sbt_directory.webm
The project contains a number of components:
Folder | Content | Dependency |
---|---|---|
models | Daml templates used in this project | Daml Finance |
demo-config | Configurations files for the initial smart contract setup. The file contains data required to onboard users to the ledger | Daml Finance, Daml templates defined in this project, operations scripts |
operations | Scripts that support party and contract setup, instruction and execution of settlements | Daml Finance, Daml templates defined in this project |
wallet-views | API for the UI | Daml Finance, Daml templates from models |
wallet-ui | UI app | Daml Finance, Daml templates from models |
Please follow these prerequisite steps before running any of the build/run/test steps.
Install the following first:
- Daml SDK
- Maven
- npm
- Docker
- jq
- Python 3 (only needed for running the demo)
- Participant Query Store (PQS) (See the instructions below)
Download the Scribe component (JAR file) of PQS. You will need
a Daml Enterprise license in order to access this. Save it to your machine and export the absolute path to the file
in your terminal environment using the variable SCRIBE_LOCATION
. Using a component which requires an enterprise
license is somewhat of a limitation but there are few alternatives other than writing a custom component for this
purpose. For reference, the previous version of this project was implemented without need for the enterprise license
using the (now deprecated) Custom Views library, and can be found
here.
The build has been tested with Java version 17, Maven 3.6.3, npm 8.19 and PQS 2.8.0.
This repository comes with a demo that demonstrates how the wallet can be used by investors, issuers and other parties, as shown in the above videos. This section explains the configuration of the UI, users and parties.
There are two ways to run the run the UI based on the below user profiles.
UI user profile | Description |
---|---|
Issuer | Issuer can create instruments and offers, mint assets and enter into a settlement with other parties. Issuer can access and use the issuer wallet. |
Investor | Investor can accept offers and enter into settlement with other parties. Investor can access and use the investor wallet. |
Each user on the ledger needs to use one or many parties to communicate with the ledger to complete the required workflow.
UI user profile | Primary Party | Description |
---|---|---|
Issuer | StableCoinIssuer | The party manages stablecoin issuing |
Issuer | StableCoinDepository | Depository for the stablecoin instrument |
Issuer | SbtIssuer | The party manages party/soul-bound token issuing |
Issuer | SbtDepository | Depository for the party/soul-bound token instrument |
Issuer | FundA | The party manages the fund issuing |
N/A (UI login not required) | FundDepository | Depository for the fund instrument |
Investor | FundManagerA | The party which takes the commission in fund settlement workflow |
N/A (UI login not required) | SynfiniValidator | This party witnesses and validates the movements of assets (act as custodian in Daml Finance). They delegate responsibility for minting/burning Holding s to the asset issuers |
Investor | InvestorA | Investor party |
Investor | InvestorB | Investor party |
You can use the instructions in this section to launch the demo on your local machine on a single participant node (Daml sandbox).
This will guide you through the steps to set up Auth0 authentication in your React app as a Single Page Application (SPA). In this application, we leverage Auth0's Universal Login to streamline the authentication and token generation process.
This authentication service provides a seamless and secure user experience by centralizing login functionality, allowing users to access their blockchain wallet through a unified and authenticated session managed by Auth0.
The solution currently only supports Auth0, however it could be modified to support other authentication and authorization platform providers if needed.
- Go to Auth0 and sign up for a free account.
- Once logged in, go to the Dashboard.
- Click on the "Create Application" button.
- Choose "Single Page Web Applications" as the application type.
- Configure your application settings, including the Allowed Callback URLs, Allowed Logout URLs, and Allowed Web Origins. Typically, for development, you can set these to http://localhost:3000.
- Save the changes.
REACT_APP_AUTH0_DOMAIN=your-auth0-domain
REACT_APP_AUTH0_CLIENT_ID=your-auth0-client-id
Replace your-auth0-domain and your-auth0-client-id with the values from your Auth0 application settings.
-
In your Auth0 Dashboard, navigate to the APIs section.
-
Click on the "Create API" button.
-
Fill in the required information:
Name: Choose a name for your API. Identifier (Audience): This is a unique identifier for your API. It can be a URL, such as https://your-api.com. Signing Algorithm: RS256 is commonly used. Click on the "Create" button to create your API.
Once the API is created, you'll see the details on the API settings page.
Take note of the "Identifier" (Audience). This value will be used in your React app to specify the audience when making authentication requests.
-
Update your React app's .env file to include the API Identifier:
REACT_APP_AUTH0_AUDIENCE=your-api-identifier
Replace your-api-identifier with the audience identifier you obtained from the Auth0 Dashboard.
In the demo-config/users folder, there is a users.json file to store user information for ledger identification. The users.json file has an array of user objects, each containing the userId from Auth0 and the corresponding primaryParty for ledger identification. Replace the user IDs with the actual user IDs from Auth0. Unfortunately, this process has not yet been automated so manual editing is required. Ensure that the userId in each object corresponds to the sub (subject) field in the Auth0 user profile. The primaryParty field is the default party used to issue commands to the ledger. If logged into the UI, the user will act as this party. There is no need to edit this field.
Whenever a user logs in, the UI retrieves the user from the participant node which has a user ID matching the Auth0 subject. This allows it to find the user's corresponding primaryParty.
- Start a local postgres DB by running:
cd wallet-views/typescript-client && docker compose up -d db && cd ../..
- Run:
./launch-local-demo-processes.sh
. - Start the UI using
./run-local-demo-ui.sh
If the following error occurs
opensslErrorStack: [ 'error:03000086:digital envelope routines::initialization error' ],
library: 'digital envelope routines',
reason: 'unsupported',
code: 'ERR_OSSL_EVP_UNSUPPORTED'
Set up the following node option and try again
export NODE_OPTIONS=--openssl-legacy-provider
To stop the demo, press control-C and then run ./kill-local-demo-processes.sh
.
Please refer to each of the folders for documentation on how to build each component. Note that all the builds are
managed by the Makefile
in the base directory.
To clean the build state:
make clean
- Refer to wallet views readme for deploying daml packages, Scribe and the wallet API.
- Refer to wallet ui readme for deploying wallet ui.
There are a number of tasks ahead to complete and enhance this solution. A complete list can be found within the Github issues. Some of the highest priority tasks are listed below:
- Use the latest solution from DA which replaces the public party feature (i.e. use explicit disclosure). This will make it easier to share commononly used utility contracts (such as factories) without need for a public party hosted on multiple participants. Refer to this issue.
- Upgrade to the latest version of Daml Finance. Refer to this issue.
- Adding additional features within the issuer wallet UI, such as a breakdown of who owns the assets they have issued. Refer to this issue.