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.
- 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
To get started, it is best to have the latest JDK and Gradle installed.
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>
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
HarmonyJ reads the below listed user specified properties from {user.home}/hmy-config.properties
file.
node
Harmony URL to connect tokeystore.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 usemnemonics.file.path
The path to mnemonics file that stores the generated mnemonics
An example hmy-config.properties file looks like:
node=https://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 = "https://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);
HarmonyJ provides information about SDK classes and methods also in the form of javadoc
that can be generated using gradle.
gradle javadoc
The key API classes can be found under one.harmony.cmd
are:
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 = "https://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 = "https://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",
...
]
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
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 ; "https://127.0.0.1:9500";
t.prepare(passphrase, nodeUrl);
String txHash = t.execute(LOCAL_NET, dryRun, waitToConfirmTime)
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.
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);
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
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);
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);
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);
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.
To retrieve the local keystore path use getKeysLocation
api.
import one.harmony.cmd.Keys;
String keydir = Keys.getKeysLocation();
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");
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.
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:
- Generate
.abi
and.bin
file for your contract - Generate
.java
wrapper for your contract - 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.
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 = "https://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 = "https://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