[Caver] How to Use Fee Delegation with Contract

Tech at Klaytn
Klaytn
Published in
9 min readJun 11, 2021

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 FeeDelegatedSmartContractExecution
TransactionReceipt.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!

--

--