Skip to content

Integrating with blockchains

Introduction

Noumena Cloud supports integration with blockchain networks through a dedicated Blockchain Connector service. The Blockchain Connector allows you to create wallets, deploy contracts and call functions on contracts. This guide will help you set up the Blockchain Connector and integrate it with your Noumena Cloud application.

serviceBlockchainConnector.png

Getting started

To get started with the Blockchain Connector, you need to enable it in your Noumena Cloud application. Once enabled, you can configure it to connect to different blockchains and deploy compiled smart contracts from a provided archive. You will also need to implement the necessary logic in NPL to send notifications to the Blockchain Connector.

Following sequence diagram illustrates the flow of an NPL application using the Blockchain Connector to create a blockchain offering. It shows the steps involved in creating an offering on the blockchain:

  • Create a wallet
  • Deploy a smart contract
  • Mint tokens
  • Whitelist the investor wallets
  • Transfer tokens to the investor wallets

blockchainOfferingSequence.png

Dependencies

npl-connectors-library needs to be added as a dependency to the NPL project.

<!-- pom.xml -->
<!-- add dependency -->
<dependencies>
  <!--...-->
  <dependency>
    <groupId>com.noumenadigital.contrib</groupId>
    <artifactId>npl-connectors-library</artifactId>
    <version>1.0.19</version>
  </dependency>
</dependencies>
<!-- unpack library -->
<build>
  <plugins>
    <!--...-->
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-dependency-plugin</artifactId>
      <executions>
        <execution>
          <id>copy-npl-contrib</id>
          <goals>
            <goal>copy-dependencies</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
            <outputDirectory>${project.build.directory}/contrib</outputDirectory>
            <includeGroupIds>com.noumenadigital.contrib</includeGroupIds>
            <includeArtifactIds>npl-connectors-library</includeArtifactIds>
            <overWriteIfNewer>true</overWriteIfNewer>
          </configuration>
        </execution>
        <execution>
          <id>unzip-contrib-libs</id>
          <goals>
            <goal>unpack-dependencies</goal>
          </goals>
          <phase>generate-sources</phase>
          <configuration>
            <outputDirectory>${project.build.directory}/classes</outputDirectory>
            <includeArtifactIds>npl-connectors-library</includeArtifactIds>
            <fileMappers>
              <fileMapper implementation="org.codehaus.plexus.components.io.filemappers.RegExpFileMapper">
                <pattern>npl-connectors-library-.*/connector</pattern>
                <replacement>connector</replacement>
              </fileMapper>
            </fileMappers>
          </configuration>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>

Configuring the Blockchain Connector

Blockchain Connector can be enabled in your Noumena Cloud application by navigating to the services section in the Noumena Cloud portal and toggling the Blockchain service on. This will allow you to use the Blockchain Connector for interacting with blockchain directly from your NPL applications.

Once the Blockchain Connector is enabled, it can be configured to connect to different blockchains and deploy compiled smart contracts from a provided archive. At the moment, Noumena Cloud supports the following blockchains:

  • EVM
  • Stellar

The archive with compiled smart contracts can be provided via URL. If the access to it is protected, you can specify an authentication header that will be used when fetching the smart contracts.

blockchainConfig.png

  • EVM
  • Faucet wallet private key
  • RPC URL

blockchainEVM.png

  • Stellar
  • Network
  • Faucet wallet URL
  • RPC URL
  • Horizon API URL

blockchainStellar.png

  • Smart contracts
  • URL to the smart contract archive (ZIP file)
  • The Authorization header

blockchainSmartContract.png

Using the Blockchain Connector in NPL

Once the Blockchain Connector is configured, you can use it in your NPL applications. The Blockchain Connector provides a set of APIs that can be used to interact with the blockchain. You can use the Blockchain module in your NPL code to access the Blockchain Connector APIs.

Blockchain Connector provides a set of APIs that can be used to interact with the blockchain using the NPL's notify-resume pattern. It provides 4 main types of messages:

  • WalletCreateMessage: Used to create a new wallet on the blockchain.
  • WalletFaucetFundMessage: Used to fund a wallet with tokens from a faucet.
  • BlockchainDeployMessage: Used to deploy a smart contract on the blockchain.
  • BlockchainFunctionMessage: Used to invoke a function on a deployed smart contract.

Creating a wallet

permission[issuer] createWallet() {
    var args = WalletCreate(
        storageType = WalletStorageType.Vault,
        walletType = WalletType.Stellar,
        faucetFund = optionalOf(0)
    );
    notify WalletCreateMessage(args) resume createWalletCallback;
}

@api
permission[connector] createWalletCallback(res: NotifyResult<WalletCreateResponse>) | creation_pending {
    match(res) {
        is NotifySuccess<WalletCreateResponse> -> match(res.result.status) {
            BlockchainStatus.Success -> this.createWalletSucceeded[connector](
                res.result.walletAddress.getOrFail()
            )
            BlockchainStatus.Failure -> this.createWalletFailed[connector](res.result.details.getOrFail())
        }
        // This will never be called -- the blockchain response, including errors, is passed via NotifySuccess.
        is NotifyFailure -> { }
    };
};

Funding a wallet

permission[issuer] fudnWallet(walletAddress: Text) {
    var workerWalletFaucetFundArgs = WalletFaucetFund(
        address = this.address.getOrFail(),
        amount = 10,
        blockchainType = BlockchainType.Stellar
    );

    notify WalletFaucetFundMessage(
        workerWalletFaucetFundArgs
    ) resume fundWalletCallback;
};

@api
permission[connector] fundWalletCallback(res: NotifyResult<WalletFaucetFundResponse>) | funding_pending {
    match (res) {
        is NotifySuccess<WalletFaucetFundResponse> -> match (res.result.status) {
            BlockchainStatus.Success -> this.fundWalletSuccess[connector](res.result.hash.getOrFail())
            BlockchainStatus.Failure -> this.fundWalletFailure[connector](res.result.details.getOrFail())
        }
        // This will never be called -- the blockchain response, including errors, is passed via NotifySuccess.
        is NotifyFailure -> {}
    };
};

Deploying a smart contract

When deploying a smart contract, you need to specify the path to the compiled contract within the provided archive. Archive itself doesn't prescribe any specific structure, but you need to know the path to the compiled contract you want to deploy, in the example below stellar/token.wasm.

permission[issuer] startDistribution() | subscriptionCompleted {
    notify BlockchainDeployMessage(
        BlockchainDeploy(
            senderAddress = this.wallet.address.getOrFail(),
            compiledContractPath = "stellar/token.wasm",
            blockchainType = blockchainType(this.productDetails.blockchain),
            args = newSmartContractDeploymentArguments(
                wallet = this.wallet.address.getOrFail(),
                isin = this.isin,
                asset = this.offeringName,
                tickerSymbol = this.tickerSymbol
            )
        )
    ) resume deploymentCallback;
    become blockchainDeploymentInProgress;
};

@api
permission[issuer | connector] deploymentCallback(
    res: NotifyResult<BlockchainDeployResponse>
) | blockchainDeploymentInProgress, blockchainDeploymentFailed {
    match(res) {
        is NotifySuccess<BlockchainDeployResponse> -> match(res.result.status) {
            BlockchainStatus.Success -> this.deploymentSucceeded[connector](
                res.result.contractAddress.getOrFail()
            )
            BlockchainStatus.Failure -> this.deploymentFailed[connector](res.result.details.getOrFail())
        }
        // This will never be called -- the blockchain response, including errors, is passed via NotifySuccess.
        is NotifyFailure -> { }
    };
};

Invoking functions on the smart contract

permission[issuer | connector] mint() | blockchainDeploymentInProgress, blockchainDeploymentFailed {
    notify BlockchainFunctionMessage(BlockchainFunction(
        functionName = "mint",
        contractAddress = this.contractAddress.getOrFail(),
        senderAddress = this.wallet.address.getOrFail(),
        blockchainType = blockchainType(this.productDetails.blockchain),
        args = newMintArguments(this.wallet.address.getOrFail(), this.productDetails.totalTokens)
    )) resume mintingCallback;
}

@api
permission[issuer | connector] mintingCallback (
    res: NotifyResult<BlockchainFunctionResponse>
) | blockchainMinting, blockchainMintingFailed {
    match(res) {
        is NotifySuccess<BlockchainFunctionResponse> -> match(res.result.status) {
            BlockchainStatus.Success -> this.mintingSucceeded[connector](res.result.hash.getOrFail())
            BlockchainStatus.Failure -> this.mintingFailed[connector](res.result.details.getOrFail())
        }
        // This will never be called -- the blockchain response, including errors, is passed via NotifySuccess.
        is NotifyFailure -> { }
    };
};

Supported types of arguments for the smart contract functions:

union ArgData {
    Text,
    Boolean,

    Int8,
    Int16,
    Int32,
    Int64,
    Int128,
    Int256,

    Uint8,
    Uint16,
    Uint32,
    Uint64,
    Uint128,
    Uint256,

    Address,
    TokenBalance /* EVM: uint256, Stellar: i128 */,
    TokenDecimal /* EVM: uint8, Stellar: u32 */,
    Bytes32,
    List<ArgData>
};