Creating offline/raw transactions with Go-Ethereum

Akshay Meher
6 min readDec 13, 2017

--

Offline transactions are needed when we do not want to share our private keys with the ethereum’s node (geth).

We shall see how to create raw transactions in go using modules given in go-ethereum for three scenarios

  1. Send balance to a given account
  2. Deploying a contract
  3. Transacting a contract function.

(NOTE: It is expected that you have good view of solidity)

You would need to import following packages for all three scenarios.

We need to import private key, for convenience we have just pasted the key-file in program itself.

You would need to create a connection to client object as shown in the image. We have used IPC connection, however RPC can be used just by replacing /home/akshay/Desktop…. the with RPC-URL

The client object is passed on to other function which will be used there.

Sending balance to any account

complete code for raw transaction for sending balance to an account.

ctx object need to be created and passed while querying. The ctx object created here says that the code must be executed withing 1000 millisecond from the time of commencement or else it would simply pop overslept. You can avoid passing context object simply by passing nil argument. However, it is a good practice to pass context object.

d := time.Now().Add(1000 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()

unlockey is the unlocked key with passcode.

unlockedKey, err := keystore.DecryptKey([]byte(key), "password")

To create a raw transaction nonce is must. Hence we get the account nonce using

cleint.NonceAt(ctx,unlockedKey.Address,nil)

The third parameter is nil because we want the latest account nonce. To get nonce at a particular block height

Now create a transaction object tx using types.NewTransaction

tx := types.NewTransaction(nonce, common.HexToAddress(“0x56724a9e4d2bb2dca01999acade2e88a92b11a9e”), big.NewInt(12400000), big.NewInt(10000000), big.NewInt(0), nil)

The arguments to this function in order are

1. nonce

2. to-address (we would need to convert this using common.HexToAddress(public address) )

3. balance to be sent (use big number)

4. gas limit

5. gas price

6. data (since this is not a contract transaction, we can pass nil )

once the transaction is created, we need to sign it using private-key

signTx, err := types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)

signTx is the signed transaction with the private key. I have used HomesteadSigner which is older one, however after EIP155 they have issued new signer. If you wish to use that, replace the above line with

signTx, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(1)), unlockedKey.PrivateKey)

The NewEIP155Signer takes chainId as argument.

You can console the transaction and check using

fmt.Println(signTx)

If R and S fields of signTx are filled, it means the transaction is signed else unsigned.

After signing the transaction, simply send the transaction to using

err := client.SendTransaction(ctx, signTx)

Deploying contracts using raw transaction

There are two difference while deploying a contract to ethereum using offline/raw transaction. Firstly, there is no to address which we have to send. Secondly, contract and its constructor arguments are to be encoded in payload data.

Encoding payload in correct format is a major challenge.

We first need to generate ABI and byte-code of our contract. There are several ways to do so.

We would discuss one simple way here. If you already have generated ABI and byte-code you can skip the following step.

(NOTE: make sure abigen and solc are installed on your system)

abigen is a binary that comes along with geth binary when ethereum is installed on your device.

To install solc :

sudo apt install solc

For generating ABI and Bytecode:

abigen -sol <<solidityfile.sol>> -pkg <<customPackageName>> -out <<outputFile.go>>
example how i have generated ABI and byte-code

The generated file looks somewhat like this.

TestABI and TestBin are ABI and bytecode of my contract respectively

Now we have everything that we need to deploy the contract

complete code for deploying the contract

As mentioned in previous steps, first we have to create a context object.

d := time.Now().Add(1000 * time.Millisecond)
ctx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()

Then, extract the byte-code from binary

byteCode := common.Hex2Bytes(TestBin[2:])

We have to remove 0x in the TestBin and convert to byte array

parse the TestABI as it is a string when created.

testabi, _ := abi.JSON(strings.NewReader(TestABI))

create input byte array. this consists of constructor arguments

input, _ := testabi.Pack(“”)

Contract here doesn't have any constructor argument. However if your contract does have any constructor argument change it to

input,_ := testabi.Pack(“”,arg1,arg2,arg3)

now append the byteCode array with input byte array

byteCode = append(byteCode, input…)

The next step is to unlock private-key( mentioned in previous section of raw transaction)

unlockedKey, _ := keystore.DecryptKey([]byte(key), “password”)

Once the key is unlocked, we need to create contract transaction.

account nonce must be found before creating the transaction, which can be found out using

nonce, _ := client.NonceAt(ctx, unlockedKey.Address, nil)

after getting the nonce, use types.NewContractCreation function to create a transaction as our transaction now will create a contract.

tx := types.NewContractCreation(nonce, big.NewInt(0), big.NewInt(10000000), big.NewInt(0), byteCode)

1. The arguments to this function are account nonce, which must be determined before. It is mentioned in previous section.

2. Second argument is amount that has to be sent, since it is a contract, keep it as zero.

3. Third argument is gas limit.

4. Fourth argument is gas price.

5. Last argument is bytecode , which have already generated using TestABI and TestBin

From here the process is exactly same as in previous section

Sign the transaction with your unlockedKey and send it to the client

signTx, _ := types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)err := client.SendTransaction(ctx, signTx)

(Note: After EIP155 new signer has been used. Replace HomesteadSigner with NewEIP155Signer which I have already mentioned in previous section)

Transacting a contract function using raw transactions

Transacting a contract function is not much different from deploying the contract when it comes to raw transactions.

complete code for transacting a contract function

As mentioned in previous sections we need to have TestABI for transacting the contract function.

The steps are same as in previous sections, only other thing is we must know the contract address of our previously deployed contract.

address := contractAddress
d := time.Now().Add(1000 * time.Millisecond)
tx, cancel := context.WithDeadline(context.Background(), d)
defer cancel()
testabi, err := abi.JSON(strings.NewReader(TestABI))
unlockedKey, err := keystore.DecryptKey([]byte(key), "password")
nonce, _ := client.NonceAt(ctx, unlockedKey.Address, nil)

Now we need to create a byteData that has to be passed to transaction.

bytesData, _ := testabi.Pack("<<function name>>", "<<argument1>>", "<<>argument2>")

this bytesData needs to passed on the transaction payload, signed with your private key and passed to your client object.

tx := types.NewTransaction(nonce, common.HexToAddress(address), nil, big.NewInt(10000000), big.NewInt(0), bytesData)signTx, _ := types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)err := client.SendTransaction(ctx, signTx)

In summary, the major difference in creating a raw transaction for “sending ether” and “transacting/deploying a contract” is the payload that has to be passed along with the transaction. Once the payload is properly created and added to transaction, that transaction is signed sent to client in exactly similar process.

(NOTE: Please share your queries and problems in the responses-sections which will be useful for us in writing new blogs. Twitter: Akshay Meher)

--

--