Skip to content

Commit

Permalink
Add fireblocks files
Browse files Browse the repository at this point in the history
  • Loading branch information
csebryam committed Oct 4, 2019
1 parent 571144f commit 415c0fd
Show file tree
Hide file tree
Showing 7 changed files with 355 additions and 0 deletions.
24 changes: 24 additions & 0 deletions lib/fireblocks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

require 'fireblocks/version'
require 'fireblocks/configuration'
require 'fireblocks/api/api'
require 'fireblocks/api/request'
require 'fireblocks/api/token'

# Parent module for all classes
module Fireblocks
class << self
def configure
yield(configuration)
end

def configuration
@configuration ||= Configuration.new
end

def reset
@configuration = Configuration.new
end
end
end
64 changes: 64 additions & 0 deletions lib/fireblocks/api/api.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# frozen_string_literal: true

module Fireblocks
# Namespace to access Fireblocks api methods
class API
class << self
def get_vault_accounts
Request.get(path: '/v1/vault/accounts')
end

def create_vault_account(name:)
Request.post(body: { name: name }, path: '/v1/vault/accounts')
end

def get_vault_account(id)
Request.get(path: "/v1/vault/accounts/#{id}")
end

def update_vault_account(vault_account_id, name:)
Request.put(
body: { name: name },
path: "/v1/vault/accounts/#{vault_account_id}"
)
end

def get_vault_account_asset(vault_account_id, asset_id)
Request.get(path: "/v1/vault/accounts/#{vault_account_id}/#{asset_id}")
end

def create_vault_account_asset(vault_account_id, asset_id)
Request.post(path: "/v1/vault/accounts/#{vault_account_id}/#{asset_id}")
end

def create_deposit_address(vault_account_id, asset_id, description:)
Request.post(
body: { description: description },
path: "/v1/vault/accounts/#{vault_account_id}/#{asset_id}/addresses"
)
end

def get_deposit_addressess(vault_account_id, asset_id)
Request.get(
path: "/v1/vault/accounts/#{vault_account_id}/#{asset_id}/addresses"
)
end

def get_internal_wallet(wallet_id)
Request.get(path: "/v1/internal_wallets/#{wallet_id}")
end

def get_internal_wallets
Request.get(path: '/v1/internal_wallets')
end

def create_internal_wallet(name:)
Request.post(body: { name: name }, path: '/v1/internal_wallets')
end

def get_supported_assets
Request.get(path: '/v1/supported_assets')
end
end
end
end
80 changes: 80 additions & 0 deletions lib/fireblocks/api/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# frozen_string_literal: true

require 'net/http'

module Fireblocks
# Interface to GET, POST, PUT
class Request
Error = Class.new(StandardError)
class << self
def get(path:, body: '')
new(path: path).get(body)
end

def put(path:, body:)
new(path: path).put(body)
end

def post(path:, body:)
new(path: path).post(body)
end
end

attr_accessor :path, :uri

def initialize(path:)
@path = path
@uri = URI("#{Fireblocks.configuration.base_url}#{path}")
end

def get(body)
req = Net::HTTP::Get.new(uri)
request_headers(body).each { |rk, rv| req[rk] = rv }

valid_response!(send_request(req))
end

def put(body)
req = Net::HTTP::Put.new(uri)
request_headers(body).each { |rk, rv| req[rk] = rv }
req.body = body.to_json

valid_response!(send_request(req))
end

def post(body)
req = Net::HTTP::Post.new(uri)
request_headers(body).each { |rk, rv| req[rk] = rv }
req.body = body.to_json

valid_response!(send_request(req))
end

private

def request_headers(body)
{
'X-API-Key' => Fireblocks.configuration.api_key,
'Authorization' => "Bearer #{token(body)}",
'Content-Type' => 'application/json'
}
end

def send_request(request)
Net::HTTP.start(
uri.hostname, uri.port, use_ssl: true
) { |http| http.request(request) }
end

def token(body)
Token.call(body, uri)
end

def valid_response!(req_response)
resp = JSON.parse(req_response.body)
return resp if req_response.message == 'OK'

raise Error, resp
end
end
end
62 changes: 62 additions & 0 deletions lib/fireblocks/api/token.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# frozen_string_literal: true

require 'digest'
require 'jwt'
require 'openssl'

module Fireblocks
# This class will issue a new Fireblocks token
class Token
class << self
def call(body, uri)
new(body, uri).call
end
end

attr_accessor :body, :uri

def initialize(body, uri)
@body = body
@uri = uri
end

def created_at
Time.now.to_i
end

def expire_at
Time.now.to_i + 60
end

def call
JWT.encode jwt_headers, rsa_private, 'RS256', typ: 'JWT'
end

def body_hash
Digest::SHA256.hexdigest(body.to_json)
end

def decode_token
JWT.decode token, rsa_private.public_key, true, algorithm: 'RS256'
end

def jwt_headers
{
uri: uri.request_uri,
nonce: nonce,
iat: created_at,
exp: expire_at,
sub: Fireblocks.configuration.api_key,
bodyHash: body_hash
}
end

def nonce
Time.now.to_i * 1000
end

def rsa_private
OpenSSL::PKey::RSA.new(Fireblocks.configuration.private_key)
end
end
end
37 changes: 37 additions & 0 deletions lib/fireblocks/configuration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# frozen_string_literal: true

# Global configuration settings for the gem
module Fireblocks
#
# base_url is set to a default but can be configured
#
class Configuration
Error = Class.new(StandardError)
attr_writer :api_key, :private_key
attr_accessor :base_url

def initialize
@api_key = nil
@private_key = nil
@base_url = 'https://api.fireblocks.io'
end

def api_key
message =
'Fireblocks api key not set. See Fireblocks documentation ' \
'to get a hold of your api key'
raise Error, message unless @api_key

@api_key
end

def private_key
message =
'Fireblocks private key not set. See Fireblocks documentation ' \
'to get a hold of your private key'
raise Error, message unless @private_key

@private_key
end
end
end
54 changes: 54 additions & 0 deletions test/fireblocks/configuration_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require 'test_helper'

class FireblocksConfiguration < Minitest::Test
describe Fireblocks::Configuration do
let(:new_base_url) { 'example.com' }

describe 'with configuration block' do
before do
# To get unique jwt headers for fast requests, add a sleep
sleep(1)

Fireblocks.configure do |config|
config.api_key = ENV['FIREBLOCKS_API_KEY']
config.private_key = ENV['FIREBLOCKS_PRIVATE_KEY']
end
end

def test_it_returns_api_key
assert_equal Fireblocks.configuration.api_key, ENV['FIREBLOCKS_API_KEY']
end

def test_it_returns_private_key
assert_equal(
Fireblocks.configuration.private_key, ENV['FIREBLOCKS_PRIVATE_KEY']
)
end

def test_it_resassigns_base_url
Fireblocks.configure { |config| config.base_url = new_base_url }
assert_equal Fireblocks.configuration.base_url, new_base_url
end
end

describe 'without configuration keys' do
before do
Fireblocks.reset
end

def test_it_raises_api_key_error
assert_raises(Fireblocks::Configuration::Error) do
Fireblocks.configuration.api_key
end
end

def test_it_raises_private_key_error
assert_raises(Fireblocks::Configuration::Error) do
Fireblocks.configuration.private_key
end
end
end
end
end
34 changes: 34 additions & 0 deletions test/fireblocks/fireblocks_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

require 'test_helper'

class FireblocksAPI < Minitest::Test
describe Fireblocks::API do
describe 'send GET Fireblocks requests' do
let(:vault_name) { 'test_vault' }

before do
# To get unique jwt headers for fast requests, add a sleep
sleep(1)

Fireblocks.reset
Fireblocks.configure do |config|
config.api_key = ENV['FIREBLOCKS_API_KEY']
config.private_key = ENV['FIREBLOCKS_PRIVATE_KEY']
end
end

def test_create_vault_account
vault_account = Fireblocks::API.create_vault_account(name: vault_name)
assert_equal vault_account['name'], vault_name
end

def test_get_vault_accounts
Fireblocks::API.create_vault_account(name: vault_name)
sleep(1)
vault_accounts = Fireblocks::API.get_vault_accounts
refute_empty vault_accounts
end
end
end
end

0 comments on commit 415c0fd

Please sign in to comment.