[Caver] How to Use Fee Delegation with Contract
See the list of articles here.
🇰🇷: [Caver] Contract를 사용하여 수수료를 대납해보자
Starting from caver v1.6.1, caver.contract
supports the fee delegation model. In this post, we will explain how to use fee delegation when using Contract with Caver.
In each section, we will show you little code snippets; you can check the entire code in the links below.
📢 Note
Your account needs to have a sufficient KLAY balance in order to execute the code shown in this tutorial.
1. Adding test account to in-memory wallet
Before deploying and executing a smart contract using caver.contract
, you need to add an account with sufficient KLAY to the in-memory caver.wallet
. For this you need a deployer
account that sends the transaction, and a feePayer
account that pays the transaction fee.
This is how you add an account to caver.wallet
using caver-js:
// Create keyrings and add to the in-memory wallet - caver-js
const deployerAddress = '0x{address}'
const deployerPrivateKey = '0x{private key}'
const feePayerAddress = '0x{address}'
const feePayerPrivateKey = '0x{private key}'
const deployerKeyring = caver.wallet.keyring.create(deployerAddress, deployerPrivateKey)
caver.wallet.add(deployerKeyring)const feePayerKeyring = caver.wallet.keyring.create(feePayerAddress, feePayerPrivateKey)
caver.wallet.add(feePayerKeyring)
Create a Keyring instance with the address and the private key to be used in the test account with the caver.wallet.keyring.create
function as parameter. The Keyring instance can then be added to the in-memory wallet using the caver.wallet.add
function.
If you have a keystore instead of a private key, you can create a Keyring
instance using caver.wallet.keyring.decrypt
. If your account key has been updated with a decoupled key, you can create the Keyring
instance using caver.wallet.keyring.create
.
You can also use the caver.wallet.add
function for caver-java as below:
// Create a keyring and add to the in-memory wallet - caver-java
String deployerAddress = "0x{address}";
String deployerPrivateKey = "0x{private key}";String feePayerAddress = "0x{address}";
String feePayerPrivateKey = "0x{private key}";
SingleKeyring deployerKeyring = caver.wallet.keyring.create(deployerAddress, deployerPrivateKey);
caver.wallet.add(deployerKeyring);
SingleKeyring feePayerKeyring = caver.wallet.keyring.create(feePayerAddress, feePayerPrivateKey);
caver.wallet.add(feePayerKeyring);
2. Deploying smart contract
In this section, we will be demonstrating a simple smart contract that maps keys with values. The contract looks like this:
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;
}
}
Below shows how you can deploy a smart contract using fee delegation with caver-js. The abi
and byteCode
in the source code below are the result of compiling the smart contract above.
// Deploy a smart contract to the Klaytn - caver-js
const byteCode = '0x608060405234801561001057600080fd5b5060405161072d38038061072d8339810180604052604081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190602001805164010000000081111561009a57600080fd5b828101905060208101848111156100b057600080fd5b81518560018202830111640100000000821117156100cd57600080fd5b5050929190505050806000836040518082805190602001908083835b6020831061010c57805182526020820191506020810190506020830392506100e9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061015292919061015a565b5050506101ff565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019b57805160ff19168380011785556101c9565b828001600101855582156101c9579182015b828111156101c85782518255916020019190600101906101ad565b5b5090506101d691906101da565b5090565b6101fc91905b808211156101f85760008160009055506001016101e0565b5090565b90565b61051f8061020e6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063693ec85e1461003b578063e942b5161461016f575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102c1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610134578082015181840152602081019050610119565b50505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102bf6004803603604081101561018557600080fd5b81019080803590602001906401000000008111156101a257600080fd5b8201836020820111156101b457600080fd5b803590602001918460018302840111640100000000831117156101d657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561023957600080fd5b82018360208201111561024b57600080fd5b8035906020019184600183028401116401000000008311171561026d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506103cc565b005b60606000826040518082805190602001908083835b602083106102f957805182526020820191506020810190506020830392506102d6565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103c05780601f10610395576101008083540402835291602001916103c0565b820191906000526020600020905b8154815290600101906020018083116103a357829003601f168201915b50505050509050919050565b806000836040518082805190602001908083835b6020831061040357805182526020820191506020810190506020830392506103e0565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061044992919061044e565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061048f57805160ff19168380011785556104bd565b828001600101855582156104bd579182015b828111156104bc5782518255916020019190600101906104a1565b5b5090506104ca91906104ce565b5090565b6104f091905b808211156104ec5760008160009055506001016104d4565b5090565b9056fea165627a7a72305820adabefbb9574a90843d986f100c723c37f37e79f289b16aa527705b5341499aa0029'const 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',
},]
let contract = caver.contract.create(abi)const keyString = 'keyString'// Send a FeeDelegatedSmartContractDeploy transaction directly to Klaytn.contract = await contract.deploy({
from: deployerKeyring.address,
feeDelegation: true,
feePayer: feePayerKeyring.address,
gas: 1000000,}, byteCode, keyString, 'valueString')console.log(`The address of deployed smart contract: ${contract.options.address}`)
Create a Contract
instance by passing the smart contract’s ABI (Application Binary Interface) as a parameter of the caver.contract.create
function. And then deploy the smart contract on Klaytn using the contract.deploy
method. The first parameter of the method is the options
object, where the values required for sending transactions are defined. From the example above, you can see that feeDelegation: true
and feePayer
are defined inside the options
object. If the feeDelegation
field is true
, fee delegation transaction will be used for deploying smart contracts. The FeeDelegatedSmartContractDeployWithRatio
transaction will be used if the feeRatio
field is defined in the options
object; otherwise FeeDelegatedSmartContractDeploy
transaction will be used.
contract.deploy
from the above example creates aFeeDelegatedSmartContractDeploy
transaction to deploy a smart contract. Since the keyring of the deployer and fee payer keyring managed by caver.wallet
are used for signing transactions before being sent to Klaytn, the fee payer account needs to be defined in the feePayer
field when using fee delegation. Pass the byte code of the smart contract as the second parameter, followed by the constructor parameters of the contract.
contract.deploy
returns the Contract instance that stores the address of the deployed smart contract.
If the transaction cannot be sent right away because the deployer and fee payer are different , you need a separate signature from each of them.
// The deployer signs the transaction to deploy a smart contract. - caver-js
const deployTx = await contract.sign({
from: deployerKeyring.address,
feeDelegation: true,
gas: 1000000,
}, 'constructor', byteCode, keyString, 'valueString')
console.log(`Deployer signed transaction: `)
console.log(deployTx)
const rlpEncoded = deployTx.getRLPEncoding()
You can get the transaction with the deployer’s signature using the contract.sign
function. In the example above, the options
object, which includes the data required for creating a transaction, is the first parameter of the contract.sign
function. You will notice constructor
in the place of the second parameter. contract.sign
requires the name of the method as the second parameter, but if you are deploying a smart contract, use constructor
instead.
You don’t have to define feePayer
in options
for contract.sign
, since it is simply for adding the deployer’s signature to the transaction. The signedFeeDelegatedSmartContractDeploy
transaction returned from contract.sign
can be retrieved as an RLP-encoded string using the getRLPEncoding
function.
// The fee payer signs the transaction to deploy a smart contract. - caver-js
const deployTx = caver.transaction.decode(rlpEncoded)
await caver.wallet.signAsFeePayer(feePayerKeyring.address, deployTx) // Signs the transaction as a fee payer
const receipt = await caver.rpc.klay.sendRawTransaction(deployTx)
console.log(`The address of deployed smart contract: ${receipt.contractAddress}`)
contract = caver.contract.create(abi, receipt.contractAddress)
The fee payer receives the RLP-encoded string and creates a transaction instance using caver.transaction.decode
. The fee payer can sign the FeeDelegatedSmartContractDeploy
transaction, returned from caver.transaction.decode
, using caver.wallet.signAsFeePayer
. Once the transaction has been signed by both the deployer and fee payer, it can be sent to the Klaytn network using caver.rpc.klay.sendRawTransaction
.
Use contract.signAsFeePayer
if the fee payer has to give the signature first. contract.signAsFeePayer
returns the transaction with the fee payer’s signature added to feePayerSignatures
. After that the deployer’s signature is added using caver.wallet.sign
and the transaction is sent to Klaytn using caver.rpc.klay.sendRawTransaction
.
With caver-java, you can deploy a smart contract with fee delegation as shown below:
// Deploy a smart contract to the Klaytn - caver-javaString byteCode = "0x608060405234801561001057600080fd5b5060405161072d38038061072d8339810180604052604081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190602001805164010000000081111561009a57600080fd5b828101905060208101848111156100b057600080fd5b81518560018202830111640100000000821117156100cd57600080fd5b5050929190505050806000836040518082805190602001908083835b6020831061010c57805182526020820191506020810190506020830392506100e9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061015292919061015a565b5050506101ff565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019b57805160ff19168380011785556101c9565b828001600101855582156101c9579182015b828111156101c85782518255916020019190600101906101ad565b5b5090506101d691906101da565b5090565b6101fc91905b808211156101f85760008160009055506001016101e0565b5090565b90565b61051f8061020e6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063693ec85e1461003b578063e942b5161461016f575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102c1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610134578082015181840152602081019050610119565b50505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102bf6004803603604081101561018557600080fd5b81019080803590602001906401000000008111156101a257600080fd5b8201836020820111156101b457600080fd5b803590602001918460018302840111640100000000831117156101d657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561023957600080fd5b82018360208201111561024b57600080fd5b8035906020019184600183028401116401000000008311171561026d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506103cc565b005b60606000826040518082805190602001908083835b602083106102f957805182526020820191506020810190506020830392506102d6565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103c05780601f10610395576101008083540402835291602001916103c0565b820191906000526020600020905b8154815290600101906020018083116103a357829003601f168201915b50505050509050919050565b806000836040518082805190602001908083835b6020831061040357805182526020820191506020810190506020830392506103e0565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061044992919061044e565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061048f57805160ff19168380011785556104bd565b828001600101855582156104bd579182015b828111156104bc5782518255916020019190600101906104a1565b5b5090506104ca91906104ce565b5090565b6104f091905b808211156104ec5760008160009055506001016104d4565b5090565b9056fea165627a7a72305820adabefbb9574a90843d986f100c723c37f37e79f289b16aa527705b5341499aa0029";
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);
String keyString = "keyString";
SendOptions sendOptionsForDeployment = new SendOptions();
sendOptionsForDeployment.setFrom(deployerKeyring.getAddress());
sendOptionsForDeployment.setGas(BigInteger.valueOf(1000000));
sendOptionsForDeployment.setFeeDelegation(true);
sendOptionsForDeployment.setFeePayer(feePayerKeyring.getAddress());
// Send a FeeDelegatedSmartContractDeploy transaction to the Klaytn directly.
contract.deploy(sendOptionsForDeployment, byteCode, keyString, "valueString");
System.out.println("The address of deployed smart contract:" + contract.getContractAddress());
If the transaction cannot be sent right away because the deployer and fee payer are different, you need a separate signature from each of them.
// The deployer and fee payer each sign the transaction to deploy a smart contract. - caver-java
SendOptions sendOptionsForSigning = new SendOptions();
sendOptionsForSigning.setFrom(deployerKeyring.getAddress());
sendOptionsForSigning.setGas(BigInteger.valueOf(1000000));
sendOptionsForSigning.setFeeDelegation(true);
AbstractTransaction signedTx = contract.sign(sendOptionsForSigning, "constructor", byteCode, keyString, "valueString");String rlpEncoded = signedTx.getRLPEncoding();// The fee payer signs the transaction to deploy a smart contract. - caver-java
AbstractTransaction signedTx = caver.transaction.decode(rlpEncoded);
caver.wallet.signAsFeePayer(feePayer.getAddress(), (AbstractFeeDelegatedTransaction)signedTx);
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(signedTx).send();String txHash = sendResult.getResult();
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash);
System.out.println("The address of deployed smart contract:" + receiptData.getContractAddress());
3. Calling the smart contract function
3.1. call
To confirm that the key and value passed to the constructor have been stored properly, call the get
function in the smart contract. The get
function simply returns data without changing the contract state. You can call function using contract.call
.
Below is how you call the get
function using caver-js:
// Read value from smart contract. - caver-js
let valueString = await contract.call('get', keyString)
console.log(`key: ${keyString} / value: ${valueString}`)
You can call the smart contract functions using contract.call
. Pass the function name as the first parameter, and the parameters for that function as the second parameter. If you want to define options
when calling the function, pass the object as the very first parameter. For more details, please refer to Klaytn Docs.
Below is how you can call the get
function using caver-java:
// Read value from smart contract. - caver-java
Utf8String valueString = (Utf8String)contract.call("get", keyString).get(0);
System.out.println("key: " + keyString + " / value: " + valueString.toString());
3.2. send
To make changes to the data stored in the smart contract, call the set
function of the deployed smart contract. The set
function makes changes to the smart contract’s state, and you need to send a transaction to call it. You can call the function using contract.send
. contract.send
will accept the object where the values required for creating a transaction are defined as the first parameter. When you are using fee delegation, you can additionally define feeDelegation
, feePayer
and feeRatio
in the object.
Below is how to call the set
function using caver-js:
// Send a FeeDelegatedSmartContractExecutionWithRatio transaction to the Klaytn directly. - caver-js
const setResult = await contract.send({
from: deployerKeyring.address,
feeDelegation: true,
feePayer: feePayerKeyring.address,
feeRatio: 50, // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution
gas: 1000000,
}, 'set', keyString, 'anotherValue')
In the object passed as the first parameter for contract.send
, feeDelegation
is defined as true
. This means that you will be using fee delegation to execute the set
function. Since feeRatio
is also defined, FeeDelegatedSmartContractExecutionWithRatio transaction will be used. Pass the name of the function that you want to call as the second parameter, followed by the parameters required by the function. After contract.send
creates a transaction, it requires both the deployer and fee payer to give their signatures before sending it to Klaytn. Therefore, feePayer
needs to be defined in the first parameter if you are using fee delegation.
If the transaction cannot be sent right away because the deployer and the fee payer are different, they can sign the transactions separately as shown below:
// The deployer and fee payer each sign the transaction to execute a smart contract. - caver-js
const executionTx = await contract.sign({
from: deployerKeyring.address,
feeDelegation: true,
feeRatio: 50, // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution
gas: 1000000,
}, 'set', keyString, 'anotherValue')
console.log(`Deployer signed transaction: `)
console.log(executionTx)
await caver.wallet.signAsFeePayer(feePayerKeyring.address, executionTx) // Signs the transaction as a fee payer
const setResult = await caver.rpc.klay.sendRawTransaction(executionTx)
Deployer signs the transaction using the contract.sign
function and adds the signature of the fee payer using the caver.wallet.signAsFeePayer
function. Once the transaction is signed, it will be sent to the Klaytn network using caver.rpc.klay.sendRawTransaction
.
Once the above transaction has been executed, you can use contract.call
to look at the result.
// Read value string from the smart contract. - caver-jsvalueString = await contract.call('get', keyString)
console.log(`After executing set function => key: ${keyString} / value: ${valueString}`)
In caver-java, call the set
function like this:
// Send a FeeDelegatedSmartContractExecutionWithRatio transaction to the Klaytn directly. - caver-java
SendOptions sendOptionsForExecution = new SendOptions();
sendOptionsForExecution.setFrom(deployer.getAddress());
sendOptionsForExecution.setGas(BigInteger.valueOf(1000000));
sendOptionsForExecution.setFeeDelegation(true);
sendOptionsForExecution.setFeePayer(feePayer.getAddress());
sendOptionsForExecution.setFeeRatio(BigInteger.valueOf(50)); // Without feeRatio, `send` will use FeeDelegatedSmartContractExecutionTransactionReceipt.TransactionReceiptData receiptData = contract.send(sendOptionsForExecution, "set", "key_inserted", "value_inserted");
System.out.println("Transaction type : " + receiptData.getType());
System.out.println("From : " + receiptData.getFrom());
System.out.println("Fee payer : " + receiptData.getFeePayer());
System.out.println("Fee ratio : " + receiptData.getFeeRatio());
If you need separate signatures from the deployer and fee payer for caver-java:
// The deployer and fee payer each sign the transaction to execute a smart contract. - caver-javaSendOptions sendOptionsForExecution = new SendOptions();
sendOptionsForExecution.setFrom(deployer.getAddress());
sendOptionsForExecution.setGas(BigInteger.valueOf(1000000));
sendOptionsForExecution.setFeeDelegation(true);
sendOptionsForExecution.setFeeRatio(BigInteger.valueOf(50)); // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution
AbstractTransaction executionTx = contract.sign(sendOptionsForExecution, "set", "key_inserted", "value_inserted");
caver.wallet.signAsFeePayer(feePayer.getAddress(), (AbstractFeeDelegatedTransaction)executionTx);
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(executionTx).send();String txHash_executed = sendResult.getResult();
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash_executed);
Once the transaction has been sent, use contract.call
to check the result.
// Use short-cut function to read value from smart contract. - caver-java
Utf8String value = (Utf8String)contract.call("get", keyString).get(0);
System.out.println("After executing set function => key: " + keyString + "value: " + value.toString());
Entire Source Code from the Article
We hope this post was helpful for you. For any questions or issues, leave them in the comment section or in our Klaytn Developer Forum.
Thank you for reading!