Deploying Your Smart Contract using Fee Delegation

In an earlier topic, we went through the concept and the basic usage of Fee Delegation (FD). Today, we are going through one specific example — deploying a smart contract using FD.

The idea is simple: you create a transaction, give it to a payer, and then payer sends it to the Klaytn blockchain. Easy, right? Let’s do this one by one.

Smart Contract Bytecode

First, let’s take a look at the smart contract we want to deploy. We are going to use Count contract:

pragma solidity >=0.4.24 <=0.5.6;

contract Count {
    // Storage variable `count` (type: uint256)
    uint256 public count = 0;

    // Get current node's block number.
    function getBlockNumber() public view returns (uint256) {
      return block.number;
    }

    // Set value of storage variable `count`.
    function setCount(uint256 _count) public {
      count = _count;
    }
}

When you compile Count, you will get the following bytecode:

0x60806040526000805534801561001457600080fd5b5060e8806100236000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c806306661abd14604157806342cbb15c14605d578063d14e62b8146079575b600080fd5b604760a4565b6040518082815260200191505060405180910390f35b606360aa565b6040518082815260200191505060405180910390f35b60a260048036036020811015608d57600080fd5b810190808035906020019092919050505060b2565b005b60005481565b600043905090565b806000819055505056fea165627a7a7230582087453d981a85f80c5262508e1fe5abe94dc38b1167c49b6e3477b74293e9e7000029

This long hexadecimal string represents Count contract. Now, let’s create a contract deploying transaction.

Fee Delegated Smart Contract Deploy Transaction

All write actions on Klaytn must be described in transactions. Since deploying a contract is equivalent as creating an account and storing code in Klaytn, deploying a smart contract must be done through submitting a transaction. Let’s take a look at the following transaction object:

let tx = {
    type: 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY',
    from: 'SENDER_ADDRESS',
    data: '0x6080...0029',
    gas: '3000000',
    value: 0,
}

This is probably the minimum set of information you need to specify. From the top to bottom:

  1. type is `‘FEE_DELEGATED_SMART_CONTRACT_DEPLOY’; you must specifically state this type in order to deploy your smart contract using FD
  2. from is always required
  3. Put the bytecode we obtained earlier in data field. Make sure it starts with 0x
  4. Put sufficient amount in gas
  5. value is a required field — put 0 if you don’t plan to transfer any KLAY to a new contract

There is also another transaction type called 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY_WITH_RATIO'. This particular transaction type is used when sender and payer split the fee based on the ratio indicated in the transaction. We’ll discuss this topic later.

All set! Let’s sign this transaction to get the raw transaction.

const { rawTransaction: raw } = caver.klay.accounts.signTransaction(tx, 'SENDER_PRIV_KEY');

Note that caver.klay.accounts.signTransaction is not a blockchain call and returns Promise. You might want to use await in an async function.

Payer Action

The rest is as identical as any other FD transaction usages. We send this signed raw transaction to a payer and hope he sends it to the Klaytn blockchain. Assuming there is a hypothetical payer accepting your raw transaction, he or she might handle it as follows:

caver.klay.sendTransaction({
        senderRawTransaction: senderRawTransaction,
        feePayer: payer.address
})
    .on('transactionHash', function (hash) {
        console.log(">>> tx_hash for deploy =", hash);
        // should notify the sender
    })
    .on('receipt', function (receipt) {
        console.log(">>> receipt arrived: ", receipt);
        // should notify the sender
    })
    .on('error', function (err) {
        console.error(">>> error: ", err);
    });

Contract Address?

Unfortunately, since you are deploying a smart contract via a FD payer, you won’t be receiving the receipt of your transaction. Then how do you know the address of your contract?

Luckily, contract addresses are deterministically chosen based on your address and the nonce you used for the contract deploying transaction (yes, it’s the same method Ethereum used before CREATE2). Here is a code directly taken off from the Klaytn blockchain source code:

// CreateAddress creates a Klaytn address given the bytes and the nonce
func CreateAddress(b common.Address, nonce uint64) common.Address {
	data, _ := rlp.EncodeToBytes(struct {
		Addr  common.Address
		Nonce uint64
	}{b, nonce})
	return common.BytesToAddress(Keccak256(data)[12:])
}

So, in case your payer is not kind enough to provide you details of your transaction, you can still get your contract address assuming that your transaction went through.

Code Sample

Although unlikely, to understand contract deployment using FD, we bundle sender and payer code together in one single code. Copy the code below and have fun :wink:

const Caver = require('caver-js');
const caver = new Caver('https://api.baobab.klaytn.net:8651/');

(async () => {

    // Sender account retrieved from a private key.
    const senderKey = 'SENDER_PRIVATE_KEY';
    const sender = caver.klay.accounts.wallet.add(senderKey);

    // An arbitrary contract is used. Take the bytecode of your contract compilation outcome.
    // Make sure
    // 1. type is 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY'
    // 2. `data` starts with 0x
    // 3. you put sufficient gas
    const tx = {
        type: 'FEE_DELEGATED_SMART_CONTRACT_DEPLOY',
        from: sender.address,
        data: '0x60806040526000805534801561001457600080fd5b5060e8806100236000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c806306661abd14604157806342cbb15c14605d578063d14e62b8146079575b600080fd5b604760a4565b6040518082815260200191505060405180910390f35b606360aa565b6040518082815260200191505060405180910390f35b60a260048036036020811015608d57600080fd5b810190808035906020019092919050505060b2565b005b60005481565b600043905090565b806000819055505056fea165627a7a7230582087453d981a85f80c5262508e1fe5abe94dc38b1167c49b6e3477b74293e9e7000029',
        gas: '3000000',
        value: 0
    };

    const { rawTransaction: senderRawTransaction } = await caver.klay.accounts.signTransaction(tx, sender.privateKey);

    // This is your signed raw transaction
    console.log("Raw TX:\n", senderRawTransaction);


    // Send your signed raw transaction to a payer. 

    // Generally, sender and payer are two separate entities, and they do not share their codes.
    // However, to make things easier, we are putting both sender and payer into a single JS file.

    // Payer information required to build a payer account.
    // You can specify which key to bind to the presented address since Klaytn allows multiple 
    // keys each assigned with a different role. If your account does not have multiple keys, 
    // simply use the key that was used to derive your address.
    const payerKey = 'PAYER_PRIVATE_KEY'
    const payerAddress = 'PAYER_ADDRESS';
    const payer = caver.klay.accounts.wallet.add(payerKey, payerAddress);

    // This is part is not different from other fee delegated transactions.
    // Make sure you put `senderRawTransaction` and `feePayer` fields.
    caver.klay.sendTransaction({
        senderRawTransaction: senderRawTransaction,
        feePayer: payer.address
    })
        .on('transactionHash', function (hash) {
            console.log(">>> tx_hash for deploy =", hash);
        })
        .on('receipt', function (receipt) {
            console.log(">>> receipt arrived: ", receipt);
        })
        .on('error', function (err) {
            console.error(">>> error: ", err);
        });
})();

2개의 좋아요