-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
355 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |