From f07b9c964babe5bc044f23d9e9bfa463ff0958a0 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Mon, 30 May 2022 17:05:46 +0300 Subject: [PATCH 1/7] initial v2 commit --- fireblocks_sdk/__init__.py | 2 +- fireblocks_sdk/api_types.py | 71 +- fireblocks_sdk/common/__init__.py | 0 fireblocks_sdk/common/wrappers.py | 22 + fireblocks_sdk/connectors/__init__.py | 0 fireblocks_sdk/demo/__init__.py | 0 fireblocks_sdk/demo/run_demo.py | 95 ++ fireblocks_sdk/entities/__init__.py | 0 fireblocks_sdk/entities/api_response.py | 10 + fireblocks_sdk/entities/deserializable.py | 13 + fireblocks_sdk/sdk.py | 1394 +---------------- fireblocks_sdk/sdk_token_provider.py | 17 +- fireblocks_sdk/services/__init__.py | 0 fireblocks_sdk/services/base_service.py | 6 + fireblocks_sdk/services/contracts/__init__.py | 0 fireblocks_sdk/services/exchange/__init__.py | 0 fireblocks_sdk/services/fee_payer/__init__.py | 0 fireblocks_sdk/services/fee_payer/fee_info.py | 23 + .../fee_payer/fee_payer_configuration.py | 18 + .../services/fee_payer/fee_payer_info.py | 18 + .../services/fee_payer/fee_payer_service.py | 50 + fireblocks_sdk/services/fiat/__init__.py | 0 .../services/gas_station/__init__.py | 0 .../services/gas_station/gas_station_info.py | 37 + .../services/transactions/__init__.py | 0 .../transactions/entities/__init__.py | 0 .../entities/aml_screening_result.py | 27 + .../transactions/entities/amount_info.py | 25 + .../entities/authorization_info.py | 23 + .../entities/authorization_info_groups.py | 20 + .../transactions/entities/block_info.py | 20 + .../entities/create_transaction_response.py | 15 + .../estimate_transaction_fee_response.py | 24 + .../entities/estimated_transaction_fee.py | 29 + .../entities/message_signature.py | 24 + .../transactions/entities/page_details.py | 20 + .../transactions/entities/reward_info.py | 20 + .../entities/signed_message_response.py | 28 + .../entities/transaction_page_response.py | 22 + .../entities/transaction_response.py | 79 + .../transaction_response_destination.py | 31 + .../entities/transfer_peer_path_response.py | 28 + .../entities/validate_address_response.py | 20 + .../services/transfer_tickets/__init__.py | 0 .../create_transfer_ticket_response.py | 12 + .../transfer_tickets/term_response.py | 33 + .../transfer_ticket_response.py | 28 + fireblocks_sdk/services/vaults/__init__.py | 0 fireblocks_sdk/services/wallets/__init__.py | 0 .../services/wallets/external_wallet_asset.py | 28 + .../services/wallets/internal_wallet_asset.py | 18 + .../wallets/wallet_container_response.py | 26 + fireblocks_sdk/services/web_hooks/__init__.py | 0 .../services/web_hooks/web_hooks_service.py | 27 + setup.py | 41 +- 55 files changed, 1030 insertions(+), 1414 deletions(-) create mode 100644 fireblocks_sdk/common/__init__.py create mode 100644 fireblocks_sdk/common/wrappers.py create mode 100644 fireblocks_sdk/connectors/__init__.py create mode 100644 fireblocks_sdk/demo/__init__.py create mode 100644 fireblocks_sdk/demo/run_demo.py create mode 100644 fireblocks_sdk/entities/__init__.py create mode 100644 fireblocks_sdk/entities/api_response.py create mode 100644 fireblocks_sdk/entities/deserializable.py create mode 100644 fireblocks_sdk/services/__init__.py create mode 100644 fireblocks_sdk/services/base_service.py create mode 100644 fireblocks_sdk/services/contracts/__init__.py create mode 100644 fireblocks_sdk/services/exchange/__init__.py create mode 100644 fireblocks_sdk/services/fee_payer/__init__.py create mode 100644 fireblocks_sdk/services/fee_payer/fee_info.py create mode 100644 fireblocks_sdk/services/fee_payer/fee_payer_configuration.py create mode 100644 fireblocks_sdk/services/fee_payer/fee_payer_info.py create mode 100644 fireblocks_sdk/services/fee_payer/fee_payer_service.py create mode 100644 fireblocks_sdk/services/fiat/__init__.py create mode 100644 fireblocks_sdk/services/gas_station/__init__.py create mode 100644 fireblocks_sdk/services/gas_station/gas_station_info.py create mode 100644 fireblocks_sdk/services/transactions/__init__.py create mode 100644 fireblocks_sdk/services/transactions/entities/__init__.py create mode 100644 fireblocks_sdk/services/transactions/entities/aml_screening_result.py create mode 100644 fireblocks_sdk/services/transactions/entities/amount_info.py create mode 100644 fireblocks_sdk/services/transactions/entities/authorization_info.py create mode 100644 fireblocks_sdk/services/transactions/entities/authorization_info_groups.py create mode 100644 fireblocks_sdk/services/transactions/entities/block_info.py create mode 100644 fireblocks_sdk/services/transactions/entities/create_transaction_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/estimate_transaction_fee_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/estimated_transaction_fee.py create mode 100644 fireblocks_sdk/services/transactions/entities/message_signature.py create mode 100644 fireblocks_sdk/services/transactions/entities/page_details.py create mode 100644 fireblocks_sdk/services/transactions/entities/reward_info.py create mode 100644 fireblocks_sdk/services/transactions/entities/signed_message_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/transaction_page_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/transaction_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/transaction_response_destination.py create mode 100644 fireblocks_sdk/services/transactions/entities/transfer_peer_path_response.py create mode 100644 fireblocks_sdk/services/transactions/entities/validate_address_response.py create mode 100644 fireblocks_sdk/services/transfer_tickets/__init__.py create mode 100644 fireblocks_sdk/services/transfer_tickets/create_transfer_ticket_response.py create mode 100644 fireblocks_sdk/services/transfer_tickets/term_response.py create mode 100644 fireblocks_sdk/services/transfer_tickets/transfer_ticket_response.py create mode 100644 fireblocks_sdk/services/vaults/__init__.py create mode 100644 fireblocks_sdk/services/wallets/__init__.py create mode 100644 fireblocks_sdk/services/wallets/external_wallet_asset.py create mode 100644 fireblocks_sdk/services/wallets/internal_wallet_asset.py create mode 100644 fireblocks_sdk/services/wallets/wallet_container_response.py create mode 100644 fireblocks_sdk/services/web_hooks/__init__.py create mode 100644 fireblocks_sdk/services/web_hooks/web_hooks_service.py diff --git a/fireblocks_sdk/__init__.py b/fireblocks_sdk/__init__.py index c8c9aa3..10b4bc2 100644 --- a/fireblocks_sdk/__init__.py +++ b/fireblocks_sdk/__init__.py @@ -1,3 +1,3 @@ +from fireblocks_sdk.api_types import * from fireblocks_sdk.sdk import FireblocksSDK from fireblocks_sdk.sdk_token_provider import SdkTokenProvider -from fireblocks_sdk.api_types import * \ No newline at end of file diff --git a/fireblocks_sdk/api_types.py b/fireblocks_sdk/api_types.py index 14bf9ed..f2d2d7f 100644 --- a/fireblocks_sdk/api_types.py +++ b/fireblocks_sdk/api_types.py @@ -1,10 +1,10 @@ - -class TransferPeerPath(object): +class TransferPeerPath: def __init__(self, peer_type, peer_id): """Defines a source or a destination for a transfer Args: - peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, FIAT_ACCOUNT, NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER + peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, FIAT_ACCOUNT, + NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER peer_id (str): the account/wallet id """ @@ -14,18 +14,20 @@ def __init__(self, peer_type, peer_id): if peer_id is not None: self.id = str(peer_id) + class DestinationTransferPeerPath(TransferPeerPath): def __init__(self, peer_type, peer_id=None, one_time_address=None): """Defines a destination for a transfer Args: - peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, FIAT_ACCOUNT, NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER + peer_type (str): either VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, + FIAT_ACCOUNT, NETWORK_CONNECTION, ONE_TIME_ADDRESS or UNKNOWN_PEER peer_id (str): the account/wallet id one_time_address (JSON object): The destination address (and tag) for a non whitelisted address. """ TransferPeerPath.__init__(self, peer_type, peer_id) - if one_time_address != None: + if one_time_address: self.oneTimeAddress = one_time_address @@ -38,18 +40,19 @@ def __init__(self, peer_type, peer_id=None, one_time_address=None): CONTRACT_CALL = "CONTRACT_CALL" ONE_TIME_ADDRESS = "ONE_TIME_ADDRESS" -TRANSACTION_TYPES = (TRANSACTION_TRANSFER, TRANSACTION_MINT, TRANSACTION_BURN, TRANSACTION_SUPPLY_TO_COMPOUND, TRANSACTION_REDEEM_FROM_COMPOUND, RAW, CONTRACT_CALL, ONE_TIME_ADDRESS) +TRANSACTION_TYPES = (TRANSACTION_TRANSFER, TRANSACTION_MINT, TRANSACTION_BURN, TRANSACTION_SUPPLY_TO_COMPOUND, + TRANSACTION_REDEEM_FROM_COMPOUND, RAW, CONTRACT_CALL, ONE_TIME_ADDRESS) TRANSACTION_STATUS_SUBMITTED = "SUBMITTED" TRANSACTION_STATUS_QUEUED = "QUEUED" -TRANSACTION_STATUS_PENDING_SIGNATURE= "PENDING_SIGNATURE" +TRANSACTION_STATUS_PENDING_SIGNATURE = "PENDING_SIGNATURE" TRANSACTION_STATUS_PENDING_AUTHORIZATION = "PENDING_AUTHORIZATION" TRANSACTION_STATUS_PENDING_3RD_PARTY_MANUAL_APPROVAL = "PENDING_3RD_PARTY_MANUAL_APPROVAL" TRANSACTION_STATUS_PENDING_3RD_PARTY = "PENDING_3RD_PARTY" -TRANSACTION_STATUS_PENDING = "PENDING" # Deprecated +TRANSACTION_STATUS_PENDING = "PENDING" # Deprecated TRANSACTION_STATUS_BROADCASTING = "BROADCASTING" TRANSACTION_STATUS_CONFIRMING = "CONFIRMING" -TRANSACTION_STATUS_CONFIRMED = "CONFIRMED" # Deprecated +TRANSACTION_STATUS_CONFIRMED = "CONFIRMED" # Deprecated TRANSACTION_STATUS_COMPLETED = "COMPLETED" TRANSACTION_STATUS_PENDING_AML_SCREENING = "PENDING_AML_SCREENING" TRANSACTION_STATUS_PARTIALLY_COMPLETED = "PARTIALLY_COMPLETED" @@ -61,25 +64,25 @@ def __init__(self, peer_type, peer_id=None, one_time_address=None): TRANSACTION_STATUS_BLOCKED = "BLOCKED" TRANSACTION_STATUS_TYPES = (TRANSACTION_STATUS_SUBMITTED, -TRANSACTION_STATUS_QUEUED, -TRANSACTION_STATUS_PENDING_SIGNATURE, -TRANSACTION_STATUS_PENDING_AUTHORIZATION, -TRANSACTION_STATUS_PENDING_3RD_PARTY_MANUAL_APPROVAL, -TRANSACTION_STATUS_PENDING_3RD_PARTY, -TRANSACTION_STATUS_PENDING, -TRANSACTION_STATUS_BROADCASTING, -TRANSACTION_STATUS_CONFIRMING, -TRANSACTION_STATUS_CONFIRMED, -TRANSACTION_STATUS_COMPLETED, -TRANSACTION_STATUS_PENDING_AML_SCREENING, -TRANSACTION_STATUS_PARTIALLY_COMPLETED, -TRANSACTION_STATUS_CANCELLING, -TRANSACTION_STATUS_CANCELLED, -TRANSACTION_STATUS_REJECTED, -TRANSACTION_STATUS_FAILED, -TRANSACTION_STATUS_TIMEOUT, -TRANSACTION_STATUS_BLOCKED -) + TRANSACTION_STATUS_QUEUED, + TRANSACTION_STATUS_PENDING_SIGNATURE, + TRANSACTION_STATUS_PENDING_AUTHORIZATION, + TRANSACTION_STATUS_PENDING_3RD_PARTY_MANUAL_APPROVAL, + TRANSACTION_STATUS_PENDING_3RD_PARTY, + TRANSACTION_STATUS_PENDING, + TRANSACTION_STATUS_BROADCASTING, + TRANSACTION_STATUS_CONFIRMING, + TRANSACTION_STATUS_CONFIRMED, + TRANSACTION_STATUS_COMPLETED, + TRANSACTION_STATUS_PENDING_AML_SCREENING, + TRANSACTION_STATUS_PARTIALLY_COMPLETED, + TRANSACTION_STATUS_CANCELLING, + TRANSACTION_STATUS_CANCELLED, + TRANSACTION_STATUS_REJECTED, + TRANSACTION_STATUS_FAILED, + TRANSACTION_STATUS_TIMEOUT, + TRANSACTION_STATUS_BLOCKED + ) VAULT_ACCOUNT = "VAULT_ACCOUNT" EXCHANGE_ACCOUNT = "EXCHANGE_ACCOUNT" @@ -90,7 +93,9 @@ def __init__(self, peer_type, peer_id=None, one_time_address=None): NETWORK_CONNECTION = "NETWORK_CONNECTION" COMPOUND = "COMPOUND" -PEER_TYPES = (VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, NETWORK_CONNECTION, COMPOUND, ONE_TIME_ADDRESS) +PEER_TYPES = ( + VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, NETWORK_CONNECTION, + COMPOUND, ONE_TIME_ADDRESS) MPC_ECDSA_SECP256K1 = "MPC_ECDSA_SECP256K1" MPC_EDDSA_ED25519 = "MPC_EDDSA_ED25519" @@ -183,6 +188,7 @@ class FireblocksApiException(Exception): message: explanation of the error error_code: error code of the error """ + def __init__(self, message="Fireblocks SDK error", error_code=None): self.message = message self.error_code = error_code @@ -195,7 +201,8 @@ class PagedVaultAccountsRequestFilters(object): Args name_prefix (string, optional): Vault account name prefix name_suffix (string, optional): Vault account name suffix - min_amount_threshold (number, optional): The minimum amount for asset to have in order to be included in the results + min_amount_threshold (number, optional): The minimum amount for asset to have in order to be included + in the results asset_id (string, optional): The asset symbol order_by (ASC/DESC, optional): Order of results by vault creation time (default: DESC) limit (number, optional): Results page size @@ -207,7 +214,9 @@ class PagedVaultAccountsRequestFilters(object): - You should only insert 'before' or 'after' (or none of them), but not both - For default and max 'limit' values please see: https://docs.fireblocks.com/api/swagger-ui/#/ """ - def __init__(self, name_prefix=None, name_suffix=None, min_amount_threshold=None, asset_id=None, order_by=None, limit=None, before=None, after=None): + + def __init__(self, name_prefix=None, name_suffix=None, min_amount_threshold=None, asset_id=None, order_by=None, + limit=None, before=None, after=None): self.name_prefix = name_prefix self.name_suffix = name_suffix self.min_amount_threshold = min_amount_threshold diff --git a/fireblocks_sdk/common/__init__.py b/fireblocks_sdk/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/common/wrappers.py b/fireblocks_sdk/common/wrappers.py new file mode 100644 index 0000000..669a97d --- /dev/null +++ b/fireblocks_sdk/common/wrappers.py @@ -0,0 +1,22 @@ +from typing import TypeVar + +from fireblocks_sdk.entities.deserializable import Deserializable + +T = TypeVar('T') + + +def response_deserializer(response_type: T) -> T: + def inner(func) -> T: + def wrapper(*args, **kwargs) -> T: + return_value = func(*args, **kwargs) + if isinstance(return_value, list): + return [response_type.deserialize(item) for item in return_value] + + if not issubclass(response_type, Deserializable): + return return_value + + return response_type.deserialize(return_value) + + return wrapper + + return inner diff --git a/fireblocks_sdk/connectors/__init__.py b/fireblocks_sdk/connectors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/demo/__init__.py b/fireblocks_sdk/demo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/demo/run_demo.py b/fireblocks_sdk/demo/run_demo.py new file mode 100644 index 0000000..ad37403 --- /dev/null +++ b/fireblocks_sdk/demo/run_demo.py @@ -0,0 +1,95 @@ +""" +This is a demo runner to experience with all the SDK functions from the CLI +In order to use it, please install "inquirer" that is not included as a requirement + +pip3 install inquirer + +Before running the file, make sure you set API_SERVER_ADDRESS, PRIVATE_KEY_PATH and USER_ID +to the correct values corresponding the environment you are working with. +""" + +import inspect +import json +from typing import Dict, List + +import inquirer +from inquirer.themes import GreenPassion + +from fireblocks_sdk import FireblocksSDK + +""" +Change these: +""" +API_SERVER_ADDRESS = 'https://api.fireblocks.io' +PRIVATE_KEY_PATH = '' +USER_ID = '' + + +class DemoRunner: + + def __init__(self, api_server: str, key_path: str, user_id: str) -> None: + private_key = open(key_path, 'r').read() + self.client = FireblocksSDK(private_key, user_id, api_server) + self.client_methods = self._get_client_methods() + + def run(self): + while True: + action_question = [ + inquirer.List('action', + message="What would you like to do?", + choices=self.client_methods.keys()), + ] + + action_answer = inquirer.prompt(action_question, theme=GreenPassion()) + requested_action = str(action_answer['action']) + print(f'needed params: {self.client_methods[requested_action]}') + + params_questions = [] + for param in self.client_methods[requested_action]: + params_questions.append(inquirer.Text(f'{param}', f'{param}')) + + entered_params = [] + if params_questions: + entered_params = inquirer.prompt(params_questions, theme=GreenPassion()) + for k, v in entered_params.items(): + if v == '': + entered_params[k] = None + + print(f'entered_params: {entered_params}') + + service, method = requested_action.split('::') + return_value = getattr(getattr(self.client, service), method)(*entered_params.values()) + self.print_obj(return_value) + continue + + def print_obj(self, obj): + if isinstance(obj, list): + for i in range(len(obj)): + self.print_obj(obj[i]) + elif isinstance(obj, Dict): + print(json.dumps(obj)) + elif isinstance(obj, object): + print(obj.__dict__) + + def _get_client_methods(self) -> Dict[str, List[str]]: + services = ['vault', 'exchange', 'contracts', 'wallets', 'gas_station', 'transfer_tickets', 'transactions', + 'web_hooks'] + methods: Dict[str, List[str]] = {} + for service in services: + client_service = getattr(self.client, service) + object_methods = [f'{service}::{method_name}' for method_name in dir(client_service) + if callable(getattr(client_service, method_name)) and not method_name.startswith('_')] + + for m in object_methods: + service, method = m.split('::') + method_instance = getattr(getattr(self.client, service), method) + method_args_spec = inspect.getfullargspec(method_instance) + + methods[m] = method_args_spec[0][1:] + + return methods + + +if __name__ == '__main__': + runner = DemoRunner(API_SERVER_ADDRESS, PRIVATE_KEY_PATH, USER_ID) + runner.run() diff --git a/fireblocks_sdk/entities/__init__.py b/fireblocks_sdk/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/entities/api_response.py b/fireblocks_sdk/entities/api_response.py new file mode 100644 index 0000000..27c1b20 --- /dev/null +++ b/fireblocks_sdk/entities/api_response.py @@ -0,0 +1,10 @@ +from typing import Dict, TypeVar, Generic + +T = TypeVar('T') + + +class ApiResponse(Generic[T]): + def __init__(self, status_code: int, content, extras: Dict[str, str]) -> None: + self.status_code = status_code + self.content = content + self.extras = extras diff --git a/fireblocks_sdk/entities/deserializable.py b/fireblocks_sdk/entities/deserializable.py new file mode 100644 index 0000000..c3f14b6 --- /dev/null +++ b/fireblocks_sdk/entities/deserializable.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import Dict, TypeVar, Generic + +T = TypeVar('T') + + +class Deserializable(Generic[T]): + @classmethod + @abstractmethod + def deserialize(cls: T, data: Dict[str, any]) -> T: + pass diff --git a/fireblocks_sdk/sdk.py b/fireblocks_sdk/sdk.py index 5722bb5..33a8b95 100644 --- a/fireblocks_sdk/sdk.py +++ b/fireblocks_sdk/sdk.py @@ -1,36 +1,21 @@ -from operator import attrgetter - -import requests -import urllib -import json - +from typing import List + +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.asset_type import AssetType +from fireblocks_sdk.entities.network_connection_response import NetworkConnectionResponse +from fireblocks_sdk.services.transfer_tickets.transfer_tickets_service import TransferTicketsService +from fireblocks_sdk.services.vaults.vaults_service import VaultsService +from fireblocks_sdk.services.wallets.wallets_service import WalletsService +from fireblocks_sdk.services.web_hooks.web_hooks_service import WebHooksService +from .common.wrappers import response_deserializer from .sdk_token_provider import SdkTokenProvider -from .api_types import FireblocksApiException, TRANSACTION_TYPES, TRANSACTION_STATUS_TYPES, PEER_TYPES, \ - TransferPeerPath, DestinationTransferPeerPath, TransferTicketTerm, TRANSACTION_TRANSFER, SIGNING_ALGORITHM, \ - UnsignedMessage, FEE_LEVEL, PagedVaultAccountsRequestFilters -from fireblocks_sdk.api_types import TransactionDestination - - -def handle_response(response, page_mode=False): - try: - response_data = response.json() - except: - response_data = None - if response.status_code >= 300: - if type(response_data) is dict: - error_code = response_data.get("code") - raise FireblocksApiException("Got an error from fireblocks server: " + response.text, error_code) - else: - raise FireblocksApiException("Got an error from fireblocks server: " + response.text) - else: - if page_mode: - return {'transactions': response_data, - 'pageDetails': {'prevPage': response.headers.get('prev-page', ''), - 'nextPage': response.headers.get('next-page', '')}} - return response_data +from .services.contracts.contracts_service import ContractsService +from .services.exchange.exchange_service import ExchangeService +from .services.gas_station.gas_station_service import GasStationService +from .services.transactions.transactions_service import TransactionsService -class FireblocksSDK(object): +class FireblocksSDK: def __init__(self, private_key, api_key, api_base_url="https://api.fireblocks.io", timeout=None): """Creates a new Fireblocks API Client. @@ -42,474 +27,37 @@ def __init__(self, private_key, api_key, api_base_url="https://api.fireblocks.io timeout (number): Timeout for http requests in seconds """ self.private_key = private_key - self.api_key = api_key - self.base_url = api_base_url - self.token_provider = SdkTokenProvider(private_key, api_key) - self.timeout = timeout - def get_supported_assets(self): + self.connector = RestConnector(SdkTokenProvider(private_key, api_key), api_base_url, api_key, timeout) + self.vault = VaultsService(self.connector) + self.exchange = ExchangeService(self.connector) + self.contracts = ContractsService(self.connector) + self.wallets = WalletsService(self.connector) + self.gas_station = GasStationService(self.connector) + self.transfer_tickets = TransferTicketsService(self.connector) + self.transactions = TransactionsService(self.connector) + self.web_hooks = WebHooksService(self.connector) + + def get_supported_assets(self) -> List[AssetType]: """Gets all assets that are currently supported by Fireblocks""" - return self._get_request("/v1/supported_assets") - - def get_vault_accounts(self, name_prefix=None, name_suffix=None, min_amount_threshold=None, assetId=None): - """Gets all vault accounts for your tenant - - Args: - name_prefix (string, optional): Vault account name prefix - name_suffix (string, optional): Vault account name suffix - min_amount_threshold (number, optional): The minimum amount for asset to have in order to be included in the results - assetId (string, optional): The asset symbol - """ - - url = f"/v1/vault/accounts" - - params = {} - - if name_prefix: - params['namePrefix'] = name_prefix - - if name_suffix: - params['nameSuffix'] = name_suffix - - if min_amount_threshold is not None: - params['minAmountThreshold'] = min_amount_threshold - - if assetId is not None: - params['assetId'] = assetId - - if params: - url = url + "?" + urllib.parse.urlencode(params) - - return self._get_request(url) - - def get_vault_accounts_with_page_info(self, paged_vault_accounts_request_filters: PagedVaultAccountsRequestFilters): - """Gets a page of vault accounts for your tenant according to filters given - - Args: - paged_vault_accounts_request_filters (object, optional): Possible filters to apply for request - """ - - url = f"/v1/vault/accounts_paged" - name_prefix, name_suffix, min_amount_threshold, asset_id, order_by, limit, before, after = \ - attrgetter('name_prefix', 'name_suffix', 'min_amount_threshold', 'asset_id', 'order_by', 'limit', 'before', 'after')(paged_vault_accounts_request_filters) - - params = {} - - if name_prefix: - params['namePrefix'] = name_prefix - - if name_suffix: - params['nameSuffix'] = name_suffix - - if min_amount_threshold is not None: - params['minAmountThreshold'] = min_amount_threshold - - if asset_id is not None: - params['assetId'] = asset_id - - if order_by is not None: - params['orderBy'] = order_by - - if limit is not None: - params['limit'] = limit - - if before is not None: - params['before'] = before - - if after is not None: - params['after'] = after - - if params: - url = url + "?" + urllib.parse.urlencode(params) - - return self._get_request(url) - - def get_vault_account(self, vault_account_id): - """Deprecated - Replaced by get_vault_account_by_id - Args: - vault_account_id (string): The id of the requested account - """ - - return self._get_request(f"/v1/vault/accounts/{vault_account_id}") - - def get_vault_account_by_id(self, vault_account_id): - """Gets a single vault account - Args: - vault_account_id (string): The id of the requested account - """ - - return self._get_request(f"/v1/vault/accounts/{vault_account_id}") - - def get_vault_account_asset(self, vault_account_id, asset_id): - """Gets a single vault account asset - Args: - vault_account_id (string): The id of the requested account - asset_id (string): The symbol of the requested asset (e.g BTC, ETH) - """ - - return self._get_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}") - - def refresh_vault_asset_balance(self, vault_account_id, asset_id, idempotency_key=None): - """Gets a single vault account asset after forcing refresh from the blockchain - Args: - vault_account_id (string): The id of the requested account - asset_id (string): The symbol of the requested asset (e.g BTC, ETH) - """ - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/balance", {}, idempotency_key) - - def get_deposit_addresses(self, vault_account_id, asset_id): - """Gets deposit addresses for an asset in a vault account - Args: - vault_account_id (string): The id of the requested account - asset_id (string): The symbol of the requested asset (e.g BTC, ETH) - """ - - return self._get_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses") - - def get_unspent_inputs(self, vault_account_id, asset_id): - """Gets utxo list for an asset in a vault account - Args: - vault_account_id (string): The id of the requested account - asset_id (string): The symbol of the requested asset (like BTC, DASH and utxo based assets) - """ - - return self._get_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/unspent_inputs") - - def generate_new_address(self, vault_account_id, asset_id, description=None, customer_ref_id=None, - idempotency_key=None): - """Generates a new address for an asset in a vault account - - Args: - vault_account_id (string): The vault account ID - asset_id (string): The ID of the asset for which to generate the deposit address - description (string, optional): A description for the new address - customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses", - {"description": description or '', "customerRefId": customer_ref_id or ''}, - idempotency_key) - - def set_address_description(self, vault_account_id, asset_id, address, tag=None, description=None): - """Sets the description of an existing address - - Args: - vault_account_id (string): The vault account ID - asset_id (string): The ID of the asset - address (string): The address for which to set the set_address_description - tag (string, optional): The XRP tag, or EOS memo, for which to set the description - description (string, optional): The description to set, or none for no description - """ - if tag: - return self._put_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}:{tag}", - {"description": description or ''}) - else: - return self._put_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}", - {"description": description or ''}) + response = self.connector.get("/v1/supported_assets") + return [AssetType.deserialize(asset) for asset in response.content] - def get_network_connections(self): + @response_deserializer(NetworkConnectionResponse) + def get_network_connections(self) -> List[NetworkConnectionResponse]: """Gets all network connections for your tenant""" - return self._get_request("/v1/network_connections") + return self.connector.get("/v1/network_connections").content - def get_network_connection_by_id(self, connection_id): + @response_deserializer(NetworkConnectionResponse) + def get_network_connection_by_id(self, connection_id) -> NetworkConnectionResponse: """Gets a single network connection by id Args: connection_id (string): The ID of the network connection """ - return self._get_request(f"/v1/network_connections/{connection_id}") - - def get_exchange_accounts(self): - """Gets all exchange accounts for your tenant""" - - return self._get_request("/v1/exchange_accounts") - - def get_exchange_account(self, exchange_account_id): - """Gets an exchange account for your tenant - Args: - exchange_account_id (string): The exchange ID in Fireblocks - """ - - return self._get_request(f"/v1/exchange_accounts/{exchange_account_id}") - - def get_exchange_account_asset(self, exchange_account_id, asset_id): - """Get a specific asset from an exchange account - - Args: - exchange_account_id (string): The exchange ID in Fireblocks - asset_id (string): The asset to transfer - """ - - return self._get_request(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}") - - def transfer_to_subaccount(self, exchange_account_id, subaccount_id, asset_id, amount, idempotency_key=None): - """Transfer to a subaccount from a main exchange account - - Args: - exchange_account_id (string): The exchange ID in Fireblocks - subaccount_id (string): The ID of the subaccount in the exchange - asset_id (string): The asset to transfer - amount (double): The amount to transfer - idempotency_key (str, optional) - """ - body = { - "subaccountId": subaccount_id, - "amount": amount - } - - return self._post_request(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_to_subaccount", - body, idempotency_key) - - def transfer_from_subaccount(self, exchange_account_id, subaccount_id, asset_id, amount, idempotency_key=None): - """Transfer from a subaccount to a main exchange account - - Args: - exchange_account_id (string): The exchange ID in Fireblocks - subaccount_id (string): The ID of the subaccount in the exchange - asset_id (string): The asset to transfer - amount (double): The amount to transfer - idempotency_key (str, optional) - """ - body = { - "subaccountId": subaccount_id, - "amount": amount - } - - return self._post_request(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_from_subaccount", - body, idempotency_key) - - def get_fiat_accounts(self): - """Gets all fiat accounts for your tenant""" - - return self._get_request("/v1/fiat_accounts") - - def get_fiat_account_by_id(self, account_id): - """Gets a single fiat account by ID - - Args: - account_id (string): The fiat account ID - """ - - return self._get_request(f"/v1/fiat_accounts/{account_id}") - - def redeem_to_linked_dda(self, account_id, amount, idempotency_key=None): - """Redeem from a fiat account to a linked DDA - - Args: - account_id (string): The fiat account ID in Fireblocks - amount (double): The amount to transfer - idempotency_key (str, optional) - """ - body = { - "amount": amount, - } - - return self._post_request(f"/v1/fiat_accounts/{account_id}/redeem_to_linked_dda", body, idempotency_key) - - def deposit_from_linked_dda(self, account_id, amount, idempotency_key=None): - """Deposit to a fiat account from a linked DDA - - Args: - account_id (string): The fiat account ID in Fireblocks - amount (double): The amount to transfer - idempotency_key (str, optional) - """ - body = { - "amount": amount, - } - - return self._post_request(f"/v1/fiat_accounts/{account_id}/deposit_from_linked_dda", body, idempotency_key) - - def get_transactions_with_page_info(self, before=0, after=None, status=None, limit=None, txhash=None, - assets=None, source_type=None, source_id=None, dest_type=None, dest_id=None, - next_or_previous_path=None): - """Gets a list of transactions matching the given filters or path. - Note that "next_or_previous_path" is mutually exclusive with other parameters. - If you wish to iterate over the nextPage/prevPage pages, please provide only the "next_or_previous_path" parameter from `pageDetails` response - example: - get_transactions_with_page_info(next_or_previous_path=response[pageDetails][nextPage]) - - Args: - before (int, optional): Only gets transactions created before given timestamp (in milliseconds) - after (int, optional): Only gets transactions created after given timestamp (in milliseconds) - status (str, optional): Only gets transactions with the specified status, which should one of the following: - SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, - PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, - CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED - limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used - txhash (str, optional): Only gets transactions with the specified txHash - assets (str, optional): Filter results for specified assets - source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: - VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, - NETWORK_CONNECTION, COMPOUND - source_id (str, optional): Only gets transactions with given source_id - dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: - VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, - NETWORK_CONNECTION, COMPOUND - dest_id (str, optional): Only gets transactions with given dest_id - next_or_previous_path (str, optional): get transactions matching the path, provided from pageDetails - """ - if next_or_previous_path is not None: - if not next_or_previous_path: - return {'transactions': [], 'pageDetails': {'prevPage': '', 'nextPage': ''}} - index = next_or_previous_path.index('/v1/') - length = len(next_or_previous_path) - 1 - suffix_path = next_or_previous_path[index:length] - return self._get_request(suffix_path, True) - else: - return self._get_transactions(before, after, status, limit, None, txhash, assets, source_type, source_id, - dest_type, dest_id, True) - - def get_transactions(self, before=0, after=0, status=None, limit=None, order_by=None, txhash=None, - assets=None, source_type=None, source_id=None, dest_type=None, dest_id=None): - """Gets a list of transactions matching the given filters - - Args: - before (int, optional): Only gets transactions created before given timestamp (in milliseconds) - after (int, optional): Only gets transactions created after given timestamp (in milliseconds) - status (str, optional): Only gets transactions with the specified status, which should one of the following: - SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, - PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, - CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED - limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used - order_by (str, optional): Determines the order of the returned results. Possible values are 'createdAt' or 'lastUpdated' - txhash (str, optional): Only gets transactions with the specified txHash - assets (str, optional): Filter results for specified assets - source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: - VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, - NETWORK_CONNECTION, COMPOUND - source_id (str, optional): Only gets transactions with given source_id - dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: - VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, - NETWORK_CONNECTION, COMPOUND - dest_id (str, optional): Only gets transactions with given dest_id - """ - return self._get_transactions(before, after, status, limit, order_by, txhash, assets, source_type, source_id, - dest_type, dest_id) - - def _get_transactions(self, before, after, status, limit, order_by, txhash, assets, source_type, source_id, - dest_type, dest_id, page_mode=False): - path = "/v1/transactions" - params = {} - - if status and status not in TRANSACTION_STATUS_TYPES: - raise FireblocksApiException("Got invalid transaction type: " + status) - - if before: - params['before'] = before - if after: - params['after'] = after - if status: - params['status'] = status - if limit: - params['limit'] = limit - if order_by: - params['orderBy'] = order_by - if txhash: - params['txHash'] = txhash - if assets: - params['assets'] = assets - if source_type: - params['sourceType'] = source_type - if source_id: - params['sourceId'] = source_id - if dest_type: - params['destType'] = dest_type - if dest_id: - params['destId'] = dest_id - if params: - path = path + "?" + urllib.parse.urlencode(params) - - return self._get_request(path, page_mode) - - def get_internal_wallets(self): - """Gets all internal wallets for your tenant""" - - return self._get_request("/v1/internal_wallets") - - def get_internal_wallet(self, wallet_id): - """Gets an internal wallet from your tenant - Args: - wallet_id (str): The wallet id to query - """ - - return self._get_request(f"/v1/internal_wallets/{wallet_id}") - - def get_internal_wallet_asset(self, wallet_id, asset_id): - """Gets an asset from an internal wallet from your tenant - Args: - wallet_id (str): The wallet id to query - asset_id (str): The asset id to query - """ - return self._get_request(f"/v1/internal_wallets/{wallet_id}/{asset_id}") - - def get_external_wallets(self): - """Gets all external wallets for your tenant""" - - return self._get_request("/v1/external_wallets") - - def get_external_wallet(self, wallet_id): - """Gets an external wallet from your tenant - Args: - wallet_id (str): The wallet id to query - """ - - return self._get_request(f"/v1/external_wallets/{wallet_id}") - - def get_external_wallet_asset(self, wallet_id, asset_id): - """Gets an asset from an external wallet from your tenant - Args: - wallet_id (str): The wallet id to query - asset_id (str): The asset id to query - """ - return self._get_request(f"/v1/external_wallets/{wallet_id}/{asset_id}") - - - - def get_contract_wallets(self): - """Gets all contract wallets for your tenant - """ - return self._get_request(f"/v1/contracts") - - def get_contract_wallet(self, wallet_id): - """Gets a single contract wallet - - Args: - wallet_id (str): The contract wallet ID - """ - return self._get_request(f"/v1/contracts/{wallet_id}") - - def get_contract_wallet_asset(self, wallet_id, asset_id): - """Gets a single contract wallet asset - - Args: - wallet_id (str): The contract wallet ID - asset_id (str): The asset ID - """ - return self._get_request(f"/v1/contracts/{wallet_id}/{asset_id}") - - - def get_transaction_by_id(self, txid): - """Gets detailed information for a single transaction - - Args: - txid (str): The transaction id to query - """ - - return self._get_request(f"/v1/transactions/{txid}") - - def get_transaction_by_external_id(self, external_tx_id): - """Gets detailed information for a single transaction - - Args: - external_tx_id (str): The external id of the transaction - """ - - return self._get_request(f"/v1/transactions/external_tx_id/{external_tx_id}") + return self.connector.get(f"/v1/network_connections/{connection_id}").content def get_fee_for_asset(self, asset_id): """Gets the estimated fees for an asset @@ -518,565 +66,7 @@ def get_fee_for_asset(self, asset_id): asset_id (str): The asset symbol (e.g BTC, ETH) """ - return self._get_request(f"/v1/estimate_network_fee?assetId={asset_id}") - - def estimate_fee_for_transaction(self, asset_id, amount, source, destination=None, tx_type=TRANSACTION_TRANSFER, - idempotency_key=None, destinations=None): - """Estimates transaction fee - - Args: - asset_id (str): The asset symbol (e.g BTC, ETH) - source (TransferPeerPath): The transaction source - destination (DestinationTransferPeerPath, optional): The transfer destination. - amount (str): The amount - tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. - idempotency_key (str, optional) - destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. - """ - - if tx_type not in TRANSACTION_TYPES: - raise FireblocksApiException("Got invalid transaction type: " + tx_type) - - if not isinstance(source, TransferPeerPath): - raise FireblocksApiException( - "Expected transaction source of type TransferPeerPath, but got type: " + type(source)) - - body = { - "assetId": asset_id, - "amount": amount, - "source": source.__dict__, - "operation": tx_type - } - - if destination: - if not isinstance(destination, (TransferPeerPath, DestinationTransferPeerPath)): - raise FireblocksApiException( - "Expected transaction fee estimation destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " + type( - destination)) - body["destination"] = destination.__dict__ - - if destinations: - if any([not isinstance(x, TransactionDestination) for x in destinations]): - raise FireblocksApiException("Expected destinations of type TransactionDestination") - body['destinations'] = [dest.__dict__ for dest in destinations] - - return self._post_request("/v1/transactions/estimate_fee", body, idempotency_key) - - def cancel_transaction_by_id(self, txid, idempotency_key=None): - """Cancels the selected transaction - - Args: - txid (str): The transaction id to cancel - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/transactions/{txid}/cancel", idempotency_key=idempotency_key) - - def drop_transaction(self, txid, fee_level=None, requested_fee=None, idempotency_key=None): - """Drops the selected transaction from the blockchain by replacing it with a 0 ETH transaction to itself - - Args: - txid (str): The transaction id to drop - fee_level (str): The fee level of the dropping transaction - requested_fee (str, optional): Requested fee for transaction - idempotency_key (str, optional) - """ - body = {} - - if fee_level: - body["feeLevel"] = fee_level - - if requested_fee: - body["requestedFee"] = requested_fee - - return self._post_request(f"/v1/transactions/{txid}/drop", body, idempotency_key) - - def create_vault_account(self, name, hiddenOnUI=False, customer_ref_id=None, autoFuel=False, idempotency_key=None): - """Creates a new vault account. - - Args: - name (str): A name for the new vault account - hiddenOnUI (boolean): Specifies whether the vault account is hidden from the web console, false by default - customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - body = { - "name": name, - "hiddenOnUI": hiddenOnUI, - "autoFuel": autoFuel - } - - if customer_ref_id: - body["customerRefId"] = customer_ref_id - - return self._post_request("/v1/vault/accounts", body, idempotency_key) - - def hide_vault_account(self, vault_account_id, idempotency_key=None): - """Hides the vault account from being visible in the web console - - Args: - vault_account_id (str): The vault account Id - idempotency_key (str, optional) - """ - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/hide", idempotency_key=idempotency_key) - - def unhide_vault_account(self, vault_account_id, idempotency_key=None): - """Returns the vault account to being visible in the web console - - Args: - vault_account_id (str): The vault account Id - idempotency_key (str, optional) - """ - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/unhide", idempotency_key=idempotency_key) - - def freeze_transaction_by_id(self, txId, idempotency_key=None): - """Freezes the selected transaction - - Args: - txId (str): The transaction ID to freeze - idempotency_key (str, optional) - """ - return self._post_request(f"/v1/transactions/{txId}/freeze", idempotency_key=idempotency_key) - - def unfreeze_transaction_by_id(self, txId, idempotency_key=None): - """Unfreezes the selected transaction - - Args: - txId (str): The transaction ID to unfreeze - idempotency_key (str, optional) - """ - return self._post_request(f"/v1/transactions/{txId}/unfreeze", idempotency_key=idempotency_key) - - def update_vault_account(self, vault_account_id, name): - """Updates a vault account. - - Args: - vault_account_id (str): The vault account Id - name (str): A new name for the vault account - """ - body = { - "name": name, - } - - return self._put_request(f"/v1/vault/accounts/{vault_account_id}", body) - - def create_vault_asset(self, vault_account_id, asset_id, idempotency_key=None): - """Creates a new asset within an existing vault account - - Args: - vault_account_id (str): The vault account Id - asset_id (str): The symbol of the asset to add (e.g BTC, ETH) - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}", idempotency_key=idempotency_key) - - def activate_vault_asset(self, vault_account_id, asset_id, idempotency_key=None): - """Retry to create a vault asset for a vault asset that failed - - Args: - vault_account_id (str): The vault account Id - asset_id (str): The symbol of the asset to add (e.g BTC, ETH) - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/activate", - idempotency_key=idempotency_key) - - def set_vault_account_customer_ref_id(self, vault_account_id, customer_ref_id, idempotency_key=None): - """Sets an AML/KYT customer reference ID for the vault account - - Args: - vault_account_id (str): The vault account Id - customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/set_customer_ref_id", - {"customerRefId": customer_ref_id or ''}, idempotency_key) - - def set_vault_account_customer_ref_id_for_address(self, vault_account_id, asset_id, address, customer_ref_id=None, - idempotency_key=None): - """Sets an AML/KYT customer reference ID for the given address - - Args: - vault_account_id (str): The vault account Id - asset_id (str): The symbol of the asset to add (e.g BTC, ETH) - address (string): The address for which to set the customer reference id - customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request( - f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}/set_customer_ref_id", - {"customerRefId": customer_ref_id or ''}, idempotency_key) - - def create_contract_wallet(self, name, idempotency_key=None): - """Creates a new contract wallet - - Args: - name (str): A name for the new contract wallet - """ - return self._post_request("/v1/contracts", {"name": name}, idempotency_key) - - def create_contract_wallet_asset(self, wallet_id, assetId, address, tag=None, idempotency_key=None): - """Creates a new contract wallet asset - - Args: - wallet_id (str): The wallet id - assetId (str): The asset to add - address (str): The wallet address - tag (str): (for ripple only) The ripple account tag - """ - return self._post_request(f"/v1/contracts/{wallet_id}/{assetId}", {"address": address, "tag": tag}, idempotency_key) - - - def create_external_wallet(self, name, customer_ref_id=None, idempotency_key=None): - """Creates a new external wallet - - Args: - name (str): A name for the new external wallet - customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request("/v1/external_wallets", {"name": name, "customerRefId": customer_ref_id or ''}, - idempotency_key) - - def create_internal_wallet(self, name, customer_ref_id=None, idempotency_key=None): - """Creates a new internal wallet - - Args: - name (str): A name for the new internal wallet - customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request("/v1/internal_wallets", {"name": name, "customerRefId": customer_ref_id or ''}, - idempotency_key) - - def create_external_wallet_asset(self, wallet_id, asset_id, address, tag=None, idempotency_key=None): - """Creates a new asset within an exiting external wallet - - Args: - wallet_id (str): The wallet id - asset_id (str): The symbol of the asset to add (e.g BTC, ETH) - address (str): The wallet address - tag (str, optional): (for ripple only) The ripple account tag - idempotency_key (str, optional) - """ - - body = {"address": address} - if tag: - body["tag"] = tag - - return self._post_request( - f"/v1/external_wallets/{wallet_id}/{asset_id}", body, idempotency_key - ) - - def create_internal_wallet_asset(self, wallet_id, asset_id, address, tag=None, idempotency_key=None): - """Creates a new asset within an exiting internal wallet - - Args: - wallet_id (str): The wallet id - asset_id (str): The symbol of the asset to add (e.g BTC, ETH) - address (str): The wallet address - tag (str, optional): (for ripple only) The ripple account tag - idempotency_key (str, optional) - """ - - body = {"address": address} - if tag: - body["tag"] = tag - - return self._post_request( - f"/v1/internal_wallets/{wallet_id}/{asset_id}", body, idempotency_key - ) - - def create_transaction(self, asset_id=None, amount=None, source=None, destination=None, fee=None, gas_price=None, - wait_for_status=False, tx_type=TRANSACTION_TRANSFER, note=None, network_fee=None, - customer_ref_id=None, replace_tx_by_hash=None, extra_parameters=None, destinations=None, - fee_level=None, fail_on_low_fee=None, max_fee=None, gas_limit=None, idempotency_key=None, - external_tx_id=None, treat_as_gross_amount=None, force_sweep=None): - """Creates a new transaction - - Args: - asset_id (str, optional): The asset symbol (e.g BTC, ETH) - source (TransferPeerPath, optional): The transfer source - destination (DestinationTransferPeerPath, optional): The transfer destination. Leave empty (None) if the transaction has no destination - amount (double): The amount - fee (double, optional): Sathoshi/Latoshi per byte. - gas_price (number, optional): gasPrice for ETH and ERC-20 transactions. - wait_for_status (bool, optional): If true, waits for transaction status. Default is false. - tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. - note (str, optional): A custome note that can be associated with the transaction. - network_fee (str, optional): Transaction blockchain fee (For Ethereum, you can't pass gasPrice, gasLimit and networkFee all together) - customer_ref_id (string, optional): The ID for AML providers to associate the owner of funds with transactions - extra_parameters (object, optional) - destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. - fee_level (FeeLevel, optional): Transaction fee level: either HIGH, MEDIUM, LOW. - fail_on_low_fee (bool, optional): False by default, if set to true and MEDIUM fee level is higher than the one specified in the transaction, the transction will fail. - max_fee (str, optional): The maximum fee (gas price or fee per byte) that should be payed for the transaction. - gas_limit (number, optional): For ETH-based assets only. - idempotency_key (str, optional) - external_tx_id (str, optional): A unique key for transaction provided externally - treat_as_gross_amount (bool, optional): Determine if amount should be treated as gross or net - force_sweep (bool, optional): Determine if transaction should be treated as a forced sweep - """ - - if tx_type not in TRANSACTION_TYPES: - raise FireblocksApiException("Got invalid transaction type: " + tx_type) - - if source: - if not isinstance(source, TransferPeerPath): - raise FireblocksApiException( - "Expected transaction source of type TransferPeerPath, but got type: " + type(source)) - - body = { - "waitForStatus": wait_for_status, - "operation": tx_type, - } - - if asset_id: - body["assetId"] = asset_id - - if source: - body["source"] = source.__dict__ - - if amount is not None: - body["amount"] = amount - - if fee: - body["fee"] = fee - - if fee_level: - if fee_level not in FEE_LEVEL: - raise FireblocksApiException("Got invalid fee level: " + fee_level) - body["feeLevel"] = fee_level - - if max_fee: - body["maxFee"] = max_fee - - if fail_on_low_fee: - body["failOnLowFee"] = fail_on_low_fee - - if gas_price: - body["gasPrice"] = str(gas_price) - - if gas_limit: - body["gasLimit"] = str(gas_limit) - - if note: - body["note"] = note - - if destination: - if not isinstance(destination, (TransferPeerPath, DestinationTransferPeerPath)): - raise FireblocksApiException( - "Expected transaction destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " + type( - destination)) - body["destination"] = destination.__dict__ - - if network_fee: - body["networkFee"] = network_fee - - if customer_ref_id: - body["customerRefId"] = customer_ref_id - - if replace_tx_by_hash: - body["replaceTxByHash"] = replace_tx_by_hash - - if treat_as_gross_amount: - body["treatAsGrossAmount"] = treat_as_gross_amount - - if destinations: - if any([not isinstance(x, TransactionDestination) for x in destinations]): - raise FireblocksApiException("Expected destinations of type TransactionDestination") - - body['destinations'] = [dest.__dict__ for dest in destinations] - - if extra_parameters: - body["extraParameters"] = extra_parameters - - if external_tx_id: - body["externalTxId"] = external_tx_id - - if force_sweep: - body["forceSweep"] = force_sweep - - return self._post_request("/v1/transactions", body, idempotency_key) - - def delete_contract_wallet(self, wallet_id): - """Deletes a single contract wallet - - Args: - wallet_id (string): The contract wallet ID - """ - return self._delete_request(f"/v1/contracts/{wallet_id}") - - def delete_contract_wallet_asset(self, wallet_id, asset_id): - """Deletes a single contract wallet - - Args: - wallet_id (string): The contract wallet ID - asset_id (string): The asset ID - """ - - return self._delete_request(f"/v1/contracts/{wallet_id}/{asset_id}") - - def delete_internal_wallet(self, wallet_id): - """Deletes a single internal wallet - - Args: - wallet_id (string): The internal wallet ID - """ - - return self._delete_request(f"/v1/internal_wallets/{wallet_id}") - - def delete_external_wallet(self, wallet_id): - """Deletes a single external wallet - - Args: - wallet_id (string): The external wallet ID - """ - - return self._delete_request(f"/v1/external_wallets/{wallet_id}") - - def delete_internal_wallet_asset(self, wallet_id, asset_id): - """Deletes a single asset from an internal wallet - - Args: - wallet_id (string): The internal wallet ID - asset_id (string): The asset ID - """ - - return self._delete_request(f"/v1/internal_wallets/{wallet_id}/{asset_id}") - - def delete_external_wallet_asset(self, wallet_id, asset_id): - """Deletes a single asset from an external wallet - - Args: - wallet_id (string): The external wallet ID - asset_id (string): The asset ID - """ - - return self._delete_request(f"/v1/external_wallets/{wallet_id}/{asset_id}") - - def set_customer_ref_id_for_internal_wallet(self, wallet_id, customer_ref_id=None, idempotency_key=None): - """Sets an AML/KYT customer reference ID for the specific internal wallet - - Args: - wallet_id (string): The external wallet ID - customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/internal_wallets/{wallet_id}/set_customer_ref_id", - {"customerRefId": customer_ref_id or ''}, idempotency_key) - - def set_customer_ref_id_for_external_wallet(self, wallet_id, customer_ref_id=None, idempotency_key=None): - """Sets an AML/KYT customer reference ID for the specific external wallet - - Args: - wallet_id (string): The external wallet ID - customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/external_wallets/{wallet_id}/set_customer_ref_id", - {"customerRefId": customer_ref_id or ''}, idempotency_key) - - def get_transfer_tickets(self): - """Gets all transfer tickets of your tenant""" - - return self._get_request("/v1/transfer_tickets") - - def create_transfer_ticket(self, terms, external_ticket_id=None, description=None, idempotency_key=None): - """Creates a new transfer ticket - - Args: - terms (list of TransferTicketTerm objects): The list of TransferTicketTerm - external_ticket_id (str, optional): The ID for of the transfer ticket on customer's platform - description (str, optional): A description for the new ticket - idempotency_key (str, optional) - """ - - body = {} - - if external_ticket_id: - body["externalTicketId"] = external_ticket_id - - if description: - body["description"] = description - - if any([not isinstance(x, TransferTicketTerm) for x in terms]): - raise FireblocksApiException("Expected Tranfer Assist ticket's term of type TranferTicketTerm") - - body['terms'] = [term.__dict__ for term in terms] - - return self._post_request(f"/v1/transfer_tickets", body, idempotency_key) - - def get_transfer_ticket_by_id(self, ticket_id): - """Retrieve a transfer ticket - - Args: - ticket_id (str): The ID of the transfer ticket. - """ - - return self._get_request(f"/v1/transfer_tickets/{ticket_id}") - - def get_transfer_ticket_term(self, ticket_id, term_id): - """Retrieve a transfer ticket - - Args: - ticket_id (str): The ID of the transfer ticket - term_id (str): The ID of the term within the transfer ticket - """ - - return self._get_request(f"/v1/transfer_tickets/{ticket_id}/{term_id}") - - def cancel_transfer_ticket(self, ticket_id, idempotency_key=None): - """Cancel a transfer ticket - - Args: - ticket_id (str): The ID of the transfer ticket to cancel - idempotency_key (str, optional) - """ - - return self._post_request(f"/v1/transfer_tickets/{ticket_id}/cancel", idempotency_key=idempotency_key) - - def execute_ticket_term(self, ticket_id, term_id, source=None, idempotency_key=None): - """Initiate a transfer ticket transaction - - Args: - ticket_id (str): The ID of the transfer ticket - term_id (str): The ID of the term within the transfer ticket - source (TransferPeerPath): JSON object of the source of the transaction. The network connection's vault account by default - """ - - body = {} - - if source: - if not isinstance(source, TransferPeerPath): - raise FireblocksApiException( - "Expected ticket term source Of type TransferPeerPath, but got type: " + type(source)) - body["source"] = source.__dict__ - - return self._post_request(f"/v1/transfer_tickets/{ticket_id}/{term_id}/transfer", body, idempotency_key) - - def set_confirmation_threshold_for_txid(self, txid, required_confirmations_number, idempotency_key=None): - """Set the required number of confirmations for transaction - - Args: - txid (str): The transaction id - required_confirmations_Number (number): Required confirmation threshold fot the txid - idempotency_key (str, optional) - """ - - body = { - "numOfConfirmations": required_confirmations_number - } - - return self._post_request(f"/v1/transactions/{txid}/set_confirmation_threshold", body, idempotency_key) + return self.connector.get(f"/v1/estimate_network_fee?assetId={asset_id}") def set_confirmation_threshold_for_txhash(self, txhash, required_confirmations_number, idempotency_key=None): """Set the required number of confirmations for transaction by txhash @@ -1091,234 +81,14 @@ def set_confirmation_threshold_for_txhash(self, txhash, required_confirmations_n "numOfConfirmations": required_confirmations_number } - return self._post_request(f"/v1/txHash/{txhash}/set_confirmation_threshold", body, idempotency_key) - - def get_public_key_info(self, algorithm, derivation_path, compressed=None): - """Get the public key information - - Args: - algorithm (str, optional) - derivation_path (str) - compressed (boolean, optional) - """ - - url = "/v1/vault/public_key_info" - if algorithm: - url += f"?algorithm={algorithm}" - if derivation_path: - url += f"&derivationPath={urllib.parse.quote(derivation_path)}" - if compressed: - url += f"&compressed={compressed}" - return self._get_request(url) - - def get_public_key_info_for_vault_account(self, asset_id, vault_account_id, change, address_index, compressed=None): - """Get the public key information for a vault account - - Args: - assetId (str) - vaultAccountId (number) - change (number) - addressIndex (number) - compressed (boolean, optional) - """ - - url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/{change}/{address_index}/public_key_info" - if compressed: - url += f"?compressed={compressed}" - - return self._get_request(url) - - def allocate_funds_to_private_ledger(self, vault_account_id, asset, allocation_id, amount, - treat_as_gross_amount=None, idempotency_key=None): - """Allocate funds from your default balance to a private ledger - - Args: - vault_account_id (string) - asset (string) - allocation_id (string) - amount (string) - treat_as_gross_amount (bool, optional) - idempotency_key (string, optional) - """ - - url = f"/v1/vault/accounts/{vault_account_id}/{asset}/lock_allocation" - - return self._post_request(url, {"allocationId": allocation_id, "amount": amount, - "treatAsGrossAmount": treat_as_gross_amount or False}, idempotency_key) - - def deallocate_funds_from_private_ledger(self, vault_account_id, asset, allocation_id, amount, - idempotency_key=None): - """deallocate funds from a private ledger to your default balance - - Args: - vault_account_id (string) - asset (string) - allocation_id (string) - amount (string) - idempotency_key (string, optional) - """ - - url = f"/v1/vault/accounts/{vault_account_id}/{asset}/release_allocation" - - return self._post_request(url, {"allocationId": allocation_id, "amount": amount}, idempotency_key) - - def get_gas_station_info(self, asset_id=None): - """Get configuration and status of the Gas Station account" - - Args: - asset_id (string, optional) - """ - - url = f"/v1/gas_station" - - if asset_id: - url = url + f"/{asset_id}" - - return self._get_request(url) - - def set_gas_station_configuration(self, gas_threshold, gas_cap, max_gas_price=None, asset_id=None): - """Set configuration of the Gas Station account - - Args: - gasThreshold (str) - gasCap (str) - maxGasPrice (str, optional) - asset_id (str, optional) - """ - - url = f"/v1/gas_station/configuration" - - if asset_id: - url = url + f"/{asset_id}" - - body = { - "gasThreshold": gas_threshold, - "gasCap": gas_cap, - "maxGasPrice": max_gas_price - } - - return self._put_request(url, body) - - def get_vault_assets_balance(self, account_name_prefix=None, account_name_suffix=None): - """Gets vault assets accumulated balance - - Args: - account_name_prefix (string, optional): Vault account name prefix - account_name_suffix (string, optional): Vault account name suffix - """ - url = f"/v1/vault/assets" - - params = {} - - if account_name_prefix: - params['accountNamePrefix'] = account_name_prefix - - if account_name_suffix: - params['accountNameSuffix'] = account_name_suffix - - if params: - url = url + "?" + urllib.parse.urlencode(params) - - return self._get_request(url) - - def get_vault_balance_by_asset(self, asset_id=None): - """Gets vault accumulated balance by asset - - Args: - asset_id (str, optional): The asset symbol (e.g BTC, ETH) - """ - url = f"/v1/vault/assets" - - if asset_id: - url += f"/{asset_id}" - - return self._get_request(url) - - def create_raw_transaction(self, raw_message, source=None, asset_id=None, note=None): - """Creates a new raw transaction with the specified parameters - - Args: - raw_message (RawMessage): The messages that should be signed - source (TransferPeerPath, optional): The transaction source - asset_id (str, optional): Transaction asset id - note (str, optional): A custome note that can be associated with the transaction - """ - - if asset_id is None: - if raw_message.algorithm not in SIGNING_ALGORITHM: - raise Exception("Got invalid signing algorithm type: " + raw_message.algorithm) - - if not all([isinstance(x, UnsignedMessage) for x in raw_message.messages]): - raise FireblocksApiException("Expected messages of type UnsignedMessage") - - raw_message.messages = [message.__dict__ for message in raw_message.messages] - - return self.create_transaction(asset_id, source=source, tx_type="RAW", - extra_parameters={"rawMessageData": raw_message.__dict__}, note=note) - - def get_max_spendable_amount(self, vault_account_id, asset_id, manual_signing=False): - """Get max spendable amount per asset and vault. - - Args: - vault_account_id (str): The vault account Id. - asset_id (str): Asset id. - manual_signing (boolean, optional): False by default. - """ - url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/max_spendable_amount?manual_signing={manual_signing}" - - return self._get_request(url) - - def set_auto_fuel(self, vault_account_id, auto_fuel, idempotency_key=None): - """Sets autoFuel to true/false for a vault account - - Args: - vault_account_id (str): The vault account Id - auto_fuel (boolean): The new value for the autoFuel flag - idempotency_key (str, optional) - """ - body = { - "autoFuel": auto_fuel - } - - return self._post_request(f"/v1/vault/accounts/{vault_account_id}/set_auto_fuel", body, idempotency_key) - - def validate_address(self, asset_id, address): - """Gets vault accumulated balance by asset - - Args: - asset_id (str): The asset symbol (e.g XRP, EOS) - address (str): The address to be verified - """ - url = f"/v1/transactions/validate_address/{asset_id}/{address}" - - return self._get_request(url) - - def resend_webhooks(self): - """Resend failed webhooks of your tenant""" - - return self._post_request("/v1/webhooks/resend") - - def resend_transaction_webhooks_by_id(self, tx_id, resend_created, resend_status_updated): - """Resend webhooks of transaction - - Args: - tx_id (str): The transaction for which the message is sent. - resend_created (boolean): If true, a webhook will be sent for the creation of the transaction. - resend_status_updated (boolean): If true, a webhook will be sent for the status of the transaction. - """ - body = { - "resendCreated": resend_created, - "resendStatusUpdated": resend_status_updated - } - - return self._post_request(f"/v1/webhooks/resend/{tx_id}", body) + return self.connector.post(f"/v1/txHash/{txhash}/set_confirmation_threshold", body, idempotency_key) def get_users(self): """Gets all users of your tenant""" url = "/v1/users" - return self._get_request(url) + return self.connector.get(url) def get_off_exchanges(self): """ @@ -1326,7 +96,7 @@ def get_off_exchanges(self): """ url = f"/v1/off_exchange_accounts" - return self._get_request(url) + return self.connector.get(url) def get_off_exchange_by_id(self, off_exchange_id): """ @@ -1337,7 +107,7 @@ def get_off_exchange_by_id(self, off_exchange_id): url = f"/v1/off_exchange_accounts/{off_exchange_id}" - return self._get_request(url) + return self.connector.get(url) def settle_off_exchange_by_id(self, off_exchange_id, idempotency_key=None): """ @@ -1348,88 +118,4 @@ def settle_off_exchange_by_id(self, off_exchange_id, idempotency_key=None): url = f"/v1/off_exchanges/{off_exchange_id}/settle" - return self._post_request(url, {}, idempotency_key) - - def set_fee_payer_configuration(self, base_asset, fee_payer_account_id, idempotency_key=None): - """ - Setting fee payer configuration for base asset - :param base_asset: ID of the base asset you want to configure fee payer for (for example: SOL) - :param fee_payer_account_id: ID of the vault account you want your fee to be paid from - :param idempotency_key - """ - - url = f"/v1/fee_payer/{base_asset}" - - body = { - "feePayerAccountId": fee_payer_account_id - } - - return self._post_request(url, body, idempotency_key) - - def get_fee_payer_configuration(self, base_asset): - """ - Get fee payer configuration for base asset - :param base_asset: ID of the base asset - :return: the fee payer configuration - """ - - url = f"/v1/fee_payer/{base_asset}" - - return self._get_request(url) - - def remove_fee_payer_configuration(self, base_asset): - """ - Delete fee payer configuration for base asset - :param base_asset: ID of the base asset - """ - url = f"/v1/fee_payer/{base_asset}" - - return self._delete_request(url) - - def _get_request(self, path, page_mode=False): - token = self.token_provider.sign_jwt(path) - headers = { - "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}" - } - - response = requests.get(self.base_url + path, headers=headers, timeout=self.timeout) - return handle_response(response, page_mode) - - def _delete_request(self, path): - token = self.token_provider.sign_jwt(path) - headers = { - "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}" - } - - response = requests.delete(self.base_url + path, headers=headers, timeout=self.timeout) - return handle_response(response) - - def _post_request(self, path, body={}, idempotency_key=None): - token = self.token_provider.sign_jwt(path, body) - if idempotency_key is None: - headers = { - "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}" - } - else: - headers = { - "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}", - "Idempotency-Key": idempotency_key - } - - response = requests.post(self.base_url + path, headers=headers, json=body, timeout=self.timeout) - return handle_response(response) - - def _put_request(self, path, body={}): - token = self.token_provider.sign_jwt(path, body) - headers = { - "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}", - "Content-Type": "application/json" - } - - response = requests.put(self.base_url + path, headers=headers, data=json.dumps(body), timeout=self.timeout) - return handle_response(response) + return self.connector.post(url, {}, idempotency_key) diff --git a/fireblocks_sdk/sdk_token_provider.py b/fireblocks_sdk/sdk_token_provider.py index 240d1d5..bf706b5 100644 --- a/fireblocks_sdk/sdk_token_provider.py +++ b/fireblocks_sdk/sdk_token_provider.py @@ -1,28 +1,29 @@ -import jwt -import json -import time import math import secrets +import time from hashlib import sha256 +import jwt + + class SdkTokenProvider(object): def __init__(self, private_key, api_key): self.private_key = private_key self.api_key = api_key - def sign_jwt(self, path, body_json=""): + def sign_jwt(self, path, content: str): timestamp = time.time() nonce = secrets.randbits(63) timestamp_secs = math.floor(timestamp) - path= path.replace("[", "%5B") - path= path.replace("]", "%5D") + path = path.replace("[", "%5B") + path = path.replace("]", "%5D") token = { "uri": path, "nonce": nonce, "iat": timestamp_secs, - "exp": timestamp_secs + 55, + "exp": timestamp_secs + 55, "sub": self.api_key, - "bodyHash": sha256(json.dumps(body_json).encode("utf-8")).hexdigest() + "bodyHash": sha256(content.encode("utf-8")).hexdigest() } return jwt.encode(token, key=self.private_key, algorithm="RS256") diff --git a/fireblocks_sdk/services/__init__.py b/fireblocks_sdk/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/base_service.py b/fireblocks_sdk/services/base_service.py new file mode 100644 index 0000000..5350829 --- /dev/null +++ b/fireblocks_sdk/services/base_service.py @@ -0,0 +1,6 @@ +from fireblocks_sdk.connectors.rest import RestConnector + + +class BaseService: + def __init__(self, connector: RestConnector) -> None: + self.connector = connector diff --git a/fireblocks_sdk/services/contracts/__init__.py b/fireblocks_sdk/services/contracts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/exchange/__init__.py b/fireblocks_sdk/services/exchange/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/fee_payer/__init__.py b/fireblocks_sdk/services/fee_payer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/fee_payer/fee_info.py b/fireblocks_sdk/services/fee_payer/fee_info.py new file mode 100644 index 0000000..442e110 --- /dev/null +++ b/fireblocks_sdk/services/fee_payer/fee_info.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Dict, Union + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class FeeInfo(Deserializable): + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> FeeInfo: + return cls( + data.get('networkFee'), + data.get('serviceFee'), + data.get('gasPrice'), + ) + + def __init__(self, network_fee: Union[str, None], service_fee: Union[str, None], + gas_price: Union[str, None]) -> None: + super().__init__() + self.network_fee = network_fee + self.service_fee = service_fee + self.gas_price = gas_price diff --git a/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py b/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py new file mode 100644 index 0000000..4bdf339 --- /dev/null +++ b/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class FeePayerConfiguration(Deserializable): + + def __init__(self, fee_payer_account_id: str) -> None: + super().__init__() + self.fee_payer_accountId = fee_payer_account_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> FeePayerConfiguration: + return cls( + data.get('feePayerAccountId') + ) diff --git a/fireblocks_sdk/services/fee_payer/fee_payer_info.py b/fireblocks_sdk/services/fee_payer/fee_payer_info.py new file mode 100644 index 0000000..e8d846e --- /dev/null +++ b/fireblocks_sdk/services/fee_payer/fee_payer_info.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class FeePayerInfo(Deserializable): + + def __init__(self, fee_payer_account_id: str) -> None: + super().__init__() + self.fee_payer_account_id = fee_payer_account_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> FeePayerInfo: + return cls( + data.get('feePayerAccountId') + ) diff --git a/fireblocks_sdk/services/fee_payer/fee_payer_service.py b/fireblocks_sdk/services/fee_payer/fee_payer_service.py new file mode 100644 index 0000000..b8263c1 --- /dev/null +++ b/fireblocks_sdk/services/fee_payer/fee_payer_service.py @@ -0,0 +1,50 @@ +from typing import Union + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.fee_payer.fee_payer_configuration import FeePayerConfiguration + + +class FeePayerService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(FeePayerConfiguration) + def set_fee_payer_configuration(self, base_asset: str, fee_payer_account_id: str, + idempotency_key: Union[str, None] = None) -> FeePayerConfiguration: + """ + Setting fee payer configuration for base asset + :param base_asset: ID of the base asset you want to configure fee payer for (for example: SOL) + :param fee_payer_account_id: ID of the vault account you want your fee to be paid from + :param idempotency_key + """ + + url = f"/v1/fee_payer/{base_asset}" + + body = { + "feePayerAccountId": fee_payer_account_id + } + + return self.connector.post(url, body, idempotency_key).content + + @response_deserializer(FeePayerConfiguration) + def get_fee_payer_configuration(self, base_asset: str) -> FeePayerConfiguration: + """ + Get fee payer configuration for base asset + :param base_asset: ID of the base asset + :return: the fee payer configuration + """ + + url = f"/v1/fee_payer/{base_asset}" + + return self.connector.get(url).content + + def remove_fee_payer_configuration(self, base_asset: str): + """ + Delete fee payer configuration for base asset + :param base_asset: ID of the base asset + """ + url = f"/v1/fee_payer/{base_asset}" + + return self.connector.delete(url) diff --git a/fireblocks_sdk/services/fiat/__init__.py b/fireblocks_sdk/services/fiat/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/gas_station/__init__.py b/fireblocks_sdk/services/gas_station/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/gas_station/gas_station_info.py b/fireblocks_sdk/services/gas_station/gas_station_info.py new file mode 100644 index 0000000..7793bdc --- /dev/null +++ b/fireblocks_sdk/services/gas_station/gas_station_info.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class GasStationInfoConfiguration(Deserializable): + + def __init__(self, gas_threshold: str, gas_cap: str, max_gas_price: str) -> None: + super().__init__() + self.gas_threshold = gas_threshold + self.gas_cap = gas_cap + self.max_gas_price = max_gas_price + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> GasStationInfoConfiguration: + return cls( + data.get('gasThreshold'), + data.get('gasCap'), + data.get('maxGasPrice'), + ) + + +class GasStationInfo(Deserializable): + + def __init__(self, balance: Dict[str, str], configuration: GasStationInfoConfiguration) -> None: + super().__init__() + self.balance = balance + self.configuration = configuration + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> GasStationInfo: + return cls( + data.get('balance'), + GasStationInfoConfiguration.deserialize(data.get('configuration')) + ) diff --git a/fireblocks_sdk/services/transactions/__init__.py b/fireblocks_sdk/services/transactions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/transactions/entities/__init__.py b/fireblocks_sdk/services/transactions/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/transactions/entities/aml_screening_result.py b/fireblocks_sdk/services/transactions/entities/aml_screening_result.py new file mode 100644 index 0000000..6d938d8 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/aml_screening_result.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable, T + + +class AmlScreeningResult(Deserializable): + + def __init__(self, provider: str, payload: object, screening_status: str, bypass_reason: str, + timestamp: str) -> None: + super().__init__() + self.provider = provider + self.payload = payload + self.screeningStatus = screening_status + self.bypass_reason = bypass_reason + self.timestamp = timestamp + + @classmethod + def deserialize(cls: T, data: Dict[str, object]) -> AmlScreeningResult: + return cls( + data.get('provider'), + data.get('payload'), + data.get('screeningStatus'), + data.get('bypassReason'), + data.get('timestamp'), + ) diff --git a/fireblocks_sdk/services/transactions/entities/amount_info.py b/fireblocks_sdk/services/transactions/entities/amount_info.py new file mode 100644 index 0000000..a732e2b --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/amount_info.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import Union, Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class AmountInfo(Deserializable): + + def __init__(self, amount: Union[str, None], requested_amount: Union[str, None], + net_amount: Union[str, None], amount_usd: Union[str, None]) -> None: + super().__init__() + self.amount = amount + self.requested_amount = requested_amount + self.net_amount = net_amount + self.amount_usd = amount_usd + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AmountInfo: + return cls( + data.get('amount'), + data.get('requestedAmount'), + data.get('netAmount'), + data.get('amountUSD') + ) diff --git a/fireblocks_sdk/services/transactions/entities/authorization_info.py b/fireblocks_sdk/services/transactions/entities/authorization_info.py new file mode 100644 index 0000000..fc951ba --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/authorization_info.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable +from fireblocks_sdk.services.transactions.entities.authorization_info_groups import AuthorizationInfoGroups + + +class AuthorizationInfo(Deserializable): + + def __init__(self, allow_operator_as_authorizer: bool, logic: str, groups: AuthorizationInfoGroups) -> None: + super().__init__() + self.allow_operator_as_authorizer = allow_operator_as_authorizer + self.logic = logic + self.groups = groups + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AuthorizationInfo: + return cls( + data.get('allowOperatorAsAuthorizer'), + data.get('logic'), + AuthorizationInfoGroups.deserialize(data.get('groups', {})) + ) diff --git a/fireblocks_sdk/services/transactions/entities/authorization_info_groups.py b/fireblocks_sdk/services/transactions/entities/authorization_info_groups.py new file mode 100644 index 0000000..3b48c86 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/authorization_info_groups.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class AuthorizationInfoGroups(Deserializable): + + def __init__(self, users: Dict[str, str], th: int) -> None: + super().__init__() + self.users = users + self.th = th + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AuthorizationInfoGroups: + return cls( + data.get('users'), + data.get('th') + ) diff --git a/fireblocks_sdk/services/transactions/entities/block_info.py b/fireblocks_sdk/services/transactions/entities/block_info.py new file mode 100644 index 0000000..4fcd1cf --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/block_info.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class BlockInfo(Deserializable): + + def __init__(self, block_height: str, block_hash: str) -> None: + super().__init__() + self.block_height = block_height + self.block_hash = block_hash + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> BlockInfo: + return cls( + data.get('blockHeight'), + data.get('blockHash') + ) diff --git a/fireblocks_sdk/services/transactions/entities/create_transaction_response.py b/fireblocks_sdk/services/transactions/entities/create_transaction_response.py new file mode 100644 index 0000000..5bea154 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/create_transaction_response.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from typing import Dict + + +class CreateTransactionResponse: + + def __init__(self, id: str, status: str) -> None: + super().__init__() + self.id = id + self.status = status + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> CreateTransactionResponse: + return cls(data.get('id'), data.get('status')) diff --git a/fireblocks_sdk/services/transactions/entities/estimate_transaction_fee_response.py b/fireblocks_sdk/services/transactions/entities/estimate_transaction_fee_response.py new file mode 100644 index 0000000..6ef2ba4 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/estimate_transaction_fee_response.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable +from fireblocks_sdk.services.transactions.entities.estimated_transaction_fee import EstimatedTransactionFee + + +class EstimateTransactionFeeResponse(Deserializable): + + def __init__(self, low: EstimatedTransactionFee, medium: EstimatedTransactionFee, + high: EstimatedTransactionFee) -> None: + super().__init__() + self.low = low + self.medium = medium + self.high = high + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> EstimateTransactionFeeResponse: + return cls( + EstimatedTransactionFee.deserialize(data.get('low')), + EstimatedTransactionFee.deserialize(data.get('medium')), + EstimatedTransactionFee.deserialize(data.get('high')) + ) diff --git a/fireblocks_sdk/services/transactions/entities/estimated_transaction_fee.py b/fireblocks_sdk/services/transactions/entities/estimated_transaction_fee.py new file mode 100644 index 0000000..ad727ce --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/estimated_transaction_fee.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class EstimatedTransactionFee(Deserializable): + + def __init__(self, network_fee: str, gas_price: str, gas_limit: str, fee_per_byte: str, base_fee: str, + priority_fee: str) -> None: + super().__init__() + self.network_fee = network_fee + self.gas_price = gas_price + self.gas_limit = gas_limit + self.fee_per_byte = fee_per_byte + self.base_fee = base_fee + self.priority_fee = priority_fee + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> EstimatedTransactionFee: + return cls( + data.get('networkFee'), + data.get('gasPrice'), + data.get('gasLimit'), + data.get('feePerByte'), + data.get('baseFee'), + data.get('priorityFee'), + ) diff --git a/fireblocks_sdk/services/transactions/entities/message_signature.py b/fireblocks_sdk/services/transactions/entities/message_signature.py new file mode 100644 index 0000000..ec49378 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/message_signature.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class MessageSignature(Deserializable): + + def __init__(self, sig: str, r: str, s: str, v: str) -> None: + super().__init__() + self.sig = sig + self.r = r + self.s = s + self.v = v + + @classmethod + def deserialize(cls, data: Dict[str, str]) -> MessageSignature: + return cls( + data.get('fullSig'), + data.get('r'), + data.get('s'), + data.get('v') + ) diff --git a/fireblocks_sdk/services/transactions/entities/page_details.py b/fireblocks_sdk/services/transactions/entities/page_details.py new file mode 100644 index 0000000..13e0fcc --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/page_details.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable, T + + +class PageDetails(Deserializable): + + def __init__(self, prev_page: str, next_page: str) -> None: + super().__init__() + self.prev_page = prev_page + self.next_page = next_page + + @classmethod + def deserialize(cls: T, data: Dict[str, str]) -> PageDetails: + return cls( + data.get('prevPage'), + data.get('nextPage') + ) diff --git a/fireblocks_sdk/services/transactions/entities/reward_info.py b/fireblocks_sdk/services/transactions/entities/reward_info.py new file mode 100644 index 0000000..6bd33e8 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/reward_info.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class RewardInfo(Deserializable): + + def __init__(self, src_rewards: str, dest_rewards: str) -> None: + super().__init__() + self.src_rewards = src_rewards + self.dest_rewards = dest_rewards + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> RewardInfo: + return cls( + data.get('srcRewards'), + data.get('destRewards') + ) diff --git a/fireblocks_sdk/services/transactions/entities/signed_message_response.py b/fireblocks_sdk/services/transactions/entities/signed_message_response.py new file mode 100644 index 0000000..72f69d7 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/signed_message_response.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable +from fireblocks_sdk.services.transactions.entities.message_signature import MessageSignature + + +class SignedMessageResponse(Deserializable): + + def __init__(self, content: str, algorithm: str, derivation_path: str, signature: MessageSignature, + public_key: str) -> None: + super().__init__() + self.content = content + self.algorithm = algorithm + self.derivation_path = derivation_path + self.signature = signature + self.public_key = public_key + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> SignedMessageResponse: + return cls( + data.get('content'), + data.get('algorithm'), + data.get('derivationPath'), + MessageSignature.deserialize(data.get('signature')), + data.get('publicKey'), + ) diff --git a/fireblocks_sdk/services/transactions/entities/transaction_page_response.py b/fireblocks_sdk/services/transactions/entities/transaction_page_response.py new file mode 100644 index 0000000..05c4ee1 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/transaction_page_response.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import List, Dict + +from fireblocks_sdk.entities.deserializable import Deserializable, T +from fireblocks_sdk.services.transactions.entities.page_details import PageDetails +from fireblocks_sdk.services.transactions.entities.transaction_response import TransactionResponse + + +class TransactionPageResponse(Deserializable): + + def __init__(self, transactions: List[TransactionResponse], page_details: PageDetails) -> None: + super().__init__() + self.transactions = transactions + self.page_details = page_details + + @classmethod + def deserialize(cls: T, data: Dict[str, any]) -> TransactionPageResponse: + return cls( + [TransactionResponse.deserialize(transaction) for transaction in data.get('transactions')], + PageDetails.deserialize(data.get('pageDetails')) + ) diff --git a/fireblocks_sdk/services/transactions/entities/transaction_response.py b/fireblocks_sdk/services/transactions/entities/transaction_response.py new file mode 100644 index 0000000..1082554 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/transaction_response.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import Dict, List + +from fireblocks_sdk.entities.deserializable import Deserializable +from fireblocks_sdk.services.fee_payer.fee_info import FeeInfo +from fireblocks_sdk.services.fee_payer.fee_payer_info import FeePayerInfo +from fireblocks_sdk.services.transactions.entities.aml_screening_result import AmlScreeningResult +from fireblocks_sdk.services.transactions.entities.amount_info import AmountInfo +from fireblocks_sdk.services.transactions.entities.authorization_info import AuthorizationInfo +from fireblocks_sdk.services.transactions.entities.block_info import BlockInfo +from fireblocks_sdk.services.transactions.entities.reward_info import RewardInfo +from fireblocks_sdk.services.transactions.entities.signed_message_response import SignedMessageResponse +from fireblocks_sdk.services.transactions.entities.transaction_response_destination import \ + TransactionResponseDestination +from fireblocks_sdk.services.transactions.entities.transfer_peer_path_response import TransferPeerPathResponse + + +class TransactionResponse(Deserializable): + + def __init__(self, id: str, asset_id: str, source: TransferPeerPathResponse, + destination: TransferPeerPathResponse, amount: int, + fee: float, network_fee: float, amount_usd: float, net_amount: float, + created_at: int, last_updated: int, + status: str, tx_hash: str, num_confirmations: int, sub_status: str, signed_by: List[str], + created_by: str, rejected_by: str, destination_address: str, destination_address_description: str, + destination_tag: str, address_type: str, note: str, exchange_tx_id: str, requested_amount: float, + service_fee: float, fee_currency: str, aml_screening_result: AmlScreeningResult, customer_ref_id: str, + amount_info: AmountInfo, fee_info: FeeInfo, signed_messages: List[SignedMessageResponse], + extra_parameters: object, + external_tx_id: str, destinations: List[TransactionResponseDestination], block_info: BlockInfo, + authorization_info: AuthorizationInfo, index: int, reward_info: RewardInfo, + fee_payer_info: FeePayerInfo) -> None: + super().__init__() + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> TransactionResponse: + return cls( + data.get('id'), + data.get('assetId'), + TransferPeerPathResponse.deserialize(data.get('source')), + TransferPeerPathResponse.deserialize(data.get('destination')), + data.get('amount'), + data.get('fee'), + data.get('networkFee'), + data.get('amountUSD'), + data.get('netAmount'), + data.get('createdAt'), + data.get('lastUpdated'), + data.get('status'), + data.get('txHash'), + data.get('numOfConfirmations'), + data.get('subStatus'), + data.get('signedBy'), + data.get('createdBy'), + data.get('rejectedBy'), + data.get('destinationAddress'), + data.get('destinationAddressDescription'), + data.get('destinationTag'), + data.get('addressType'), + data.get('note'), + data.get('exchangeTxId'), + data.get('requestedAmount'), + data.get('serviceFee'), + data.get('feeCurrency'), + AmlScreeningResult.deserialize(data.get('amlScreeningResult', {})), + data.get('customerRefId'), + AmountInfo.deserialize(data.get('amountInfo', {})), + FeeInfo.deserialize(data.get('feeInfo', {})), + [SignedMessageResponse.deserialize(message) for message in data.get('signedMessages', [])], + data.get('extraParameters'), + data.get('externalTxId'), + [TransactionResponseDestination.deserialize(destination) for destination in data.get('destinations', [])], + data.get('blockInfo'), + AuthorizationInfo.deserialize(data.get('authorizationInfo', {})), + data.get('index'), + RewardInfo.deserialize(data.get('rewardInfo', {})), + FeePayerInfo.deserialize(data.get('feePayerInfo', {})), + ) diff --git a/fireblocks_sdk/services/transactions/entities/transaction_response_destination.py b/fireblocks_sdk/services/transactions/entities/transaction_response_destination.py new file mode 100644 index 0000000..bcb7c2a --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/transaction_response_destination.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable +from fireblocks_sdk.services.transactions.entities.aml_screening_result import AmlScreeningResult +from fireblocks_sdk.services.transactions.entities.authorization_info import AuthorizationInfo +from fireblocks_sdk.services.transactions.entities.transfer_peer_path_response import TransferPeerPathResponse + + +class TransactionResponseDestination(Deserializable): + + def __init__(self, amount: str, amount_usd: str, + aml_screening_result: AmlScreeningResult, destination: TransferPeerPathResponse, + authorization_info: AuthorizationInfo) -> None: + super().__init__() + self.amount = amount + self.amount_usd = amount_usd + self.aml_screening_result = aml_screening_result + self.destination = destination + self.authorization_info = authorization_info + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> TransactionResponseDestination: + return cls( + data.get('amount'), + data.get('amountUSD'), + AmlScreeningResult.deserialize(data.get('amlScreeningResult')), + TransferPeerPathResponse.deserialize(data.get('destination')), + AuthorizationInfo.deserialize(data.get('authorizationInfo')) + ) diff --git a/fireblocks_sdk/services/transactions/entities/transfer_peer_path_response.py b/fireblocks_sdk/services/transactions/entities/transfer_peer_path_response.py new file mode 100644 index 0000000..19d02a5 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/transfer_peer_path_response.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class TransferPeerPathResponse(Deserializable): + + def __init__(self, id: str, type: str, name: str, sub_type: str, virtual_type: str, virtual_id: str) -> None: + super().__init__() + self.id = id + self.type = type + self.name = name + self.sub_type = sub_type + self.virtual_type = virtual_type + self.virtual_id = virtual_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> TransferPeerPathResponse: + return cls( + data.get('id'), + data.get('type'), + data.get('name'), + data.get('subType'), + data.get('virtualType'), + data.get('virtualId'), + ) diff --git a/fireblocks_sdk/services/transactions/entities/validate_address_response.py b/fireblocks_sdk/services/transactions/entities/validate_address_response.py new file mode 100644 index 0000000..6bb92b8 --- /dev/null +++ b/fireblocks_sdk/services/transactions/entities/validate_address_response.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import Dict + + +class ValidateAddressResponse: + + def __init__(self, valid: bool, active: bool, requires_tag: bool) -> None: + super().__init__() + self.valid = valid + self.active = active + self.requires_tag = requires_tag + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> ValidateAddressResponse: + return cls( + data.get('isValid'), + data.get('isActive'), + data.get('requiresTag'), + ) diff --git a/fireblocks_sdk/services/transfer_tickets/__init__.py b/fireblocks_sdk/services/transfer_tickets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/transfer_tickets/create_transfer_ticket_response.py b/fireblocks_sdk/services/transfer_tickets/create_transfer_ticket_response.py new file mode 100644 index 0000000..c595523 --- /dev/null +++ b/fireblocks_sdk/services/transfer_tickets/create_transfer_ticket_response.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing import Dict + + +class CreateTransferTicketResponse: + def __init__(self, ticket_id: str) -> None: + self.ticket_id = ticket_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> CreateTransferTicketResponse: + return cls(data.get('ticketId')) diff --git a/fireblocks_sdk/services/transfer_tickets/term_response.py b/fireblocks_sdk/services/transfer_tickets/term_response.py new file mode 100644 index 0000000..0e801f4 --- /dev/null +++ b/fireblocks_sdk/services/transfer_tickets/term_response.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import List, Dict + +from fireblocks_sdk.entities.deserializable import Deserializable, T + + +class TermResponse(Deserializable): + + def __init__(self, term_id: str, network_connection_id: str, outgoing: str, asset: str, + amount: str, txt_ids: List[str], status: str, note: str) -> None: + super().__init__() + self.term_id = term_id + self.network_connection_id = network_connection_id + self.outgoing = outgoing + self.asset = asset + self.amount = amount + self.txt_ids = txt_ids + self.status = status + self.note = note + + @classmethod + def deserialize(cls: T, data: Dict) -> TermResponse: + return cls( + data.get('termId'), + data.get('networkConnectionId'), + data.get('outgoing'), + data.get('asset'), + data.get('amount'), + data.get('txIds'), + data.get('status'), + data.get('note'), + ) diff --git a/fireblocks_sdk/services/transfer_tickets/transfer_ticket_response.py b/fireblocks_sdk/services/transfer_tickets/transfer_ticket_response.py new file mode 100644 index 0000000..d8d899b --- /dev/null +++ b/fireblocks_sdk/services/transfer_tickets/transfer_ticket_response.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict, List + +from fireblocks_sdk.entities.deserializable import Deserializable, T +from fireblocks_sdk.services.transfer_tickets.term_response import TermResponse + + +class TransferTicketResponse(Deserializable): + + def __init__(self, ticket_id: str, external_ticket_id: str, description: str, status: str, + terms: List[TermResponse]) -> None: + super().__init__() + self.ticket_id = ticket_id + self.external_ticket_id = external_ticket_id + self.description = description + self.status = status + self.terms = terms + + @classmethod + def deserialize(cls: T, data: Dict) -> TransferTicketResponse: + return cls( + data.get('ticketId'), + data.get('externalTicketId'), + data.get('description'), + data.get('status'), + [TermResponse.deserialize(term) for term in data.get('terms')] + ) diff --git a/fireblocks_sdk/services/vaults/__init__.py b/fireblocks_sdk/services/vaults/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/wallets/__init__.py b/fireblocks_sdk/services/wallets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/wallets/external_wallet_asset.py b/fireblocks_sdk/services/wallets/external_wallet_asset.py new file mode 100644 index 0000000..735850a --- /dev/null +++ b/fireblocks_sdk/services/wallets/external_wallet_asset.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class ExternalWalletAsset(Deserializable): + + def __init__(self, id: str, status: str, address: str, tag: str, activation_time: str) -> None: + super().__init__() + self.id = id + self.status = status + self.address = address + self.tag = tag + self.activation_time = activation_time + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> ExternalWalletAsset: + return cls( + data.get('id'), + data.get('status'), + data.get('address'), + data.get('tag'), + data.get('activationTime'), + ) + + diff --git a/fireblocks_sdk/services/wallets/internal_wallet_asset.py b/fireblocks_sdk/services/wallets/internal_wallet_asset.py new file mode 100644 index 0000000..ead6a58 --- /dev/null +++ b/fireblocks_sdk/services/wallets/internal_wallet_asset.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.services.wallets.external_wallet_asset import ExternalWalletAsset + + +class InternalWalletAsset(ExternalWalletAsset): + def __init__(self, id: str, status: str, address: str, tag: str, activation_time: str, balance: str) -> None: + super().__init__(id, status, address, tag, activation_time) + self.balance = balance + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> InternalWalletAsset: + wallet = super().deserialize(data) + return InternalWalletAsset( + wallet.id, wallet.status, wallet.address, wallet.tag, wallet.activation_time, data.get('balance') + ) diff --git a/fireblocks_sdk/services/wallets/wallet_container_response.py b/fireblocks_sdk/services/wallets/wallet_container_response.py new file mode 100644 index 0000000..c9f0064 --- /dev/null +++ b/fireblocks_sdk/services/wallets/wallet_container_response.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Generic, TypeVar, Dict, Union, List + +from fireblocks_sdk.entities.deserializable import Deserializable + +T = TypeVar('T', bound=Deserializable) + + +class WalletContainerResponse(Generic[T]): + + def __init__(self, id: str, name: str, assets: List[T], customer_ref_id: Union[str, None]) -> None: + super().__init__() + self.id = id + self.name = name + self.assets = assets + self.customer_ref_id = customer_ref_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> WalletContainerResponse[T]: + return cls( + data.get('id'), + data.get('name'), + [T.deserialize(asset) for asset in data.get('assets')], + data.get('customerRefId'), + ) diff --git a/fireblocks_sdk/services/web_hooks/__init__.py b/fireblocks_sdk/services/web_hooks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fireblocks_sdk/services/web_hooks/web_hooks_service.py b/fireblocks_sdk/services/web_hooks/web_hooks_service.py new file mode 100644 index 0000000..303f4c1 --- /dev/null +++ b/fireblocks_sdk/services/web_hooks/web_hooks_service.py @@ -0,0 +1,27 @@ +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.services.base_service import BaseService + + +class WebHooksService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + def resend_webhooks(self): + """Resend failed webhooks of your tenant""" + + return self.connector.post("/v1/webhooks/resend") + + def resend_transaction_webhooks_by_id(self, tx_id: str, resend_created: bool, resend_status_updated: bool): + """Resend webhooks of transaction + + Args: + tx_id (str): The transaction for which the message is sent. + resend_created (boolean): If true, a webhook will be sent for the creation of the transaction. + resend_status_updated (boolean): If true, a webhook will be sent for the status of the transaction. + """ + body = { + "resendCreated": resend_created, + "resendStatusUpdated": resend_status_updated + } + + return self.connector.post(f"/v1/webhooks/resend/{tx_id}", body) diff --git a/setup.py b/setup.py index 4c2935a..1e0bb47 100644 --- a/setup.py +++ b/setup.py @@ -1,23 +1,24 @@ from distutils.core import setup + setup( - name = 'fireblocks_sdk', - packages = ['fireblocks_sdk'], - version = '1.16.0', - license='MIT', - description = 'Fireblocks python SDK', - url = 'https://github.com/fireblocks/fireblocks-sdk-py', - download_url = 'https://github.com/fireblocks/fireblocks-sdk-py/archive/v1.16.0.tar.gz', - keywords = ['Fireblocks', 'SDK'], - install_requires=[ - 'PyJWT>=2.3.0', - 'cryptography>=2.7', - 'requests>=2.22.0', - ], - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Topic :: Software Development', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3.6', - ], + name='fireblocks_sdk', + packages=['fireblocks_sdk'], + version='1.16.0', + license='MIT', + description='Fireblocks python SDK', + url='https://github.com/fireblocks/fireblocks-sdk-py', + download_url='https://github.com/fireblocks/fireblocks-sdk-py/archive/v1.16.0.tar.gz', + keywords=['Fireblocks', 'SDK'], + install_requires=[ + 'PyJWT>=2.3.0', + 'cryptography>=2.7', + 'requests>=2.22.0', + ], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Software Development', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3.6', + ], ) From ecb466a38c8cba69e2b3f52e85c44b7d74f0c104 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Tue, 31 May 2022 10:39:41 +0300 Subject: [PATCH 2/7] typing fixes --- fireblocks_sdk/entities/balance_reward_info.py | 16 ++++++++++++++++ fireblocks_sdk/entities/deserializable.py | 2 +- fireblocks_sdk/services/fee_payer/fee_info.py | 2 +- .../fee_payer/fee_payer_configuration.py | 2 +- .../services/fee_payer/fee_payer_info.py | 6 +++--- .../services/gas_station/gas_station_info.py | 4 ++-- .../services/transfer_tickets/term_response.py | 2 +- .../services/wallets/external_wallet_asset.py | 4 ++-- 8 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 fireblocks_sdk/entities/balance_reward_info.py diff --git a/fireblocks_sdk/entities/balance_reward_info.py b/fireblocks_sdk/entities/balance_reward_info.py new file mode 100644 index 0000000..a80b99b --- /dev/null +++ b/fireblocks_sdk/entities/balance_reward_info.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class BalanceRewardInfo(Deserializable): + + def __init__(self, pending_rewards: str) -> None: + super().__init__() + self.pending_rewards = pending_rewards + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> BalanceRewardInfo: + return cls(data.get('pendingRewards')) diff --git a/fireblocks_sdk/entities/deserializable.py b/fireblocks_sdk/entities/deserializable.py index c3f14b6..692272d 100644 --- a/fireblocks_sdk/entities/deserializable.py +++ b/fireblocks_sdk/entities/deserializable.py @@ -9,5 +9,5 @@ class Deserializable(Generic[T]): @classmethod @abstractmethod - def deserialize(cls: T, data: Dict[str, any]) -> T: + def deserialize(cls: T, data: Dict[str]) -> T: pass diff --git a/fireblocks_sdk/services/fee_payer/fee_info.py b/fireblocks_sdk/services/fee_payer/fee_info.py index 442e110..7b9f876 100644 --- a/fireblocks_sdk/services/fee_payer/fee_info.py +++ b/fireblocks_sdk/services/fee_payer/fee_info.py @@ -8,7 +8,7 @@ class FeeInfo(Deserializable): @classmethod - def deserialize(cls, data: Dict[str, any]) -> FeeInfo: + def deserialize(cls, data: Dict[str, str]) -> FeeInfo: return cls( data.get('networkFee'), data.get('serviceFee'), diff --git a/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py b/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py index 4bdf339..af93327 100644 --- a/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py +++ b/fireblocks_sdk/services/fee_payer/fee_payer_configuration.py @@ -12,7 +12,7 @@ def __init__(self, fee_payer_account_id: str) -> None: self.fee_payer_accountId = fee_payer_account_id @classmethod - def deserialize(cls, data: Dict[str, any]) -> FeePayerConfiguration: + def deserialize(cls, data: Dict[str, str]) -> FeePayerConfiguration: return cls( data.get('feePayerAccountId') ) diff --git a/fireblocks_sdk/services/fee_payer/fee_payer_info.py b/fireblocks_sdk/services/fee_payer/fee_payer_info.py index e8d846e..9e20daf 100644 --- a/fireblocks_sdk/services/fee_payer/fee_payer_info.py +++ b/fireblocks_sdk/services/fee_payer/fee_payer_info.py @@ -1,18 +1,18 @@ from __future__ import annotations -from typing import Dict +from typing import Dict, Union from fireblocks_sdk.entities.deserializable import Deserializable class FeePayerInfo(Deserializable): - def __init__(self, fee_payer_account_id: str) -> None: + def __init__(self, fee_payer_account_id: Union[str, None]) -> None: super().__init__() self.fee_payer_account_id = fee_payer_account_id @classmethod - def deserialize(cls, data: Dict[str, any]) -> FeePayerInfo: + def deserialize(cls, data: Dict[str, str]) -> FeePayerInfo: return cls( data.get('feePayerAccountId') ) diff --git a/fireblocks_sdk/services/gas_station/gas_station_info.py b/fireblocks_sdk/services/gas_station/gas_station_info.py index 7793bdc..25e2a1b 100644 --- a/fireblocks_sdk/services/gas_station/gas_station_info.py +++ b/fireblocks_sdk/services/gas_station/gas_station_info.py @@ -14,7 +14,7 @@ def __init__(self, gas_threshold: str, gas_cap: str, max_gas_price: str) -> None self.max_gas_price = max_gas_price @classmethod - def deserialize(cls, data: Dict[str, any]) -> GasStationInfoConfiguration: + def deserialize(cls, data: Dict[str]) -> GasStationInfoConfiguration: return cls( data.get('gasThreshold'), data.get('gasCap'), @@ -30,7 +30,7 @@ def __init__(self, balance: Dict[str, str], configuration: GasStationInfoConfigu self.configuration = configuration @classmethod - def deserialize(cls, data: Dict[str, any]) -> GasStationInfo: + def deserialize(cls, data: Dict[str]) -> GasStationInfo: return cls( data.get('balance'), GasStationInfoConfiguration.deserialize(data.get('configuration')) diff --git a/fireblocks_sdk/services/transfer_tickets/term_response.py b/fireblocks_sdk/services/transfer_tickets/term_response.py index 0e801f4..5ea1ce8 100644 --- a/fireblocks_sdk/services/transfer_tickets/term_response.py +++ b/fireblocks_sdk/services/transfer_tickets/term_response.py @@ -20,7 +20,7 @@ def __init__(self, term_id: str, network_connection_id: str, outgoing: str, asse self.note = note @classmethod - def deserialize(cls: T, data: Dict) -> TermResponse: + def deserialize(cls: T, data: Dict[str]) -> TermResponse: return cls( data.get('termId'), data.get('networkConnectionId'), diff --git a/fireblocks_sdk/services/wallets/external_wallet_asset.py b/fireblocks_sdk/services/wallets/external_wallet_asset.py index 735850a..aee0d68 100644 --- a/fireblocks_sdk/services/wallets/external_wallet_asset.py +++ b/fireblocks_sdk/services/wallets/external_wallet_asset.py @@ -1,13 +1,13 @@ from __future__ import annotations -from typing import Dict +from typing import Dict, Union from fireblocks_sdk.entities.deserializable import Deserializable class ExternalWalletAsset(Deserializable): - def __init__(self, id: str, status: str, address: str, tag: str, activation_time: str) -> None: + def __init__(self, id: str, status: str, address: str, tag: str, activation_time: Union[str, None]) -> None: super().__init__() self.id = id self.status = status From 89c61b0229fb194c25f6211fe0decd2b49d031a3 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Wed, 15 Jun 2022 11:21:29 +0300 Subject: [PATCH 3/7] entities --- fireblocks_sdk/common/wrappers.py | 2 + fireblocks_sdk/connectors/rest.py | 58 +++ fireblocks_sdk/demo/run_demo.py | 24 +- fireblocks_sdk/entities/allocated_balances.py | 32 ++ fireblocks_sdk/entities/asset_response.py | 55 +++ fireblocks_sdk/entities/asset_type.py | 19 + .../entities/network_connection_response.py | 26 ++ fireblocks_sdk/entities/network_id.py | 15 + .../entities/op_success_response.py | 12 + fireblocks_sdk/entities/routing_dest.py | 18 + fireblocks_sdk/entities/routing_policy.py | 26 ++ .../entities/vault_accounts_filter.py | 26 ++ .../services/contracts/contracts_service.py | 81 ++++ .../services/exchange/exchange_response.py | 27 ++ .../services/exchange/exchange_service.py | 80 ++++ .../services/fiat/fiat_account_response.py | 24 + fireblocks_sdk/services/fiat/fiat_service.py | 62 +++ .../gas_station/gas_station_service.py | 52 +++ .../transactions/transactions_service.py | 427 +++++++++++++++++ .../transfer_tickets_service.py | 98 ++++ .../vaults/deposit_address_response.py | 35 ++ .../vaults/generate_address_response.py | 22 + .../services/vaults/vault_account_response.py | 28 ++ .../services/vaults/vaults_service.py | 429 ++++++++++++++++++ .../services/wallets/wallets_service.py | 196 ++++++++ setup.py | 2 +- 26 files changed, 1863 insertions(+), 13 deletions(-) create mode 100644 fireblocks_sdk/connectors/rest.py create mode 100644 fireblocks_sdk/entities/allocated_balances.py create mode 100644 fireblocks_sdk/entities/asset_response.py create mode 100644 fireblocks_sdk/entities/asset_type.py create mode 100644 fireblocks_sdk/entities/network_connection_response.py create mode 100644 fireblocks_sdk/entities/network_id.py create mode 100644 fireblocks_sdk/entities/op_success_response.py create mode 100644 fireblocks_sdk/entities/routing_dest.py create mode 100644 fireblocks_sdk/entities/routing_policy.py create mode 100644 fireblocks_sdk/entities/vault_accounts_filter.py create mode 100644 fireblocks_sdk/services/contracts/contracts_service.py create mode 100644 fireblocks_sdk/services/exchange/exchange_response.py create mode 100644 fireblocks_sdk/services/exchange/exchange_service.py create mode 100644 fireblocks_sdk/services/fiat/fiat_account_response.py create mode 100644 fireblocks_sdk/services/fiat/fiat_service.py create mode 100644 fireblocks_sdk/services/gas_station/gas_station_service.py create mode 100644 fireblocks_sdk/services/transactions/transactions_service.py create mode 100644 fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py create mode 100644 fireblocks_sdk/services/vaults/deposit_address_response.py create mode 100644 fireblocks_sdk/services/vaults/generate_address_response.py create mode 100644 fireblocks_sdk/services/vaults/vault_account_response.py create mode 100644 fireblocks_sdk/services/vaults/vaults_service.py create mode 100644 fireblocks_sdk/services/wallets/wallets_service.py diff --git a/fireblocks_sdk/common/wrappers.py b/fireblocks_sdk/common/wrappers.py index 669a97d..af2f237 100644 --- a/fireblocks_sdk/common/wrappers.py +++ b/fireblocks_sdk/common/wrappers.py @@ -1,3 +1,4 @@ +import functools from typing import TypeVar from fireblocks_sdk.entities.deserializable import Deserializable @@ -7,6 +8,7 @@ def response_deserializer(response_type: T) -> T: def inner(func) -> T: + @functools.wraps(func) def wrapper(*args, **kwargs) -> T: return_value = func(*args, **kwargs) if isinstance(return_value, list): diff --git a/fireblocks_sdk/connectors/rest.py b/fireblocks_sdk/connectors/rest.py new file mode 100644 index 0000000..87d80a6 --- /dev/null +++ b/fireblocks_sdk/connectors/rest.py @@ -0,0 +1,58 @@ +import json +from typing import Dict, Union + +import requests + +from fireblocks_sdk import FireblocksApiException +from fireblocks_sdk.entities.api_response import ApiResponse +from fireblocks_sdk.sdk_token_provider import SdkTokenProvider + + +class RestConnector: + def __init__(self, token_provider: SdkTokenProvider, base_url: str, api_key: str, timeout: int) -> None: + self.token_provider = token_provider + self.base_url = base_url + self.api_key = api_key + self.timeout = timeout + + def _generate_headers(self, path, body: Union[Dict, None] = None, idempotency_key: str = None): + token = self.token_provider.sign_jwt(path, json.dumps(body) if body else json.dumps({})) + headers = { + "X-API-Key": self.api_key, + "Authorization": f"Bearer {token}" + } + + if idempotency_key: + headers.update({"Idempotency-Key": idempotency_key}) + + return headers + + def get(self, path) -> ApiResponse: + response = requests.get(self.base_url + path, + headers=self._generate_headers(path), timeout=self.timeout) + return self.handle_response(response) + + def delete(self, path) -> ApiResponse: + response = requests.delete( + self.base_url + path, headers=self._generate_headers(path), timeout=self.timeout) + return self.handle_response(response) + + def post(self, path, body=Union[Dict, None], idempotency_key=None) -> ApiResponse: + response = requests.post(self.base_url + path, headers=self._generate_headers(path, body, idempotency_key), + json=body or {}, timeout=self.timeout) + return self.handle_response(response) + + def put(self, path, body: Union[Dict, None] = None) -> ApiResponse: + headers = self._generate_headers(path, body) + headers.update({"Content-Type": "application/json"}) + response = requests.put(self.base_url + path, headers=headers, + data=json.dumps(body), timeout=self.timeout) + return self.handle_response(response) + + @staticmethod + def handle_response(response) -> ApiResponse: + if response.status_code >= 300: + raise FireblocksApiException( + f"Got an error from fireblocks server: {response.content}") + + return ApiResponse(response.status_code, response.json(), response.headers) diff --git a/fireblocks_sdk/demo/run_demo.py b/fireblocks_sdk/demo/run_demo.py index ad37403..4db0552 100644 --- a/fireblocks_sdk/demo/run_demo.py +++ b/fireblocks_sdk/demo/run_demo.py @@ -10,6 +10,7 @@ import inspect import json +import os from typing import Dict, List import inquirer @@ -17,16 +18,11 @@ from fireblocks_sdk import FireblocksSDK -""" -Change these: -""" -API_SERVER_ADDRESS = 'https://api.fireblocks.io' -PRIVATE_KEY_PATH = '' -USER_ID = '' - +API_SERVER_ADDRESS = os.getenv('API_SERVER_ADDRESS', 'https://api.fireblocks.io') +PRIVATE_KEY_PATH = os.getenv('PRIVATE_KEY_PATH', '') +USER_ID = os.getenv('USER_ID', '') class DemoRunner: - def __init__(self, api_server: str, key_path: str, user_id: str) -> None: private_key = open(key_path, 'r').read() self.client = FireblocksSDK(private_key, user_id, api_server) @@ -58,7 +54,13 @@ def run(self): print(f'entered_params: {entered_params}') service, method = requested_action.split('::') - return_value = getattr(getattr(self.client, service), method)(*entered_params.values()) + + if entered_params: + return_value = getattr(getattr(self.client, service), method)( + *entered_params.values() if entered_params else None) + else: + return_value = getattr(getattr(self.client, service), method)() + self.print_obj(return_value) continue @@ -83,9 +85,7 @@ def _get_client_methods(self) -> Dict[str, List[str]]: for m in object_methods: service, method = m.split('::') method_instance = getattr(getattr(self.client, service), method) - method_args_spec = inspect.getfullargspec(method_instance) - - methods[m] = method_args_spec[0][1:] + methods[m] = list(inspect.signature(method_instance).parameters.keys()) return methods diff --git a/fireblocks_sdk/entities/allocated_balances.py b/fireblocks_sdk/entities/allocated_balances.py new file mode 100644 index 0000000..99a99a6 --- /dev/null +++ b/fireblocks_sdk/entities/allocated_balances.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import Dict, Union + + +class AllocatedBalances: + def __init__(self, allocation_id: str, total: str, available: str, third_party_account_id: Union[str, None], + affiliation: Union[str, None], virtual_type: Union[str, None], pending: Union[str, None], + frozen: Union[str, None], locked: Union[str, None]) -> None: + self.virtual_type = virtual_type + self.total = total + self.available = available + self.pending = pending + self.frozen = frozen + self.locked = locked + self.affiliation = affiliation + self.third_party_account_id = third_party_account_id + self.allocation_id = allocation_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AllocatedBalances: + return cls( + data.get('allocationId'), + data.get('total'), + data.get('available'), + data.get('thirdPartyAccountId'), + data.get('affiliation'), + data.get('virtualType'), + data.get('pending'), + data.get('frozen'), + data.get('locked') + ) diff --git a/fireblocks_sdk/entities/asset_response.py b/fireblocks_sdk/entities/asset_response.py new file mode 100644 index 0000000..d22cd32 --- /dev/null +++ b/fireblocks_sdk/entities/asset_response.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import Dict, Union + +from fireblocks_sdk.entities.allocated_balances import AllocatedBalances +from fireblocks_sdk.entities.balance_reward_info import BalanceRewardInfo +from fireblocks_sdk.entities.deserializable import Deserializable + + +class AssetResponse(Deserializable): + def __init__(self, id: str, total: str, balance: Union[str, None], locked_amount: Union[str, None], available: str, + pending: str, + self_staked_cpu: Union[str, None], self_staked_network: Union[str, None], + pending_refund_cpu: Union[str, None], pending_refund_network: Union[str, None], + total_staked_cpu: Union[str, None], total_staked_network: Union[str, None], + reward_info: Union[BalanceRewardInfo, None], block_height: Union[str, None], + block_hash: Union[str, None], + allocated_balances: Union[AllocatedBalances, None]) -> None: + self.id = id + self.total = total + self.balance = balance + self.locked_amount = locked_amount + self.available = available + self.pending = pending + self.self_staked_cpu = self_staked_cpu + self.self_staked_network = self_staked_network + self.pending_refund_cpu = pending_refund_cpu + self.pending_refund_network = pending_refund_network + self.total_staked_cpu = total_staked_cpu + self.total_staked_network = total_staked_network + self.reward_info = reward_info + self.block_height = block_height + self.block_hash = block_hash + self.allocated_balances = allocated_balances + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AssetResponse: + return cls( + data.get('id'), + data.get('total'), + data.get('balance'), + data.get('lockedAmount'), + data.get('available'), + data.get('pending'), + data.get('selfStakedCPU'), + data.get('selfStakedNetwork'), + data.get('pendingRefundCPU'), + data.get('pendingRefundNetwork'), + data.get('totalStakedCPU'), + data.get('totalStakedNetwork'), + BalanceRewardInfo.deserialize(data.get('rewardInfo', {})), + data.get('blockHeight'), + data.get('blockHash'), + AllocatedBalances.deserialize(data.get('allocatedBalances', {})) + ) diff --git a/fireblocks_sdk/entities/asset_type.py b/fireblocks_sdk/entities/asset_type.py new file mode 100644 index 0000000..bbf7de4 --- /dev/null +++ b/fireblocks_sdk/entities/asset_type.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import Union, Dict + + +class AssetType: + def __init__(self, id: str, name: str, type: str, contract_address: str, native_asset: str, + decimals: Union[float, None] = None) -> None: + self.id = id + self.name = name + self.type = type + self.contract_address = contract_address + self.native_asset = native_asset + self.decimals = decimals + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> AssetType: + return cls(data.get("id"), data.get("name"), data.get("type"), data.get("contractAddress"), + data.get("nativeAsset"), data.get("decimals", None)) diff --git a/fireblocks_sdk/entities/network_connection_response.py b/fireblocks_sdk/entities/network_connection_response.py new file mode 100644 index 0000000..9cb5baf --- /dev/null +++ b/fireblocks_sdk/entities/network_connection_response.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Dict, Union + +from fireblocks_sdk.entities.network_id import NetworkId +from fireblocks_sdk.entities.routing_policy import RoutingPolicy + + +class NetworkConnectionResponse: + def __init__(self, id: str, status: str, legacy_address: NetworkId, + enterprise_address: NetworkId, routing_policy: Union[RoutingPolicy, None]) -> None: + self.routing_policy = routing_policy + self.id = id + self.status = status + self.legacy_address = legacy_address + self.enterprise_address = enterprise_address + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> NetworkConnectionResponse: + return cls( + data.get('id'), + data.get('status'), + NetworkId.deserialize(data.get('remoteNetworkId')), + NetworkId.deserialize(data.get('localNetworkId')), + RoutingPolicy.deserialize(data.get('routingPolicy', {})) + ) diff --git a/fireblocks_sdk/entities/network_id.py b/fireblocks_sdk/entities/network_id.py new file mode 100644 index 0000000..5ffdd37 --- /dev/null +++ b/fireblocks_sdk/entities/network_id.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from typing import Dict + + +class NetworkId: + def __init__(self, id: str, name: str) -> None: + self.id = id + self.name = name + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> NetworkId: + return NetworkId( + data.get('id'), + data.get('name')) diff --git a/fireblocks_sdk/entities/op_success_response.py b/fireblocks_sdk/entities/op_success_response.py new file mode 100644 index 0000000..335c088 --- /dev/null +++ b/fireblocks_sdk/entities/op_success_response.py @@ -0,0 +1,12 @@ +from __future__ import annotations + +from typing import Dict + + +class OperationSuccessResponse: + def __init__(self, success: bool) -> None: + self.success = success + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> OperationSuccessResponse: + return cls(data.get('success')) diff --git a/fireblocks_sdk/entities/routing_dest.py b/fireblocks_sdk/entities/routing_dest.py new file mode 100644 index 0000000..bf9b510 --- /dev/null +++ b/fireblocks_sdk/entities/routing_dest.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from typing import Dict + + +class RoutingDest: + def __init__(self, scheme: str, dst_type: str, dst_id: str) -> None: + self.scheme = scheme + self.dst_type = dst_type + self.dst_id = dst_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> RoutingDest: + return RoutingDest( + data.get('scheme'), + data.get('dstType'), + data.get('dstId') + ) diff --git a/fireblocks_sdk/entities/routing_policy.py b/fireblocks_sdk/entities/routing_policy.py new file mode 100644 index 0000000..eb60360 --- /dev/null +++ b/fireblocks_sdk/entities/routing_policy.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from typing import Union, Dict + +from fireblocks_sdk.entities.routing_dest import RoutingDest + + +class RoutingPolicy: + def __init__(self, crypto: Union[RoutingDest, None], sen: Union[RoutingDest, None], + signet: Union[RoutingDest, None], sen_test: Union[RoutingDest, None], + signet_test: Union[RoutingDest, None]) -> None: + self.crypto = crypto + self.sen = sen + self.signet = signet + self.sen_test = sen_test + self.signet_test = signet_test + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> RoutingPolicy: + return RoutingPolicy( + data.get('crypto'), + data.get('sen'), + data.get('signet'), + data.get('sen_test'), + data.get('signet_test'), + ) diff --git a/fireblocks_sdk/entities/vault_accounts_filter.py b/fireblocks_sdk/entities/vault_accounts_filter.py new file mode 100644 index 0000000..33d0ded --- /dev/null +++ b/fireblocks_sdk/entities/vault_accounts_filter.py @@ -0,0 +1,26 @@ +from typing import Union +from urllib import parse + +FilterParamType = Union[str, None] + + +class VaultAccountsFilter: + def __init__(self, name_prefix: FilterParamType, name_suffix: FilterParamType, + min_amount_threshold: FilterParamType, asset_id: FilterParamType) -> None: + self.name_prefix = name_prefix + self.name_suffix = name_suffix + self.min_amount_threshold = min_amount_threshold + self.asset_id = asset_id + + def serialize(self) -> str: + params = {} + if self.name_prefix: + params.update({'namePrefix': self.name_prefix}) + if self.name_suffix: + params.update({'nameSuffix': self.name_suffix}) + if self.min_amount_threshold: + params.update({'minAmountThreshold': self.min_amount_threshold}) + if self.asset_id: + params.update({'assetId': self.asset_id}) + + return parse.urlencode(params) diff --git a/fireblocks_sdk/services/contracts/contracts_service.py b/fireblocks_sdk/services/contracts/contracts_service.py new file mode 100644 index 0000000..d77279b --- /dev/null +++ b/fireblocks_sdk/services/contracts/contracts_service.py @@ -0,0 +1,81 @@ +from typing import Union + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.wallets.external_wallet_asset import ExternalWalletAsset +from fireblocks_sdk.services.wallets.wallet_container_response import WalletContainerResponse + + +class ContractsService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(ExternalWalletAsset) + def get_contract_wallets(self) -> ExternalWalletAsset: + """Gets all contract wallets for your tenant + """ + return self.connector.get(f"/v1/contracts").content + + @response_deserializer(ExternalWalletAsset) + def get_contract_wallet(self, wallet_id) -> ExternalWalletAsset: + """Gets a single contract wallet + + Args: + wallet_id (str): The contract wallet ID + """ + return self.connector.get(f"/v1/contracts/{wallet_id}").content + + @response_deserializer(ExternalWalletAsset) + def get_contract_wallet_asset(self, wallet_id, asset_id) -> ExternalWalletAsset: + """Gets a single contract wallet asset + + Args: + wallet_id (str): The contract wallet ID + asset_id (str): The asset ID + """ + return self.connector.get(f"/v1/contracts/{wallet_id}/{asset_id}").content + + @response_deserializer(WalletContainerResponse[ExternalWalletAsset]) + def create_contract_wallet(self, name, idempotency_key=None) -> WalletContainerResponse[ExternalWalletAsset]: + """Creates a new contract wallet + + Args: + name (str): A name for the new contract wallet + """ + return self.connector.post("/v1/contracts", {"name": name}, idempotency_key).content + + @response_deserializer(ExternalWalletAsset) + def create_contract_wallet_asset(self, wallet_id: str, asset_id: str, address: str, tag: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> ExternalWalletAsset: + """Creates a new contract wallet asset + + Args: + wallet_id (str): The wallet id + assetId (str): The asset to add + address (str): The wallet address + tag (str): (for ripple only) The ripple account tag + """ + return self.connector.post(f"/v1/contracts/{wallet_id}/{asset_id}", {"address": address, "tag": tag}, + idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def delete_contract_wallet(self, wallet_id: str) -> OperationSuccessResponse: + """Deletes a single contract wallet + + Args: + wallet_id (string): The contract wallet ID + """ + return self.connector.delete(f"/v1/contracts/{wallet_id}").content + + @response_deserializer(OperationSuccessResponse) + def delete_contract_wallet_asset(self, wallet_id: str, asset_id: str) -> OperationSuccessResponse: + """Deletes a single contract wallet + + Args: + wallet_id (string): The contract wallet ID + asset_id (string): The asset ID + """ + + return self.connector.delete(f"/v1/contracts/{wallet_id}/{asset_id}").content diff --git a/fireblocks_sdk/services/exchange/exchange_response.py b/fireblocks_sdk/services/exchange/exchange_response.py new file mode 100644 index 0000000..25bf9b4 --- /dev/null +++ b/fireblocks_sdk/services/exchange/exchange_response.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import Dict, List + +from fireblocks_sdk.entities.asset_response import AssetResponse + + +class ExchangeResponse: + def __init__(self, id: str, type: str, name: str, assets: List[AssetResponse], is_sub_account: bool, + status: str) -> None: + self.id = id + self.type = type + self.name = name + self.assets = assets + self.is_sub_account = is_sub_account + self.status = status + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> ExchangeResponse: + return cls( + data.get('id'), + data.get('type'), + data.get('name'), + [AssetResponse.deserialize(asset) for asset in data.get('assets')], + data.get('isSubaccount'), + data.get('status') + ) diff --git a/fireblocks_sdk/services/exchange/exchange_service.py b/fireblocks_sdk/services/exchange/exchange_service.py new file mode 100644 index 0000000..4392010 --- /dev/null +++ b/fireblocks_sdk/services/exchange/exchange_service.py @@ -0,0 +1,80 @@ +from typing import Union + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.exchange.exchange_response import ExchangeResponse + + +class ExchangeService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(ExchangeResponse) + def get_exchange_accounts(self): + """Gets all exchange accounts for your tenant""" + + response = self.connector.get("/v1/exchange_accounts") + return [ExchangeResponse.deserialize(exchange) for exchange in response.content] + + @response_deserializer(ExchangeResponse) + def get_exchange_account(self, exchange_account_id: str) -> ExchangeResponse: + """Gets an exchange account for your tenant + Args: + exchange_account_id (string): The exchange ID in Fireblocks + """ + + return self.connector.get(f"/v1/exchange_accounts/{exchange_account_id}").content + + @response_deserializer(ExchangeResponse) + def get_exchange_account_asset(self, exchange_account_id: str, asset_id: str) -> ExchangeResponse: + """Get a specific asset from an exchange account + + Args: + exchange_account_id (string): The exchange ID in Fireblocks + asset_id (string): The asset to transfer + """ + + return self.connector.get(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}").content + + @response_deserializer(OperationSuccessResponse) + def transfer_to_subaccount(self, exchange_account_id: str, sub_account_id: str, asset_id: str, amount: float, + idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: + """Transfer to a subaccount from a main exchange account + + Args: + exchange_account_id (string): The exchange ID in Fireblocks + sub_account_id (string): The ID of the subaccount in the exchange + asset_id (string): The asset to transfer + amount (float): The amount to transfer + idempotency_key (str, optional) + """ + body = { + "subaccountId": sub_account_id, + "amount": amount + } + + return self.connector.post(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_to_subaccount", + body, idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def transfer_from_subaccount(self, exchange_account_id: str, sub_account_id: str, asset_id: str, amount: float, + idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: + """Transfer from a subaccount to a main exchange account + + Args: + exchange_account_id (string): The exchange ID in Fireblocks + sub_account_id (string): The ID of the subaccount in the exchange + asset_id (string): The asset to transfer + amount (float): The amount to transfer + idempotency_key (str, optional) + """ + body = { + "subaccountId": sub_account_id, + "amount": amount + } + + return self.connector.post( + f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}/transfer_from_subaccount", + body, idempotency_key).content diff --git a/fireblocks_sdk/services/fiat/fiat_account_response.py b/fireblocks_sdk/services/fiat/fiat_account_response.py new file mode 100644 index 0000000..85f52f7 --- /dev/null +++ b/fireblocks_sdk/services/fiat/fiat_account_response.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from typing import Dict, List, Union + +from fireblocks_sdk.entities.asset_response import AssetResponse + + +class FiatAccountResponse: + def __init__(self, id: str, type: str, name: str, assets: List[AssetResponse], address: Union[str, None]) -> None: + self.id = id + self.type = type + self.name = name + self.assets = assets + self.address = address + + @classmethod + def deserialize(cls, data: Dict[str, str]) -> FiatAccountResponse: + return FiatAccountResponse( + data.get('id'), + data.get('type'), + data.get('name'), + [AssetResponse.deserialize(asset) for asset in data.get('assets')], + data.get('address'), + ) diff --git a/fireblocks_sdk/services/fiat/fiat_service.py b/fireblocks_sdk/services/fiat/fiat_service.py new file mode 100644 index 0000000..dac717b --- /dev/null +++ b/fireblocks_sdk/services/fiat/fiat_service.py @@ -0,0 +1,62 @@ +from typing import List, Union + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.fiat.fiat_account_response import FiatAccountResponse + + +class FiatService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(FiatAccountResponse) + def get_fiat_accounts(self) -> List[FiatAccountResponse]: + """Gets all fiat accounts for your tenant""" + + return self.connector.get("/v1/fiat_accounts").content + + @response_deserializer(FiatAccountResponse) + def get_fiat_account_by_id(self, account_id: str) -> FiatAccountResponse: + """Gets a single fiat account by ID + + Args: + account_id (string): The fiat account ID + """ + + return self.connector.get(f"/v1/fiat_accounts/{account_id}").content + + @response_deserializer(OperationSuccessResponse) + def redeem_to_linked_dda(self, account_id: str, amount: float, + idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: + """Redeem from a fiat account to a linked DDA + + Args: + account_id (string): The fiat account ID in Fireblocks + amount (double): The amount to transfer + idempotency_key (str, optional) + """ + body = { + "amount": amount, + } + + return self.connector.post( + f"/v1/fiat_accounts/{account_id}/redeem_to_linked_dda", body, idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def deposit_from_linked_dda(self, account_id: str, amount: float, + idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: + """Deposit to a fiat account from a linked DDA + + Args: + account_id (string): The fiat account ID in Fireblocks + amount (float): The amount to transfer + idempotency_key (str, optional) + """ + body = { + "amount": amount, + } + + return self.connector.post( + f"/v1/fiat_accounts/{account_id}/deposit_from_linked_dda", body, idempotency_key).content diff --git a/fireblocks_sdk/services/gas_station/gas_station_service.py b/fireblocks_sdk/services/gas_station/gas_station_service.py new file mode 100644 index 0000000..659d156 --- /dev/null +++ b/fireblocks_sdk/services/gas_station/gas_station_service.py @@ -0,0 +1,52 @@ +from typing import Union + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.gas_station.gas_station_info import GasStationInfo + + +class GasStationService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(GasStationInfo) + def get_gas_station_info(self, asset_id: Union[str, None] = None) -> GasStationInfo: + """Get configuration and status of the Gas Station account" + + Args: + asset_id (string, optional) + """ + + url = f"/v1/gas_station" + + if asset_id: + url = url + f"/{asset_id}" + + return self.connector.get(url).content + + @response_deserializer(OperationSuccessResponse) + def set_gas_station_configuration(self, gas_threshold: str, gas_cap: str, max_gas_price: Union[str, None] = None, + asset_id: Union[str, None] = None) -> OperationSuccessResponse: + """Set configuration of the Gas Station account + + Args: + gas_threshold (str) + gas_cap (str) + max_gas_price (str, optional) + asset_id (str, optional) + """ + + url = f"/v1/gas_station/configuration" + + if asset_id: + url = url + f"/{asset_id}" + + body = { + "gasThreshold": gas_threshold, + "gasCap": gas_cap, + "maxGasPrice": max_gas_price + } + + return self.connector.put(url, body).content diff --git a/fireblocks_sdk/services/transactions/transactions_service.py b/fireblocks_sdk/services/transactions/transactions_service.py new file mode 100644 index 0000000..d278a65 --- /dev/null +++ b/fireblocks_sdk/services/transactions/transactions_service.py @@ -0,0 +1,427 @@ +import urllib +from typing import Union + +from fireblocks_sdk.api_types import FEE_LEVEL, TRANSACTION_STATUS_TYPES, TRANSACTION_TRANSFER, TRANSACTION_TYPES, \ + DestinationTransferPeerPath, FireblocksApiException, TransactionDestination, TransferPeerPath, SIGNING_ALGORITHM, \ + UnsignedMessage +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.transactions.entities.create_transaction_response import CreateTransactionResponse +from fireblocks_sdk.services.transactions.entities.estimate_transaction_fee_response import \ + EstimateTransactionFeeResponse +from fireblocks_sdk.services.transactions.entities.page_details import PageDetails +from fireblocks_sdk.services.transactions.entities.transaction_page_response import TransactionPageResponse +from fireblocks_sdk.services.transactions.entities.transaction_response import TransactionResponse +from fireblocks_sdk.services.transactions.entities.validate_address_response import ValidateAddressResponse + + +class TransactionsService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + def get_transactions_with_page_info(self, before: Union[int, None] = 0, after: Union[int, None] = None, + status: Union[str, None] = None, limit: Union[int, None] = None, + txhash: Union[str, None] = None, assets: Union[str, None] = None, + source_type: Union[str, None] = None, source_id: Union[str, None] = None, + dest_type: Union[str, None] = None, dest_id: Union[str, None] = None, + next_or_previous_path: Union[str, None] = None) -> TransactionPageResponse: + """Gets a list of transactions matching the given filters or path. + Note that "next_or_previous_path" is mutually exclusive with other parameters. + If you wish to iterate over the nextPage/prevPage pages, please provide only the "next_or_previous_path" + parameter from `pageDetails` response + example: + get_transactions_with_page_info(next_or_previous_path=response[pageDetails][nextPage]) + + Args: + before (int, optional): Only gets transactions created before given timestamp (in milliseconds) + after (int, optional): Only gets transactions created after given timestamp (in milliseconds) + status (str, optional): Only gets transactions with the specified status, which should one of the following: + SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, + PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, + CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED + limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used + txhash (str, optional): Only gets transactions with the specified txHash + assets (str, optional): Filter results for specified assets + source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: + VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, + NETWORK_CONNECTION, COMPOUND + source_id (str, optional): Only gets transactions with given source_id + dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: + VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, + NETWORK_CONNECTION, COMPOUND + dest_id (str, optional): Only gets transactions with given dest_id + next_or_previous_path (str, optional): get transactions matching the path, provided from pageDetails + """ + if next_or_previous_path is not None: + if not next_or_previous_path: + return TransactionPageResponse([], PageDetails('', '')) + index = next_or_previous_path.index('/v1/') + length = len(next_or_previous_path) - 1 + suffix_path = next_or_previous_path[index:length] + + response = self.connector.get(suffix_path) + + return TransactionPageResponse( + [TransactionResponse.deserialize(transaction) for transaction in response.content], + PageDetails(response.extras.get('prevPage'), response.extras.get('nextPage'))) + else: + return self._get_transactions(before, after, status, limit, None, txhash, assets, source_type, source_id, + dest_type, dest_id) + + def get_transactions(self, before: Union[int, None] = 0, after: Union[int, None] = None, + status: Union[str, None] = None, limit: Union[int, None] = None, + order_by: Union[str, None] = None, txhash: Union[str, None] = None, + assets: Union[str, None] = None, + source_type: Union[str, None] = None, source_id: Union[str, None] = None, + dest_type: Union[str, None] = None, + dest_id: Union[str, None] = None) -> TransactionPageResponse: + """Gets a list of transactions matching the given filters + + Args: + before (int, optional): Only gets transactions created before given timestamp (in milliseconds) + after (int, optional): Only gets transactions created after given timestamp (in milliseconds) + status (str, optional): Only gets transactions with the specified status, which should one of the following: + SUBMITTED, QUEUED, PENDING_SIGNATURE, PENDING_AUTHORIZATION, PENDING_3RD_PARTY_MANUAL_APPROVAL, + PENDING_3RD_PARTY, BROADCASTING, CONFIRMING, COMPLETED, PENDING_AML_CHECKUP, PARTIALLY_COMPLETED, + CANCELLING, CANCELLED, REJECTED, FAILED, TIMEOUT, BLOCKED + limit (int, optional): Limit the amount of returned results. If not specified, a limit of 200 results will be used + order_by (str, optional): Determines the order of the returned results. Possible values are 'createdAt' or 'lastUpdated' + txhash (str, optional): Only gets transactions with the specified txHash + assets (str, optional): Filter results for specified assets + source_type (str, optional): Only gets transactions with given source_type, which should be one of the following: + VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, + NETWORK_CONNECTION, COMPOUND + source_id (str, optional): Only gets transactions with given source_id + dest_type (str, optional): Only gets transactions with given dest_type, which should be one of the following: + VAULT_ACCOUNT, EXCHANGE_ACCOUNT, INTERNAL_WALLET, EXTERNAL_WALLET, UNKNOWN_PEER, FIAT_ACCOUNT, + NETWORK_CONNECTION, COMPOUND + dest_id (str, optional): Only gets transactions with given dest_id + """ + return self._get_transactions(before, after, status, limit, order_by, txhash, assets, source_type, source_id, + dest_type, dest_id) + + def _get_transactions(self, before: Union[int, None] = 0, after: Union[int, None] = None, + status: Union[str, None] = None, limit: Union[int, None] = None, + order_by: Union[str, None] = None, txhash: Union[str, None] = None, + assets: Union[str, None] = None, + source_type: Union[str, None] = None, source_id: Union[str, None] = None, + dest_type: Union[str, None] = None, + dest_id: Union[str, None] = None) -> TransactionPageResponse: + path = "/v1/transactions" + params = {} + + if status and status not in TRANSACTION_STATUS_TYPES: + raise FireblocksApiException("Got invalid transaction type: " + status) + + if before: + params['before'] = before + if after: + params['after'] = after + if status: + params['status'] = status + if limit: + params['limit'] = limit + if order_by: + params['orderBy'] = order_by + if txhash: + params['txHash'] = txhash + if assets: + params['assets'] = assets + if source_type: + params['sourceType'] = source_type + if source_id: + params['sourceId'] = source_id + if dest_type: + params['destType'] = dest_type + if dest_id: + params['destId'] = dest_id + if params: + path = path + "?" + urllib.parse.urlencode(params) + + response = self.connector.get(path) + + return TransactionPageResponse( + [TransactionResponse.deserialize(transaction) for transaction in response.content], + PageDetails(response.extras.get('prevPage'), response.extras.get('nextPage'))) + + @response_deserializer(TransactionResponse) + def get_transaction_by_id(self, tx_id: str) -> TransactionResponse: + """Gets detailed information for a single transaction + + Args: + tx_id (str): The transaction id to query + """ + + return self.connector.get(f"/v1/transactions/{tx_id}").content + + @response_deserializer(TransactionResponse) + def get_transaction_by_external_id(self, external_tx_id) -> TransactionResponse: + """Gets detailed information for a single transaction + + Args: + external_tx_id (str): The external id of the transaction + """ + + return self.connector.get(f"/v1/transactions/external_tx_id/{external_tx_id}").content + + @response_deserializer(EstimateTransactionFeeResponse) + def estimate_fee_for_transaction(self, asset_id, amount, source: TransferPeerPath, destination=None, + tx_type=TRANSACTION_TRANSFER, + idempotency_key=None, destinations=None) -> EstimateTransactionFeeResponse: + """Estimates transaction fee + + Args: + asset_id (str): The asset symbol (e.g BTC, ETH) + source (TransferPeerPath): The transaction source + destination (DestinationTransferPeerPath, optional): The transfer destination. + amount (str): The amount + tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. + idempotency_key (str, optional) + destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. + """ + + if tx_type not in TRANSACTION_TYPES: + raise FireblocksApiException("Got invalid transaction type: " + tx_type) + + if not isinstance(source, TransferPeerPath): + raise FireblocksApiException( + "Expected transaction source of type TransferPeerPath, but got type: " + str(type(source))) + + body = { + "assetId": asset_id, + "amount": amount, + "source": source.__dict__, + "operation": tx_type + } + + if destination: + if not isinstance(destination, (TransferPeerPath, DestinationTransferPeerPath)): + raise FireblocksApiException( + "Expected transaction fee estimation destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " + type( + destination)) + body["destination"] = destination.__dict__ + + if destinations: + if any([not isinstance(x, TransactionDestination) for x in destinations]): + raise FireblocksApiException( + "Expected destinations of type TransactionDestination") + body['destinations'] = [dest.__dict__ for dest in destinations] + + return self.connector.post("/v1/transactions/estimate_fee", body, idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def cancel_transaction_by_id(self, txid, idempotency_key=None) -> OperationSuccessResponse: + """Cancels the selected transaction + + Args: + txid (str): The transaction id to cancel + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/transactions/{txid}/cancel", idempotency_key=idempotency_key).content + + def drop_transaction(self, txid, fee_level=None, requested_fee=None, idempotency_key=None): + """Drops the selected transaction from the blockchain by replacing it with a 0 ETH transaction to itself + + Args: + txid (str): The transaction id to drop + fee_level (str): The fee level of the dropping transaction + requested_fee (str, optional): Requested fee for transaction + idempotency_key (str, optional) + """ + body = {} + + if fee_level: + body["feeLevel"] = fee_level + + if requested_fee: + body["requestedFee"] = requested_fee + + return self.connector.post(f"/v1/transactions/{txid}/drop", body, idempotency_key) + + @response_deserializer(OperationSuccessResponse) + def freeze_transaction_by_id(self, txId, idempotency_key=None) -> OperationSuccessResponse: + """Freezes the selected transaction + + Args: + txId (str): The transaction ID to freeze + idempotency_key (str, optional) + """ + return self.connector.post(f"/v1/transactions/{txId}/freeze", idempotency_key=idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def unfreeze_transaction_by_id(self, txId, idempotency_key=None) -> OperationSuccessResponse: + """Unfreezes the selected transaction + + Args: + txId (str): The transaction ID to unfreeze + idempotency_key (str, optional) + """ + return self.connector.post(f"/v1/transactions/{txId}/unfreeze", idempotency_key=idempotency_key).content + + @response_deserializer(CreateTransactionResponse) + def create_transaction(self, asset_id=None, amount=None, source=None, destination=None, fee=None, gas_price=None, + wait_for_status=False, tx_type=TRANSACTION_TRANSFER, note=None, network_fee=None, + customer_ref_id=None, replace_tx_by_hash=None, extra_parameters=None, destinations=None, + fee_level=None, fail_on_low_fee=None, max_fee=None, gas_limit=None, idempotency_key=None, + external_tx_id=None, treat_as_gross_amount=None, + force_sweep=None) -> CreateTransactionResponse: + """Creates a new transaction + + Args: + asset_id (str, optional): The asset symbol (e.g BTC, ETH) + source (TransferPeerPath, optional): The transfer source + destination (DestinationTransferPeerPath, optional): The transfer destination. Leave empty (None) if the transaction has no destination + amount (double): The amount + fee (double, optional): Sathoshi/Latoshi per byte. + gas_price (number, optional): gasPrice for ETH and ERC-20 transactions. + wait_for_status (bool, optional): If true, waits for transaction status. Default is false. + tx_type (str, optional): Transaction type: either TRANSFER, MINT, BURN, TRANSACTION_SUPPLY_TO_COMPOUND or TRANSACTION_REDEEM_FROM_COMPOUND. Default is TRANSFER. + note (str, optional): A custome note that can be associated with the transaction. + network_fee (str, optional): Transaction blockchain fee (For Ethereum, you can't pass gasPrice, gasLimit and networkFee all together) + customer_ref_id (string, optional): The ID for AML providers to associate the owner of funds with transactions + extra_parameters (object, optional) + destinations (list of TransactionDestination objects, optional): For UTXO based assets, send to multiple destinations which should be specified using this field. + fee_level (FeeLevel, optional): Transaction fee level: either HIGH, MEDIUM, LOW. + fail_on_low_fee (bool, optional): False by default, if set to true and MEDIUM fee level is higher than the one specified in the transaction, the transction will fail. + max_fee (str, optional): The maximum fee (gas price or fee per byte) that should be payed for the transaction. + gas_limit (number, optional): For ETH-based assets only. + idempotency_key (str, optional) + external_tx_id (str, optional): A unique key for transaction provided externally + treat_as_gross_amount (bool, optional): Determine if amount should be treated as gross or net + force_sweep (bool, optional): Determine if transaction should be treated as a forced sweep + """ + + if tx_type not in TRANSACTION_TYPES: + raise FireblocksApiException("Got invalid transaction type: " + tx_type) + + if source: + if not isinstance(source, TransferPeerPath): + raise FireblocksApiException( + "Expected transaction source of type TransferPeerPath, but got type: " + type(source)) + + body = { + "waitForStatus": wait_for_status, + "operation": tx_type, + } + + if asset_id: + body["assetId"] = asset_id + + if source: + body["source"] = source.__dict__ + + if amount is not None: + body["amount"] = amount + + if fee: + body["fee"] = fee + + if fee_level: + if fee_level not in FEE_LEVEL: + raise FireblocksApiException("Got invalid fee level: " + fee_level) + body["feeLevel"] = fee_level + + if max_fee: + body["maxFee"] = max_fee + + if fail_on_low_fee: + body["failOnLowFee"] = fail_on_low_fee + + if gas_price: + body["gasPrice"] = str(gas_price) + + if gas_limit: + body["gasLimit"] = str(gas_limit) + + if note: + body["note"] = note + + if destination: + if not isinstance(destination, (TransferPeerPath, DestinationTransferPeerPath)): + raise FireblocksApiException( + "Expected transaction destination of type DestinationTransferPeerPath or TransferPeerPath, but got type: " + type( + destination)) + body["destination"] = destination.__dict__ + + if network_fee: + body["networkFee"] = network_fee + + if customer_ref_id: + body["customerRefId"] = customer_ref_id + + if replace_tx_by_hash: + body["replaceTxByHash"] = replace_tx_by_hash + + if treat_as_gross_amount: + body["treatAsGrossAmount"] = treat_as_gross_amount + + if destinations: + if any([not isinstance(x, TransactionDestination) for x in destinations]): + raise FireblocksApiException("Expected destinations of type TransactionDestination") + + body['destinations'] = [dest.__dict__ for dest in destinations] + + if extra_parameters: + body["extraParameters"] = extra_parameters + + if external_tx_id: + body["externalTxId"] = external_tx_id + + if force_sweep: + body["forceSweep"] = force_sweep + + return self.connector.post("/v1/transactions", body, idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def set_confirmation_threshold_for_txid(self, txid, required_confirmations_number, + idempotency_key=None) -> OperationSuccessResponse: + """Set the required number of confirmations for transaction + + Args: + txid (str): The transaction id + required_confirmations_Number (number): Required confirmation threshold fot the txid + idempotency_key (str, optional) + """ + + body = { + "numOfConfirmations": required_confirmations_number + } + + return self.connector.post(f"/v1/transactions/{txid}/set_confirmation_threshold", body, idempotency_key).content + + @response_deserializer(ValidateAddressResponse) + def validate_address(self, asset_id, address) -> ValidateAddressResponse: + """Gets vault accumulated balance by asset + + Args: + asset_id (str): The asset symbol (e.g XRP, EOS) + address (str): The address to be verified + """ + url = f"/v1/transactions/validate_address/{asset_id}/{address}" + + return self.connector.get(url).content + + def create_raw_transaction(self, raw_message, source=None, asset_id=None, note=None) -> CreateTransactionResponse: + """Creates a new raw transaction with the specified parameters + + Args: + raw_message (RawMessage): The messages that should be signed + source (TransferPeerPath, optional): The transaction source + asset_id (str, optional): Transaction asset id + note (str, optional): A custome note that can be associated with the transaction + """ + + if asset_id is None: + if raw_message.algorithm not in SIGNING_ALGORITHM: + raise Exception("Got invalid signing algorithm type: " + raw_message.algorithm) + + if not all([isinstance(x, UnsignedMessage) for x in raw_message.messages]): + raise FireblocksApiException("Expected messages of type UnsignedMessage") + + raw_message.messages = [message.__dict__ for message in raw_message.messages] + + return self.create_transaction(asset_id, source=source, tx_type="RAW", + extra_parameters={"rawMessageData": raw_message.__dict__}, note=note) diff --git a/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py b/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py new file mode 100644 index 0000000..8fef37c --- /dev/null +++ b/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py @@ -0,0 +1,98 @@ +from typing import List + +from fireblocks_sdk.api_types import FireblocksApiException, TransferPeerPath, TransferTicketTerm +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.transfer_tickets.create_transfer_ticket_response import CreateTransferTicketResponse +from fireblocks_sdk.services.transfer_tickets.term_response import TermResponse +from fireblocks_sdk.services.transfer_tickets.transfer_ticket_response import TransferTicketResponse + + +class TransferTicketsService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(TransferTicketResponse) + def get_transfer_tickets(self) -> List[TransferTicketResponse]: + """Gets all transfer transfer_tickets of your tenant""" + + return self.connector.get("/v1/transfer_tickets").content + + @response_deserializer(CreateTransferTicketResponse) + def create_transfer_ticket(self, terms, external_ticket_id=None, description=None, + idempotency_key=None) -> CreateTransferTicketResponse: + """Creates a new transfer ticket + + Args: + terms (list of TransferTicketTerm objects): The list of TransferTicketTerm + external_ticket_id (str, optional): The ID for of the transfer ticket on customer's platform + description (str, optional): A description for the new ticket + idempotency_key (str, optional) + """ + + body = {} + + if external_ticket_id: + body["externalTicketId"] = external_ticket_id + + if description: + body["description"] = description + + if any([not isinstance(x, TransferTicketTerm) for x in terms]): + raise FireblocksApiException( + "Expected Tranfer Assist ticket's term of type TranferTicketTerm") + + body['terms'] = [term.__dict__ for term in terms] + + return self.connector.post(f"/v1/transfer_tickets", body, idempotency_key).content + + @response_deserializer(TransferTicketResponse) + def get_transfer_ticket_by_id(self, ticket_id) -> TransferTicketResponse: + """Retrieve a transfer ticket + + Args: + ticket_id (str): The ID of the transfer ticket. + """ + + return self.connector.get(f"/v1/transfer_tickets/{ticket_id}").content + + @response_deserializer(TermResponse) + def get_transfer_ticket_term(self, ticket_id, term_id) -> TermResponse: + """Retrieve a transfer ticket + + Args: + ticket_id (str): The ID of the transfer ticket + term_id (str): The ID of the term within the transfer ticket + """ + + return self.connector.get(f"/v1/transfer_tickets/{ticket_id}/{term_id}").content + + def cancel_transfer_ticket(self, ticket_id, idempotency_key=None): + """Cancel a transfer ticket + + Args: + ticket_id (str): The ID of the transfer ticket to cancel + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/transfer_tickets/{ticket_id}/cancel", idempotency_key=idempotency_key) + + def execute_ticket_term(self, ticket_id, term_id, source: TransferPeerPath = None, idempotency_key=None): + """Initiate a transfer ticket transaction + + Args: + ticket_id (str): The ID of the transfer ticket + term_id (str): The ID of the term within the transfer ticket + source (TransferPeerPath): JSON object of the source of the transaction. The network connection's vault account by default + """ + + body = {} + + if source: + if not isinstance(source, TransferPeerPath): + raise FireblocksApiException( + "Expected ticket term source Of type TransferPeerPath, but got type: " + type(source)) + body["source"] = source.__dict__ + + return self.connector.post(f"/v1/transfer_tickets/{ticket_id}/{term_id}/transfer", body, idempotency_key) diff --git a/fireblocks_sdk/services/vaults/deposit_address_response.py b/fireblocks_sdk/services/vaults/deposit_address_response.py new file mode 100644 index 0000000..76bddff --- /dev/null +++ b/fireblocks_sdk/services/vaults/deposit_address_response.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from ctypes import Union +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class DepositAddressResponse(Deserializable): + def __init__(self, asset_id: str, address: str, address_format: str, + legacy_address: Union[str, None], enterprise_address: Union[str, None], type: str, + tag: Union[str, None], description: Union[str, None], customer_ref_id: Union[str, None]) -> None: + self.asset_id = asset_id + self.address = address + self.address_format = address_format + self.legacy_address = legacy_address + self.enterprise_address = enterprise_address + self.type = type + self.tag = tag + self.description = description + self.customer_ref_id = customer_ref_id + + @classmethod + def deserialize(cls, data: Dict[str]) -> DepositAddressResponse: + return cls( + data.get('assetId'), + data.get('address'), + data.get('tag'), + data.get('description'), + data.get('type'), + data.get('customerRefId'), + data.get('addressFormat'), + data.get('legacyAddress'), + data.get('enterpriseAddress') + ) diff --git a/fireblocks_sdk/services/vaults/generate_address_response.py b/fireblocks_sdk/services/vaults/generate_address_response.py new file mode 100644 index 0000000..2f484f9 --- /dev/null +++ b/fireblocks_sdk/services/vaults/generate_address_response.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from ctypes import Union +from typing import Dict + + +class GenerateAddressResponse: + def __init__(self, address: str, tag: Union[str, None], legacy_address: Union[str, None], + enterprise_address: Union[str, None]) -> None: + self.address = address + self.tag = tag + self.legacy_address = legacy_address + self.enterprise_address = enterprise_address + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> GenerateAddressResponse: + return cls( + data.get('address'), + data.get('tag'), + data.get('legacyAddress'), + data.get('enterpriseAddress') + ) diff --git a/fireblocks_sdk/services/vaults/vault_account_response.py b/fireblocks_sdk/services/vaults/vault_account_response.py new file mode 100644 index 0000000..cf18f39 --- /dev/null +++ b/fireblocks_sdk/services/vaults/vault_account_response.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from typing import Dict, List, Union + +from fireblocks_sdk.entities.asset_response import AssetResponse +from fireblocks_sdk.entities.deserializable import Deserializable + + +class VaultAccountResponse(Deserializable): + def __init__(self, id: str, name: str, hidden_on_ui: bool, assets: List[AssetResponse], auto_fuel: bool, + customer_ref_id: Union[str, None]) -> None: + self.id = id + self.name = name + self.hidden_on_ui = hidden_on_ui + self.assets = assets + self.auto_fuel = auto_fuel + self.customerRefId = customer_ref_id + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> VaultAccountResponse: + print('deserializing....') + assets = [AssetResponse.deserialize(asset) for asset in data.get('assets')] + return cls(data.get('id'), + data.get('name'), + data.get('hiddenOnUI'), + assets, + data.get('autoFuel'), + data.get('customerRefId')) diff --git a/fireblocks_sdk/services/vaults/vaults_service.py b/fireblocks_sdk/services/vaults/vaults_service.py new file mode 100644 index 0000000..6810d1c --- /dev/null +++ b/fireblocks_sdk/services/vaults/vaults_service.py @@ -0,0 +1,429 @@ +import urllib +from operator import attrgetter +from typing import List, Union + +from fireblocks_sdk.api_types import PagedVaultAccountsRequestFilters +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.entities.asset_response import AssetResponse +from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse +from fireblocks_sdk.entities.vault_accounts_filter import VaultAccountsFilter +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.vaults.deposit_address_response import DepositAddressResponse +from fireblocks_sdk.services.vaults.generate_address_response import GenerateAddressResponse +from fireblocks_sdk.services.vaults.vault_account_response import VaultAccountResponse + + +class VaultsService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + @response_deserializer(VaultAccountResponse) + def get_vault_accounts(self, filter: Union[VaultAccountsFilter, None] = None) -> List[VaultAccountResponse]: + """Gets all vault accounts for your tenant + + Args: + name_prefix (string, optional): Vault account name prefix + name_suffix (string, optional): Vault account name suffix + min_amount_threshold (number, optional): The minimum amount for asset to have in order to be included in the results + assetId (string, optional): The asset symbol + """ + + url = "/v1/vault/accounts" + + if filter: + url = url + "?" + filter.serialize() + + return self.connector.get(url).content + # return [VaultAccountResponse.deserialize(account) for account in response] + + def get_vault_accounts_with_page_info(self, paged_vault_accounts_request_filters: PagedVaultAccountsRequestFilters): + """Gets a page of vault accounts for your tenant according to filters given + + Args: + paged_vault_accounts_request_filters (object, optional): Possible filters to apply for request + """ + + url = f"/v1/vault/accounts_paged" + name_prefix, name_suffix, min_amount_threshold, asset_id, order_by, limit, before, after = \ + attrgetter('name_prefix', 'name_suffix', 'min_amount_threshold', 'asset_id', + 'order_by', 'limit', 'before', 'after')(paged_vault_accounts_request_filters) + + params = {} + + if name_prefix: + params['namePrefix'] = name_prefix + + if name_suffix: + params['nameSuffix'] = name_suffix + + if min_amount_threshold is not None: + params['minAmountThreshold'] = min_amount_threshold + + if asset_id is not None: + params['assetId'] = asset_id + + if order_by is not None: + params['orderBy'] = order_by + + if limit is not None: + params['limit'] = limit + + if before is not None: + params['before'] = before + + if after is not None: + params['after'] = after + + if params: + url = url + "?" + urllib.parse.urlencode(params) + + return self.connector.get(url) + + @response_deserializer(VaultAccountResponse) + def get_vault_account(self, vault_account_id: str) -> VaultAccountResponse: + """Deprecated - Replaced by get_vault_account_by_id + Args: + vault_account_id (string): The id of the requested account + """ + + return self.connector.get(f"/v1/vault/accounts/{vault_account_id}").content + + @response_deserializer(VaultAccountResponse) + def get_vault_account_by_id(self, vault_account_id: str) -> VaultAccountResponse: + """Gets a single vault account + Args: + vault_account_id (string): The id of the requested account + """ + + return self.connector.get(f"/v1/vault/accounts/{vault_account_id}").content + + @response_deserializer(AssetResponse) + def get_vault_account_asset(self, vault_account_id: str, asset_id: str) -> AssetResponse: + """Gets a single vault account asset + Args: + vault_account_id (string): The id of the requested account + asset_id (string): The symbol of the requested asset (e.g BTC, ETH) + """ + + return self.connector.get(f"/v1/vault/accounts/{vault_account_id}/{asset_id}").content + + @response_deserializer(AssetResponse) + def refresh_vault_asset_balance(self, vault_account_id: str, asset_id: str, + idempotency_key: Union[str, None] = None) -> AssetResponse: + """Gets a single vault account asset after forcing refresh from the blockchain + Args: + vault_account_id (string): The id of the requested account + asset_id (string): The symbol of the requested asset (e.g BTC, ETH) + """ + + return self.connector.post( + f"/v1/vault/accounts/{vault_account_id}/{asset_id}/balance", {}, idempotency_key).content + + @response_deserializer(DepositAddressResponse) + def get_deposit_addresses(self, vault_account_id: str, asset_id: str) -> List[DepositAddressResponse]: + """Gets deposit addresses for an asset in a vault account + Args: + vault_account_id (string): The id of the requested account + asset_id (string): The symbol of the requested asset (e.g BTC, ETH) + """ + + return self.connector.get(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses").content + + @response_deserializer(DepositAddressResponse) + def get_unspent_inputs(self, vault_account_id: str, asset_id: str) -> List[DepositAddressResponse]: + """Gets utxo list for an asset in a vault account + Args: + vault_account_id (string): The id of the requested account + asset_id (string): The symbol of the requested asset (like BTC, DASH and utxo based assets) + """ + + return self.connector.get( + f"/v1/vault/accounts/{vault_account_id}/{asset_id}/unspent_inputs").content + + @response_deserializer(GenerateAddressResponse) + def generate_new_address(self, vault_account_id: str, asset_id: str, description: Union[str, None] = None, + customer_ref_id: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> GenerateAddressResponse: + """Generates a new address for an asset in a vault account + + Args: + vault_account_id (string): The vault account ID + asset_id (string): The ID of the asset for which to generate the deposit address + description (string, optional): A description for the new address + customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses", + {"description": description or '', + "customerRefId": customer_ref_id or ''}, + idempotency_key).content + + @response_deserializer(GenerateAddressResponse) + def set_address_description(self, vault_account_id: str, asset_id: str, address: str, tag: Union[str, None] = None, + description: Union[str, None] = None) -> GenerateAddressResponse: + """Sets the description of an existing address + + Args: + vault_account_id (string): The vault account ID + asset_id (string): The ID of the asset + address (string): The address for which to set the set_address_description + tag (string, optional): The XRP tag, or EOS memo, for which to set the description + description (string, optional): The description to set, or none for no description + """ + if tag: + return self.connector.put(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}:{tag}", + {"description": description or ''}).content + else: + return self.connector.put(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}", + {"description": description or ''}).content + + @response_deserializer(VaultAccountResponse) + def create_vault_account(self, name: str, hidden_on_ui: bool = False, customer_ref_id: Union[str, None] = None, + auto_fuel: bool = False, idempotency_key: Union[str, None] = None) -> VaultAccountResponse: + """Creates a new vault account. + + Args: + name (str): A name for the new vault account + hidden_on_ui (boolean): Specifies whether the vault account is hidden from the web console, false by default + customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + body = { + "name": name, + "hiddenOnUI": hidden_on_ui, + "autoFuel": auto_fuel + } + + if customer_ref_id: + body["customerRefId"] = customer_ref_id + + return self.connector.post("/v1/vault/accounts", body, idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def hide_vault_account(self, vault_account_id: str, idempotency_key=None) -> OperationSuccessResponse: + """Hides the vault account from being visible in the web console + + Args: + vault_account_id (str): The vault account Id + idempotency_key (str, optional) + """ + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/hide", + idempotency_key=idempotency_key).content + + @response_deserializer(OperationSuccessResponse) + def unhide_vault_account(self, vault_account_id: str, + idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: + """Returns the vault account to being visible in the web console + + Args: + vault_account_id (str): The vault account Id + idempotency_key (str, optional) + """ + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/unhide", + idempotency_key=idempotency_key).content + + @response_deserializer(VaultAccountResponse) + def update_vault_account(self, vault_account_id: str, name: str) -> VaultAccountResponse: + """Updates a vault account. + + Args: + vault_account_id (str): The vault account Id + name (str): A new name for the vault account + """ + body = { + "name": name, + } + + return self.connector.put(f"/v1/vault/accounts/{vault_account_id}", body).content + + def create_vault_asset(self, vault_account_id: str, asset_id: str, idempotency_key: Union[str, None] = None): + """Creates a new asset within an existing vault account + + Args: + vault_account_id (str): The vault account Id + asset_id (str): The symbol of the asset to add (e.g BTC, ETH) + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/{asset_id}", idempotency_key=idempotency_key) + + def activate_vault_asset(self, vault_account_id: str, asset_id: str, idempotency_key: Union[str, None] = None): + """Retry to create a vault asset for a vault asset that failed + + Args: + vault_account_id (str): The vault account Id + asset_id (str): The symbol of the asset to add (e.g BTC, ETH) + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/activate", + idempotency_key=idempotency_key) + + def set_vault_account_customer_ref_id(self, vault_account_id: str, customer_ref_id: str, + idempotency_key: Union[str, None] = None): + """Sets an AML/KYT customer reference ID for the vault account + + Args: + vault_account_id (str): The vault account Id + customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/set_customer_ref_id", + {"customerRefId": customer_ref_id or ''}, idempotency_key) + + @response_deserializer(OperationSuccessResponse) + def set_vault_account_customer_ref_id_for_address(self, vault_account_id: str, asset_id: str, address: str, + customer_ref_id: Union[str, None] = None, + idempotency_key: Union[ + str, None] = None) -> OperationSuccessResponse: + """Sets an AML/KYT customer reference ID for the given address + + Args: + vault_account_id (str): The vault account Id + asset_id (str): The symbol of the asset to add (e.g BTC, ETH) + address (string): The address for which to set the customer reference id + customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post( + f"/v1/vault/accounts/{vault_account_id}/{asset_id}/addresses/{address}/set_customer_ref_id", + {"customerRefId": customer_ref_id or ''}, idempotency_key).content + + def get_public_key_info_for_vault_account(self, asset_id: str, vault_account_id: str, change: int, + address_index: int, compressed: Union[bool, None] = None): + """Get the public key information for a vault account + + Args: + assetId (str) + vaultAccountId (number) + change (number) + addressIndex (number) + compressed (boolean, optional) + """ + + url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/{change}/{address_index}/public_key_info" + if compressed: + url += f"?compressed={compressed}" + + return self.connector.get(url) + + def allocate_funds_to_private_ledger(self, vault_account_id: str, asset: str, allocation_id: str, amount: str, + treat_as_gross_amount: Union[bool, None] = None, + idempotency_key: Union[str, None] = None): + """Allocate funds from your default balance to a private ledger + + Args: + vault_account_id (string) + asset (string) + allocation_id (string) + amount (string) + treat_as_gross_amount (bool, optional) + idempotency_key (string, optional) + """ + + url = f"/v1/vault/accounts/{vault_account_id}/{asset}/lock_allocation" + + return self.connector.post(url, {"allocationId": allocation_id, "amount": amount, + "treatAsGrossAmount": treat_as_gross_amount or False}, idempotency_key) + + def deallocate_funds_from_private_ledger(self, vault_account_id: str, asset: str, allocation_id: str, amount: str, + idempotency_key: Union[str, None] = None): + """deallocate funds from a private ledger to your default balance + + Args: + vault_account_id (string) + asset (string) + allocation_id (string) + amount (string) + idempotency_key (string, optional) + """ + + url = f"/v1/vault/accounts/{vault_account_id}/{asset}/release_allocation" + + return self.connector.post(url, {"allocationId": allocation_id, "amount": amount}, idempotency_key) + + def get_max_spendable_amount(self, vault_account_id: str, asset_id: str, manual_signing: bool = False): + """Get max spendable amount per asset and vault. + + Args: + vault_account_id (str): The vault account Id. + asset_id (str): Asset id. + manual_signing (boolean, optional): False by default. + """ + url = f"/v1/vault/accounts/{vault_account_id}/{asset_id}/max_spendable_amount?manual_signing={manual_signing}" + + return self.connector.get(url) + + def set_auto_fuel(self, vault_account_id: str, auto_fuel: bool, idempotency_key=None): + """Sets autoFuel to true/false for a vault account + + Args: + vault_account_id (str): The vault account Id + auto_fuel (boolean): The new value for the autoFuel flag + idempotency_key (str, optional) + """ + body = { + "autoFuel": auto_fuel + } + + return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/set_auto_fuel", body, idempotency_key) + + def get_public_key_info(self, algorithm: str, derivation_path: str, compressed=None): + """Get the public key information + + Args: + algorithm (str, optional) + derivation_path (str) + compressed (boolean, optional) + """ + + url = "/v1/vault/public_key_info" + if algorithm: + url += f"?algorithm={algorithm}" + if derivation_path: + url += f"&derivationPath={urllib.parse.quote(derivation_path)}" + if compressed: + url += f"&compressed={compressed}" + return self.connector.get(url) + + @response_deserializer(AssetResponse) + def get_vault_assets_balance(self, account_name_prefix: Union[str, None] = None, + account_name_suffix: Union[str, None] = None) -> List[AssetResponse]: + """Gets vault assets accumulated balance + + Args: + account_name_prefix (string, optional): Vault account name prefix + account_name_suffix (string, optional): Vault account name suffix + """ + url = f"/v1/vault/assets" + + params = {} + + if account_name_prefix: + params['accountNamePrefix'] = account_name_prefix + + if account_name_suffix: + params['accountNameSuffix'] = account_name_suffix + + if params: + url = url + "?" + urllib.parse.urlencode(params) + + return self.connector.get(url).content + + @response_deserializer(AssetResponse) + def get_vault_balance_by_asset(self, asset_id: Union[str, None] = None) -> AssetResponse: + """Gets vault accumulated balance by asset + + Args: + asset_id (str, optional): The asset symbol (e.g BTC, ETH) + """ + url = f"/v1/vault/assets" + + if asset_id: + url += f"/{asset_id}" + + return self.connector.get(url).content diff --git a/fireblocks_sdk/services/wallets/wallets_service.py b/fireblocks_sdk/services/wallets/wallets_service.py new file mode 100644 index 0000000..892943a --- /dev/null +++ b/fireblocks_sdk/services/wallets/wallets_service.py @@ -0,0 +1,196 @@ +from typing import Union, List + +from fireblocks_sdk.common.wrappers import response_deserializer +from fireblocks_sdk.connectors.rest import RestConnector +from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.wallets.external_wallet_asset import ExternalWalletAsset +from fireblocks_sdk.services.wallets.internal_wallet_asset import InternalWalletAsset +from fireblocks_sdk.services.wallets.wallet_container_response import WalletContainerResponse + + +class WalletsService(BaseService): + def __init__(self, connector: RestConnector) -> None: + super().__init__(connector) + + def delete_internal_wallet(self, wallet_id: str): + """Deletes a single internal wallet + + Args: + wallet_id (string): The internal wallet ID + """ + + return self.connector.delete(f"/v1/internal_wallets/{wallet_id}") + + def delete_external_wallet(self, wallet_id: str): + """Deletes a single external wallet + + Args: + wallet_id (string): The external wallet ID + """ + + return self.connector.delete(f"/v1/external_wallets/{wallet_id}") + + def get_internal_wallets(self): + """Gets all internal wallets for your tenant""" + + return self.connector.get("/v1/internal_wallets") + + def delete_internal_wallet_asset(self, wallet_id: str, asset_id: str): + """Deletes a single asset from an internal wallet + + Args: + wallet_id (string): The internal wallet ID + asset_id (string): The asset ID + """ + + return self.connector.delete(f"/v1/internal_wallets/{wallet_id}/{asset_id}") + + def delete_external_wallet_asset(self, wallet_id: str, asset_id: str): + """Deletes a single asset from an external wallet + + Args: + wallet_id (string): The external wallet ID + asset_id (string): The asset ID + """ + + return self.connector.delete(f"/v1/external_wallets/{wallet_id}/{asset_id}") + + def set_customer_ref_id_for_internal_wallet(self, wallet_id: str, customer_ref_id: Union[str, None] = None, + idempotency_key: Union[str, None] = None): + """Sets an AML/KYT customer reference ID for the specific internal wallet + + Args: + wallet_id (string): The external wallet ID + customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/internal_wallets/{wallet_id}/set_customer_ref_id", + {"customerRefId": customer_ref_id or ''}, idempotency_key) + + def set_customer_ref_id_for_external_wallet(self, wallet_id: str, customer_ref_id: Union[str, None] = None, + idempotency_key: Union[str, None] = None): + """Sets an AML/KYT customer reference ID for the specific external wallet + + Args: + wallet_id (string): The external wallet ID + customer_ref_id (str): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post(f"/v1/external_wallets/{wallet_id}/set_customer_ref_id", + {"customerRefId": customer_ref_id or ''}, idempotency_key) + + @response_deserializer(WalletContainerResponse[InternalWalletAsset]) + def get_internal_wallet(self, wallet_id: str) -> WalletContainerResponse[InternalWalletAsset]: + """Gets an internal wallet from your tenant + Args: + wallet_id (str): The wallet id to query + """ + + return self.connector.get(f"/v1/internal_wallets/{wallet_id}").content + + @response_deserializer(InternalWalletAsset) + def get_internal_wallet_asset(self, wallet_id: str, asset_id: str) -> InternalWalletAsset: + """Gets an asset from an internal wallet from your tenant + Args: + wallet_id (str): The wallet id to query + asset_id (str): The asset id to query + """ + return self.connector.get(f"/v1/internal_wallets/{wallet_id}/{asset_id}").content + + @response_deserializer(List[WalletContainerResponse[ExternalWalletAsset]]) + def get_external_wallets(self): + """Gets all external wallets for your tenant""" + + return self.connector.get("/v1/external_wallets").content + + @response_deserializer(WalletContainerResponse[ExternalWalletAsset]) + def get_external_wallet(self, wallet_id: str) -> WalletContainerResponse[ExternalWalletAsset]: + """Gets an external wallet from your tenant + Args: + wallet_id (str): The wallet id to query + """ + + return self.connector.get(f"/v1/external_wallets/{wallet_id}").content + + @response_deserializer(ExternalWalletAsset) + def get_external_wallet_asset(self, wallet_id: str, asset_id: str) -> ExternalWalletAsset: + """Gets an asset from an external wallet from your tenant + Args: + wallet_id (str): The wallet id to query + asset_id (str): The asset id to query + """ + return self.connector.get(f"/v1/external_wallets/{wallet_id}/{asset_id}").content + + @response_deserializer(ExternalWalletAsset) + def create_external_wallet_asset(self, wallet_id: str, asset_id: str, address: str, tag: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> ExternalWalletAsset: + """Creates a new asset within an exiting external wallet + + Args: + wallet_id (str): The wallet id + asset_id (str): The symbol of the asset to add (e.g BTC, ETH) + address (str): The wallet address + tag (str, optional): (for ripple only) The ripple account tag + idempotency_key (str, optional) + """ + + body = {"address": address} + if tag: + body["tag"] = tag + + return self.connector.post( + f"/v1/external_wallets/{wallet_id}/{asset_id}", body, idempotency_key + ).content + + @response_deserializer(InternalWalletAsset) + def create_internal_wallet_asset(self, wallet_id: str, asset_id: str, address: str, tag: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> InternalWalletAsset: + """Creates a new asset within an exiting internal wallet + + Args: + wallet_id (str): The wallet id + asset_id (str): The symbol of the asset to add (e.g BTC, ETH) + address (str): The wallet address + tag (str, optional): (for ripple only) The ripple account tag + idempotency_key (str, optional) + """ + + body = {"address": address} + if tag: + body["tag"] = tag + + return self.connector.post( + f"/v1/internal_wallets/{wallet_id}/{asset_id}", body, idempotency_key + ).content + + @response_deserializer(WalletContainerResponse[ExternalWalletAsset]) + def create_external_wallet(self, name: str, customer_ref_id: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> ( + WalletContainerResponse[ExternalWalletAsset]): + """Creates a new external wallet + + Args: + name (str): A name for the new external wallet + customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post("/v1/external_wallets", {"name": name, "customerRefId": customer_ref_id or ''}, + idempotency_key).content + + @response_deserializer(WalletContainerResponse[InternalWalletAsset]) + def create_internal_wallet(self, name: str, customer_ref_id: Union[str, None] = None, + idempotency_key: Union[str, None] = None) -> WalletContainerResponse[ + InternalWalletAsset]: + """Creates a new internal wallet + + Args: + name (str): A name for the new internal wallet + customer_ref_id (str, optional): The ID for AML providers to associate the owner of funds with transactions + idempotency_key (str, optional) + """ + + return self.connector.post("/v1/internal_wallets", {"name": name, "customerRefId": customer_ref_id or ''}, + idempotency_key).content diff --git a/setup.py b/setup.py index 1e0bb47..1bd31c0 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ install_requires=[ 'PyJWT>=2.3.0', 'cryptography>=2.7', - 'requests>=2.22.0', + 'requests>=2.22.0' ], classifiers=[ 'Development Status :: 4 - Beta', From 99147a5b99d49b2c02eed40ed564868adaba3363 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Thu, 30 Jun 2022 09:03:13 +0300 Subject: [PATCH 4/7] sync function names with JS SDK --- fireblocks_sdk/sdk.py | 6 +++--- .../exchange/convert_exchange_asset_response.py | 15 +++++++++++++++ .../services/exchange/exchange_service.py | 16 ++++++++++++++-- .../transactions/transactions_service.py | 2 +- .../transfer_tickets/transfer_tickets_service.py | 2 +- fireblocks_sdk/services/vaults/vaults_service.py | 4 ++-- 6 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 fireblocks_sdk/services/exchange/convert_exchange_asset_response.py diff --git a/fireblocks_sdk/sdk.py b/fireblocks_sdk/sdk.py index 33a8b95..ac8ae18 100644 --- a/fireblocks_sdk/sdk.py +++ b/fireblocks_sdk/sdk.py @@ -90,7 +90,7 @@ def get_users(self): return self.connector.get(url) - def get_off_exchanges(self): + def get_off_exchange_accounts(self): """ Get your connected off exchanges virtual accounts """ @@ -98,7 +98,7 @@ def get_off_exchanges(self): return self.connector.get(url) - def get_off_exchange_by_id(self, off_exchange_id): + def get_off_exchange_account_by_id(self, off_exchange_id): """ Get your connected off exchange by it's ID :param off_exchange_id: ID of the off exchange virtual account @@ -109,7 +109,7 @@ def get_off_exchange_by_id(self, off_exchange_id): return self.connector.get(url) - def settle_off_exchange_by_id(self, off_exchange_id, idempotency_key=None): + def settle_off_exchange_account_by_id(self, off_exchange_id, idempotency_key=None): """ Create a settle request to your off exchange by it's ID :param off_exchange_id: ID of the off exchange virtual account diff --git a/fireblocks_sdk/services/exchange/convert_exchange_asset_response.py b/fireblocks_sdk/services/exchange/convert_exchange_asset_response.py new file mode 100644 index 0000000..692b458 --- /dev/null +++ b/fireblocks_sdk/services/exchange/convert_exchange_asset_response.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from typing import Dict + + +class ConvertExchangeAssetResponse: + def __init__(self, status: bool) -> None: + + self.status = status + + @classmethod + def deserialize(cls, data: Dict[str, any]) -> ConvertExchangeAssetResponse: + return cls( + data.get('status') + ) diff --git a/fireblocks_sdk/services/exchange/exchange_service.py b/fireblocks_sdk/services/exchange/exchange_service.py index 4392010..1f14910 100644 --- a/fireblocks_sdk/services/exchange/exchange_service.py +++ b/fireblocks_sdk/services/exchange/exchange_service.py @@ -4,6 +4,7 @@ from fireblocks_sdk.connectors.rest import RestConnector from fireblocks_sdk.entities.op_success_response import OperationSuccessResponse from fireblocks_sdk.services.base_service import BaseService +from fireblocks_sdk.services.exchange.convert_exchange_asset_response import ConvertExchangeAssetResponse from fireblocks_sdk.services.exchange.exchange_response import ExchangeResponse @@ -19,7 +20,7 @@ def get_exchange_accounts(self): return [ExchangeResponse.deserialize(exchange) for exchange in response.content] @response_deserializer(ExchangeResponse) - def get_exchange_account(self, exchange_account_id: str) -> ExchangeResponse: + def get_exchange_account_by_id(self, exchange_account_id: str) -> ExchangeResponse: """Gets an exchange account for your tenant Args: exchange_account_id (string): The exchange ID in Fireblocks @@ -28,7 +29,7 @@ def get_exchange_account(self, exchange_account_id: str) -> ExchangeResponse: return self.connector.get(f"/v1/exchange_accounts/{exchange_account_id}").content @response_deserializer(ExchangeResponse) - def get_exchange_account_asset(self, exchange_account_id: str, asset_id: str) -> ExchangeResponse: + def get_exchange_asset(self, exchange_account_id: str, asset_id: str) -> ExchangeResponse: """Get a specific asset from an exchange account Args: @@ -38,6 +39,17 @@ def get_exchange_account_asset(self, exchange_account_id: str, asset_id: str) -> return self.connector.get(f"/v1/exchange_accounts/{exchange_account_id}/{asset_id}").content + @response_deserializer(ConvertExchangeAssetResponse) + def convert_exchange_asset(self, exchange_account_id: str, source_asset_id: str, destination_asset_id: str, amount: float) -> ExchangeResponse: + body = { + "srcAsset": source_asset_id, + "destAsset": destination_asset_id, + "amount": amount + + } + + return self.connector.post(f"/v1/exchange_accounts/{exchange_account_id}/convert", body).content + @response_deserializer(OperationSuccessResponse) def transfer_to_subaccount(self, exchange_account_id: str, sub_account_id: str, asset_id: str, amount: float, idempotency_key: Union[str, None] = None) -> OperationSuccessResponse: diff --git a/fireblocks_sdk/services/transactions/transactions_service.py b/fireblocks_sdk/services/transactions/transactions_service.py index d278a65..8372191 100644 --- a/fireblocks_sdk/services/transactions/transactions_service.py +++ b/fireblocks_sdk/services/transactions/transactions_service.py @@ -157,7 +157,7 @@ def get_transaction_by_id(self, tx_id: str) -> TransactionResponse: return self.connector.get(f"/v1/transactions/{tx_id}").content @response_deserializer(TransactionResponse) - def get_transaction_by_external_id(self, external_tx_id) -> TransactionResponse: + def get_transaction_by_external_tx_id(self, external_tx_id) -> TransactionResponse: """Gets detailed information for a single transaction Args: diff --git a/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py b/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py index 8fef37c..b6b73b2 100644 --- a/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py +++ b/fireblocks_sdk/services/transfer_tickets/transfer_tickets_service.py @@ -78,7 +78,7 @@ def cancel_transfer_ticket(self, ticket_id, idempotency_key=None): return self.connector.post(f"/v1/transfer_tickets/{ticket_id}/cancel", idempotency_key=idempotency_key) - def execute_ticket_term(self, ticket_id, term_id, source: TransferPeerPath = None, idempotency_key=None): + def execute_transfer_ticket_term(self, ticket_id, term_id, source: TransferPeerPath = None, idempotency_key=None): """Initiate a transfer ticket transaction Args: diff --git a/fireblocks_sdk/services/vaults/vaults_service.py b/fireblocks_sdk/services/vaults/vaults_service.py index 6810d1c..39bee61 100644 --- a/fireblocks_sdk/services/vaults/vaults_service.py +++ b/fireblocks_sdk/services/vaults/vaults_service.py @@ -261,7 +261,7 @@ def activate_vault_asset(self, vault_account_id: str, asset_id: str, idempotency return self.connector.post(f"/v1/vault/accounts/{vault_account_id}/{asset_id}/activate", idempotency_key=idempotency_key) - def set_vault_account_customer_ref_id(self, vault_account_id: str, customer_ref_id: str, + def set_customer_ref_id_for_vault_account(self, vault_account_id: str, customer_ref_id: str, idempotency_key: Union[str, None] = None): """Sets an AML/KYT customer reference ID for the vault account @@ -275,7 +275,7 @@ def set_vault_account_customer_ref_id(self, vault_account_id: str, customer_ref_ {"customerRefId": customer_ref_id or ''}, idempotency_key) @response_deserializer(OperationSuccessResponse) - def set_vault_account_customer_ref_id_for_address(self, vault_account_id: str, asset_id: str, address: str, + def set_customer_ref_id_for_address(self, vault_account_id: str, asset_id: str, address: str, customer_ref_id: Union[str, None] = None, idempotency_key: Union[ str, None] = None) -> OperationSuccessResponse: From 3438e514317e4c8266facc2f8a2e2fb8988b47c5 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Thu, 30 Jun 2022 09:17:44 +0300 Subject: [PATCH 5/7] add user agent header --- fireblocks_sdk/connectors/rest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fireblocks_sdk/connectors/rest.py b/fireblocks_sdk/connectors/rest.py index 87d80a6..3d3fd43 100644 --- a/fireblocks_sdk/connectors/rest.py +++ b/fireblocks_sdk/connectors/rest.py @@ -1,12 +1,17 @@ import json from typing import Dict, Union +import pkg_resources import requests from fireblocks_sdk import FireblocksApiException from fireblocks_sdk.entities.api_response import ApiResponse from fireblocks_sdk.sdk_token_provider import SdkTokenProvider +SDK_BUILD_PLATFORM = pkg_resources.get_build_platform() +SDK_VERSION = pkg_resources.get_distribution("fireblocks_sdk").version +SDK_PACKAGE = 'fireblocks-sdk-py' +SDK_USER_AGENT = f'{SDK_PACKAGE}/{SDK_VERSION} ({SDK_BUILD_PLATFORM})' class RestConnector: def __init__(self, token_provider: SdkTokenProvider, base_url: str, api_key: str, timeout: int) -> None: @@ -19,7 +24,8 @@ def _generate_headers(self, path, body: Union[Dict, None] = None, idempotency_ke token = self.token_provider.sign_jwt(path, json.dumps(body) if body else json.dumps({})) headers = { "X-API-Key": self.api_key, - "Authorization": f"Bearer {token}" + "Authorization": f"Bearer {token}", + "User-Agent": SDK_USER_AGENT } if idempotency_key: From 3aa2e757242b1c0ce1cd4e9ddf07b199ac8a1f01 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Sun, 3 Jul 2022 10:13:03 +0300 Subject: [PATCH 6/7] added python version to user agent --- fireblocks_sdk/connectors/rest.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fireblocks_sdk/connectors/rest.py b/fireblocks_sdk/connectors/rest.py index 3d3fd43..290ff81 100644 --- a/fireblocks_sdk/connectors/rest.py +++ b/fireblocks_sdk/connectors/rest.py @@ -1,4 +1,5 @@ import json +import platform from typing import Dict, Union import pkg_resources @@ -10,8 +11,9 @@ SDK_BUILD_PLATFORM = pkg_resources.get_build_platform() SDK_VERSION = pkg_resources.get_distribution("fireblocks_sdk").version + SDK_PACKAGE = 'fireblocks-sdk-py' -SDK_USER_AGENT = f'{SDK_PACKAGE}/{SDK_VERSION} ({SDK_BUILD_PLATFORM})' +SDK_USER_AGENT = f'{SDK_PACKAGE}/{SDK_VERSION}; Python {platform.python_version()} ({SDK_BUILD_PLATFORM})' class RestConnector: def __init__(self, token_provider: SdkTokenProvider, base_url: str, api_key: str, timeout: int) -> None: From ced96a35a90459ddf4dcb0438270fd09fb5f9d55 Mon Sep 17 00:00:00 2001 From: Idan Yael Date: Mon, 29 Aug 2022 14:26:38 +0300 Subject: [PATCH 7/7] added some missing structures --- .../entities/off_exchange_entity_response.py | 37 +++++++++++++++++ fireblocks_sdk/entities/user.py | 22 ++++++++++ fireblocks_sdk/sdk.py | 17 +++++--- .../entities/transaction_response.py | 40 +++++++++++++++++++ 4 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 fireblocks_sdk/entities/off_exchange_entity_response.py create mode 100644 fireblocks_sdk/entities/user.py diff --git a/fireblocks_sdk/entities/off_exchange_entity_response.py b/fireblocks_sdk/entities/off_exchange_entity_response.py new file mode 100644 index 0000000..76456c7 --- /dev/null +++ b/fireblocks_sdk/entities/off_exchange_entity_response.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable, T + + +class ExchangeBalance(Deserializable): + def __init__(self, total: str, locked: str, pending: str, frozen: str) -> None: + super().__init__() + self.total = total + self.locked = locked + self.pending = pending + self.frozen = frozen + + @classmethod + def deserialize(cls: T, data: Dict[str]) -> ExchangeBalance: + return ExchangeBalance(data.get('total'), data.get('locked'), data.get('pending'), data.get('frozen')) + + +class OffExchangeEntityResponse(Deserializable): + def __init__(self, id: str, vault_account_id: str, third_party_account_id: str) -> None: + self.id = id + self.vault_account_id = vault_account_id + self.third_party_account_id = third_party_account_id + self.balance: Dict[str, ExchangeBalance] = {} + + def set_balance(self, asset_id: str, balance: ExchangeBalance): + self.balance[asset_id] = balance + + @classmethod + def deserialize(cls, data: Dict[str]) -> OffExchangeEntityResponse: + response = cls(data.get('id'), data.get('vaultAccountId'), data.get('thirdPartyAccountId')) + for k, v in data.get('balance', {}).items(): + response.set_balance(k, ExchangeBalance.deserialize(v)) + + return response diff --git a/fireblocks_sdk/entities/user.py b/fireblocks_sdk/entities/user.py new file mode 100644 index 0000000..84e9f88 --- /dev/null +++ b/fireblocks_sdk/entities/user.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import Dict + +from fireblocks_sdk.entities.deserializable import Deserializable + + +class User(Deserializable): + + def __init__(self, id: str, first_name: str, last_name: str, email: str, enabled: bool, role: str) -> None: + super().__init__() + self.id = id + self.first_name = first_name + self.last_name = last_name + self.email = email + self.enabled = enabled + self.role = role + + @classmethod + def deserialize(cls, data: Dict[str]) -> User: + return User(data.get('id'), data.get('firstName'), data.get('lastName'), data.get('email'), data.get('enabled'), + data.get('role')) diff --git a/fireblocks_sdk/sdk.py b/fireblocks_sdk/sdk.py index ac8ae18..84aa1fc 100644 --- a/fireblocks_sdk/sdk.py +++ b/fireblocks_sdk/sdk.py @@ -8,6 +8,8 @@ from fireblocks_sdk.services.wallets.wallets_service import WalletsService from fireblocks_sdk.services.web_hooks.web_hooks_service import WebHooksService from .common.wrappers import response_deserializer +from .entities.off_exchange_entity_response import OffExchangeEntityResponse +from .entities.user import User from .sdk_token_provider import SdkTokenProvider from .services.contracts.contracts_service import ContractsService from .services.exchange.exchange_service import ExchangeService @@ -83,22 +85,25 @@ def set_confirmation_threshold_for_txhash(self, txhash, required_confirmations_n return self.connector.post(f"/v1/txHash/{txhash}/set_confirmation_threshold", body, idempotency_key) - def get_users(self): + @response_deserializer(User) + def get_users(self) -> List[User]: """Gets all users of your tenant""" url = "/v1/users" - return self.connector.get(url) + return self.connector.get(url).content - def get_off_exchange_accounts(self): + @response_deserializer(OffExchangeEntityResponse) + def get_off_exchange_accounts(self) -> List[OffExchangeEntityResponse]: """ Get your connected off exchanges virtual accounts """ url = f"/v1/off_exchange_accounts" - return self.connector.get(url) + return self.connector.get(url).content - def get_off_exchange_account_by_id(self, off_exchange_id): + @response_deserializer(OffExchangeEntityResponse) + def get_off_exchange_account_by_id(self, off_exchange_id) -> OffExchangeEntityResponse: """ Get your connected off exchange by it's ID :param off_exchange_id: ID of the off exchange virtual account @@ -107,7 +112,7 @@ def get_off_exchange_account_by_id(self, off_exchange_id): url = f"/v1/off_exchange_accounts/{off_exchange_id}" - return self.connector.get(url) + return self.connector.get(url).content def settle_off_exchange_account_by_id(self, off_exchange_id, idempotency_key=None): """ diff --git a/fireblocks_sdk/services/transactions/entities/transaction_response.py b/fireblocks_sdk/services/transactions/entities/transaction_response.py index 1082554..5731946 100644 --- a/fireblocks_sdk/services/transactions/entities/transaction_response.py +++ b/fireblocks_sdk/services/transactions/entities/transaction_response.py @@ -32,6 +32,46 @@ def __init__(self, id: str, asset_id: str, source: TransferPeerPathResponse, authorization_info: AuthorizationInfo, index: int, reward_info: RewardInfo, fee_payer_info: FeePayerInfo) -> None: super().__init__() + self.id = id + self.asset_id = asset_id + self.source = source + self.destination = destination + self.amount = amount + self.fee = fee + self.network_fee = network_fee + self.amount_usd = amount_usd + self.net_amount = net_amount + self.created_at = created_at + self.last_updated = last_updated + self.status = status + self.tx_hash = tx_hash + self.num_confirmations = num_confirmations + self.sub_status = sub_status + self.signed_by = signed_by + self.created_by = created_by + self.rejected_by = rejected_by + self.destination_address = destination_address + self.destination_address_description = destination_address_description + self.destination_tag = destination_tag + self.address_type = address_type + self.note = note + self.exchange_tx_id = exchange_tx_id + self.requested_amount = requested_amount + self.service_fee = service_fee + self.fee_currency = fee_currency + self.aml_screening_result = aml_screening_result + self.customer_ref_id = customer_ref_id + self.amount_info = amount_info + self.fee_info = fee_info + self.signed_messages = signed_messages + self.extra_parameters = extra_parameters + self.external_tx_id = external_tx_id + self.destinations = destinations + self.block_info = block_info + self.authorization_info = authorization_info + self.index = index + self.reward_info = reward_info + self.fee_payer_info = fee_payer_info @classmethod def deserialize(cls, data: Dict[str, any]) -> TransactionResponse: