[Caver-java] Dynamic ABI Loader
See the list of articles here.
🇰🇷: [Caver-java] Dynamic ABI Loader
Caver-java’s Contract Dynamic ABI Loader allows you to easily deploy and execute contracts by loading Smart Contract ABI (Application Binary Interface) at runtime, and is included in caver.contract.Contract. With the Dynamic ABI Loader, you can load and execute various contracts with greater ease.
New Features
In the versions before caver-java v1.5.1, we had to use caver-java CLI to create a java file with the ABI file and binary data of a contract, and register it on the project. It was inconvenient that a new java file had to be created, registered and compiled, every time a contract changed.
But from caver-java v1.5.1 and onwards, the Dynamic ABI Loader feature has been added, so that the ABI file and binary data can be dynamically loaded. In the tutorial below, we will demonstrate how you can use it.
In each section, we will go through little snippets of the whole code; you can check the entire code in the link below.
The complete source code for caver-java
Creating Contract Instance
First, we will make a sample contract to load the ABI with caver-java. Name the file ‘KVstore.sol’ and enter the code below. This contract can query and retrieve key-value pairs.
// KVstore.sol
pragma solidity ^0.5.6;contract KVstore {
mapping(string=>string) store;constructor (string memory key, string memory value) public {
store[key] = value;
}
function get(string memory key) public view returns (string. memory) {
return store[key];
}
function set(string memory key, string memory value) public {
store[key] = value;
}
}
And then create the ABI and binary data using Solidity compiler.
solc --abi --bin ./KVStore.sol======= ./KVstore.sol:KVStore =======
Binary:
0x608060405234801561001057600080fd5b5060405161072d38038061072d8339810180604052604081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190602001805164010000000081111561009a57600080fd5b828101905060208101848111156100b057600080fd5b81518560018202830111640100000000821117156100cd57600080fd5b5050929190505050806000836040518082805190602001908083835b6020831061010c57805182526020820191506020810190506020830392506100e9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061015292919061015a565b5050506101ff565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019b57805160ff19168380011785556101c9565b828001600101855582156101c9579182015b828111156101c85782518255916020019190600101906101ad565b5b5090506101d691906101da565b5090565b6101fc91905b808211156101f85760008160009055506001016101e0565b5090565b90565b61051f8061020e6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063693ec85e1461003b578063e942b5161461016f575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102c1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610134578082015181840152602081019050610119565b50505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102bf6004803603604081101561018557600080fd5b81019080803590602001906401000000008111156101a257600080fd5b8201836020820111156101b457600080fd5b803590602001918460018302840111640100000000831117156101d657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561023957600080fd5b82018360208201111561024b57600080fd5b8035906020019184600183028401116401000000008311171561026d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506103cc565b005b60606000826040518082805190602001908083835b602083106102f957805182526020820191506020810190506020830392506102d6565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103c05780601f10610395576101008083540402835291602001916103c0565b820191906000526020600020905b8154815290600101906020018083116103a357829003601f168201915b50505050509050919050565b806000836040518082805190602001908083835b6020831061040357805182526020820191506020810190506020830392506103e0565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061044992919061044e565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061048f57805160ff19168380011785556104bd565b828001600101855582156104bd579182015b828111156104bc5782518255916020019190600101906104a1565b5b5090506104ca91906104ce565b5090565b6104f091905b808211156104ec5760008160009055506001016104d4565b5090565b9056fea165627a7a72305820adabefbb9574a90843d986f100c723c37f37e79f289b16aa527705b5341499aa0029Contract JSON ABI
[{"constant":true,"inputs":[{"name":"key","type":"string"}],"name":"get","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"key","type":"string"},{"name":"value","type":"string"}],"name":"set","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"key","type":"string"},{"name":"value","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]
With the code below, you can create a contract instance directly from the ABI data using Dynamic ABI Loader, and check which functions of the contract can be called in the instance.
String abi = "[\n" +" {\n" +
" \"constant\":true,\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"name\":\"get\",\n" +
" \"outputs\":[\n" +
" {\n" +
" \"name\":\"\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"view\",\n" +
" \"type\":\"function\"\n" +
" },\n" +
" {\n" +
" \"constant\":false,\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" },\n" +
" {\n" +
" \"name\":\"value\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"name\":\"set\",\n" +
" \"outputs\":[],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"nonpayable\",\n" +
" \"type\":\"function\"\n" +
" },\n" +
" {\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" },\n" +
" {\n" +
" \"name\":\"value\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"nonpayable\",\n" +
" \"type\":\"constructor\"\n" +
" }\n" + "]";Contract contract = caver.contract.create(abi);
contract.getMethods().forEach((name, method) ->{
System.out.println(method.getType() + " " + caver.abi.buildFunctionString(method));
});
Execute the code, you will get this result:
function set(string,string)
function get(string)
Deploying Contracts
You can deploy contracts using binary data and ABI as shown below.
String abi = "Contract ABI Data";
String byteCode = "Contract Byte Code";
Contract contract = caver.contract.create(abi);
contract.getMethods().forEach((name, method) ->{
System.out.println(method.getType() + " " + caver.abi.buildFunctionString(method));
});
SingleKeyring deployerKeyring = caver.wallet.keyring.create(deployerAddress, deployerPrivateKey);
caver.wallet.add(deployerKeyring);
SendOptions sendOptions = new SendOptions(deployerKeyring.getAddress(), BigInteger.valueOf(650000));
Contract deployedContract = contract.deploy(sendOptions, byteCode, "Just", "Test");
System.out.println("Deployed address of contract: " + deployedContract.getContractAddress());
When executing the deploy function, the first parameter is a SendOptions
object, which sets the transaction send options, and the second parameter is the contract binary data. After that, pass the parameters in the order as declared in the contract’s constructor. The deploy function creates an instance for the smart contract deploy transaction type.
SendOptions
is an object for storing data required for creating transactions. SendOptions
is defined as three fields: from, gas, and value, each playing a role as explained below:
- from
The value for the “from” field of the transaction instance. When signing the transaction, you can search for the keyring using this value in caver.wallet. - gas
The value for the “gas” field of the transaction instance. - value
The value for the “value” field of the transaction instance.
When you execute the above code, you will see in the result the contract address deployed on the Klaytn network.
0xc9267c4a39c606f4f90df4bb326ec1d0572d5750
Connecting to the Deployed Contract
In order to connect with the deployed contract, you need the ABI and address of the contract. Below is an example with which you can connect to a deployed contract.
String abi = "Contract ABI Data";
String contractAddress = "0xbe3867496bc619dff5467ceeb8d72779927dbe7a";
Contract contract = caver.contract.create(abi, contractAddress);
System.out.println(contract.getContractAddress());
When you run the code above, you will get a contract address as a result.
0xbe3867496bc619dff5467ceeb8d72779927dbe7a
Calling the Contract Function
The ABI data analyzed by Dynamic ABI Loader will be categorized according to type (Function or Event), and be created as an instance of ContractMethod and ContractEvent. The contract instance manages ContractMethod and ContractEvent objects as lists.
There are two ways to call a contract function.
call()
: Receives data from a contract without changing the state of the contract. Internally, JSON-RPC klay_call() is used.send()
: Changes the state of the contract. Internally, Smart Contract Execution transaction will be used to send transactions to Klaytn.
Keep in mind these two ways for calling the contract function, so that you can use either call or send, depending on the function.
Contract function send()
Sending a contract function is to call a function that changes the state of the contract.
This send()
function submits a Smart Contract Execution transaction to the network and returns a Transaction Receipt.
For the KVStore contract, created and deployed above, set()
is the proper function using send()
. Executing the below code will call the set()
of the predeployed KVStore contract and get the Transaction Receipt returned.
String abi = "Contract ABI Data";
String contractAddress = "0xbe3867496bc619dff5467ceeb8d72779927dbe7a";
Contract contract = caver.contract.create(abi, contractAddress);
SingleKeyring executorKeyring = caver.wallet.keyring.create(executorAddress, executorPrivateKey);
caver.wallet.add(executorKeyring);
SendOptions sendOptions = new SendOptions(executorKeyring.getAddress(), BigInteger.valueOf(650000));
TransactionReceipt.TransactionReceiptData receiptData = contract.send(sendOptions, "set", "Just", "Test");
System.out.println(receiptData.getTransactionHash());
The first parameter for calling send()
is SendOptions
, and the second parameter is the name of the function to call. And then enter the necessary parameters in the right order. Send will create a SmartContractExecution transaction internally.
SendOptions
is an object that stores the data necessary for creating transactions. SendOptions
is defined with from, gas, and value, each playing a role as explained below:
- from
The value for the “from” field of the transaction instance. When signing the transaction, search for keyring with this value in caver.wallet. - gas
The value for the “gas” field of the transaction instance. - value
The value for the “value” field of the transaction instance.
The above code will return a transaction hash like the one below. Of course, since it’s just an example; the actual value will be different.
0x3d8dca23ca735e352444cba79dab16fe77ef3fc2121e42b1c1eedcfeb26eeed3
Contract function
call()
Calling a contract function is essentially returning the data without changing its state.
Internally, this call()
function will call klay_call of JSON-RPC API and obtain the corresponding data.
For the KVStore contract, created and deployed above, get()
is the appropriate function using call()
. Below is an example for calling the get()
function and to get data.
String abi = "Contract ABI Data";
String contractAddress = "0xbe3867496bc619dff5467ceeb8d72779927dbe7a";
Contract contract = caver.contract.create(abi, contractAddress);
List<Type> returnedData = contract.call("get", "Just");
System.out.println((String)returnedData.get(0).getValue());
The result will be the instances of the classes that implement Solidity types. It will be expressed as a list.
The result of the above code will look like this:
Test
For more information on how to use smart contracts with caver-java, please refer to Getting Started in Klaytn Docs.
Thank you!