Lightweight Contracts

From ArdorDocs
Jump to: navigation, search
NOTE This feature is currently only available in the Ardor testnet

Introduction to Lightweight Contracts

Lightweight Contracts represent a framework for developing a layer of automation on top of the existing Ardor APIs.

Contracts are developed by implementing predefined interfaces. The contract code is deployed to the blockchain as a cloud data transaction, which stores the code itself, and a contract reference transaction which associates a specific account with an existing deployed contract and configures the contract setup parameters for this specific account.

Learn more about the concepts behind the lightweight contracts using these resources:

introduction article

frequently asked questions

video seminar


Contract developers are expected to master the Java programming language or any other language which uses the Java JVM as its runtime environment.

Some prior knowledge of blockchain in general, Ardor specifically. Familiarity with concepts like account, passphrase, transaction, and block are important but not mandatory as you can always review the sample contracts supplied with the product as reference implementation.

Getting Started


Install Java SE JDK 8 or higher.

Install the latest version of the IntelliJ IDE, the free community edition is supported. You can develop contracts using any other Java IDE but the project setup and all examples assume using the IntelliJ IDE.

Install the latest version of Ardor and make sure to leave the option checked during installation.

Contract Runner

Unlike many other contract development frameworks, lightweight contracts are not executed by every node on the blockchain, instead node operators have to configure their node to run contracts by registering the contract runner addon. The contract runner monitors every new block for trigger transactions. When found it triggers the respective contract after loading it from the blockchain. The contract runner also supports contracts which execute on every block and provides APIs for triggering contracts directly or using a transaction voucher.

To activate the contract runner for your node add the property nxt.addOns=nxt.addons.ContractRunner to your node's file.

Contract Runner General Configuration Settings

As of version 2.2, the contract runner configuration can be specified using the file, except for off-chain, contract specific setup parameters, which rely on a json configuration file.

Use the following settings to configure the contract runner.

"addon.contractRunner.secretPhrase" - specify the secret phrase of the contract runner account i.e. the account from which the contract runner will submit transactions. This passphrase is never submitted to any remote node, however you should never use an account with a large amount of tokens, as the file is stored in clear text.

"addon.contractRunner.accountRS" - alternatively if you only want your contract runner to verify transactions submitted by another contract runner, specify the account of the contract runner you would like to follow. This way your contract runner can repeat the contract calculations and verify transactions submitted by another contract but not submit its own transactions.

"addon.contractRunner.feeRateNQTPerFXT.<Chain Name>" - when submitting transactions the contract runner will use this value specified in NQT to calculate the child chain fee to submit. For each chain your contract runner is expected to submit transactions, you need to specify this value. If no fee ratio for no chain is specified the contract runner will not start.

Example: to set the rate to 2.5 IGNIS per ARDR, set this property to addon.contractRunner.feeRateNQTPerFXT.IGNIS=250000000 i.e. 2.5 IGNIS specified in NQT.

"addon.contractRunner.validator" - if set to true (default: false), the contract runner will watch for transactions submitted by other contract runners and will try to verify that the other contract runner indeed run the contract stored on the blockchain.

"addon.contractRunner.validatorSecretPhrase" - when addon.contractRunner.validator is set to true, and another contract runner is using an account under account control, specify here the secret phrase of the controlling account. Your contract runner will intercept the transactions submitted by the other contract runner, repeat the calculations, and if it receives the same result, will submit an approval transaction for the transaction submitted by the other contract runner.

"addon.contractRunner.catchUpInterval" - a timeout value specified in seconds (default: 3600 i.e. one hour). During blockchain download the contract runner will only submit transactions when downloading a block with a timestamp later than the current time minus the defined catchUpInterval. The purpose of this setting is to prevent a contract runner from flooding the unconfirmed transaction pool with duplicate transactions during blockchain download.

"addon.contractRunner.seed" - supply random seed to the contract runner formatted as hex string (default: the public key of the contract account which is useless from randomness perspective). Convert any random value to hex string using the hexConvert API and specify the resulting hex string value as a seed. A seed of less than 16 bytes can be easily brute forced so make sure your seed is longer. Keep your seed secret, it can be used in the future to validate your contract execution.

Contract Runner Contract Specific Settings

Note that in most cases there is no need to specify these parameters. Always prefer using contract setup parameters which are stored on the blockchain itself except when a contract parameter has to remain secret (for example, a passphrase or some secret credentials to a 3rd party service).

In the rare cases when this configuration is needed, specify the configuration file used by the contract runner to load contract specific setup parameters by adding the property addon.contractRunner.configFile, for example addon.contractRunner.configFile=./conf/contracts.json, the specified path is relative to the user directory.

A sample contract configuration file is provided in the ./addons/resources/contracts.json file under the installation folder. Copy this file to the conf folder under your project's user directory and configure it as follows.

"params" - specify contract setup parameters per contract. Use this setting only for private setup parameters that you don't like to submit to the blockchain such as passwords, API keys, etc. Otherwise specify the setup parameters in the contract reference transaction as explained in the contract manager configuration. The contract parameters configured in the params object are accessed by the contract code using the context method


Alternatively, create an inner interface decorated with the @ContractParametersProvider annotation inside your contract and define a method with the annotation @ContractRunnerParameter using the same name as the corresponding parameter in the contract runner configuration file. For example see the secretPhrases() method of the LeaseRenewal sample contract which returns the array of passphrases specified in contracts.json

{ "params": { "LeaseRenewal": { "secretPhrases": [ ... ] }}}

Monitoring the contract runner

To verify that the contract runner has started, look for the message "ContractRunner Started" in the node log file.

To check the status of the contract runner and deployed contracts, invoke the getSupportedContracts API.

In response you'll receive an array of contracts supported by the specific contract runner and their properties, and the contractAccount parameter which specifies the owner account of the contract runner, which is based on the passphrase or account id you specified in the contract runner configuration.

In case the contract runner has failed to start, the getSupportedContracts API will return a descriptive error message.

As of version 2.2.1 when your wallet is connected to a contract runner node, you can choose "Contracts" from the "Settings" menu to view information about the contract runner and the deployed contracts. Including information about invocation parameters for the contracts and validation used by the contract.

Contracts Page

Contract Manager

The contract manager is a command line utility which manages the contract lifecycle. Its primary usage is to deploy contracts to the blockchain, use it also to add or change a reference to an existing contract, to update contract setup parameters, to remove old contract references, and to verify that a specific contract was compiled from a specific Java source file.

Contract Manager Configuration

The settings which control the operation of the contract manager are defined in the file, their documentation is specified in the ./conf/ file under the installation folder in the "#### CONTRACT MANAGER ####" section.

You should define the secret phrase for transactions submitted by the contract manager using the property contract.manager.secretPhrase=[Secret Phrase]

The contract manager connects to an Ardor node to use the Ardor APIs and submit transactions. By default it connects to a node running on localhost. Use the contract.manager.serverAddress=[Server Address] property to connect the contract manager to a remote node. The contract manager will never submit its passphrase to a remote node.

Use the optional contract.manager.uploadParamsFile=[Path to configuration file] property, to configure the contract manager upload parameters configuration file.

Upload Parameters Configuration File

The upload parameters file contains a json array of contract definitions named "contracts".

Note: as of version 2.2, the contract upload configuration file is optional. Every setting in this file can be configured using a matching property in file.

For each contract specify the following parameters:

"className": the name of the class without package name, this name represents the class name loaded by the contract runner from the data cloud and the name of the contract reference.

"packageName": [optional, default to package specified as command line parameter] the Java package name of the contract class as specified in the package directive inside the source code.

"filePath": only in case the contract is deployed as a Jar file. Specify the path to the Jar file which will be deployed to the cloud data. If no filePath is specified the contract manager will attempt to load the class file specified by the className attribute from the classpath.

"params": a Json object representing the contract setup parameters. These parameters are accessed by the contract through its context object or using the @ContractParametersProvider interface methods decorated with @ContractSetupParameter annotation.

The upload parameters for the sample contracts available out of the box are defined in the ./addons/resources/contract.uploader.json file under the installation folder. This file is automatically copied to the ./conf folder under the user directory in case it does not exist yet.

Alternatively, every setting in the contract uploader file can be defined in using the following format:

Contract Manager settings for a specific contract - contract.[contractName].[setting]

Contract setup parameters submitted to the blockchain - contract.[contractName].param.[setting]

For example, to define the "frequency" setup parameter of the "AllForOnePayment" contract use the following property:


Setting Up the Development Environment

Assuming all dependencies were installed, we can now open the Ardor contracts project inside IntelliJ and start developing contracts.

The IntelliJ project structure is provided as part of the "Contract Development Tools" package you selected when installing Ardor so there is no need to create a new project.

If you installed Ardor into a read only folder you will need to copy it to a folder for which you have write permissions.

Windows - copy "c:\Program Files\Ardor" to a writable location such as c:\Users\<Your user>\Documents

Mac - copy /Applications/ to in your home folder (use cp -R from the terminal for recursive copy on Mac)

Linux - this should not be an issue as long as you installed Ardor into a folder you own.

Alternatively, if you decided to develop your project in the same location where you installed Ardor, a future upgrade may override some of the project files so make a backup of your project before upgrading.

Start IntelliJ, from the top menu choose "File->Open", in the resulting "Open file or project" dialog select the Ardor folder itself.


Select the Ardor module from the left pane by clicking the "1:Project" label.

Project1.PNG Project2.PNG

The Ardor module contains the sample contracts provided with installation.

Open the Ardor module settings by pressing F4 while the module itself is in focus or right click and choose "Open Module Settings".

Select the "Project" link from the left pane and set the "Project SDK" to the Java JDK you installed earlier and set the "Project Language Level" to 8 and click "Ok".

Project structure

Build the Ardor module by selecting the "rebuild Module 'Ardor'" from the IntelliJ "Build" menu (Ctrl+Shift+F9)

Restart IntelliJ and reopen the project.

Under the Ardor module there are two Java source roots, the first contains the sample contracts, the second contains the unit tests for the sample contracts.

For example this is the HelloWorld sample contract.

HelloWorld sample

Your IntelliJ contracts project contains the following launchers, visible in the selection box on the top right.

Ardor Local - runs the node using the existing node configuration

ContractRunnerSuite - invokes the automatic tests for all contracts

You are now ready to develop your first contract

Contract Development

A contract is composed of one or more Java class files possibly packaged into a Jar and possibly relying on other Jar files.

The main contract class has to extend the nxt.addons.AbstractContract class and implement one or more of the provided callback methods which are invoked by the Contract Runner in response to specific events as shown in the following table:

Trigger Callback method Description
Trigger transaction
JO processTransaction(TransactionContext context)
Invoked when a trigger transaction is applied by the blockchain i.e. the callback is invoked for phased transactions when confirmed, and for normal transactions once they are included in a block. Special case are transactions phased by hashed secret which are also invoked when included in a block.
Every block
JO processBlock(BlockContext context)
Invoked every block
triggerContractByRequest API call
JO processRequest(RequestContext context) 
throws NxtException
Invoked by calling the triggerContractByRequest API
triggerContractByVoucher API call
JO processVoucher(VoucherContext context)
Invoked by calling the triggerContractByRequest API
Call from another contract
ReturnedData processInvocation(DelegatedContext context,
InvocationData data)
Invoked when another contract calls this contract

A contract should implement one or more of these callback methods.

Context Objects

Each of the above callback methods, receive a context object, the context object provides various services to the contract and represents the interface between the contract and the blockchain.

Always prefer using the context object over calling directly to an internal blockchain method since every method provided by the context is guaranteed to maintain backward compatibility and not break your contract code when a new Ardor release is deployed. The services provided by the context object depend on the type of callback, these services are documented in the Javadoc for the context class.

API callers

For each of the Ardor public APIs there is a specific Java caller class named the same as the API with a capital letter at the beginning and a "Call" prefix.

For example the call object for the getBlockchainStatus API is GetBlockchainStatusCall.

API invocation always follows the same pattern, create a caller instance, possibly set some API parameters, then invoke the call() method to receive a Json response as a JO object you can then work with.

For example: to invoke the getExecutedTransactions API from inside the contract:

GetExecutedTransactionsCall request = GetExecutedTransactionsCall.create(2).height(height).type(0).subtype(0).recipient(context.getConfig().getAccount());

JO getExecutedTransactionsResponse =;

Always use the API callers to access information stored on the blockchain since these API callers represent the interface between your contract and the data stored on the blockchain. The API callers will remain compatible in future releases.

Submitting Transactions

In most cases, contracts should not save any internal state between contract invocations. To make changes to the blockchain state, contracts should submit transactions. To submit a transaction, invoke the API caller for the specific transaction type, then use the context.createTransaction() method to submit the transaction to the blockchain. Internally the createTransaction method implements local signing, fee calculation and other complex processing that you as contract developer don't have to deal with.


SendMoneyCall sendMoneyCall = SendMoneyCall.create(context.getChainOfTransaction().getId()).recipient(recipient).amountNQT(returnAmount);


Contract Parameters

For contracts triggered by a transaction use


to load the specific invocation parameters or preferably define the parameter as a method of an inner interface decorated with @ContractParametersProvider annotation. Name the method the same as the parameter name and decorate it with the @ContractInvocationParameter annotation. To load the contract setup parameters specified in the contract reference transaction use


or preferably define the parameter as a method of an inner interface decorated with @ContractParametersProvider annotation. Name the method the same as the parameter name and decorate it with the @ContractSetupParameter annotation. To load the contract runner parameters specified in the contract runner configuration file for the specific contract use


or preferably define the parameter as a method of an inner interface decorated with @ContractParametersProvider annotation. Name the method the same as the parameter name and decorate it with the @ContractRunnerParameter annotation.

All the parameters getter methods return a JO object.

For usage example of the @ContractParametersProvider decorated inner interface see the HelloWorldForwarder and LeaseRenewal sample contracts.

Working with Json

All API callers and configuration parameters return a JO object which represents a Json object.

Use the JO object to query the Json object. Specifically use the getArray() method to obtain a JA object representing a Json Array.

There are plenty of usage examples in the sample contracts.

Accessing External Resources

Unlike most smart contract frameworks, lightweight contracts support access to external resources. Your contract can communicate as client of any external service which provides a Java client API library, or Http, XML, Json, Soap, ldap or a similar interface.

If necessary your contract can access code from 3rd party Jar files as long as these Jar files are included in the classpath of the contract runner node. You may add external Jar files to the classpath of the node by copying the files to the ./addons/lib folder under the installation folder.

Learn mode about Oracle Contracts

Lightweight Contracts Design

To learn more about the internal design of lightweight contracts framework see this article

Best Practices

Single Source File Multiple Classes

To simplify contract development always attempt to concentrate all your source code into a single source file which defines the contract class itself and possibly additional inner classes. When deploying the contract to the blockchain the contract manager loads the main contract class and checks if it has inner classes. If there are no inner classes, the contract class file is deployed to the blockchain, otherwise, the contract manager packs the contract and all its inner classes into a Jar file and deploys the Jar file to the blockchain.

Parameter Validation

Always start your contract code with input validity checks. For example if your contract is triggered by a sendMoney transaction, make sure the payment recipient is the contract account and that the payment uses the expected chain, that the payment amount is correct etc.

Validate that all invocation parameters represent the data type you expect and are within their valid boundaries.

For your convenience you can decorate the contract processTransaction and processVoucher methods with one of the following predefined annotations:

@ValidateTransactionType validates that the trigger transaction type is of one of the accepted types and is not of one of the rejected types.

@ValidateContractRunnerIsRecipient validates that the contract runner account is the recipient of the trigger transaction.

@ValidateChain validates that the chain of the trigger transaction is one of the accepted chains and is not one of the excluded chains.

See usage example in the RandomPayment contract.

Stateless Contracts

In most cases, contracts should only store state information in the blockchain itself or as setup parameters. If you are using member variables in the contract class itself you might be doing something wrong. Bare in mind that the contract runner may restart at any moment and that your contract callbacks may execute in parallel with other contracts. Learn more about stateless contracts

Internal Blockchain Functionality

Do not invoke public methods of the blockchain directly. Always attempt to use the context object and the caller APIs.

If you are missing some essential function or service, let us know and we will add it in the next release.

Random Data

If your contract requires random data, make sure to define a secret random seed in the contract runner configuration. See the following article about our approach to random number generation

Contract Deployment

As explained above, contract deployment is a two step process, first the contract code is deployed using a cloud data transaction then a contract reference transaction is submitted to provide an entry point to the correct version of the contract. While this deployment process can be accomplished manually using the Ardor APIs, it is much simpler to perform the deployment using the contract manager utility.

First configure the contract manager then use either the contract manager IntelliJ plugin (recommended) or invoke the command line utility (ContractManager.bat or from the command line to view the available options.

In both configurations, the contract manager connects to an existing full node, preferably running on your local workstation. Make sure your node is properly configured and fully synchronized with the blockchain.

Remember that the contract manager deploys transactions to the blockchain. For the transactions to confirm you need to wait for the next block.

Contract Manager IntelliJ Plugin

The contract manager plugin provides a simple configuration dialog to invoke the contract manager utility from inside the IntelliJ IDE. You first need to install the contract manager plugin into your IDE, this process has to be repeated after every Ardor version upgrade.

Install the Plugin

From the "File" menu choose the "Settings" option.

From the resulting "Settings" dialog select the plugins page.


Click the "Install plugin from disk" button at the bottom and using the resulting file dialog select the file from the Ardor installation folder.


You should now see an "Ardor Contract Manager" entry listed in the plugins list.

Click the OK to confirm and restart IntelliJ


The contract manager plugin is now installed.

Repeat this process whenever you upgrade Ardor to install a new version of the plugin.

Use the Plugin

Edit the runtime configurations.


Add a new configuration of type "Ardor Contract Manager"

Ardor Contract Manager

Name the configuration and define the contract manager options

Contract Manager Options

Run the configuration as usual


Review diagnostic messages logged to the IntelliJ console at the bottom of the screen.

Contract Manager Command Line Utility


Deploy a new version of the ForgingReward contract:

contractManager.bat -u -n ForgingReward -p com.jelurida.ardor.contracts



Contract class com.jelurida.ardor.contracts.ForgingReward uploaded 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748

Contract reference registered ForgingReward={"rewardArdor":"true","interval":5,"rewardNxt":"true","rewardChain":"IGNIS","rewardAmountNQT":150000000} for contract chain: IGNIS, full hash: 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748

List deployed contracts for a specific account:

contractManager.bat -l -a ARDOR-XK4R-7VJU-6EQG-7R335





Delete contract reference for a specific account (the contract itself is not deleted)

contractManager.bat -d -a ARDOR-XK4R-7VJU-6EQG-7R335 -n ForgingReward


Contract reference 10360831005888949167 deleted for account ARDOR-XK4R-7VJU-6EQG-7R335 contract name ForgingReward

Add or update contract reference

contractManager.bat -r -h 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748 -n ForgingReward

(the hash parameter -h is the full hash of the cloud data transaction storing the contract version you would like to reference)


Contract reference registered ForgingReward={"rewardArdor":"true","interval":5,"rewardNxt":"true","rewardChain":"IGNIS","rewardAmountNQT":150000000} for contract chain: IGNIS, full hash: 5f4afbead4f7f887082a36dd97df72f6ed7f980c1b3e99eccb7d67bb150c9748

Verify that a contract class in the blockchain was compiled from a specific source file, note that this action has to run using a Java JDK and not using the Java JRE provided with some of the installations.

Invoke the command below from the Ardor installation directory. The verification action has to run using an Oracle Java JDK.

"c:\Program Files\Java\jdk-10.0.2\bin\java.exe" -cp classes;lib\*;conf -v -h 4a8cd1c3fc86ad1cfbce4957142ff86c5d908af633bc6e38fddbf0be2418ff05 -s c:\Users\liory\ardor\addons\src\java\com\jelurida\ardor\contracts\


Verification succeeded - class files are identical

Contract Security

The contract runner relies on the Java JAAS framework to ensure that malicious contracts downloaded from the blockchain cannot attack the contract runner local workstation or the blockchain node operations. To achieve this, contracts are loaded using their own class loader and run in their own protection domain which forms a sandbox that limits the permissions assigned to contract code.

Most contracts should be able to run using default the permissions. However if you need to make changes to security permissions, read on.

The main configuration files for controlling the contract security are ardor.policy and ardordesktop.policy, both files are standard Java policy files. ardor.policy is the active policy file when running in command line mode and when running unit tests. ardordesktop.policy is a slightly more permissive policy file active when running the desktop wallet. In this document we will always use ardor.policy, everything explained is also applicable to ardordesktop.policy.

By default contracts are assigned the permissions defined in the virtual untrustedContractCode protection domain.

grant codeBase "file://untrustedContractCode" {
    permission "${}*", "read,write,delete";
    permission java.util.PropertyPermission "", "read";

The defined permissions grant the contract code access to the temporary folder on the contract runner workstation. The contract can freely read,write,delete files in this folder.

In addition contracts are always allowed to connect to any internet address.

See the AllowedActions contract for code samples permitted by the policy file and the ForbiddenActions for code samples not permitted by default. Neither of these samples cover the full list of permitted and non-permitted actions.

Granting Additional Contract Permissions

At the moment all sample contracts are designed to run using the default untrusted contract permissions, however developing contracts with elevated permissions is supported. A contract runner node operator can grant additional permissions by adding these permissions to the untrustedContractCode protection domain of ardor.policy, but this is discouraged since it will grant these permissions to all the contracts run by this contract runner. Instead we recommend elevating permissions per contract signer account or per specific contract.

To grant additional permissions for contracts signed by a specific account, define the public key of the account that submitted the contract to the blockchain, in a new protection domain entry using the "signedBy" token. The public key should be specified in hex string format.

For example the following ardor.policy section grants additional permissions for contracts submitted to the blockchain by the account whose public key is 112e0c5748b5ea610a44a09b1ad0d2bddc945a6ef5edc7551b80576249ba585b

grant codeBase "file://untrustedContractCode" signedBy "112e0c5748b5ea610a44a09b1ad0d2bddc945a6ef5edc7551b80576249ba585b" {
    permission "db";
    permission "getBlockchain";

Similarly to grant additional permissions to a specific contract use the "principal" setting followed by the contract's transaction full hash or the hash of the whole tagged data as saved in the blockchain or the hash of the contract class/jar itself.

grant codeBase "file://untrustedContractCode" principal "df15278b53c5c24ccb179302834608b50b94c3e91c97ffa0510357e35fec919b" {
    permission "db";

To monitor the permissions assigned to your contract in runtime, use the following Java command line flag"access". Additional information about protection domains can be obtained using"access,domain" but this creates very verbose output. Look in the generated log for permissions which are marked as "denied" and use the diagnostic information to learn the reason for denial.

To configure the ardor.policy file you can use any text editor or IntelliJ IDE but be warned that the syntax of this file is very strict, therefore every small typo will render the file useless and remove all permissions to all code resulting in errors when starting the node.

Alternatively use the graphical policytool provided with the Java JRE for making changes to the policy file without the risk of creating syntax errors.

To temporarily disable all permission checks add the following property to the file: nxt.disableSecurityPolicy=true

Checking Contract Permissions

A contract which requires elevated permissions from the contract runner should start by checking that these permissions were granted, if not, it should fail gracefully. Use the context.isPermissionGranted() API to check if a specific permission is granted. See the DatabaseAccess sample contract for example how to check for permissions. If a required permission is not granted, log an error message and return.

Sample Contracts

There are multiple sample contracts included with source code as part of the product installation. The sample contracts are documented using javadoc and internal comments. Always use the sample contracts as reference to understand correct coding techniques and best practices.

Javadoc documentation

Contract Trigger Transaction

To trigger execution of a deployed contract, any account can submit a transaction with a special message attachment. The message should be prunable (leave "Message is Never Deleted" unchecked in the wallet) and should be formatted as Json, can be either encrypted or plain text, should identify the contract to execute and optionally provide contract specific invocation parameters.


Trigger the HelloWorld contract by sending a message transaction to the contract runner account with the text:


Trigger the SplitPayment contract and describe how to split the payment, by sending a payment transaction to the contract and add the following attached message:


Note that the "params" token in the message is a Json object by itself, the data in the params object is contract specific. Consult the contract author as to which params needs to be submitted and in which format.

When a trigger transaction is submitted, all active contract runners will process this transaction. Whether or not the contract will actually process the transaction depends on the contract itself.

For example most contracts will only process payments submitted to their contract runner account and ignore payments made to other accounts but this depends on the contract logic. Contracts can choose to process certain transaction types, certain chains and certain recipient accounts based on their own internal logic.

Oracle Contracts - Interface with External Systems

One of the powerful features of Lightweight Contracts is their ability to interface with external systems to load data and register it on the blockchain and to save data to an external system based on blockchain data. Interfacing with external systems is a trade off between decentralization and utility. There are cases where a specific contract runner can load data from an external system such as exchange rate or personal data that no other contract runner can validate. There is no choice for users but to trust the contract runner not to manipulate this data the same way users trust a centralized system. Still, once the data is registered on the blockchain, it is digitally signed and timestamped by the contract runner account and can no longer change. So as long as the contract runner is a trusted entity information provided by it can be used safely.

However, there are few steps contract developers need to take to increase the reliability of Oracle contracts, as a developer you should consider that contracts should be designed to be idempotent (i.e. can run multiple times on the same data and produce the same result). For example a contract can run once when a trigger transaction is received, then again if the node switches to a better fork and the trigger transaction is included in a different fork, and again in case the contract runner is re-downloading the blockchain. Ideally in all these cases the contract should reproduce the exact same output transactions. The blockchain itself protects against including duplicate transactions in the blockchain.

However when relying on external data there is no way to guarantee that transaction data won't change between invocations. Therefore developers of Oracle contracts should always look for duplicate transactions generated by the contract based on the same trigger transaction but with different data (if the data is the same, duplicate transactions will be discarded by the blockchain itself).

Contract developers need to override the following method:

public <T extends TransactionResponse> boolean isDuplicate(T myTransaction, List<T> existingUnconfirmedTransactions) { ... }

The method accepts the transaction currently submitted by the contract and a list of transactions already waiting to be included in the blockchain. The contract should implement contract specific policy to identify if the new transaction duplicates an existing transaction. If it does, the method should return true value to instruct the contract runner not to submit the new transaction to the blockchain.

Example for Oracle contracts

The IgnisArdorRates contract uses the Bittrex exchange APIs to calculate the market rate between Ardor and Ignis and compare it to the decentralized coin exchange market rate. For this contract duplicates are no a real risk, at worst, there will be slightly different exchange rates registered on the blockchain on the same block.

The LiberlandCitizenRegistry contract uses the Liberland APIs to load citizen data. There is a small risk that the citizen data will change between invocations of this contract so a duplicate check makes sense before submitting a transaction.

Testing and Debugging your Contracts

Testing and debugging your contract is supported by the IntelliJ IDE using the standard tools used to test and debug any Java program.

Unit Tests

For each of the sample contracts provided with the product there is also a matching unit test class with the same name followed by a "Test" postfix. For example contract HelloWorld has a unit test named HelloWorldTest. Unit tests rely on the junit framework, each unit test starts from a clean blockchain copy, which only includes the Genesis block, it then deploys a contract, perform some actions on the contract and tests the output transactions.

All sample unit tests are grouped into the ContractRunnerSuite, the IntelliJ project provided by the installation includes a predefined launcher which runs this test suite. The first run will generate the unit tests blockchain database. Subsequent runs should work faster.

Our recommendation is that whenever you design a new contract you always design a matching unit test class to test it. Whenever you make changes to the contract make sure that all unit tests for the contract pass without errors. Unit tests also enables you to design your contract without deploying it to the blockchain where it is visible to others.

Debugging your Contracts

You can easily debug your contracts by placing a breakpoint inside the contract code. You can then run the unit test which test your contract in debug mode and debug the contract. Another option is to deploy your contract to the testnet then run your node using the "Ardor Local" launcher in debug mode. This will let you debug your contract exactly as it is executed in runtime. Since contracts are simply Java programs you can use any common debugging technique available by IntelliJ such as conditional breakpoint etc.

Obtaining more Diagnostic information

The contract runner and the contracts it runs, log information about their operation in the normal Ardor log.