lib/flow_client/client.rb in flow_client-0.1.2 vs lib/flow_client/client.rb in flow_client-0.2.0
- old
+ new
@@ -4,10 +4,16 @@
require "flow/execution/execution_services_pb"
require "json"
# Collection of classes to interact with the Flow blockchain
module FlowClient
+ class CadenceRuntimeError < StandardError
+ end
+
+ class ClientError < StandardError
+ end
+
# Flow client
class Client
attr_accessor :address_aliases
def initialize(node_address)
@@ -19,67 +25,330 @@
req = Access::PingRequest.new
@stub.ping(req)
end
# Accounts
+
+ # Gets an account
def get_account(address)
req = Access::GetAccountAtLatestBlockRequest.new(address: to_bytes(address))
- res = @stub.get_account_at_latest_block(req)
- res.account
+
+ begin
+ res = @stub.get_account_at_latest_block(req)
+ rescue GRPC::BadStatus => e
+ raise ClientError, e.details
+ else
+ account = FlowClient::Account.new(
+ address: res.account.address.unpack1("H*"),
+ balance: res.account.balance/100000000.0
+ )
+
+ res.account.keys.each do |key|
+ account.keys << FlowClient::AccountKey.new(
+ public_key: key.public_key.unpack1("H*"),
+ index: key.index,
+ sequence_number: key.sequence_number,
+ revoked: key.revoked,
+ weight: key.weight
+ )
+ end
+
+ account.contracts = res.account.contracts
+
+ account
+ end
end
+ # Creates a new account
+ #
+ #
+ def create_account(new_account_public_keys, contracts, payer_account, signer)
+ script = File.read(File.join("lib", "cadence", "templates", "create-account.cdc"))
+
+ arguments = [
+ CadenceType.Array(
+ new_account_public_keys.to_a.map { |key| CadenceType.String(key) }
+ ),
+ CadenceType.Dictionary(
+ contracts.to_a.map { |name, code| CadenceType.DictionaryValue(
+ CadenceType.String(name), CadenceType.String(code.unpack1("H*"))
+ ) }
+ ),
+ ]
+
+ transaction = FlowClient::Transaction.new
+ transaction.script = script
+ transaction.reference_block_id = get_latest_block.id
+ transaction.proposer_address = payer_account.address
+ transaction.proposer_key_index = 0
+ transaction.arguments = arguments
+ transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
+ transaction.payer_address = payer_account.address
+ transaction.authorizer_addresses = [payer_account.address]
+ transaction.add_envelope_signature(payer_account.address, 0, signer)
+ res = send_transaction(transaction)
+
+ new_account = nil
+ wait_for_transaction(res.id) do |response|
+ raise CadenceRuntimeError, response.error_message if response.status_code != 0
+
+ event_payload = response.events.select { |e| e.type == "flow.AccountCreated" }.first.payload
+ payload_json = JSON.parse(event_payload)
+ new_account_address = payload_json["value"]["fields"][0]["value"]["value"]
+ new_account = get_account(new_account_address)
+ end
+
+ new_account
+ end
+
+ # Adds a public key to an account
+ def add_account_key(public_key_hex, payer_account, signer, weight)
+ script = File.read(File.join("lib", "cadence", "templates", "add-account-key.cdc"))
+
+ arguments = [
+ {
+ type: "String",
+ value: public_key_hex
+ }.to_json,
+ {
+ type: "UFix64",
+ value: weight.to_s
+ }.to_json
+ ]
+
+ transaction = FlowClient::Transaction.new
+ transaction.script = script
+ transaction.reference_block_id = get_latest_block.id
+ transaction.proposer_address = payer_account.address
+ transaction.proposer_key_index = 0
+ transaction.arguments = arguments
+ transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
+ transaction.payer_address = payer_account.address
+ transaction.authorizer_addresses = [payer_account.address]
+ transaction.add_envelope_signature(payer_account.address, 0, signer)
+ res = send_transaction(transaction)
+
+ wait_for_transaction(res.id) do |response|
+ raise CadenceRuntimeError, response.error_message if response.status_code != 0
+ end
+ end
+
+ # Adds a contract to an account
+ def add_contract(name, code, payer_account, signer)
+ script = File.read(File.join("lib", "cadence", "templates", "add-contract.cdc"))
+ code_hex = code.unpack1("H*")
+
+ arguments = [
+ {
+ type: "String",
+ value: name
+ }.to_json,
+ {
+ type: "String",
+ value: code_hex
+ }.to_json
+ ]
+
+ transaction = FlowClient::Transaction.new
+ transaction.script = script
+ transaction.reference_block_id = get_latest_block.id
+ transaction.proposer_address = payer_account.address
+ transaction.proposer_key_index = 0
+ transaction.arguments = arguments
+ transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
+ transaction.payer_address = payer_account.address
+ transaction.authorizer_addresses = [payer_account.address]
+ transaction.add_envelope_signature(payer_account.address, 0, signer)
+ res = send_transaction(transaction)
+
+ wait_for_transaction(res.id) do |response|
+ raise CadenceRuntimeError, response.error_message if response.status_code != 0
+ end
+ end
+
+ # Removes a contract from an account
+ def remove_contract(name, payer_account, signer)
+ script = File.read(File.join("lib", "cadence", "templates", "remove-contract.cdc"))
+
+ arguments = [
+ {
+ type: "String",
+ value: name
+ }.to_json
+ ]
+
+ transaction = FlowClient::Transaction.new
+ transaction.script = script
+ transaction.reference_block_id = get_latest_block.id
+ transaction.proposer_address = payer_account.address
+ transaction.proposer_key_index = 0
+ transaction.arguments = arguments
+ transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
+ transaction.payer_address = payer_account.address
+ transaction.authorizer_addresses = [payer_account.address]
+ transaction.add_envelope_signature(payer_account.address, 0, signer)
+ res = send_transaction(transaction)
+
+ wait_for_transaction(res.id) do |response|
+ raise CadenceRuntimeError, response.error_message if response.status_code != 0
+ end
+ end
+
+ # Updates a contract on an account
+ def update_contract(name, code, payer_account, signer)
+ script = File.read(File.join("lib", "cadence", "templates", "update-contract.cdc"))
+ code_hex = code.unpack1("H*")
+
+ arguments = [
+ {
+ type: "String",
+ value: name
+ }.to_json,
+ {
+ type: "String",
+ value: code_hex
+ }.to_json
+ ]
+
+ transaction = FlowClient::Transaction.new
+ transaction.script = script
+ transaction.reference_block_id = get_latest_block.id
+ transaction.proposer_address = payer_account.address
+ transaction.proposer_key_index = 0
+ transaction.arguments = arguments
+ transaction.proposer_key_sequence_number = get_account(payer_account.address).keys.first.sequence_number
+ transaction.payer_address = payer_account.address
+ transaction.authorizer_addresses = [payer_account.address]
+ transaction.add_envelope_signature(payer_account.address, 0, signer)
+ res = send_transaction(transaction)
+
+ wait_for_transaction(res.id) do |response|
+ raise CadenceRuntimeError, response.error_message if response.status_code != 0
+ end
+ end
+
# Scripts
def execute_script(script, args = [])
+ processed_args = []
+ args.to_a.each do |arg|
+ processed_arg = arg.class == OpenStruct ? Utils.openstruct_to_json(arg) : arg
+ processed_args << processed_arg
+ end
+
req = Access::ExecuteScriptAtLatestBlockRequest.new(
script: FlowClient::Utils.substitute_address_aliases(script, @address_aliases),
- arguments: args
+ arguments: processed_args
)
+
res = @stub.execute_script_at_latest_block(req)
parse_json(res.value)
end
# Blocks
def get_latest_block(is_sealed: true)
req = Access::GetLatestBlockRequest.new(
is_sealed: is_sealed
)
+ res = @stub.get_latest_block(req)
+ Block.parse_grpc_block_response(res)
+ end
- @stub.get_latest_block(req)
+ def get_block_by_id(id)
+ req = Access::GetBlockByIDRequest.new(
+ id: to_bytes(id)
+ )
+ res = @stub.get_block_by_id(req)
+ Block.parse_grpc_block_response(res)
end
+ def get_block_by_height(height)
+ req = Access::GetBlockByHeightRequest.new(
+ height: height
+ )
+ res = @stub.get_block_by_height(req)
+ Block.parse_grpc_block_response(res)
+ end
+
+ # Collections
+ def get_collection_by_id(id)
+ req = Access::GetCollectionByIDRequest.new(
+ id: to_bytes(id)
+ )
+ res = @stub.get_collection_by_id(req)
+ Collection.parse_grpc_type(res)
+ end
+
# Events
def get_events(type, start_height, end_height)
req = Access::GetEventsForHeightRangeRequest.new(
type: type,
start_height: start_height,
end_height: end_height
)
- @stub.get_events_for_height_range(req)
+ begin
+ res = @stub.get_events_for_height_range(req)
+ rescue GRPC::BadStatus => e
+ raise ClientError, e.details
+ else
+ res.results.map { |event| EventsResult.parse_grpc_type(event) }
+ end
end
# Transactions
- # Send a FlowClient::Transaction transaction to the blockchain
+ # Sends a transaction to the blockchain
def send_transaction(transaction)
transaction.address_aliases = @address_aliases
req = Access::SendTransactionRequest.new(
transaction: transaction.to_protobuf_message
)
- @stub.send_transaction(req)
+
+ begin
+ res = @stub.send_transaction(req)
+ rescue GRPC::BadStatus => e
+ raise ClientError, e.details
+ else
+ TransactionResponse.parse_grpc_type(res)
+ end
end
def get_transaction(transaction_id)
req = Access::GetTransactionRequest.new(
id: to_bytes(transaction_id)
)
- @stub.get_transaction(req)
+
+ begin
+ res = @stub.get_transaction(req)
+ rescue GRPC::BadStatus => e
+ raise ClientError, e.details
+ else
+ Transaction.parse_grpc_type(res.transaction)
+ end
end
+ # Returns a transaction result
def get_transaction_result(transaction_id)
req = Access::GetTransactionRequest.new(
id: to_bytes(transaction_id)
)
- @stub.get_transaction_result(req)
+
+ begin
+ res = @stub.get_transaction_result(req)
+ rescue GRPC::BadStatus => e
+ raise ClientError, e.details
+ else
+ TransactionResult.parse_grpc_type(res)
+ end
+ end
+
+ def wait_for_transaction(transaction_id)
+ response = get_transaction_result(transaction_id)
+ while response.status != :SEALED
+ sleep(0.5)
+ response = get_transaction_result(transaction_id)
+ end
+
+ yield(response)
end
private
def parse_json(event_payload)