Interact with Flow using Ruby

October 23, 2020

Interact with Flow using Ruby by Daniel Podaru

In this article, we'll explore how to interact with Flow using the Ruby programming language. The article can also be a good example on how you can leverage any programming language that doesn't have a Flow SDK support yet to interact with Flow, expanding drastically the range of choices at your disposal.

The code for this article is available on Github Here

Terminal / Command Prompt

Use Terminal on MacOS or Command Prompt on Windows to run commands.

Ruby

Before starting, check if you have Ruby installed. In the terminal, run the following command:

ruby -vruby
2.7.1p83

If you don't have Ruby installed, you can install it from https://www.ruby-lang.org.

Project folder

Make a new folder for your project:

cd ~
mkdir flow-ruby

gRPC and Protocol Buffers

We'll interact with the Flow node using gRPC and Protocol Buffers.

Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML or JSON but smaller, faster, and simpler.

gRPC is an RPC framework based on Protocol Buffers and HTTP2.

Install the grpc, grpc-tools, and json gem:

gem install grpc grpc-tools json

Flow Protocol Buffer definitions

Clone the Flow repository from GitHub.

In the terminal, run the following commands:

cd ~
git clone https://github.com/onflow/flow

Generate the Ruby code from the Flow  .proto files.

In the terminal, run the following commands:

cd flow/protobuf
grpc_tools_ruby_protoc --proto_path=. --ruby_out=ruby/ --grpc_out=ruby/ flow/**/*.proto

Copy the newly generated flow folder from protobuf/ruby/ to flow-ruby .In the terminal, run the following command:

cp -r ~/flow/protobuf/ruby/ ~/flow-ruby/

List the flow-ruby folder contents to make sure the flow folder was copied.In the terminal, run the following command:

cd ~/flow-ruby
ls flow

Ruby code

Fire up your favorite IDE and create a new file called flow.rb .

Add the following code to the flow.rb file:

require 'flow/access/access_services_pb'
require 'flow/execution/execution_services_pb'
require 'json'

class Flow
 # 1
 def initialize(node_address)
   @stub = Access::AccessAPI::Stub.new(node_address, :this_channel_is_insecure)
 end

 # 2
 def ping
   req = Access::PingRequest.new
   @stub.ping(req)
 end

 # 3
 def get_account(address)
   req = Access::GetAccountAtLatestBlockRequest.new(address: to_bytes(address))
   res = @stub.get_account_at_latest_block(req)
   res.account
 end

 # 4
 def execute_script(script, args = [])
   req = Access::ExecuteScriptAtLatestBlockRequest.new(
       script: script,
       arguments: args
   )
   res = @stub.execute_script_at_latest_block(req)
   parse_json(res.value)
 end

  private

 # 5
 def parse_json(event_payload)
   JSON.parse(event_payload, object_class: OpenStruct)
 end

 # 6
 def to_bytes(string)
   [string].pack('H*')
 end

 # 7
 def to_string(bytes)
   bytes.unpack('H*').first
 end
end

The Flow class is a wrapper of the code that was generated from the protocol definitions.

  1. initialize will be called when we instantiate the Flow class.
    Access::AccessAPI::Stub is the name of the class that was automatically generated for us from the protocol definitions. We create a new instance of it, passing the address of a Flow Access Node. The object provides methods that allow calling methods on the Access node.
  2. The ping method allows pinging the Flow Access node to see if it's alive. We create a new ping request: Access::PingRequest, and pass it in the ping method call: @stub.ping(req).
  3. The get_account(address) method allows fetching information about an address on the Flow network. We create a request to fetch information about an address using Access::GetAccountAtLatestBlockRequest.new, and we execute that request at the latest block with @stub.execute_script_at_latest_block(req).
  4. The execute_script method allows executing a Cadence script. Again, we create a request object and execute that request.
  5. parse_json is a utility method to parse the response of a script into a structure.
  6. to_bytes is a utility method that converts a Flow address from string to bytes.
  7. to_string is a utility method. We haven't used it in the other methods, but it's useful if you want to read the code field from a Flow account.

Flow emulator

The Flow Emulator is a lightweight tool that emulates the behaviour of the real Flow network.

We'll use the emulator to test the Ruby client. Follow these steps to install the emulator.

Start the Flow emulator.In a separate terminal window, run the following command:

flow emulator start

Interacting with Flow

Start the Ruby REPL and invoke the flow.rb file.

In the terminal, run the following command:

irb -I . -r flow.rb

In the terminal window where you started the REPL, run the following commands:

f = Flow.new("127.0.0.1:3569")
f.ping
< Access::PingResponse: >

We pinged the Flow emulator and it responded successfully.

Getting info about an existing account

To test with an existing Flow address, look in the terminal window where the Flow emulator was started and use the address of the service account.

Run the following command:

a = f.get_account('0xf8d6e0586b0a20c7')

< Entities::Account: address: "\xF8\xD6\xE0Xk\n \xC7", balance: 0, code: "", keys: [<Entities::AccountKey: index: 0, public_key: "av+?\xD6\xD9NEg\x01\x93\x8CCC*\x89\x941g:\xA7Y\x05\xA9\vP\xFC\xF0\xAFN\xDD\xAEE]\x13\xF99A\xAF\x120)y\xAD\xCAa\xC0\x8E\xDF]hY\xDC\x15\x82\xA2\x96DK\xCBF\xC2\xFBB", sign_algo: 2, hash_algo: 3, weight: 1000, sequence_number: 0>]>

The response displays info about the Flow account with the address: 0xf8d6e0586b0a20c7.

The data is displayed as bytes, we can decode it into a hex string using unpack('H*').first.

a.address
"\xF8\xD6\xE0Xk\n\xC7"

a.address.unpack('H*').first
"f8d6e0586b0a20c7"

a.balance
0

a.keys.first.public_key.unpack('H*').first
"61762b3fd6d94e456701938c43432a899431673aa75905a90b50fcf0af4eddae455d13f93941af12302979adca61c08edf5d6859dc1582a296444bcb46c2fb42"

We can see that the account has a 0 balance, and that currently one public key is authorized to access it.

Execute a Cadence script

A script is a read-only Cadence snippet that is used to query the computational state of the blockchain.

Run the following command:

script = 'pub fun main(): Int { return 1 }'
result = f.execute_script(script)

result.type
"Int"

result.value
"1"

script is a simple Cadence script that returns 1.

The script was executed by the execute_script function and the result is available in the result variable.

Access and Execution APIs

There are more methods that the Flow Access node and the Flow Execution node provide.

Explore the method definitions in the flow/access, flow/execution and flow/entities folders.

Transactions

A transaction is a Cadence snippet that is executed to update the computational state of the blockchain. A transaction can update the storage of one or more signing accounts.

The code to execute a transaction is already generated in the flow/execution folder but the code needed to cryptographically sign the payload of a transaction can't be generated from the .proto definitions and must be implemented. In a secure environment, the signing would be handled by a Hardware Security Module (HSM), which is able to securely store private keys. If you are interested in signing transactions from Ruby, you will need to write this part yourself.

First, you'll need to install the rlp and openssl gems. Next, you'll need to take a look at the signature implementation in the Go SDK to understand how signatures work before porting the code to Ruby.

Conclusion

If you are developing in a programming language for which a Flow SDK is not available yet, you can still interact with Flow using that language. Protocol buffers are language-neutral and platform-neutral so you can generate code from the protocol definitions for any programming language.

The Ruby code we wrote in this article could be the start of a Flow SDK for Ruby. We encourage you to join the Flow community on Discord, where you can join forces with other developers to build Flow SDKs in your favorite programming language.

Read More

Can DeFi defy centralized finance?

Meet the Team: Kim Cope on Bringing the Mainstream to Blockchain

Interact with Flow using Ruby