Skip to content

harmony-one/harmonyj

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

37 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Harmony Java SDK

HarmonyJ is a light-weight Java library for interacting with Harmony blockchain. For high-level information about Harmony and its goals, visit harmony.one. The harmony white paper provides a complete conceptual overview.

Features

  • Partial implementation of Harmony's JSON-RPC client API over HTTP with features
  • Local key management
  • Accounts and getting balance
  • Creating, signing, and sending transactions

Dependencies

Getting started

To get started, it is best to have the latest JDK and Gradle installed.

Installation

Add the following Maven dependency to your project's pom.xml:

<dependency>
  <groupId>one.harmony</groupId>
  <artifactId>harmonyj</artifactId>
  <version>1.0.20</version>
</dependency>

Building from the command line

To perform a full build (including JavaDocs and unit/integration tests) use JDK 8+

gradle clean build

To perform a full build without unit/integration tests use:

gradle clean assemble

Specifying configuration properties

HarmonyJ reads the below listed user specified properties from {user.home}/hmy-config.properties file.

  • node Harmony URL to connect to
  • keystore.dir The keystore directory path. This should be absolute path.
  • accounts.dir The accounts directory alias where the accounts and key files are stored.
  • passphrase The default passphrase to use
  • mnemonics.file.path The path to mnemonics file that stores the generated mnemonics

An example hmy-config.properties file looks like:

node=http://localhost:9500/
keystore.dir=/Users/john/kestore.local
accounts.dir=accounts-keys
passphrase=harmony-one
mnemonics.file.path=/Users/john/kestore.local/mnemonics.txt

Another way to pass the configuration parameters is using the Config.setConfigParameters method.

import one.harmony.common.Config;

public class Test {
	public static void main(String[] args) throws Exception {
		String nodeUrl = "http://localhost:9500";
		String keystoreDir = "/Users/john/mystore";
		String accountsDir = "accounts-keys";
		String defaultPassPhrase = "harmony-one";
		Config.setConfigParameters(nodeUrl, keystoreDir, accountsDir, defaultPassPhrase);
	}
}

If the user wants to specify an absolute file path to store the generated mnemonics:

	...
	String mnemonicsFilePath = keystoreDir + "/mnemonics.txt";
	Config.setConfigParameters(nodeUrl, keystoreDir, accountsDir, defaultPassPhrase, mnemonicsFilePath);

Generating Javadoc

HarmonyJ provides information about SDK classes and methods also in the form of javadoc that can be generated using gradle.

gradle javadoc

Examples

The key API classes can be found under one.harmony.cmd are:

  1. Blockchain
  2. Balance
  3. Transfer
  4. Keys
  5. Contract

Blockchain

Currently, the Blockchain class only provides an api for fetching the Harmony protocol version. In future, this class will support more functionalities like querying block information, etc. The example shown below retrieves the protocol version.

import one.harmony.cmd.Blockchain;

public void getProtocolVersion() {
	String protocol = Blockchain.getProtocolVersion();
}

Blockchain class can be used to send raw transactions.

import one.harmony.cmd.Blockchain;

public static void main(String[] args) throws Exception {
	String nodeUrl = "http://localhost:9500";
	String rawTransaction = "0x...";
	String txHash = Blockchain.sendRawTransaction(nodeUrl, rawTransaction);
}

The account transactions history can be queried as follows:

import one.harmony.cmd.Blockchain;

public static void main(String[] args) throws Exception {
	// By default Config.node is used, however, a node url can be passed
	String node = "http://localhost:9500/";
	String address = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy";
	HistoryParams params = new HistoryParams(address);
	// For getting only transaction hashes, set `params.setFullTx(false)`
	System.out.println(getAccountTransactions(node, params));
}

The output with full transaction history will be:

[
	{
		"blockHash":"0x57db6b6622c2fc6a047d372750c4f0bbf1745847b27848126290a5e0621b9a6c",
		"blockNumber":"0x18db22",
		"from":"one18n8e7472pg5fqvcfcr5hg0npquha24wsxmjheg",
		"gas":"0x33450",
		"gasPrice":"0x174876e800",
		"hash":"0x6193734698696eb8e33354a54493e0a760d5e00ef12710bd0847cddd09e85e9a",
		"input":"0x",
		"nonce":"0x1f",
		"r":"0xa00c7c12ae9ca9dbfbea0abc0fe9abc241bcd73caf9849e7194002f354c1088e",
		"s":"0x588dcfbb8fc354465d2ba6393a09b8ccb555ec647d97de639a106a071f755b77",
		"shardID":0,
		"timestamp":"0x5de4ca90",
		"to":"one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy",
		"toShardID":0,
		"transactionIndex":"0x0",
		"v":"0x26",
		"value":"0x16345785d8a0000"
	}, ...
]

The output with only transaction hashes:

[
	"0x6193734698696eb8e33354a54493e0a760d5e00ef12710bd0847cddd09e85e9a",
	"0xd53a22fdac3697d049f1a76c528da42502a1b4be9a37bf6e11ae5ef55d3672cd",
	...
]

Balance

The Balance class provides an api for checking the balance of a Harmony account using the Harmony's one address. The check method retrieves balances on all shards (note that, Harmony is a sharded blockchain). The example below retrieves the balance of a localnet account.

import one.harmony.cmd.Balance;

public void queryBalance() throws Exception {
	String oneAddress = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy";
	Balance.check(oneAddress);
}

The sample output is:

[
	{ 
		"shard": 0, 
		"amount": 15926.97359095238 
	},
	{ 
		"shard": 1, 
		"amount": 0.0 
	}
]

For checking the balance using your own Harmony node:

import one.harmony.cmd.Balance;

public void queryBalance() throws Exception {
	String oneAddress = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy";
	Balance.checkLocal(oneAddress);
}

The sample output is: 206.1904761904762

Transfer

The transfer class provides functionality to transfer funds between any two Harmony accounts. The key api's are Transfer constructor and execute method. The execute method returns the transaction hash of the transfer. To check that transfer is committed to the blockchain, increase the waitToConfirmTime in seconds. An example is shown below:

import one.harmony.cmd.Transfer;

public void testTransfer() throws Exception {
	String from = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy"; // Harmony localnet addresses
	String to = "one1pf75h0t4am90z8uv3y0dgunfqp4lj8wr3t5rsp"; // Harmony localnet addresses
	String amount = "50.714285714";
	int fromShard = 0;
	int toShard = 1;
	boolean dryRun = false;
	int waitToConfirmTime = 0;
	String passphrase = "harmony-one";
	String memo = "0x5061796d656e7420666f722078797a";
	Transfer t = new Transfer(from, to, amount, fromShard, toShard, memo);
	t.prepare(passphrase); // prepare transfer locally, before connecting to the network
	String txHash = t.execute(LOCAL_NET, dryRun, waitToConfirmTime); // needs connection to the network
}

For the transfer to work, the from address account key must exists in the local keystore. If not, add the key using the key management apis. The transfer automatically creates a transaction, encodes it, signs the transaction using from address's key, and sends it to the blockchain.

For performing transfers using Harmony node use:

String nodeUrl ; "http://127.0.0.1:9500";
t.prepare(passphrase, nodeUrl);
String txHash = t.execute(LOCAL_NET, dryRun, waitToConfirmTime)

Keys

The Keys class provides the local key management functionality such as creating new account or adding a key, importing/exporting a private key or keystore, listing local accounts, etc.

Add key

Multiple addKey apis are provided. The addKey api returns the Harmony one address of the created account.

public static String addKey(String accountName, String passphrase, String mnemonic);
public static String addKey(String accountName, String passphrase);
public static String addKey(String accountName);

An example:

import one.harmony.cmd.Keys;

String mnemonics = ""; // long string containing words
String oneAddress = Keys.addKey("a2", "harmony", mnemonics);

Import private key

To create an account using a private key, use importPrivateKey api. This api returns the Harmony one address of the created account.

public static String importPrivateKey(String secp256k1PRV, String accountName, String passphrase);
public static String importPrivateKey(String secp256k1PRV, String accountName);

An example:

import one.harmony.cmd.Keys;

String key = "fd416cb87dcf8ed187e85545d7734a192fc8e976f5b540e9e21e896ec2bc25c3"; // dummy private key
String accountName = "a1";
String oneAddress = Keys.importPrivateKey(key, accountName);

Sample output:

one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy

Import keystore

To import a keystore file (json file describing the keystore) as shown below, use importKeyStore api and pass the account name.

{
    "address":"",
    "id":"",
    "version":3,
    "crypto":{
    	...
    }
}
public static String importKeyStore(String keyFilePath, String accountName);
public static String importKeyStore(String keyFilePath, String accountName, String passphrase);

Export private key

public static String exportPrivateKeyFromAddress(String oneAddress, String passphrase);
public static String exportPrivateKeyFromAddress(String oneAddress);
public static String exportPrivateKeyFromAccountName(String accountName, String passphrase);
public static String exportPrivateKeyFromAccountName(String accountName);

Export keystore

For exporting the keystore file exportKeyStore... apis can be used. Multiple variations of the exportKeyStore... api are provided.

public static String exportKeyStoreFromAddress(String oneAddress, String passphrase);
public static String exportKeyStoreFromAddress(String oneAddress);
public static String exportKeyStoreFromAccountName(String accountName, String passphrase);
public static String exportKeyStoreFromAccountName(String accountName);

List local accounts

The list all local account information use listAccounts api.

import one.harmony.Keys;

Map<String, String> accountInfo = Keys.listAccounts();

The output will display account names and Harmony one addresses for all the local accounts.

Get key location

To retrieve the local keystore path use getKeysLocation api.

import one.harmony.cmd.Keys;

String keydir = Keys.getKeysLocation();

Change local account name

The setAccountName(oneAddress, accountName) lets users to change the account name associated with a local account with Harmony one address.

import one.harmony.cmd.Keys;

String oneAddress = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy";
boolean status = Keys.setAccountName(oneAddress, "local-account-2");

Clean keystore

The cleanKeyStore() api wipes out the keystore and permanently deletes all the local account informations.

import one.harmony.cmd.Keys;

Keys.cleanKeyStore();

For details about all the APIs, refer to javadocs.

Contract

Similar to Web3j, Harmonyj provides a codegen feature to generate a java wrapper for your contract, which can be used to deploy and interact with contract. The steps are as follows:

  1. Generate .abi and .bin file for your contract
  2. Generate .java wrapper for your contract
  3. Use .java to deploy and interact with your contract

The detailed steps are below:

Generate .abi and .bin files for your solidity contract. e.g., if you have Counter.sol, you could use solc or solcjs

contract Counter {
    int256 private count = 0;

    function incrementCounter() public {
        count += 1;
    }

    function decrementCounter() public {
        count -= 1;
    }

    function getCount() public view returns (int256) {
        return count;
    }
}
solcjs Counter.sol --bin --abi --optimize -o <output-dir>

The output directory will contain Counter_sol_Counter.abi and Counter_sol_Counter.bin.

Generate Harmonyj wrapper, using SolidityFunctionWrapperGenerator class. e.g.,

String[] options = new String[] { "-a", "Counter_sol_Counter.abi", "-b", "Counter_sol_Counter.bin", "-o", ".", "-p", "one.harmony.cmd" };
SolidityFunctionWrapperGenerator.main(options);

The possible options that can be passed are:

  -h, --help                 Show this help message and exit.
  -V, --version              Print version information and exit.
  -a, --abiFile=<abiFile>    abi file with contract definition.
  -b, --binFile=<binFile>    bin file with contract compiled code in order to
                               generate deploy methods.
  -o, --outputDir=<destinationFileDir>
                             destination base directory.
  -p, --package=<packageName>
                             base package name.
      -jt, --javaTypes       use native java types.
                               Default: true
      -st, --solidityTypes   use solidity types.

The SolidityFunctionWrapperGenerator will generate a wrapper Counter_sol_Counter.java int the package one.harmony.cmd, which can be used to deploy the contract and interact.

Deploying the contract

Import/add the account that you wish to use for deploying the contract. e.g.,

String key = "fd416cb87dcf8ed187e85545d7734a192fc8e976f5b540e9e21e896ec2bc25c3";
String accountName = "a1";
Keys.importPrivateKey(key, accountName);

Create a handler with account information such as one-address, passphrase, node url, and chainID. For mainnet: ChainID.MAINNET, testnet: ChainID.TESTNET, localnet: ChainID.LOCAL.

String from = "one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy";
String passphrase = "harmony-one";
String node = "http://127.0.0.1:9500/";
Handler handler = new Handler(from, passphrase, node, ChainID.LOCAL);

Create a contract gas provider:

class MyGasProvider extends StaticGasProvider {
	public MyGasProvider(BigInteger gasPrice, BigInteger gasLimit) {
		super(gasPrice, gasLimit);
	}
}
MyGasProvider contractGasProvider = new MyGasProvider(new BigInteger("1"), new BigInteger("6721900")); 

Deploy contract:

Counter_sol_Counter counter = Counter_sol_Counter.deploy(handler, contractGasProvider).send();
System.out.println("Contract deploy at " + counter.getContractAddress());

Load the deployed contract for interacting:

MyGasProvider contractGasProvider = new MyGasProvider(new BigInteger("1"), new BigInteger("6721900")); 
Counter_sol_Counter contract = Counter_sol_Counter.load(contractAddress, contractGasProvider);
contract.setHandler(handler);

For read-only contract interactions (such as calling a contract to fetch some value), the handler need not be associated with an account. e.g.,

String node = "http://127.0.0.1:9500/";
Handler handler = new Handler(node, ChainID.LOCAL);

For modifying (or updating) the contract, the handler must be associated with an account, as shown for deploying.

Calling a read-only contract method. e.g., getCount() in the Counter contract:

System.out.println("Value stored in remote smart contract: " + contract.getCount().send());

Updating the contract, e.g., incrementCounter() in the Counter contract:

TransactionReceipt transactionReceipt = contract.incrementCounter().send();

The TransactionReceipt will contain all the details:

{
	transactionHash='0x54492458ea1b6200a481d38dc99cb8a19cea16ee777c5ea95bcfafabd7293392', 
	transactionIndex='0x0', 
	blockHash='0xe5b6de4343dda1433c3fcffefbc008e17a8a1d17eaa794a8541be10acc67281f', 
	blockNumber='0xb3a', 
	cumulativeGasUsed='0xa289', 
	gasUsed='0xa289', 
	contractAddress='null', 
	root='null', 
	status='0x1', 
	from='one1pdv9lrdwl0rg5vglh4xtyrv3wjk3wsqket7zxy', 
	to='one19f70ncsqp3ny7wp2h5vmd2540zr6yx2yjs45n7', 
	logs=[], logsBloom='0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
}

For the complete auto-generated Counter contract: Auto-generated Counter contract wrapper

Complete example of deploying and interacting with Counter contract: CounterContractExample

Decoding the contract logs:

After updating a contract, if we want to process the logs for events, e.g., in the Greeter.sol contract, we want to process the emitted event Modified

function newGreeting(string memory _greeting) public {
	emit Modified(greeting, _greeting, greeting, _greeting);
	greeting = _greeting;
}

event Modified(
	string indexed oldGreetingIdx, string indexed newGreetingIdx,
	string oldGreeting, string newGreeting
);
TransactionReceipt transactionReceipt = contract.newGreeting("Well hello again").send();
for (Greeter_sol_Greeter.ModifiedEventResponse event : contract.getModifiedEvents(transactionReceipt)) {
	System.out.println(
			"Modify event fired, previous value: " + event.oldGreeting + ", new value: " + event.newGreeting);
	System.out.println("Indexed event previous value: " + Numeric.toHexString(event.oldGreetingIdx)
			+ ", new value: " + Numeric.toHexString(event.newGreetingIdx));
}

The complete GreeterContractExample